[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.
|