[Practicalxml-commits] SF.net SVN: practicalxml:[62] trunk/src
Brought to you by:
kdgregory
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-28 15:56:13
|
Revision: 62 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=62&view=rev Author: kdgregory Date: 2008-12-28 15:56:10 +0000 (Sun, 28 Dec 2008) Log Message: ----------- Bug 2417477: SchemaUtil.newSchema() is for simple case, combineSchema() for bug workaround and ordering Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/SchemaUtil.java trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java Modified: trunk/src/main/java/net/sf/practicalxml/SchemaUtil.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/SchemaUtil.java 2008-12-28 00:46:35 UTC (rev 61) +++ trunk/src/main/java/net/sf/practicalxml/SchemaUtil.java 2008-12-28 15:56:10 UTC (rev 62) @@ -1,5 +1,9 @@ package net.sf.practicalxml; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeSet; import javax.xml.XMLConstants; import javax.xml.transform.dom.DOMSource; import javax.xml.validation.Schema; @@ -11,7 +15,7 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import org.apache.commons.lang.StringUtils; +import net.sf.practicalxml.util.ExceptionErrorHandler; /** @@ -19,54 +23,15 @@ * documents. Since <code>javax.xml.validation.Schema</code> is an * opaque object, most of these actually work with DOM documents that * contain an XSD. - * <p> - * <b>Behavior of {@link #newSchema newSchema()} and {@link - * #newSchemas newSchemas()}</b> - * <p> - * These methods exist to create a {@link javax.xml.validation.Schema} - * from an XML source. Their basic behavior is to hide the underlying - * factory objects, similar to {@link ParseUtil} and {@link OutputUtil}. - * However, also they support the creation of <code>Schema<code> objects - * from multiple sources, and in this have to deal with a - * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6198705"> - * bug in the JDK</a> (and also in - * <a href="http://issues.apache.org/jira/browse/XERCESJ-1130">Xerces</a>) - * that prevents the combination of schema documents that specify the same - * namespace (this also prevents <xs:import> from working). - * <p> - * Combining schema documents isn't an easy task, which is probably why the - * bug still isn't fixed. The top-level attributes (those attached to the - * <code><xs:schema></code> element) are the main problem, as they have - * to be consistent across the component schema docs. The approach used by - * {@link #newSchema newSchema()} is to take these attributes from the first - * source (for {@link #newSchemas newSchemas()}, the first source for a given - * namespace URI), and apply the following rules: - * <ul> - * <li><code>targetNamespace</code> - * <br>This must be the same for all source documents processed by {@link - * #newSchema newSchema()}; if not, it throws an exception. By comparison, - * {@link #newSchemas newSchemas()} will partition source documents by - * their value, so an incorrect source document will be silently assigned - * its own <code>Schema</code> object. - * <li><code>attributeFormDefault</code>, <code>elementFormDefault</code> - * <br>The first source document defines these attributes for the resulting - * <code>Schema</code> object. If later sources define different values, - * those values are ignored. This behavior largely reflects my view of - * how an XSD should be modularized (with a single <code>xsd:element - * </code> in the first source, type definitions in subsequent sources), - * but it is also driven by consistency in instance documents. As a side - * note, I strongly recommend that <code>elementFormDefault</code> be - * "qualified", as that allows the use of a default namespace in instance - * documents. - * <li>all other attributes - * <br>Are defined by the first source, and ignored on any subsequent sources. - * </ul> */ public class SchemaUtil { - private final static String SCHEMA_NS = XMLConstants.W3C_XML_SCHEMA_NS_URI; + private final static String NS_SCHEMA = XMLConstants.W3C_XML_SCHEMA_NS_URI; private final static String EL_SCHEMA = "schema"; + private final static String EL_IMPORT = "import"; private final static String ATTR_TARGET_NS = "targetNamespace"; + private final static String ATTR_IMPORT_NS = "namespace"; + private final static String ATTR_IMPORT_LOC = "schemaLocation"; /** @@ -83,34 +48,116 @@ /** - * Parses the passed input sources to produce a single <code>Schema</code> - * object. All sources must have the same <code>targetNamespace</code>; - * other top-level attributes will be taken from the first source. + * Parses one or more input sources to produce a <code>Schema</code> + * object. Validators produced from this schema will throw an + * {@link XmlException} on any error. + * <p> + * The caller is responsible for ordering the sources so that imported + * schemas appear first. This method is unable to combine sources from + * the same target namespace; see {@link #combineSchema combineSchema()} + * for explanation. * + * @param sources The source schema documents. Note that these are + * <code>org.xml.sax.InputSource</code> objects for + * consistency with other classes in this library; + * not the <code>javax.xml.transform.Source</code> + * objects used by <code>SchemaFactory</code>. + * + * @throws IllegalArgumentException if invoked without any sources. + * @throws XmlException if unable to create the schema object. + */ + public static Schema newSchema(InputSource... sources) + { + return newSchema(newFactory(new ExceptionErrorHandler()), sources); + } + + + /** + * Parses one or more input sources to produce a <code>Schema</code> + * object from the passed factory. This call is synchronized on the + * factory, which the JDK 1.5 docs describe as not threadsafe. + * <p> + * The caller is responsible for ordering the sources so that imported + * schemas appear first. This method is unable to combine sources from + * the same target namespace; see {@link #combineSchema combineSchema()} + * for explanation. + * + * @param factory Used to create the schema object. + * @param sources The source schema documents. Note that these are + * <code>org.xml.sax.InputSource</code> objects for + * consistency with other classes in this library; + * not the <code>javax.xml.transform.Source</code> + * objects used by <code>SchemaFactory</code>. + * + * @throws IllegalArgumentException if invoked without any sources. + * @throws XmlException if unable to parse a source or compile the schema. + */ + public static Schema newSchema(SchemaFactory factory, InputSource... sources) + { + Document[] parsed = parseSources(sources); + try + { + synchronized (factory) + { + return factory.newSchema(toDOMSources(parsed)); + } + } + catch (SAXException e) + { + throw new XmlException("unable to generate schema", e); + } + } + + + /** + * Parses one or more input sources to produce a <code>Schema</code> + * object. Unlike {@link #newSchema newSchema()}, this method will + * combine source documents with the same target namespace (a workaround + * for <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6198705"> + * JDK 1.5 bug 6198705</a>), order the source documents according to their + * dependencies, and also remove external location references from <code> + * <xsd:import></code> elements that reference provided sources. + * <p> + * When combining schema documents with the same namespace, all top-level + * attributes (eg, <code>elementFormDefault</code>) come from the first + * source specified for the namespace. The <code><xsd:schema></code> + * element, and its attributes, are ignored for subsequent sources for + * that namespace. + * <p> + * Sources must contain <code><xsd:import></code> elements for any + * referenced schemas. If the sources contain a schema for the specified + * target namespace, any <code>schemaLocation</code> specification will + * be ignored. + * * @param factory Used to create the schema object. This factory's * <code>ErrorHandler</code> is also used to report * errors when parsing the source documents. - * @param sources The source schema documents. + * @param sources The source schema documents. Note that these are + * <code>org.xml.sax.InputSource</code> objects for + * consistency with other classes in this library; + * not the <code>javax.xml.transform.Source</code> + * objects used by <code>SchemaFactory</code>. * * @throws IllegalArgumentException if invoked without any sources, or if - * a source does not have the target namespace established by the - * first source. - * @throws XmlException if the sources are not combinable, or on any - * failure that is not handled by the factory's <code>ErrorHandler - * </code> (including parse errors and local validation of the - * source root element). + * a source does not appear to be an XML Schema document (current + * checking is minimal, but that may change). + * @throws XmlException on any failure that is not handled by the factory's + * <code>ErrorHandler</code> (including parse errors). */ - public static Schema newSchema(SchemaFactory factory, InputSource... sources) + public static Schema combineSchema(SchemaFactory factory, InputSource... sources) { - if (sources.length == 0) - throw new IllegalArgumentException("must specify at least one source"); - - Document dom = parse(sources[0]); - for (int ii = 1 ; ii < sources.length ; ii++) + SchemaManager manager = new SchemaManager(parseSources(sources)); + try { - combine(dom, parse(sources[ii])); + synchronized (factory) + { + return factory.newSchema(manager.toDOMSources()); + } } - return generate(factory, dom); + catch (SAXException e) + { + throw new XmlException("unable to generate schema", e); + } } @@ -119,65 +166,190 @@ //---------------------------------------------------------------------------- /** - * Parses an XML document and performs some perfunctory checks to verify - * that it's an XML schema. Perhaps in the future we'll add a "validation - * level" property that at high levels validates against the "Schema for - * Schemas" + * Parses an array of <code>org.xml.sax.InputSource</code> objects, and + * performs some (minimal) level of validation on them. This method is + * called by <code>newSchema()</code> and <code>combineSchema()</code>, + * and will identify the source that wasn't valid. + * + * @throws IllegalArgumentException if no sources specified (this is + * a common check for callers). */ - private static Document parse(InputSource source) + public static Document[] parseSources(InputSource[] sources) { - Document dom = ParseUtil.parse(source); - Element root = dom.getDocumentElement(); - if (!DomUtil.isNamed(root, SCHEMA_NS, EL_SCHEMA)) + if (sources.length == 0) + throw new IllegalArgumentException("must specify at least one source"); + + Document[] result = new Document[sources.length]; + for (int ii = 0 ; ii < sources.length ; ii++) { - throw new XmlException("invalid root element name: {" - + root.getNamespaceURI() + "}" - + DomUtil.getLocalName(root)); + try + { + result[ii] = ParseUtil.parse(sources[ii]); + } + catch (XmlException ee) + { + throw new XmlException("unable to parse source " + ii, ee.getCause()); + } } - return dom; + + for (int ii = 0 ; ii < result.length ; ii++) + { + if (!DomUtil.isNamed(result[ii].getDocumentElement(), NS_SCHEMA, EL_SCHEMA)) + throw new XmlException("source " + ii + " does not appear to be an XSD"); + } + + return result; } + /** + * Wraps an array of DOM documents so that they can be processed by + * <code>SchemaFactory</code>. + */ + private static DOMSource[] toDOMSources(Document[] sources) + { + DOMSource[] result = new DOMSource[sources.length]; + for (int ii = 0 ; ii < sources.length ; ii++) + { + result[ii] = new DOMSource(sources[ii]); + } + return result; + } + /** - * Merges the second DOM object into the first, verifying that it (1) - * claims to be an XML Schema, and (2) has the same root attributes as - * the first document. + * This object is the brains behind {@link #combineSchema}. It is + * currently written for the quirks of the 1.5 JDK; if those quirks + * are different under 1.6, it will be moved into its own package and + * accessed via a factory. + * <p> + * Defined as protected -- as are internal methods -- so that it can be + * tested independently. */ - private static void combine(Document dst, Document src) + protected static class SchemaManager { - Element srcRoot = src.getDocumentElement(); - Element dstRoot = dst.getDocumentElement(); + private HashMap<String,Document> _documents = new HashMap<String,Document>(); - String srcTargetNS = srcRoot.getAttribute(ATTR_TARGET_NS); - String dstTargetNS = dstRoot.getAttribute(ATTR_TARGET_NS); - if (!StringUtils.equals(srcTargetNS, dstTargetNS)) - throw new IllegalArgumentException( - "cannot combine target namespaces: expected " - + "\"" + dstTargetNS + "\", was \"" + srcTargetNS + "\""); + public SchemaManager(Document[] sources) + { + for (int ii = 0 ; ii < sources.length ; ii++) + { + String srcNamespace = sources[ii].getDocumentElement().getAttribute(ATTR_TARGET_NS); + Document existing = _documents.get(srcNamespace); + if (existing != null) + merge(existing, sources[ii]); + else + _documents.put(srcNamespace, sources[ii]); + } + } - for (Element child : DomUtil.getChildren(srcRoot)) + /** + * Returns the ordered set of sources for this manager. + */ + public DOMSource[] toDOMSources() { - Node tmp = dst.importNode(child, true); - dstRoot.appendChild(tmp); + TreeSet<Document> ordered = new TreeSet<Document>(new SchemaComparator()); + for (Document doc : _documents.values()) + { + ordered.add(rebuildImports(doc)); + } + + return SchemaUtil.toDOMSources(ordered.toArray(new Document[ordered.size()])); } + + /** + * Merges one schema document into another. + */ + protected void merge(Document dst, Document src) + { + Element dstRoot = dst.getDocumentElement(); + Element srcRoot = src.getDocumentElement(); + for (Element child : DomUtil.getChildren(srcRoot)) + { + Node tmp = dst.importNode(child, true); + dstRoot.appendChild(tmp); + } + } + + /** + * Rebuilds the <code>import</code> statements for the passed + * document, removing duplicates and clearing locations for + * namespaces that are known to this manager. Returns the + * cleaned document. + */ + protected Document rebuildImports(Document doc) + { + Map<String,String> imports = new HashMap<String,String>(); + Element root = doc.getDocumentElement(); + for (Element imp : DomUtil.getChildren(root, NS_SCHEMA, EL_IMPORT)) + { + String namespace = imp.getAttribute(ATTR_IMPORT_NS); + String location = imp.getAttribute(ATTR_IMPORT_LOC); + if (_documents.containsKey(namespace)) + location = null; + imports.put(namespace, location); + root.removeChild(imp); + } + + for (String namespace : imports.keySet()) + { + String location = imports.get(namespace); + Element newImport = doc.createElementNS(NS_SCHEMA, EL_IMPORT); + newImport.setAttribute(ATTR_IMPORT_NS, namespace); + if (location != null) + newImport.setAttribute(ATTR_IMPORT_LOC, location); + root.insertBefore(newImport, root.getFirstChild()); + } + return doc; + } } /** - * Generates a new <code>Schema</code> object from a DOM document. + * Compares two schema documents: one schema is greater-than another if + * it imports the other's namespace. To impose a total ordering, schemas + * that don't have interdependencies are ordered based on their target + * namespace URLs, and schemas with no target namespace are greater-than + * those with (since they cannot be imported). + * <p> + * Defined as protected so that it can be tested independently. */ - private static Schema generate(SchemaFactory factory, Document dom) + protected static class SchemaComparator + implements Comparator<Document> { - try + public int compare(Document o1, Document o2) { - synchronized (factory) + if (o1 == o2) + return 0; + + Element root1 = o1.getDocumentElement(); + String namespace1 = root1.getAttribute(ATTR_TARGET_NS); + + Element root2 = o2.getDocumentElement(); + String namespace2 = root2.getAttribute(ATTR_TARGET_NS); + + if (namespace1.equals(namespace2)) + return 0; + else if ("".equals(namespace1)) + return 1; + else if ("".equals(namespace2)) + return -1; + + if (isImportedBy(namespace1, root2)) + return -1; + else if (isImportedBy(namespace2, root1)) + return 1; + + return namespace1.compareTo(namespace2); + } + + private boolean isImportedBy(String namespace, Element root) + { + for (Element imp : DomUtil.getChildren(root, NS_SCHEMA, EL_IMPORT)) { - return factory.newSchema(new DOMSource(dom)); + if (namespace.equals(imp.getAttribute(ATTR_IMPORT_NS))) + return true; } + return false; } - catch (SAXException e) - { - throw new XmlException("unable to generate schema", e); - } } } Modified: trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java 2008-12-28 00:46:35 UTC (rev 61) +++ trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java 2008-12-28 15:56:10 UTC (rev 62) @@ -1,12 +1,17 @@ package net.sf.practicalxml; import java.io.StringReader; +import java.util.Comparator; +import java.util.List; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; +import net.sf.practicalxml.SchemaUtil.SchemaManager; import net.sf.practicalxml.util.ExceptionErrorHandler; +import org.w3c.dom.Document; +import org.w3c.dom.Element; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; @@ -15,13 +20,7 @@ extends AbstractTestCase { //---------------------------------------------------------------------------- -// Support Code -//---------------------------------------------------------------------------- - - - -//---------------------------------------------------------------------------- -// Test Cases +// Test Cases for public methods // // Note: since we can't examine a schema once it's compiled, we need to // verify newSchema() operation with actual instance docs ... and @@ -61,9 +60,57 @@ + "<bargle>test</bargle>" + "</foo>"; + Schema schema = SchemaUtil.newSchema(new InputSource(new StringReader(xsd))); + + assertNotNull(schema); + ParseUtil.validatingParse(new InputSource(new StringReader(xml)), + schema, + new ExceptionErrorHandler()); + } + + + public void testNewSchemaMultipleNamepaces() throws Exception + { + String xsd1 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " targetNamespace='http://foo.example.com'" + + " xmlns:bar='http://bar.example.com'" + + " elementFormDefault='qualified'" + + ">" + + "<xsd:import namespace='http://bar.example.com'/>" + + "<xsd:element name='foo' type='bar:FooType'/>" + + "</xsd:schema>"; + String xsd2 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " targetNamespace='http://bar.example.com'" + + " xmlns:baz='http://baz.example.com'" + + " elementFormDefault='qualified'" + + ">" + + "<xsd:import namespace='http://baz.example.com'/>" + + "<xsd:complexType name='FooType'>" + + "<xsd:sequence>" + + "<xsd:element name='argle' type='xsd:integer'/>" + + "<xsd:element name='bargle' type='baz:BarType'/>" + + "</xsd:sequence>" + + "</xsd:complexType>" + + "</xsd:schema>"; + String xsd3 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " targetNamespace='http://baz.example.com'" + + " elementFormDefault='qualified'" + + ">" + + "<xsd:simpleType name='BarType'>" + + "<xsd:restriction base='xsd:string'>" + + "</xsd:restriction>" + + "</xsd:simpleType>" + + "</xsd:schema>"; + + String xml = "<foo xmlns='http://foo.example.com'>" + + "<argle xmlns='http://bar.example.com'>12</argle>" + + "<bargle xmlns='http://bar.example.com'>12</bargle>" + + "</foo>"; + Schema schema = SchemaUtil.newSchema( - SchemaUtil.newFactory(new ExceptionErrorHandler()), - new InputSource(new StringReader(xsd))); + new InputSource(new StringReader(xsd3)), + new InputSource(new StringReader(xsd2)), + new InputSource(new StringReader(xsd1))); assertNotNull(schema); ParseUtil.validatingParse(new InputSource(new StringReader(xml)), @@ -72,8 +119,72 @@ } - public void testNewSchemaMultipleSources() throws Exception + public void testNewSchemaFailNoSources() throws Exception { + try + { + SchemaUtil.newSchema(); + fail("no sources, no exception"); + } + catch (IllegalArgumentException ee) + { + // success + } + } + + + public void testNewSchemaFailInvalidDocument() throws Exception + { + // looks right, but no namespace definition + String xsd = "<schema>" + + "<element name='foo' type='FooType'/>" + + "<complexType name='FooType'>" + + "<sequence>" + + "<element name='argle' type='xsd:integer'/>" + + "</sequence>" + + "</complexType>" + + "</schema>"; + + try + { + SchemaUtil.newSchema(new InputSource(new StringReader(xsd))); + fail("created schema from an invalid document"); + } + catch (XmlException ee) + { + assertTrue(ee.getMessage().contains("source 0")); + } + } + + + public void testNewSchemaFailUnparsableSource() throws Exception + { + String xsd1 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " targetNamespace='http://foo.example.com'" + + " xmlns:bar='http://bar.example.com'" + + " elementFormDefault='qualified'" + + ">" + + "<xsd:import namespace='http://bar.example.com'/>" + + "<xsd:element name='foo' type='bar:FooType'/>" + + "</xsd:schema>"; + String xsd2 = "this isn't XML"; + + try + { + SchemaUtil.newSchema( + new InputSource(new StringReader(xsd1)), + new InputSource(new StringReader(xsd2))); + fail("parsed an invalid XML document"); + } + catch (XmlException ee) + { + assertTrue(ee.getMessage().contains("source 1")); + } + } + + + public void testCombineSchemaMultipleSourceNoNamespace() throws Exception + { String xsd1 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'>" + "<xsd:element name='foo' type='FooType'/>" + "</xsd:schema>"; @@ -97,7 +208,7 @@ + "<bargle>test</bargle>" + "</foo>"; - Schema schema = SchemaUtil.newSchema( + Schema schema = SchemaUtil.combineSchema( SchemaUtil.newFactory(new ExceptionErrorHandler()), new InputSource(new StringReader(xsd1)), new InputSource(new StringReader(xsd2)), @@ -110,28 +221,34 @@ } - public void testNewSchemaSingleSourceWithNamespace() throws Exception + public void testCombineSchemaMultipleSourceSingleNamespace() throws Exception { - String xsd = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" - + " xmlns='http://foo.example.com'" - + " targetNamespace='http://foo.example.com'" - + " elementFormDefault='qualified'" - + ">" - + "<xsd:element name='foo' type='FooType'/>" - + "<xsd:complexType name='FooType'>" - + "<xsd:sequence>" - + "<xsd:element name='argle' type='xsd:integer'/>" - + "</xsd:sequence>" - + "</xsd:complexType>" - + "</xsd:schema>"; + String xsd1 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " xmlns='http://foo.example.com'" + + " targetNamespace='http://foo.example.com'" + + " elementFormDefault='qualified'" + + ">" + + "<xsd:element name='foo' type='FooType'/>" + + "</xsd:schema>"; + String xsd2 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " targetNamespace='http://foo.example.com'" + + " elementFormDefault='qualified'" + + ">" + + "<xsd:complexType name='FooType'>" + + "<xsd:sequence>" + + "<xsd:element name='argle' type='xsd:integer'/>" + + "</xsd:sequence>" + + "</xsd:complexType>" + + "</xsd:schema>"; String xml = "<foo xmlns='http://foo.example.com'>" + "<argle>12</argle>" + "</foo>"; - Schema schema = SchemaUtil.newSchema( + Schema schema = SchemaUtil.combineSchema( SchemaUtil.newFactory(new ExceptionErrorHandler()), - new InputSource(new StringReader(xsd))); + new InputSource(new StringReader(xsd1)), + new InputSource(new StringReader(xsd2))); assertNotNull(schema); ParseUtil.validatingParse(new InputSource(new StringReader(xml)), @@ -140,34 +257,50 @@ } - public void testNewSchemaMultipleSourceWithNamespace() throws Exception + public void testCombineSchemaMultipleSourceMultipleNamepaces() throws Exception { String xsd1 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" - + " xmlns='http://foo.example.com'" + " targetNamespace='http://foo.example.com'" + + " xmlns:bar='http://bar.example.com'" + " elementFormDefault='qualified'" + ">" - + "<xsd:element name='foo' type='FooType'/>" + + "<xsd:import namespace='http://bar.example.com'/>" + + "<xsd:element name='foo' type='bar:FooType'/>" + "</xsd:schema>"; String xsd2 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" - + " targetNamespace='http://foo.example.com'" + + " targetNamespace='http://bar.example.com'" + + " xmlns:baz='http://baz.example.com'" + " elementFormDefault='qualified'" + ">" + + "<xsd:import namespace='http://baz.example.com'/>" + "<xsd:complexType name='FooType'>" + "<xsd:sequence>" - + "<xsd:element name='argle' type='xsd:integer'/>" + + "<xsd:element name='argle' type='xsd:integer'/>" + + "<xsd:element name='bargle' type='baz:BarType'/>" + "</xsd:sequence>" + "</xsd:complexType>" + "</xsd:schema>"; + String xsd3 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " targetNamespace='http://baz.example.com'" + + " elementFormDefault='qualified'" + + ">" + + "<xsd:simpleType name='BarType'>" + + "<xsd:restriction base='xsd:string'>" + + "</xsd:restriction>" + + "</xsd:simpleType>" + + "</xsd:schema>"; String xml = "<foo xmlns='http://foo.example.com'>" - + "<argle>12</argle>" + + "<argle xmlns='http://bar.example.com'>12</argle>" + + "<bargle xmlns='http://bar.example.com'>12</bargle>" + "</foo>"; - Schema schema = SchemaUtil.newSchema( + // note: these sources are intentionally out-of-order + Schema schema = SchemaUtil.combineSchema( SchemaUtil.newFactory(new ExceptionErrorHandler()), new InputSource(new StringReader(xsd1)), - new InputSource(new StringReader(xsd2))); + new InputSource(new StringReader(xsd2)), + new InputSource(new StringReader(xsd3))); assertNotNull(schema); ParseUtil.validatingParse(new InputSource(new StringReader(xml)), @@ -176,37 +309,160 @@ } - public void testNewSchemaFailMultipleNamepaces() throws Exception +//---------------------------------------------------------------------------- +// Test cases for internals +//---------------------------------------------------------------------------- + + public void testSchemaManagerMerge() throws Exception { - String xsd1 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" - + " xmlns:foo='http://foo.example.com'" + Document[] docs = new Document[] + { + ParseUtil.parse( + "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + " targetNamespace='http://foo.example.com'" + + ">" + + "<xsd:import namespace='http://bar.example.com'" + + " schemaLocation='http://bar.example.com'/>" + + "<xsd:element name='foo' type='bar:FooType'/>" + + "</xsd:schema>"), + ParseUtil.parse( + "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " targetNamespace='http://foo.example.com'" + + ">" + + "<xsd:complexType name='FooType'>" + + "<xsd:sequence>" + + "<xsd:element name='argle' type='xsd:integer'/>" + + "</xsd:sequence>" + + "</xsd:complexType>" + + "</xsd:schema>") + }; + + Element doc0Root = docs[0].getDocumentElement(); + List<Element> childrenBeforeManagement = DomUtil.getChildren(doc0Root); + assertEquals(2, childrenBeforeManagement.size()); + + new SchemaUtil.SchemaManager(docs); + List<Element> childrenAfterManagement = DomUtil.getChildren(doc0Root); + assertEquals(3, childrenAfterManagement.size()); + } + + + public void testSchemaManagerImportRebuild() throws Exception + { + Document[] docs = new Document[] + { + ParseUtil.parse( + "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " targetNamespace='http://foo.example.com'" + + ">" + + "<xsd:import namespace='http://bar.example.com'" + + " schemaLocation='http://bar.example.com'/>" + + "<xsd:element name='foo' type='bar:FooType'/>" + + "</xsd:schema>"), + ParseUtil.parse( + "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " targetNamespace='http://bar.example.com'" + + ">" + + "<xsd:complexType name='FooType'>" + + "<xsd:sequence>" + + "<xsd:element name='argle' type='xsd:integer'/>" + + "</xsd:sequence>" + + "</xsd:complexType>" + + "</xsd:schema>") + }; + SchemaManager manager = new SchemaUtil.SchemaManager(docs); + + Document rebuilt = manager.rebuildImports(docs[0]); + List<Element> imports = DomUtil.getChildren( + rebuilt.getDocumentElement(), + "http://www.w3.org/2001/XMLSchema", + "import"); + assertEquals(1, imports.size()); + + Element imp = imports.get(0); + assertEquals("http://bar.example.com", imp.getAttribute("namespace")); + assertEquals("", imp.getAttribute("schemaLocation")); + } + + + public void testSchemaComparator() throws Exception + { + Document doc1 = ParseUtil.parse( + "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " targetNamespace='http://foo.example.com'" + " elementFormDefault='qualified'" + ">" - + "<xsd:element name='foo' type='foo:FooType'/>" - + "</xsd:schema>"; - String xsd2 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + "<xsd:import namespace='http://bar.example.com'/>" + + "<xsd:element name='foo' type='bar:FooType'/>" + + "</xsd:schema>"); + Document doc1b = ParseUtil.parse( + "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " targetNamespace='http://foo.example.com'" + + " elementFormDefault='qualified'" + + ">" + + "<xsd:import namespace='http://bar.example.com'/>" + + "<xsd:element name='foo' type='bar:FooType'/>" + + "</xsd:schema>"); + Document doc2 = ParseUtil.parse( + "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + " targetNamespace='http://bar.example.com'" + + " xmlns:baz='http://bar.example.com'" + " elementFormDefault='qualified'" + ">" + + "<xsd:import namespace='http://baz.example.com'/>" + "<xsd:complexType name='FooType'>" + "<xsd:sequence>" + "<xsd:element name='argle' type='xsd:integer'/>" + + "<xsd:element name='bargle' type='baz:BarType'/>" + "</xsd:sequence>" + "</xsd:complexType>" - + "</xsd:schema>"; + + "</xsd:schema>"); + Document doc3 = ParseUtil.parse( + "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " targetNamespace='http://baz.example.com'" + + " elementFormDefault='qualified'" + + ">" + + "<xsd:simpleType name='BarType'>" + + "<xsd:restriction base='xsd:string'>" + + "</xsd:restriction>" + + "</xsd:simpleType>" + + "</xsd:schema>"); + Document doc4 = ParseUtil.parse( + "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " elementFormDefault='qualified'" + + ">" + + "<xsd:simpleType name='ZippyType'>" + + "<xsd:restriction base='xsd:string'>" + + "</xsd:restriction>" + + "</xsd:simpleType>" + + "</xsd:schema>"); + Document doc4b = ParseUtil.parse( + "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " elementFormDefault='qualified'" + + ">" + + "<xsd:simpleType name='ZippyType'>" + + "<xsd:restriction base='xsd:string'>" + + "</xsd:restriction>" + + "</xsd:simpleType>" + + "</xsd:schema>"); - try - { - SchemaUtil.newSchema( - SchemaUtil.newFactory(new ExceptionErrorHandler()), - new InputSource(new StringReader(xsd1)), - new InputSource(new StringReader(xsd2))); - fail("combined schemas with different target namespaces"); - } - catch (IllegalArgumentException ee) - { - // success - } + Comparator<Document> comparator = new SchemaUtil.SchemaComparator(); + + // import relationship + assertTrue(comparator.compare(doc1, doc2) > 0); + assertTrue(comparator.compare(doc2, doc1) < 0); + + // target namespace + assertTrue(comparator.compare(doc1, doc3) > 0); + assertTrue(comparator.compare(doc3, doc1) < 0); + + // target namespace versus none + assertTrue(comparator.compare(doc1, doc4) < 0); + assertTrue(comparator.compare(doc4, doc1) > 0); + + // equality + assertTrue(comparator.compare(doc1, doc1) == 0); + assertTrue(comparator.compare(doc1, doc1b) == 0); + assertTrue(comparator.compare(doc4, doc4b) == 0); } } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |