[Practicalxml-commits] SF.net SVN: practicalxml:[60] trunk/src
Brought to you by:
kdgregory
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-24 00:33:04
|
Revision: 60 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=60&view=rev Author: kdgregory Date: 2008-12-24 00:31:26 +0000 (Wed, 24 Dec 2008) Log Message: ----------- SchemaUtil.newSchema(): bug 2417477 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-20 12:35:23 UTC (rev 59) +++ trunk/src/main/java/net/sf/practicalxml/SchemaUtil.java 2008-12-24 00:31:26 UTC (rev 60) @@ -4,28 +4,69 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; - -import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import org.apache.commons.lang.StringUtils; + /** * A collection of static utility methods for working with XML Schema * 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 SCHEMA_PREFIX = "xsd"; - private final static String SCHEMA_ROOT = "schema"; + private final static String EL_SCHEMA = "schema"; + private final static String ATTR_TARGET_NS = "targetNamespace"; /** @@ -42,44 +83,34 @@ /** - * A workaround for Sun bug <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6198705"> - * 6198705</code>, also known as Xerces bug <a href="http://issues.apache.org/jira/browse/XERCESJ-1130"> - * XERCESJ-1130</code>, which prevents the <code>SchemaFactory.ewSchema</code> - * method from combining multiple sources that have the same namespace. - * <p> - * This method works by parsing the children of the passed sources, - * verifying that their root element is <code><xsd:schema></code>, - * combining the children of this root into a new document, and calling - * <code>newSchema()</code> on the result. + * 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. * * @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. * - * @throws XmlException on any failure that is not handled by the - * factory's <code>ErrorHandler</code>. This includes parse - * errors and local validation of the source root element. + * @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). */ public static Schema newSchema(SchemaFactory factory, InputSource... sources) { - Element combined = DomUtil.newDocument(SCHEMA_NS, SCHEMA_PREFIX + ":" + SCHEMA_ROOT); - for (InputSource source : 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++) { - combineSchema(combined, source); + combine(dom, parse(sources[ii])); } - - try - { - synchronized (factory) - { - return factory.newSchema(new DOMSource(combined.getOwnerDocument())); - } - } - catch (SAXException e) - { - throw new XmlException("unable to generate schema", e); - } + return generate(factory, dom); } @@ -88,31 +119,65 @@ //---------------------------------------------------------------------------- /** - * Parses, verifies, and combines a source document. + * 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" */ - private static void combineSchema(Element newRoot, InputSource source) + private static Document parse(InputSource source) { - Document doc = ParseUtil.parse(source); - NamedNodeMap attributeMap = doc.getDocumentElement().getAttributes(); - if (attributeMap != null) + Document dom = ParseUtil.parse(source); + Element root = dom.getDocumentElement(); + if (!DomUtil.isNamed(root, SCHEMA_NS, EL_SCHEMA)) { - for (int i=0; i<attributeMap.getLength(); i++) - { - Attr attribute = (Attr) attributeMap.item(i); - newRoot.setAttribute(attribute.getName(), attribute.getValue()); - } - } - Element root = doc.getDocumentElement(); - if (!DomUtil.isNamed(root, SCHEMA_NS, SCHEMA_ROOT)) - { throw new XmlException("invalid root element name: {" + root.getNamespaceURI() + "}" + DomUtil.getLocalName(root)); } - for (Element child : DomUtil.getChildren(root)) + return dom; + } + + + /** + * 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. + */ + private static void combine(Document dst, Document src) + { + Element srcRoot = src.getDocumentElement(); + Element dstRoot = dst.getDocumentElement(); + + 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 + "\""); + + for (Element child : DomUtil.getChildren(srcRoot)) { - Node tmp = newRoot.getOwnerDocument().importNode(child, true); - newRoot.appendChild(tmp); + Node tmp = dst.importNode(child, true); + dstRoot.appendChild(tmp); } } + + + /** + * Generates a new <code>Schema</code> object from a DOM document. + */ + private static Schema generate(SchemaFactory factory, Document dom) + { + try + { + synchronized (factory) + { + return factory.newSchema(new DOMSource(dom)); + } + } + 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-20 12:35:23 UTC (rev 59) +++ trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java 2008-12-24 00:31:26 UTC (rev 60) @@ -1,6 +1,5 @@ package net.sf.practicalxml; -import java.io.Reader; import java.io.StringReader; import javax.xml.validation.Schema; @@ -16,47 +15,19 @@ extends AbstractTestCase { //---------------------------------------------------------------------------- -// Definitions for newSchema() testing +// Support Code //---------------------------------------------------------------------------- - public final static String NEW_SCHEMA_START - = "<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"; - public final static String NEW_SCHEMA_DEF_1 - = "<xsd:element name=\"foo\" type=\"FooType\"/>"; - public final static String NEW_SCHEMA_DEF_2 - = "<xsd:complexType name=\"FooType\">" - + "<xsd:sequence>" - + "<xsd:element name=\"argle\" type=\"xsd:integer\"/>" - + "<xsd:element name=\"bargle\" type=\"BarType\"/>" - + "</xsd:sequence>" - + "</xsd:complexType>"; - - public final static String NEW_SCHEMA_DEF_3 - = "<xsd:simpleType name=\"BarType\">" - + "<xsd:restriction base=\"xsd:string\">" - + "</xsd:restriction>" - + "</xsd:simpleType>"; - - public final static String NEW_SCHEMA_FINISH - = "</xsd:schema>"; - - public final static String NEW_SCHEMA_WITH_NAMESPACE_START - = "<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" targetNamespace=\"http://foo/bar\" xmlns:bar=\"http://foo/bar\" elementFormDefault=\"qualified\">"; - - public final static String NEW_SCHEMA_WITH_NAMESPACE_DEF_1 - = "<xsd:element name=\"foo\" type=\"bar:FooType\"/>"; - - public final static String NEW_SCHEMA_WITH_NAMESPACE_DEF_2 - = "<xsd:complexType name=\"FooType\">" - + "<xsd:sequence>" - + "<xsd:element name=\"argle\" type=\"xsd:integer\"/>" - + "</xsd:sequence>" - + "</xsd:complexType>"; - //---------------------------------------------------------------------------- // Test Cases +// +// Note: since we can't examine a schema once it's compiled, we need to +// verify newSchema() operation with actual instance docs ... and +// to make more sense of this, each test case should define its +// schema and instance docs inline ... yes, that's a lot of repeated +// XML, but I think it will make verification easier //---------------------------------------------------------------------------- public void testNewFactory() throws Exception @@ -69,47 +40,173 @@ } - public void testNewSchemaWithSingleSource() throws Exception + public void testNewSchemaSingleSource() throws Exception { - Reader xsd = new StringReader( - NEW_SCHEMA_START - + NEW_SCHEMA_DEF_1 + NEW_SCHEMA_DEF_2 + NEW_SCHEMA_DEF_3 - + NEW_SCHEMA_FINISH); + String xsd = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'>" + + "<xsd:element name='foo' type='FooType'/>" + + "<xsd:complexType name='FooType'>" + + "<xsd:sequence>" + + "<xsd:element name='argle' type='xsd:integer'/>" + + "<xsd:element name='bargle' type='BarType'/>" + + "</xsd:sequence>" + + "</xsd:complexType>" + + "<xsd:simpleType name='BarType'>" + + "<xsd:restriction base='xsd:string'>" + + "</xsd:restriction>" + + "</xsd:simpleType>" + + "</xsd:schema>"; + String xml = "<foo>" + + "<argle>12</argle>" + + "<bargle>test</bargle>" + + "</foo>"; + Schema schema = SchemaUtil.newSchema( SchemaUtil.newFactory(new ExceptionErrorHandler()), - new InputSource(xsd)); + new InputSource(new StringReader(xsd))); + assertNotNull(schema); + ParseUtil.validatingParse(new InputSource(new StringReader(xml)), + schema, + new ExceptionErrorHandler()); } - public void testNewSchemaWithMultipleSources() throws Exception + public void testNewSchemaMultipleSources() throws Exception { - Reader xsd1 = new StringReader( - NEW_SCHEMA_START + NEW_SCHEMA_DEF_1 + NEW_SCHEMA_FINISH); - Reader xsd2 = new StringReader( - NEW_SCHEMA_START + NEW_SCHEMA_DEF_2 + NEW_SCHEMA_FINISH); - Reader xsd3 = new StringReader( - NEW_SCHEMA_START + NEW_SCHEMA_DEF_3 + NEW_SCHEMA_FINISH); + String xsd1 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'>" + + "<xsd:element name='foo' type='FooType'/>" + + "</xsd:schema>"; + String xsd2 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'>" + + "<xsd:complexType name='FooType'>" + + "<xsd:sequence>" + + "<xsd:element name='argle' type='xsd:integer'/>" + + "<xsd:element name='bargle' type='BarType'/>" + + "</xsd:sequence>" + + "</xsd:complexType>" + + "</xsd:schema>"; + String xsd3 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'>" + + "<xsd:simpleType name='BarType'>" + + "<xsd:restriction base='xsd:string'>" + + "</xsd:restriction>" + + "</xsd:simpleType>" + + "</xsd:schema>"; + String xml = "<foo>" + + "<argle>12</argle>" + + "<bargle>test</bargle>" + + "</foo>"; + Schema schema = SchemaUtil.newSchema( SchemaUtil.newFactory(new ExceptionErrorHandler()), - new InputSource(xsd1), - new InputSource(xsd2), - new InputSource(xsd3)); + new InputSource(new StringReader(xsd1)), + new InputSource(new StringReader(xsd2)), + new InputSource(new StringReader(xsd3))); + assertNotNull(schema); + ParseUtil.validatingParse(new InputSource(new StringReader(xml)), + schema, + new ExceptionErrorHandler()); } - - public void testNewSchemaWithNamespace() throws Exception + + + public void testNewSchemaSingleSourceWithNamespace() throws Exception { - Reader xsd = new StringReader( - NEW_SCHEMA_WITH_NAMESPACE_START - + NEW_SCHEMA_WITH_NAMESPACE_DEF_1 + NEW_SCHEMA_WITH_NAMESPACE_DEF_2 - + NEW_SCHEMA_FINISH); + 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 xml = "<foo xmlns='http://foo.example.com'>" + + "<argle>12</argle>" + + "</foo>"; + Schema schema = SchemaUtil.newSchema( SchemaUtil.newFactory(new ExceptionErrorHandler()), - new InputSource(xsd)); + new InputSource(new StringReader(xsd))); + assertNotNull(schema); + ParseUtil.validatingParse(new InputSource(new StringReader(xml)), + schema, + new ExceptionErrorHandler()); } + + + public void testNewSchemaMultipleSourceWithNamespace() throws Exception + { + 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( + SchemaUtil.newFactory(new ExceptionErrorHandler()), + new InputSource(new StringReader(xsd1)), + new InputSource(new StringReader(xsd2))); + + assertNotNull(schema); + ParseUtil.validatingParse(new InputSource(new StringReader(xml)), + schema, + new ExceptionErrorHandler()); + } + + + public void testNewSchemaFailMultipleNamepaces() throws Exception + { + String xsd1 = "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + + " xmlns:foo='http://foo.example.com'" + + " 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'" + + " targetNamespace='http://bar.example.com'" + + " elementFormDefault='qualified'" + + ">" + + "<xsd:complexType name='FooType'>" + + "<xsd:sequence>" + + "<xsd:element name='argle' type='xsd:integer'/>" + + "</xsd:sequence>" + + "</xsd:complexType>" + + "</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 + } + } } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |