On Thu, Sep 21, 2006 at 09:38:55AM +0000, David Holroyd wrote:
> Has anyone done work on generating AS classes from a schema, or seen
> such a tool?
>
> I'm thinking of trying to build something do do this if nothing exists.
Attached is the code I hacked together. Notes:
- Horrible code! :)
- Creates classes for named top-level complex types *only*
- Depends on unreleased version of metaas[1], so you'll need to furtle
in SVN to actually use the attached!
- Depends on the Eclipse XSD infoset model, as noted at the top of the
attached file.
Is anyone interested in turning this into a proper project?
ta,
dave
[1] http://www.badgers-in-foil.co.uk/projects/metaas/
--
http://david.holroyd.me.uk/
/*
* Copyright (c) David Holroyd 2006
*/
package uk.co.badgersinfoil.asxsd;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.xsd.XSDAnnotation;
import org.eclipse.xsd.XSDAttributeDeclaration;
import org.eclipse.xsd.XSDAttributeGroupContent;
import org.eclipse.xsd.XSDAttributeUse;
import org.eclipse.xsd.XSDComplexTypeContent;
import org.eclipse.xsd.XSDComplexTypeDefinition;
import org.eclipse.xsd.XSDCompositor;
import org.eclipse.xsd.XSDElementDeclaration;
import org.eclipse.xsd.XSDModelGroup;
import org.eclipse.xsd.XSDNamedComponent;
import org.eclipse.xsd.XSDParticle;
import org.eclipse.xsd.XSDParticleContent;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.XSDSimpleTypeDefinition;
import org.eclipse.xsd.XSDTypeDefinition;
import org.eclipse.xsd.util.XSDResourceFactoryImpl;
import org.eclipse.xsd.util.XSDResourceImpl;
import org.eclipse.xsd.util.XSDSchemaQueryTools;
import org.w3c.dom.Element;
import uk.co.badgersinfoil.metaas.ASClassType;
import uk.co.badgersinfoil.metaas.ASField;
import uk.co.badgersinfoil.metaas.ASMethod;
import uk.co.badgersinfoil.metaas.ASSourceFactory;
import uk.co.badgersinfoil.metaas.CompilationUnit;
import uk.co.badgersinfoil.metaas.StatementContainer;
import uk.co.badgersinfoil.metaas.Visibility;
/*
* To use this class, you will need both metaas, and the Eclpise XSD infoset
* model, and supporting packages. I used emf-sdo-xsd-Standalone-2.2.0,
* available at,
*
* http://www.eclipse.org/downloads/download.php?file=/tools/emf/downloads/drops/2.2.0/R200606271057/emf-sdo-xsd-Standalone-2.2.0.zip
*
* After unpacking the contents of this archive, the emf_common, emf_ecore and
* xsd jars (from the archive's 'emf/bin' directory) must be added to your
* classpath.
*
* NOTE: this code is a bit ugly; a proper imlementation might be allowed to
* grow to more than a single class ;)
*/
/*
* TODOs:
* - WSDL? [ http://dev.eclipse.org/newslists/news.eclipse.technology.xsd/msg00401.html ]
*/
public class Main {
private static final String SCHEMA_NAMESPACE = "http://www.w3.org/2001/XMLSchema";
private static String destDir;
public static void main(String[] args) throws IOException {
// need to do this in order to have Eclipse's XSD 'resource'
// support work,
Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xsd", new XSDResourceFactoryImpl());
String filename = args[0];
if (args.length > 1) {
destDir = args[1];
} else {
destDir = ".";
}
XSDSchema mainSchema = loadSchema(filename);
processSchema(mainSchema);
}
private static XSDSchema loadSchema(String filename) throws IOException {
ResourceSet resourceSet = new ResourceSetImpl();
XSDResourceImpl resource = (XSDResourceImpl)resourceSet.createResource(URI.createURI("*.xsd"));
resource.setURI(URI.createFileURI(filename));
resource.load(resourceSet.getLoadOptions());
XSDSchema mainSchema = resource.getSchema();
return mainSchema;
}
private static void processSchema(XSDSchema schema) throws IOException {
processSchemaCreateTypes(schema);
processSchemaCreateUnmarshaler(schema);
}
private static void processSchemaCreateTypes(XSDSchema schema) throws IOException {
List types = schema.getTypeDefinitions();
for (Iterator i = types.iterator(); i.hasNext(); ) {
XSDTypeDefinition typeDef = (XSDTypeDefinition)i.next();
if (typeDef instanceof XSDComplexTypeDefinition) {
processComplexType((XSDComplexTypeDefinition)typeDef);
}
}
}
/**
* Handle a top-level complex type definition by creating an AS
* class.
*/
private static void processComplexType(XSDComplexTypeDefinition typeDef) throws IOException {
ASSourceFactory fact = new ASSourceFactory();
CompilationUnit unit = fact.newClass(typeName(typeDef));
ASClassType clazz = (ASClassType)unit.getType();
processComplexTypeBaseType(typeDef, clazz);
processComplexTypeAnnotation(typeDef, clazz);
processAllComplexTypeAttributes(typeDef, clazz);
processAllComplexTypeElements(typeDef, clazz);
fact.write(destDir, unit);
}
/**
* Super-type handling
*/
private static void processComplexTypeBaseType(XSDComplexTypeDefinition typeDef, ASClassType clazz) {
XSDTypeDefinition baseType = typeDef.getBaseType();
if (!isXSDAnyType(baseType)) {
clazz.setSuperclass(typeName(baseType));
}
}
/**
* turn attrubutes defined by the complexType into class properties
*/
private static void processAllComplexTypeAttributes(XSDComplexTypeDefinition typeDef,
ASClassType clazz)
{
List attrs = typeDef.getAttributeContents();
for (Iterator i=attrs.iterator(); i.hasNext(); ) {
XSDAttributeGroupContent attrContent = (XSDAttributeGroupContent)i.next();
if (attrContent instanceof XSDAttributeUse) {
processComplexTypeAttribute((XSDAttributeUse)attrContent, clazz);
}
}
}
/**
* turn this particular attribute into a property on the given AS class
*/
private static void processComplexTypeAttribute(XSDAttributeUse attrUse,
ASClassType clazz)
{
XSDAttributeDeclaration attrDecl = attrUse.getAttributeDeclaration();
ASField field = clazz.newField(fieldName(attrDecl), Visibility.PUBLIC,
typeName(attrDecl.getTypeDefinition()));
String doc = findDocumentation(attrDecl.getAnnotation());
if (doc != null) {
field.setDocComment(doc);
}
}
/**
* create a field name based on the given attribute declaration
*/
private static String fieldName(XSDAttributeDeclaration attrDecl) {
// TODO: name sanitization etc.
return attrDecl.getName();
}
/**
* add any annotation on the given complexType as the documentation
* comment for the given AS class.
*/
private static void processComplexTypeAnnotation(XSDComplexTypeDefinition typeDef,
ASClassType clazz)
{
String doc = findDocumentation(typeDef.getAnnotation());
if (doc != null) {
clazz.setDocComment("\n"+doc+"\n");
}
}
/**
* attempt to extract simple text from the documentation element of the
* given annotation.
*/
private static String findDocumentation(XSDAnnotation annotation) {
if (annotation != null) {
List docs = annotation.getUserInformation();
for (Iterator i = docs.iterator(); i.hasNext(); ) {
// maybe we can do better..?
Element doc = (Element)i.next();
return preProcessComment(doc.getTextContent());
}
}
return null;
}
/**
* Strip initial whitespace from all lines in the given string, and
* return a string which starts each line with a single space character,
* ready to go into a javadoc comment.
*/
private static String preProcessComment(String text) {
return text.replaceFirst("\\A\\s*", " ").replaceAll("([\n\r])\\s+", "$1 ");
}
/**
* returns the name of the AS type that holds data for the given
* component of the schema
*/
private static String typeName(XSDNamedComponent named) {
if (named instanceof XSDSimpleTypeDefinition) {
XSDSimpleTypeDefinition simpleType = (XSDSimpleTypeDefinition)named;
return lookupTypeName(simpleType);
}
if (isXSDAnyType(named)) {
return "XML";
}
String pkgName = toPackageName(named.getTargetNamespace());
return pkgName + "." + named.getName();
}
private static boolean isXSDAnyType(XSDNamedComponent named) {
return "anyType".equals(named.getName())
&& named.getTargetNamespace().equals(SCHEMA_NAMESPACE);
}
private static String toPackageName(String targetNamespace) {
URI uri = URI.createURI(targetNamespace);
String name = reverseJoin(sanitize(uri.host().split("\\.")), ".");
if (uri.hasPath()) {
String path = uri.path().replaceAll("/+", "/").replaceFirst("\\A/", "");
name = name + "." + join(sanitize(path.split("/")), ".");
}
return name;
}
private static String[] sanitize(String[] strings) {
for (int i=0; i<strings.length; i++) {
strings[i] = sanitize(strings[i]);
}
return strings;
}
private static String sanitize(String string) {
StringBuffer result = new StringBuffer();
if (!Character.isJavaIdentifierStart(string.charAt(0))
&& Character.isJavaIdentifierPart(string.charAt(0)))
{
// e.g. if this fragment starts with a number, prefix
// it with an underscore to create a valid identifier
result.append("_");
}
for (int i=0; i<string.length(); i++) {
char c = string.charAt(i);
if (Character.isJavaIdentifierPart(c)) {
result.append(c);
} else {
result.append("_");
}
}
return result.toString();
}
private static String reverseJoin(String[] strings, String delimiter) {
StringBuffer result = new StringBuffer();
for (int i=strings.length-1; i>=0; i--) {
result.append(strings[i]);
if (i>0) {
result.append(delimiter);
}
}
return result.toString();
}
private static String join(String[] strings, String delimiter) {
StringBuffer result = new StringBuffer();
for (int i=0; i<strings.length; i++) {
if (i>0) {
result.append(delimiter);
}
result.append(strings[i]);
}
return result.toString();
}
/**
* tries to map XML Schema standard types to AS types
*/
private static String lookupTypeName(XSDSimpleTypeDefinition simpleType) {
if (simpleType == null) {
return null;
}
if (simpleType.getTargetNamespace().equals(SCHEMA_NAMESPACE)) {
if (simpleType.getName().equals("string")) {
return "String";
}
if (simpleType.getName().equals("int")) {
return "Number";
}
if (simpleType.getName().equals("float")) {
return "Number";
}
System.err.println("Unhandled type "+simpleType.getURI());
return null;
}
return lookupTypeName(simpleType.getBaseTypeDefinition());
}
/**
* adds all elements declared by the given complexType as properties
* to the given AS class
*/
private static void processAllComplexTypeElements(XSDComplexTypeDefinition typeDef,
ASClassType clazz)
{
XSDComplexTypeContent complexContent = typeDef.getContent();
if (complexContent instanceof XSDParticle) {
XSDParticle particle = (XSDParticle)complexContent;
XSDParticleContent particleContent = particle.getContent();
if (particleContent instanceof XSDModelGroup) {
XSDModelGroup modelGroup = (XSDModelGroup)particleContent;
if (modelGroup.getCompositor().equals(XSDCompositor.SEQUENCE_LITERAL)) {
processComplexTypeSequence(typeDef, modelGroup, clazz);
}
}
}
}
/**
* auxiliary function used by processAllComplexTypeElements() to handle
* the xs:sequence within an xs:complexType
*/
private static void processComplexTypeSequence(XSDComplexTypeDefinition typeDef,
XSDModelGroup modelGroup,
ASClassType clazz)
{
List particles = modelGroup.getParticles();
for (Iterator i=particles.iterator(); i.hasNext(); ) {
XSDParticle part = (XSDParticle)i.next();
XSDParticleContent partContent = part.getContent();
if (partContent instanceof XSDElementDeclaration) {
processComplexTypeElementDeclaration(part, (XSDElementDeclaration)partContent, clazz);
}
}
}
/**
* handles an xs:element within the xs:sequence of an xs:complexType
* by adding a property to the given AS class.
*/
private static void processComplexTypeElementDeclaration(XSDParticle part,
XSDElementDeclaration decl,
ASClassType clazz)
{
if (decl.isElementDeclarationReference()) {
decl = decl.getResolvedElementDeclaration();
}
XSDElementDeclaration listElement = getElementIfContainerForList(decl);
String typeName = null;
String doc = findDocumentation(decl.getAnnotation());
if (listElement != null) {
typeName = "Array";
if (doc == null) {
doc = "";
} else {
doc += "\n\n";
}
doc += " Elements of type [EMAIL PROTECTED] " + typeName(listElement.getType())+"}";
} else if (isMultiplyOccuring(part)) {
typeName = "Array";
if (doc == null) {
doc = "";
} else {
doc += "\n\n";
}
if (decl.getType() != null) {
doc += "Elements of type [EMAIL PROTECTED] " + typeName(decl.getType()) + "}\n";
}
doc += "minOccurs "+describeMultiplicity(part.getMinOccurs())+", maxOccurs "+describeMultiplicity(part.getMaxOccurs());
} else if (decl.getType() != null) {
typeName = typeName(decl.getType());
}
if (typeName == null) {
System.err.println("no AS type resulted from: "+decl.getType());
}
ASField field = clazz.newField(fieldName(decl), Visibility.PUBLIC, typeName);
if (doc != null) {
field.setDocComment(doc);
}
}
/**
* returns the name of the AS field which should hold values of the
* given element declaration.
*/
private static String fieldName(XSDElementDeclaration decl) {
// TODO: sanitise value, etc.
return decl.getName();
}
/**
* If the given element appears to be implementing a list-container
* design pattern then return the definition of the element repeated in
* the list
*/
private static XSDElementDeclaration getElementIfContainerForList(XSDElementDeclaration decl) {
XSDTypeDefinition typeDef = decl.getAnonymousTypeDefinition();
if (typeDef == null) {
return null;
}
// look for the definition pattern which allows documents like:
// ...<eggs><egg/><egg/><egg/></eggs>...
// so that we can add a 'eggs' array to the defining class,
// rather than creating a useless 'Eggs' class which just holds
// the array.
// TODO: check for attrs on container,
XSDParticle ctype = typeDef.getComplexType();
XSDParticleContent particleContent = ctype.getContent();
if (particleContent instanceof XSDModelGroup) {
XSDModelGroup modelGroup = (XSDModelGroup)particleContent;
if (modelGroup.getCompositor().equals(XSDCompositor.SEQUENCE_LITERAL)) {
List particles = modelGroup.getParticles();
if (particles.size() == 1) {
XSDParticle part = (XSDParticle)particles.get(0);
XSDParticleContent partContent = part.getContent();
if (partContent instanceof XSDElementDeclaration) {
if (isMultiplyOccuring(part)) {
return (XSDElementDeclaration)partContent;
}
}
}
}
}
return null;
}
/**
* returns true if the given particle's maxOccurs attribute is either
* 'unbounded', or an integer greater than 1.
*/
private static boolean isMultiplyOccuring(XSDParticle part) {
int max = part.getMaxOccurs();
return max == -1 || max > 1; // -1 : unbounded
}
/**
* returns a text string with either the value "unbounded" if the
* given integer is -1, or the decimal representation of the given
* integer otherwise.
*/
private static String describeMultiplicity(int occurs) {
if (occurs == -1) {
return "unbounded";
} else {
return String.valueOf(occurs);
}
}
// ---- marshaling / unmarshaling ----
private static void processSchemaCreateUnmarshaler(XSDSchema schema) throws IOException {
ASSourceFactory fact = new ASSourceFactory();
CompilationUnit unit = fact.newClass(toPackageName(schema.getTargetNamespace())+".Unmarshaler");
ASClassType clazz = (ASClassType)unit.getType();
List types = schema.getTypeDefinitions();
for (Iterator i = types.iterator(); i.hasNext(); ) {
XSDTypeDefinition typeDef = (XSDTypeDefinition)i.next();
if (typeDef instanceof XSDComplexTypeDefinition) {
XSDComplexTypeDefinition ctype = (XSDComplexTypeDefinition)typeDef;
String typeName = typeName(ctype);
ASMethod meth = clazz.newMethod(unmarshalerMethodFor(ctype), Visibility.PUBLIC, typeName(ctype));
meth.addParam("thisElement", "XML");
meth.addStmt("var _result:"+typeName+" = new "+typeName+"()");
processComplexTypeBaseTypeUnmarshal(ctype, meth);
processAllComplexTypeAttributesUnmarshal(ctype, meth);
processAllComplexTypeElementsUnmarshal(ctype, meth);
meth.addStmt("return _result");
}
}
fact.write(destDir, unit);
}
private static void processComplexTypeBaseTypeUnmarshal(XSDComplexTypeDefinition ctype, ASMethod meth) {
XSDTypeDefinition baseType = ctype.getBaseType();
if (!isXSDAnyType(baseType)) {
// TODO: handle base class initialisation
}
}
private static void processAllComplexTypeAttributesUnmarshal(XSDComplexTypeDefinition ctype, ASMethod meth) {
List attrs= ctype.getAttributeContents();
for (Iterator i=attrs.iterator(); i.hasNext(); ) {
XSDAttributeGroupContent attrContent = (XSDAttributeGroupContent)i.next();
if (attrContent instanceof XSDAttributeUse) {
processComplexTypeAttributeUnmarshal((XSDAttributeUse)attrContent, meth);
}
}
}
private static void processComplexTypeAttributeUnmarshal(XSDAttributeUse attrUse, ASMethod meth) {
XSDAttributeDeclaration attrDecl = attrUse.getAttributeDeclaration();
String accessExpr = "thisElement."+attrAccess(attrDecl);
accessExpr = doBasicTypeCoercion(accessExpr, attrDecl.getType());
meth.addStmt("_result."+fieldName(attrDecl)+" = "+accessExpr);
}
private static String doBasicTypeCoercion(String expr, XSDTypeDefinition type) {
if (typeIsA(type, SCHEMA_NAMESPACE, "string")) {
return expr;
}
if (typeIsA(type, SCHEMA_NAMESPACE, "int")
||typeIsA(type, SCHEMA_NAMESPACE, "float")) {
return "Number("+expr+")";
}
System.err.println("Unable to produce type convertion expression for "+type.getURI());
return expr+" /* <-- didn't know how to convert */";
}
private static boolean typeIsA(XSDTypeDefinition type, String namespace, String name) {
return (type.getName().equals(name) && type.getTargetNamespace().equals(namespace))
|| XSDSchemaQueryTools.isTypeDerivedFrom(type, namespace, name);
}
private static String attrAccess(XSDAttributeDeclaration attrDecl) {
if (isValidIdent(attrDecl.getName())) {
return "@" + attrDecl.getName();
}
return "@[" + ASSourceFactory.str(attrDecl.getName()) + "]";
}
private static boolean isValidIdent(String name) {
if (!Character.isJavaIdentifierStart(name.charAt(0))) {
return false;
}
for (int i=1; i<name.length(); i++) {
if (!Character.isJavaIdentifierPart(name.charAt(0))) {
return false;
}
}
return true;
}
private static void processAllComplexTypeElementsUnmarshal(XSDComplexTypeDefinition typeDef,
ASMethod meth)
{
XSDComplexTypeContent complexContent = typeDef.getContent();
if (complexContent instanceof XSDParticle) {
XSDParticle particle = (XSDParticle)complexContent;
XSDParticleContent particleContent = particle.getContent();
if (particleContent instanceof XSDModelGroup) {
XSDModelGroup modelGroup = (XSDModelGroup)particleContent;
if (modelGroup.getCompositor().equals(XSDCompositor.SEQUENCE_LITERAL)) {
processComplexTypeSequenceUnmarshal(typeDef, modelGroup, meth);
}
}
}
}
private static void processComplexTypeSequenceUnmarshal(XSDComplexTypeDefinition typeDef,
XSDModelGroup modelGroup,
ASMethod meth)
{
List particles = modelGroup.getParticles();
if (!particles.isEmpty()) {
//meth.addComment(" process child elements,");
meth.addStmt("var _children:XMLList = thisElement.children()");
meth.addStmt("var _seq:int = 0");
}
for (Iterator i=particles.iterator(); i.hasNext(); ) {
StatementContainer block = meth;
XSDParticle part = (XSDParticle)i.next();
XSDParticleContent partContent = part.getContent();
if (partContent instanceof XSDElementDeclaration) {
XSDElementDeclaration elementDecl = (XSDElementDeclaration)partContent;
if (elementDecl.isElementDeclarationReference()) {
elementDecl = elementDecl.getResolvedElementDeclaration();
}
if (isOptional(part)) {
block = meth.newIf("_children[_seq].name()==new QName("+ASSourceFactory.str(elementDecl.getTargetNamespace())+", "+ASSourceFactory.str(elementDecl.getName()));
}
processComplexTypeSequenceElementDeclarationUnmarshal(part, elementDecl, block);
} else {
meth.addStmt("_seq++");
}
}
}
private static boolean isOptional(XSDParticle part) {
return part.getMinOccurs()==0 && part.getMaxOccurs()==1;
}
private static void processComplexTypeSequenceElementDeclarationUnmarshal(XSDParticle part,
XSDElementDeclaration decl,
StatementContainer block)
{
String accessExpr = "_children[_seq++]";
XSDTypeDefinition typeDef = decl.getType();
String fieldAccess = "_result."+fieldName(decl);
if (typeDef instanceof XSDSimpleTypeDefinition) {
accessExpr = doBasicTypeCoercion(accessExpr+".text()", typeDef);
block.addStmt(fieldAccess+" = "+accessExpr);
} else {
XSDElementDeclaration listElement = getElementIfContainerForList(decl);
if (listElement == null) {
String unmarshaled = unmarshaledFrom(typeDef, accessExpr);
if (isMultiplyOccuring(part)) {
block.addStmt(fieldAccess+" = new Array()");
block = block.newWhile("_seq<_children.length() && _children[_seq].name()==new QName("+ASSourceFactory.str(decl.getTargetNamespace())+", "+ASSourceFactory.str(decl.getName())+")");
block.addStmt(fieldAccess+".push("+unmarshaled+")");
} else {
block.addStmt(fieldAccess+" = "+unmarshaled);
}
} else {
block.addStmt(fieldAccess + " = new Array()");
StatementContainer loop = block.newForEachIn("var _child:XML", accessExpr+".elements()");
String unmarshaled = unmarshaledFrom(listElement.getType(), "_child");
loop.addStmt(fieldAccess + ".push("+unmarshaled+")");
}
}
}
private static String unmarshaledFrom(XSDTypeDefinition typeDef, String expr) {
if (isXSDAnyType(typeDef)) {
// result is just the XML node
return expr;
}
return unmarshalerMethodFor(typeDef)+"("+expr+")";
}
private static String unmarshalerMethodFor(XSDTypeDefinition typeDef) {
return "unmarshal"+typeDef.getName();
}
}_______________________________________________
osflash mailing list
[email protected]
http://osflash.org/mailman/listinfo/osflash_osflash.org