Thread: [Practicalxml-commits] SF.net SVN: practicalxml:[28] trunk/src
Brought to you by:
kdgregory
From: Auto-Generated S. C. M. <pra...@li...> - 2008-10-08 00:08:43
|
Revision: 28 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=28&view=rev Author: kdgregory Date: 2008-10-08 00:08:37 +0000 (Wed, 08 Oct 2008) Log Message: ----------- SchemaUtil: initial revision Added Paths: ----------- trunk/src/main/java/net/sf/practicalxml/SchemaUtil.java trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java Added: trunk/src/main/java/net/sf/practicalxml/SchemaUtil.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/SchemaUtil.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/SchemaUtil.java 2008-10-08 00:08:37 UTC (rev 28) @@ -0,0 +1,109 @@ +package net.sf.practicalxml; + +import javax.xml.XMLConstants; +import javax.xml.transform.dom.DOMSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + + +/** + * 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. + */ +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"; + + + /** + * Creates a new <code>javax.xml.validation.SchemaFactory</code> instance + * for validation with XML Schema, which reports errors via the specified + * error handler. + */ + public synchronized static SchemaFactory newFactory(ErrorHandler errHandler) + { + SchemaFactory fact = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + fact.setErrorHandler(errHandler); + return fact; + } + + + /** + * 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. + * + * @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. + */ + public static Schema newSchema(SchemaFactory factory, InputSource... sources) + { + Element combined = DomUtil.newDocument(SCHEMA_NS, SCHEMA_PREFIX + ":" + SCHEMA_ROOT); + for (InputSource source : sources) + { + combineSchema(combined, source); + } + + System.out.println(OutputUtil.indentedString(combined.getOwnerDocument(), 4)); + + try + { + synchronized (factory) + { + return factory.newSchema(new DOMSource(combined.getOwnerDocument())); + } + } + catch (SAXException e) + { + throw new XmlException("unable to generate schema", e); + } + } + + +//---------------------------------------------------------------------------- +// Internals +//---------------------------------------------------------------------------- + + /** + * Parses, verifies, and combines a source document. + */ + private static void combineSchema(Element newRoot, InputSource source) + { + Document doc = ParseUtil.parse(source); + 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)) + { + Node tmp = newRoot.getOwnerDocument().importNode(child, true); + newRoot.appendChild(tmp); + } + } +} Added: trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java 2008-10-08 00:08:37 UTC (rev 28) @@ -0,0 +1,91 @@ +package net.sf.practicalxml; + +import java.io.Reader; +import java.io.StringReader; + +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import net.sf.practicalxml.misc.ExceptionErrorHandler; + +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; + +import junit.framework.TestCase; + + +public class TestSchemaUtil extends TestCase +{ +//---------------------------------------------------------------------------- +// Definitions for newSchema() testing +//---------------------------------------------------------------------------- + + 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>"; + + +//---------------------------------------------------------------------------- +// Test Cases +//---------------------------------------------------------------------------- + + public void testNewFactory() throws Exception + { + ErrorHandler errHandler = new ExceptionErrorHandler(); + SchemaFactory fact = SchemaUtil.newFactory(errHandler); + + assertNotNull(fact); + assertSame(errHandler, fact.getErrorHandler()); + } + + + public void testNewSchemaWithSingleSource() throws Exception + { + Reader xsd = new StringReader( + NEW_SCHEMA_START + + NEW_SCHEMA_DEF_1 + NEW_SCHEMA_DEF_2 + NEW_SCHEMA_DEF_3 + + NEW_SCHEMA_FINISH); + + Schema schema = SchemaUtil.newSchema( + SchemaUtil.newFactory(new ExceptionErrorHandler()), + new InputSource(xsd)); + assertNotNull(schema); + } + + + public void testNewSchemaWithMultipleSources() 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); + + Schema schema = SchemaUtil.newSchema( + SchemaUtil.newFactory(new ExceptionErrorHandler()), + new InputSource(xsd1), + new InputSource(xsd2), + new InputSource(xsd3)); + assertNotNull(schema); + } +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-10-08 01:43:47
|
Revision: 29 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=29&view=rev Author: kdgregory Date: 2008-10-08 01:43:42 +0000 (Wed, 08 Oct 2008) Log Message: ----------- ParseUtil: rename "parseWithDTD" to "validatingParse" Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/ParseUtil.java trunk/src/test/java/net/sf/practicalxml/TestParseUtil.java Modified: trunk/src/main/java/net/sf/practicalxml/ParseUtil.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/ParseUtil.java 2008-10-08 00:08:37 UTC (rev 28) +++ trunk/src/main/java/net/sf/practicalxml/ParseUtil.java 2008-10-08 01:43:42 UTC (rev 29) @@ -84,7 +84,7 @@ * * @throws XmlException for any configuration or fatal execution error. */ - public static Document parseWithDTD( + public static Document validatingParse( InputSource source, EntityResolver resolver, ErrorHandler errHandler) { @@ -116,10 +116,10 @@ * * @throws XmlException for any configuration or fatal execution error. */ - public static Document parseWithDTD( + public static Document validatingParse( InputSource source, ErrorHandler errHandler) { - return parseWithDTD(source, null, errHandler); + return validatingParse(source, null, errHandler); } Modified: trunk/src/test/java/net/sf/practicalxml/TestParseUtil.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestParseUtil.java 2008-10-08 00:08:37 UTC (rev 28) +++ trunk/src/test/java/net/sf/practicalxml/TestParseUtil.java 2008-10-08 01:43:42 UTC (rev 29) @@ -1,6 +1,5 @@ package net.sf.practicalxml; -import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.List; @@ -8,7 +7,6 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; -import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -22,15 +20,24 @@ // Support Code //---------------------------------------------------------------------------- + /** A basic DTD, shared between validating parser tests */ + private final static String BASIC_DTD + = "<!ELEMENT foo (bar*,baz+)>" + + "<!ELEMENT bar (#PCDATA)>" + + "<!ELEMENT baz EMPTY>" + + "<!ATTLIST foo name CDATA #REQUIRED>"; + + /** - * An error handler that records its invocations. + * An ErrorHandler that records its invocations, and provides asserts + * on them. */ - private static class MockErrorHandler + private static class TestErrorHandler implements ErrorHandler { - public List<SAXParseException> warnings = new ArrayList<SAXParseException>(); - public List<SAXParseException> errors = new ArrayList<SAXParseException>(); public List<SAXParseException> fatalErrors = new ArrayList<SAXParseException>(); + public List<SAXParseException> errors = new ArrayList<SAXParseException>(); + public List<SAXParseException> warnings = new ArrayList<SAXParseException>(); public void error(SAXParseException exception) throws SAXException { @@ -46,6 +53,13 @@ { warnings.add(exception); } + + public void assertResults(int numFatal, int numErrors, int numWarnings) + { + assertEquals("TestErrorHandler fatal errors", numFatal, fatalErrors.size()); + assertEquals("TestErrorHandler errors", numErrors, errors.size()); + assertEquals("TestErrorHandler warnings", numWarnings, warnings.size()); + } } @@ -86,23 +100,6 @@ } - public void testMalformedStringParseWithErrorHandler() throws Exception - { - String xml = "<foo><bar>baz</foo>"; - MockErrorHandler handler = new MockErrorHandler(); - try - { - ParseUtil.parse(new InputSource(new StringReader(xml)), handler); - fail("able to parse malformed XML"); - } - catch (XmlException e) - { - // even though we're handling the error, the parser will abend - } - assertEquals(1, handler.fatalErrors.size()); - } - - public void testNamespacedStringParse() throws Exception { String xml = "<foo xmlns:me=\"argle\">" @@ -136,103 +133,39 @@ } - public void testValidatingParseWithMissingDoctype() throws Exception + public void testValidDocumentWithInternalDoctype() throws Exception { - InputSource input = new InputSource(new StringReader( - "<foo>bar</foo>")); - MockErrorHandler errHandler = new MockErrorHandler(); + String xml + = "<!DOCTYPE foo [" + BASIC_DTD + "]>" + + "<foo name='zippy'>" + + "<bar>something here</bar>" + + "<baz/>" + + "</foo>"; - Document dom = ParseUtil.parseWithDTD(input, errHandler); - assertEquals("foo", dom.getDocumentElement().getNodeName()); - assertTrue(errHandler.errors.size() > 0); - assertTrue(errHandler.fatalErrors.size() == 0); - assertTrue(errHandler.warnings.size() == 0); + TestErrorHandler errHandler = new TestErrorHandler(); + Document doc = ParseUtil.validatingParse( + new InputSource(new StringReader(xml)), + errHandler); + + assertEquals("foo", doc.getDocumentElement().getTagName()); + errHandler.assertResults(0, 0, 0); } - public void testValidatingParseWithInternalDTD() throws Exception + public void testInvalidDocumentWithInternalDoctype() throws Exception { - String dtd - = "<!ELEMENT foo (bar*)>" - + "<!ELEMENT bar (#PCDATA)>" - + "<!ATTLIST foo baz CDATA #REQUIRED>"; + String xml + = "<!DOCTYPE foo [" + BASIC_DTD + "]>" + + "<foo>" + + "<bar>something here</bar>" + + "</foo>"; - // first test will be valid - InputSource in1 = new InputSource(new StringReader( - "<!DOCTYPE foo [" + dtd + "]>" - + "<foo baz=\"argle\">" - + "<bar>argle</bar>" - + "</foo>")); - MockErrorHandler eh1 = new MockErrorHandler(); + TestErrorHandler errHandler = new TestErrorHandler(); + Document doc = ParseUtil.validatingParse( + new InputSource(new StringReader(xml)), + errHandler); - Document dom1 = ParseUtil.parseWithDTD(in1, eh1); - assertEquals("foo", dom1.getDocumentElement().getNodeName()); - assertTrue(eh1.errors.size() == 0); - assertTrue(eh1.fatalErrors.size() == 0); - assertTrue(eh1.warnings.size() == 0); - - // second won't -- missing required attribute - InputSource in2 = new InputSource(new StringReader( - "<!DOCTYPE foo [" + dtd + "]>\n" - + "<foo>\n" - + "<bar>argle</bar>\n" - + "</foo>")); - MockErrorHandler eh2 = new MockErrorHandler(); - - Document dom2 = ParseUtil.parseWithDTD(in2, eh2); - assertEquals("foo", dom2.getDocumentElement().getNodeName()); - assertTrue(eh2.errors.size() > 0); - assertTrue(eh2.fatalErrors.size() == 0); - assertTrue(eh2.warnings.size() == 0); - - assertEquals(2, eh2.errors.get(0).getLineNumber()); -// assertEquals(4, eh2.errors.get(0).getColumnNumber()); + assertEquals("foo", doc.getDocumentElement().getTagName()); + errHandler.assertResults(0, 2, 0); } - - - // note: this will fail if you don't have Internet access - // ... also, unit tests shouldn't reach outside the JVM -// public void testValidatingParseWithExternalDTD() throws Exception -// { -// InputSource input = new InputSource(new StringReader( -// "<!DOCTYPE web-app SYSTEM \"http://java.sun.com/dtd/web-app_2_3.dtd\">" -// + "<web-app>" -// + "<display-name>" -// + "Simple Web-App" -// + "</display-name>" -// + "</web-app>")); -// MockErrorHandler errHandler = new MockErrorHandler(); -// -// Document dom = ParseUtil.parseWithDTD(input, null, errHandler); -// assertEquals("web-app", dom.getDocumentElement().getNodeName()); -// assertTrue(errHandler.errors.size() == 0); -// assertTrue(errHandler.fatalErrors.size() == 0); -// assertTrue(errHandler.warnings.size() == 0); -// } - - - public void testValidatingParseWithLocalResolver() throws Exception - { - InputSource input = new InputSource(new StringReader( - "<!DOCTYPE foo SYSTEM \"http://java.sun.com/dtd/web-app_2_3.dtd\">" - + "<foo><bar>baz</bar></foo>")); - MockErrorHandler errHandler = new MockErrorHandler(); - EntityResolver resolver = new EntityResolver() - { - public InputSource resolveEntity(String publicId, String systemId) - throws SAXException, IOException - { - // we don't care what they want; return what we want - return new InputSource(new StringReader( - "<!ELEMENT foo (bar*)>" - + "<!ELEMENT bar (#PCDATA)>")); - } - }; - - Document dom = ParseUtil.parseWithDTD(input, resolver, errHandler); - assertEquals("foo", dom.getDocumentElement().getNodeName()); - assertTrue(errHandler.errors.size() == 0); - assertTrue(errHandler.fatalErrors.size() == 0); - assertTrue(errHandler.warnings.size() == 0); - } } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-10-12 16:57:17
|
Revision: 31 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=31&view=rev Author: kdgregory Date: 2008-10-12 16:57:10 +0000 (Sun, 12 Oct 2008) Log Message: ----------- ParseUtil: add schema validation Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/ParseUtil.java trunk/src/test/java/net/sf/practicalxml/TestParseUtil.java Modified: trunk/src/main/java/net/sf/practicalxml/ParseUtil.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/ParseUtil.java 2008-10-12 13:05:05 UTC (rev 30) +++ trunk/src/main/java/net/sf/practicalxml/ParseUtil.java 2008-10-12 16:57:10 UTC (rev 31) @@ -6,6 +6,7 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.validation.Schema; import net.sf.practicalxml.misc.ExceptionErrorHandler; @@ -119,10 +120,40 @@ public static Document validatingParse( InputSource source, ErrorHandler errHandler) { - return validatingParse(source, null, errHandler); + return validatingParse(source, (EntityResolver)null, errHandler); } + /** + * Parses the supplied source with a namespace-aware, XSD-validating + * parser, using a caller-supplied error handler and entity resolver. + * Both of these objects may be <code>null</code>, to use the built-in + * defaults. + * + * @throws XmlException for any configuration or fatal execution error. + */ + public static Document validatingParse( + InputSource source, Schema schema, ErrorHandler errHandler) + { + DocumentBuilder db = newXSDDocumentBuilder(schema); + if (errHandler != null) + db.setErrorHandler(errHandler); + + try + { + return db.parse(source); + } + catch (IOException e) + { + throw new XmlException("unable to parse", e); + } + catch (SAXException e) + { + throw new XmlException("unable to parse", e); + } + } + + //---------------------------------------------------------------------------- // Internals //---------------------------------------------------------------------------- @@ -134,6 +165,9 @@ private static DocumentBuilderFactory _dtdDbf; + /** + * Returns a namespace-aware, non-validating parser. + */ private static synchronized DocumentBuilder newNVDocumentBuilder() { if (_nvDbf == null) @@ -155,6 +189,9 @@ } + /** + * Returns a namespace-aware, DTD-validating parser. + */ private static synchronized DocumentBuilder newDTDDocumentBuilder() { if (_dtdDbf == null) @@ -174,4 +211,28 @@ throw new XmlException("unable to confiure parser", e); } } + + + /** + * Returns a namespace-aware, XSD-validating parser using the supplied + * schema. Note that we don't use a singleton factory, because the schema + * gets applied to the factory, not the parser. + */ + private static synchronized DocumentBuilder newXSDDocumentBuilder(Schema schema) + { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + dbf.setValidating(false); + dbf.setCoalescing(true); + dbf.setSchema(schema); + + try + { + return _dtdDbf.newDocumentBuilder(); + } + catch (ParserConfigurationException e) + { + throw new XmlException("unable to confiure parser", e); + } + } } Modified: trunk/src/test/java/net/sf/practicalxml/TestParseUtil.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestParseUtil.java 2008-10-12 13:05:05 UTC (rev 30) +++ trunk/src/test/java/net/sf/practicalxml/TestParseUtil.java 2008-10-12 16:57:10 UTC (rev 31) @@ -5,6 +5,11 @@ import java.util.ArrayList; import java.util.List; +import javax.xml.XMLConstants; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; @@ -30,7 +35,43 @@ + "<!ATTLIST foo name CDATA #REQUIRED>"; + /** An XSD that replicates the DTD above */ + private final static String BASIC_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=\"bar\" type=\"xsd:string\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>" + + "<xsd:element name=\"baz\" minOccurs=\"1\" maxOccurs=\"unbounded\">" + + "<xsd:complexType>" + + "</xsd:complexType>" + + "</xsd:element>" + + "</xsd:sequence>" + + "<xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\"/>" + + "</xsd:complexType>" + + "</xsd:schema>"; + + /** + * Creates a <code>Schema</code> object from source XML. We could use + * {@link SchemaUtil}, but I'd like this test to be as self-contained + * as possible. + */ + private static Schema createSchema(String xsd) + { + try + { + SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + return sf.newSchema(new StreamSource(new StringReader(xsd))); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + + /** * An ErrorHandler that records its invocations, and provides asserts * on them. */ @@ -63,38 +104,8 @@ assertEquals("TestErrorHandler warnings", numWarnings, warnings.size()); } } - - - /** - * An EntityResolver that will resolve a single entity. - */ - private static class TestEntityResolver - implements EntityResolver - { - private String _publicId; - private String _systemId; - private String _content; - - public TestEntityResolver(String publicId, String systemId, String content) - { - _publicId = publicId; - _systemId = systemId; - _content = content; - } - public InputSource resolveEntity(String publicId, String systemId) - throws SAXException, IOException - { - if (((publicId == null) || publicId.equals(_publicId)) - && ((systemId == null) || systemId.equals(_systemId))) - { - return new InputSource(new StringReader(_content)); - } - return null; - } - } - //---------------------------------------------------------------------------- // Test Cases //---------------------------------------------------------------------------- @@ -229,13 +240,40 @@ + "<baz/>" + "</foo>"; + EntityResolver resolver = new EntityResolver() + { + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException + { + return new InputSource(new StringReader(BASIC_DTD)); + } + }; TestErrorHandler errHandler = new TestErrorHandler(); - TestEntityResolver resolver = new TestEntityResolver(null, "test", BASIC_DTD); Document doc = ParseUtil.validatingParse( new InputSource(new StringReader(xml)), - resolver, errHandler); + resolver, + errHandler); assertEquals("foo", doc.getDocumentElement().getTagName()); errHandler.assertResults(0, 0, 0); } + + + public void testValidDocumentWithSchema() throws Exception + { + String xml + = "<foo name='zippy'>" + + "<bar>something here</bar>" + + "<baz/>" + + "</foo>"; + + TestErrorHandler errHandler = new TestErrorHandler(); + Document doc = ParseUtil.validatingParse( + new InputSource(new StringReader(xml)), + createSchema(BASIC_XSD), + errHandler); + + assertEquals("foo", doc.getDocumentElement().getTagName()); + errHandler.assertResults(0, 0, 0); + } } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-11-28 21:29:25
|
Revision: 35 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=35&view=rev Author: kdgregory Date: 2008-11-28 21:29:19 +0000 (Fri, 28 Nov 2008) Log Message: ----------- add NamespaceResolver, SimpleNamespaceResolver Added Paths: ----------- trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java trunk/src/main/java/net/sf/practicalxml/misc/SimpleNamespaceResolver.java trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java trunk/src/test/java/net/sf/practicalxml/misc/TestSimpleNamespaceResolver.java Added: trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java 2008-11-28 21:29:19 UTC (rev 35) @@ -0,0 +1,217 @@ +package net.sf.practicalxml.misc; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; + + +/** + * Maintains a bi-directional lookup table for mappings between namespace URIs + * and prefixes. Follows the "builder" pattern, in that the methods to add + * mappings may be chained: a resolver can be created and fully configured in + * a single expression. + * <p> + * Usage note: <code>NamespaceContext</code> allows multiple prefixes per URI, + * in keeping with the Namespace spec. This implementation supports that, but + * it's a bad idea to actually use this feature when writing an XPath. You'll + * be much happier if you limit yourself to a 1:1 mapping. + * <p> + * If you have a single namespace mapping, this implementation is overkill. + * Instead, use {@link SimpleNamespaceResolver}. + */ +public class NamespaceResolver +implements NamespaceContext +{ + private final static SortedSet<String> DEFAULT_PREFIXES = + new TreeSet<String>(); + private final static SortedSet<String> XML_NS_URI_PREFIXES = + new TreeSet<String>(); + private final static SortedSet<String> XML_NS_ATTR_PREFIXES = + new TreeSet<String>(); + static + { + DEFAULT_PREFIXES.add(""); + XML_NS_URI_PREFIXES.add(XMLConstants.XML_NS_PREFIX); + XML_NS_ATTR_PREFIXES.add(XMLConstants.XMLNS_ATTRIBUTE); + } + + private Map<String,String> _prefix2ns = new HashMap<String,String>(); + private Map<String,SortedSet<String>> _ns2prefix = new HashMap<String,SortedSet<String>>(); + private String _defaultNS = ""; + + +//---------------------------------------------------------------------------- +// Public methods +//---------------------------------------------------------------------------- + + /** + * Adds a namespace to this resolver. + * + * @return The resolver instance, so that calls may be chained. + * + * @throws IllegalArgumentException if either <code>prefix</code> + * or <code>nsURI</code> is <code>null</code>. + */ + public NamespaceResolver addNamespace(String prefix, String nsURI) + { + if (prefix == null) + throw new IllegalArgumentException("prefix may not be null"); + if (nsURI == null) + throw new IllegalArgumentException("nsURI may not be null"); + + _prefix2ns.put(prefix, nsURI); + getPrefixSet(nsURI).add(prefix); + return this; + } + + + /** + * Sets the default namespace -- the namespace that will be returned + * when an empty string is passed to the resolver. + * + * @return The resolver instance, so that calls may be chained. + * + * @throws IllegalArgumentException if <code>nsURI</code> is + * <code>null</code>. + */ + public NamespaceResolver setDefaultNamespace(String nsURI) + { + if (nsURI == null) + throw new IllegalArgumentException("nsURI may not be null"); + + _defaultNS = nsURI; + return this; + } + + +//---------------------------------------------------------------------------- +// NamespaceContext implementation +//---------------------------------------------------------------------------- + + /** + * Returns the namespace URI bound to a given prefix, <code>null</code> + * if no URI is bound to the specified prefix. See interface doc for + * default bindings. + */ + public String getNamespaceURI(String prefix) + { + if (prefix == null) + throw new IllegalArgumentException("prefix may not be null"); + else if ("".equals(prefix)) + return _defaultNS; + else if (XMLConstants.XML_NS_PREFIX.equals(prefix)) + return XMLConstants.XML_NS_URI; + else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) + return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; + else + return _prefix2ns.get(prefix); + } + + + /** + * Returns the first prefix in alphabetical order bound to this namespace + * URI, <code>null</code> if there is no binding for the namespace. See + * interface doc for default bindings. + */ + public String getPrefix(String nsURI) + { + Iterator<String> itx = getPrefixes(nsURI); + return itx.hasNext() ? itx.next() : null; + } + + + /** + * Returns an iterator over all prefixes bound to this namespace URI, in + * alphabetical order, an empty iterator if there are no bindings. See + * interface doc for default bindings. + */ + public Iterator<String> getPrefixes(String nsURI) + { + if (nsURI == null) + throw new IllegalArgumentException("nsURI may not be null"); + else if (_defaultNS.equals(nsURI)) + return DEFAULT_PREFIXES.iterator(); + else if (XMLConstants.XML_NS_URI.equals(nsURI)) + return XML_NS_URI_PREFIXES.iterator(); + else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(nsURI)) + return XML_NS_ATTR_PREFIXES.iterator(); + else + return getPrefixSet(nsURI).iterator(); + } + + +//---------------------------------------------------------------------------- +// Object overrides +//---------------------------------------------------------------------------- + + /** + * Two instances are considered equal if they have the same mappings, + * including default namespace. + */ + @Override + public final boolean equals(Object obj) + { + if (obj instanceof NamespaceResolver) + { + NamespaceResolver that = (NamespaceResolver)obj; + return this._prefix2ns.equals(that._prefix2ns) + && this._defaultNS.equals(that._defaultNS); + } + return false; + } + + + @Override + public int hashCode() + { + // rely on these objects caching their hashcode + return _prefix2ns.hashCode() ^ _defaultNS.hashCode(); + } + + + /** + * Returns a string containing the the <code>xmlns</code> attribute + * specifications that would result from this resolver. + */ + @Override + public String toString() + { + StringBuilder buf = new StringBuilder(50 * _prefix2ns.size()); + if (!"".equals(_defaultNS)) + { + buf.append("xmlns=\"").append(_defaultNS).append("\""); + } + for (String prefix : new TreeSet<String>(_prefix2ns.keySet())) + { + if (buf.length() > 0) + buf.append(" "); + buf.append("xmlns:").append(prefix).append("=\"") + .append(_prefix2ns.get(prefix)).append("\""); + } + return buf.toString(); + } + + +//---------------------------------------------------------------------------- +// Internals +//---------------------------------------------------------------------------- + + /** + * Returns the set of prefixes for a given namespace, creating a new + * entry if one doesn't already exist. + */ + private SortedSet<String> getPrefixSet(String nsURI) + { + SortedSet<String> prefixes = _ns2prefix.get(nsURI); + if (prefixes == null) + { + prefixes = new TreeSet<String>(); + _ns2prefix.put(nsURI, prefixes); + } + return prefixes; + } +} Property changes on: trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java ___________________________________________________________________ Added: svn:executable + * Added: trunk/src/main/java/net/sf/practicalxml/misc/SimpleNamespaceResolver.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/misc/SimpleNamespaceResolver.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/misc/SimpleNamespaceResolver.java 2008-11-28 21:29:19 UTC (rev 35) @@ -0,0 +1,141 @@ +package net.sf.practicalxml.misc; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; + + +/** + * Implements a bidirectional lookup between a single namespace URI and its + * prefix, for use with XPath expressions. Does not support the "default" + * namespace, unless explicitly created with a prefix "". + */ +public class SimpleNamespaceResolver +implements NamespaceContext +{ + private final String _prefix; + private final String _nsURI; + private final List<String> _prefixes; + + public SimpleNamespaceResolver(String prefix, String nsURI) + { + _prefix = prefix; + _nsURI = nsURI; + _prefixes = Arrays.asList(prefix); + } + + +//---------------------------------------------------------------------------- +// NamespaceContext implementation +//---------------------------------------------------------------------------- + + /** + * Returns the namespace URI bound to the passed prefix, <code>null</code> + * if the prefix does not correspond to the binding defined by this + * instance. Also supports "standard" bindings; see JDK doc for details. + */ + public String getNamespaceURI(String prefix) + { + if (prefix == null) + throw new IllegalArgumentException("prefix may not be null"); + else if (_prefix.equals(prefix)) + return _nsURI; + else if (XMLConstants.XML_NS_PREFIX.equals(prefix)) + return XMLConstants.XML_NS_URI; + else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) + return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; + + else + return null; + } + + + /** + * Returns the prefix bound to the passed namespace URI, <code>null</code> + * if the URI does not correspond to the binding defined by this instance. + * Also supports "standard" bindings; see JDK doc for details. + */ + public String getPrefix(String nsURI) + { + if (nsURI == null) + throw new IllegalArgumentException("nsURI may not be null"); + else if (nsURI.equals(_nsURI)) + return _prefix; + else if (nsURI.equals(XMLConstants.XML_NS_URI)) + return XMLConstants.XML_NS_PREFIX; + else if (nsURI.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) + return XMLConstants.XMLNS_ATTRIBUTE; + else + return null; + } + + + /** + * Returns an iterator over prefixes for the passed URI, an empty iterator + * if the URI does not correspond to the binding defined by this instance. + * Also supports "standard" bindings; see JDK doc for details. + */ + public Iterator<String> getPrefixes(String nsURI) + { + String prefix = getPrefix(nsURI); + if (_prefix.equals(prefix)) + return _prefixes.iterator(); + else if (_prefix == null) + return Collections.<String>emptyList().iterator(); + else + return Arrays.asList(prefix).iterator(); + } + + +//---------------------------------------------------------------------------- +// Object overrides +//---------------------------------------------------------------------------- + + /** + * Two instances are considered equal if they have the same mappings, + * including default namespace. + */ + @Override + public final boolean equals(Object obj) + { + if (obj instanceof SimpleNamespaceResolver) + { + SimpleNamespaceResolver that = (SimpleNamespaceResolver)obj; + return this._prefix.equals(that._prefix) + && this._nsURI.equals(that._nsURI); + } + return false; + } + + + @Override + public int hashCode() + { + return _prefix.hashCode() ^ _nsURI.hashCode(); + } + + + /** + * Returns a string containing the the <code>xmlns</code> attribute + * specifications that would result from this resolver. + */ + @Override + public String toString() + { + StringBuilder buf = new StringBuilder(_prefix.length() + _nsURI.length() + 10); + if ("".equals(_prefix)) + { + buf.append("xmlns=\"").append(_nsURI).append("\""); + } + else + { + buf.append("xmlns:").append(_prefix).append("=\"") + .append(_nsURI).append("\""); + } + return buf.toString(); + } +} Property changes on: trunk/src/main/java/net/sf/practicalxml/misc/SimpleNamespaceResolver.java ___________________________________________________________________ Added: svn:executable + * Added: trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java 2008-11-28 21:29:19 UTC (rev 35) @@ -0,0 +1,198 @@ +package net.sf.practicalxml.misc; + +import java.util.Iterator; +import javax.xml.XMLConstants; + +import net.sf.practicalxml.AbstractTestCase; + + +public class TestNamespaceResolver extends AbstractTestCase +{ + public void testSingleNamespace() throws Exception + { + final String prefix = "foo"; + final String nsURI = "bar"; + + NamespaceResolver resolv = new NamespaceResolver(); + + assertSame(resolv, resolv.addNamespace(prefix, nsURI)); + + assertEquals(nsURI, resolv.getNamespaceURI(prefix)); + assertEquals(prefix, resolv.getPrefix(nsURI)); + + Iterator<String> itx = resolv.getPrefixes(nsURI); + assertEquals(prefix, itx.next()); + assertFalse(itx.hasNext()); + } + + + public void testTwoNamespaces() throws Exception + { + final String prefix1 = "foo"; + final String nsURI1 = "bar"; + final String prefix2 = "argle"; + final String nsURI2 = "bargle"; + + NamespaceResolver resolv = new NamespaceResolver() + .addNamespace(prefix1, nsURI1) + .addNamespace(prefix2, nsURI2); + + assertEquals(nsURI1, resolv.getNamespaceURI(prefix1)); + assertEquals(nsURI2, resolv.getNamespaceURI(prefix2)); + + assertEquals(prefix1, resolv.getPrefix(nsURI1)); + assertEquals(prefix2, resolv.getPrefix(nsURI2)); + + Iterator<String> itx1 = resolv.getPrefixes(nsURI1); + assertEquals(prefix1, itx1.next()); + assertFalse(itx1.hasNext()); + + Iterator<String> itx2 = resolv.getPrefixes(nsURI2); + assertEquals(prefix2, itx2.next()); + assertFalse(itx2.hasNext()); + } + + + public void testOneNamespaceTwoPrefixes() throws Exception + { + final String prefix1 = "foo"; + final String prefix2 = "argle"; + final String nsURI = "bargle"; + + NamespaceResolver resolv = new NamespaceResolver() + .addNamespace(prefix1, nsURI) + .addNamespace(prefix2, nsURI); + + assertEquals(nsURI, resolv.getNamespaceURI(prefix1)); + assertEquals(nsURI, resolv.getNamespaceURI(prefix2)); + + assertEquals(prefix2, resolv.getPrefix(nsURI)); + + Iterator<String> itx1 = resolv.getPrefixes(nsURI); + assertEquals(prefix2, itx1.next()); + assertEquals(prefix1, itx1.next()); + assertFalse(itx1.hasNext()); + } + + + public void testStandardMappings() throws Exception + { + NamespaceResolver resolv = new NamespaceResolver(); + + assertEquals(XMLConstants.XML_NS_URI, resolv.getNamespaceURI(XMLConstants.XML_NS_PREFIX)); + assertEquals(XMLConstants.XML_NS_PREFIX, resolv.getPrefix(XMLConstants.XML_NS_URI)); + Iterator<String> itx1 = resolv.getPrefixes(XMLConstants.XML_NS_URI); + assertEquals(XMLConstants.XML_NS_PREFIX, itx1.next()); + assertFalse(itx1.hasNext()); + + assertEquals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, resolv.getNamespaceURI(XMLConstants.XMLNS_ATTRIBUTE)); + assertEquals(XMLConstants.XMLNS_ATTRIBUTE, resolv.getPrefix(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)); + Iterator<String> itx2 = resolv.getPrefixes(XMLConstants.XMLNS_ATTRIBUTE_NS_URI); + assertEquals(XMLConstants.XMLNS_ATTRIBUTE, itx2.next()); + assertFalse(itx2.hasNext()); + + try + { + resolv.getNamespaceURI(null); + fail("should throw IllegalArgumentException"); + } + catch (IllegalArgumentException e) + { + // success + } + + try + { + resolv.getPrefix(null); + fail("should throw IllegalArgumentException"); + } + catch (IllegalArgumentException e) + { + // success + } + + try + { + resolv.getPrefixes(null); + fail("should throw IllegalArgumentException"); + } + catch (IllegalArgumentException e) + { + // success + } + } + + + public void testDefaultNamespace() throws Exception + { + NamespaceResolver resolv = new NamespaceResolver(); + + assertEquals("", resolv.getNamespaceURI("")); + assertEquals("", resolv.getPrefix("")); + Iterator<String> itx1 = resolv.getPrefixes(""); + assertEquals("", itx1.next()); + assertFalse(itx1.hasNext()); + + assertSame(resolv, resolv.setDefaultNamespace("foo")); + + assertEquals("foo", resolv.getNamespaceURI("")); + assertEquals("", resolv.getPrefix("foo")); + Iterator<String> itx2 = resolv.getPrefixes("foo"); + assertEquals("", itx2.next()); + assertFalse(itx2.hasNext()); + } + + + public void testEqualsAndHashCode() throws Exception + { + NamespaceResolver resolv1 = new NamespaceResolver() + .addNamespace("foo", "bar") + .setDefaultNamespace("zippy"); + NamespaceResolver resolv2 = new NamespaceResolver() + .addNamespace("foo", "bar") + .setDefaultNamespace("zippy"); + NamespaceResolver resolv3 = new NamespaceResolver() + .addNamespace("foo", "bar"); + NamespaceResolver resolv4 = new NamespaceResolver() + .addNamespace("argle", "bargle"); + + assertTrue(resolv1.equals(resolv2)); + assertTrue(resolv2.equals(resolv1)); + assertEquals(resolv1.hashCode(), resolv2.hashCode()); + + assertFalse(resolv1.equals(resolv3)); + assertFalse(resolv3.equals(resolv1)); + + assertFalse(resolv3.equals(resolv4)); + assertFalse(resolv4.equals(resolv3)); + + // this works today ... assume that the underlying calcs don't change + assertFalse(resolv3.hashCode() == resolv4.hashCode()); + } + + + public void testToString() throws Exception + { + NamespaceResolver resolv = new NamespaceResolver(); + String str0 = resolv.toString(); + assertEquals(0, str0.length()); + + resolv.setDefaultNamespace("foo"); + String str1 = resolv.toString(); + assertTrue(str1.contains("xmlns=\"foo\"")); + assertEquals(1, str1.split(" +").length); + + resolv.addNamespace("argle", "bargle"); + String str2 = resolv.toString(); + assertTrue(str2.contains("xmlns=\"foo\"")); + assertTrue(str2.contains("xmlns:argle=\"bargle\"")); + assertEquals(2, str2.split(" +").length); + + resolv.addNamespace("zippy", "pinhead"); + String str3 = resolv.toString(); + assertTrue(str3.contains("xmlns=\"foo\"")); + assertTrue(str3.contains("xmlns:argle=\"bargle\"")); + assertTrue(str3.contains("xmlns:zippy=\"pinhead\"")); + assertEquals(3, str3.split(" +").length); + } +} Property changes on: trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java ___________________________________________________________________ Added: svn:executable + * Added: trunk/src/test/java/net/sf/practicalxml/misc/TestSimpleNamespaceResolver.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/misc/TestSimpleNamespaceResolver.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/misc/TestSimpleNamespaceResolver.java 2008-11-28 21:29:19 UTC (rev 35) @@ -0,0 +1,101 @@ +package net.sf.practicalxml.misc; + +import java.util.Iterator; +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; + +import net.sf.practicalxml.AbstractTestCase; + + +public class TestSimpleNamespaceResolver extends AbstractTestCase +{ + public void testLookup() throws Exception + { + final String prefix = "foo"; + final String nsURI = "bar"; + + NamespaceContext resolv = new SimpleNamespaceResolver(prefix, nsURI); + + assertEquals(nsURI, resolv.getNamespaceURI(prefix)); + assertEquals(prefix, resolv.getPrefix(nsURI)); + + Iterator<String> itx = resolv.getPrefixes(nsURI); + assertEquals(prefix, itx.next()); + assertFalse(itx.hasNext()); + } + + + public void testStandardMappings() throws Exception + { + NamespaceResolver resolv = new NamespaceResolver(); + + assertEquals(XMLConstants.XML_NS_URI, resolv.getNamespaceURI(XMLConstants.XML_NS_PREFIX)); + assertEquals(XMLConstants.XML_NS_PREFIX, resolv.getPrefix(XMLConstants.XML_NS_URI)); + Iterator<String> itx1 = resolv.getPrefixes(XMLConstants.XML_NS_URI); + assertEquals(XMLConstants.XML_NS_PREFIX, itx1.next()); + assertFalse(itx1.hasNext()); + + assertEquals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, resolv.getNamespaceURI(XMLConstants.XMLNS_ATTRIBUTE)); + assertEquals(XMLConstants.XMLNS_ATTRIBUTE, resolv.getPrefix(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)); + Iterator<String> itx2 = resolv.getPrefixes(XMLConstants.XMLNS_ATTRIBUTE_NS_URI); + assertEquals(XMLConstants.XMLNS_ATTRIBUTE, itx2.next()); + assertFalse(itx2.hasNext()); + + try + { + resolv.getNamespaceURI(null); + fail("should throw IllegalArgumentException"); + } + catch (IllegalArgumentException e) + { + // success + } + + try + { + resolv.getPrefix(null); + fail("should throw IllegalArgumentException"); + } + catch (IllegalArgumentException e) + { + // success + } + + try + { + resolv.getPrefixes(null); + fail("should throw IllegalArgumentException"); + } + catch (IllegalArgumentException e) + { + // success + } + } + + + public void testEqualsAndHashCode() throws Exception + { + Object resolv1 = new SimpleNamespaceResolver("foo", "bar"); + Object resolv2 = new SimpleNamespaceResolver("foo", "bar"); + Object resolv3 = new SimpleNamespaceResolver("argle", "bargle"); + + assertTrue(resolv1.equals(resolv2)); + assertTrue(resolv2.equals(resolv1)); + assertTrue(resolv1.hashCode() == resolv2.hashCode()); + + assertFalse(resolv1.equals(resolv3)); + assertFalse(resolv3.equals(resolv1)); + // this works today ... assume that the underlying calcs don't change + assertFalse(resolv1.hashCode() == resolv3.hashCode()); + } + + + public void testToString() throws Exception + { + String str1 = new SimpleNamespaceResolver("", "foo").toString(); + assertEquals("xmlns=\"foo\"", str1); + + String str2 = new SimpleNamespaceResolver("foo", "bar").toString(); + assertEquals("xmlns:foo=\"bar\"", str2); + } +} Property changes on: trunk/src/test/java/net/sf/practicalxml/misc/TestSimpleNamespaceResolver.java ___________________________________________________________________ Added: svn:executable + * This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-01 00:09:32
|
Revision: 36 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=36&view=rev Author: kdgregory Date: 2008-12-01 00:09:30 +0000 (Mon, 01 Dec 2008) Log Message: ----------- add XPathWrapper Added Paths: ----------- trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java Added: trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java 2008-12-01 00:09:30 UTC (rev 36) @@ -0,0 +1,373 @@ +package net.sf.practicalxml; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.xml.namespace.QName; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionResolver; +import javax.xml.xpath.XPathVariableResolver; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import net.sf.practicalxml.misc.NamespaceResolver; + + + +/** + * This class simplifies the use of XPath expressions, hiding the factory and + * return types, and providing a simple builder-style interface for adding + * resolvers. It also maintains the expression in a compiled form, improving + * reuse performance. + */ +public class XPathWrapper +{ + private final String _expr; + private final NamespaceResolver _nsResolver = new NamespaceResolver(); + private Map<QName,Object> _variables = new HashMap<QName,Object>(); + private Map<QName,XPathFunction> _functions = new HashMap<QName,XPathFunction>(); + + private XPathExpression _compiled; + + + /** + * Creates a new instance, which may then be customized with various + * resolvers, and used to evaluate expressions. + */ + public XPathWrapper(String expr) + { + _expr = expr; + } + + +//---------------------------------------------------------------------------- +// Public methods +//---------------------------------------------------------------------------- + + /** + * Adds a namespace binding to this expression. All bindings must be + * added prior to the first call to <code>evaluate()</code>. + * + * @param prefix The prefix used to reference this namespace in the + * XPath expression. Note that this does <em>not</em> + * need to be the same prefix used by the document. + * @param nsURI The namespace URI to associate with this prefix. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindNamespace(String prefix, String nsURI) + { + _nsResolver.addNamespace(prefix, nsURI); + return this; + } + + + /** + * Sets the default namespace binding: this will be applied to all + * expressions that do not explicitly specify a prefix (although + * they must use the colon separating the non-existent prefix from + * the element name). + * + * @param nsURI The default namespace for this document. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindDefaultNamespace(String nsURI) + { + _nsResolver.setDefaultNamespace(nsURI); + return this; + } + + + /** + * Binds a value to a variable, replacing any previous value for that + * variable. Unlike other configuration methods, this may be called + * after calling <code>evaluate()</code>; the new values will be used + * for subsequent evaluations. + * + * @param name The name of the variable; this is turned into a + * <code>QName</code> without namespace. + * @param value The value of the variable; the XPath evaluator must + * be able to convert this value into a type usable in + * an XPath expression. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindVariable(String name, Object value) + { + return bindVariable(new QName(name), value); + } + + + /** + * Binds a value to a variable, replacing any previous value for that + * variable. Unlike other configuration methods, this may be called + * after calling <code>evaluate()</code>; the new values will be used + * for subsequent evaluations. + * + * @param name The fully-qualified name of the variable. + * @param value The value of the variable; the XPath evaluator must + * be able to convert this value into a type usable in + * an XPath expression. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindVariable(QName name, Object value) + { + _variables.put(name, value); + return this; + } + + + /** + * Binds a function to this expression, replacing any previous binding + * for the specified name. Note that <code>XPathFunctionResolver</code> + * can support multiple functions with the same name; we don't. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. You will need to add a namespace binding along with + * your function binding. + * + * @param name The fully-qualified name for this binding. + * @param func The function to bind to this name. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindFunction(QName name, XPathFunction func) + { + _functions.put(name, func); + return this; + } + + + /** + * Applies this expression to the root of the specified document, + * converting the resulting NodeList into a java.util.List for ease + * in iteration. + */ + public List<Node> evaluate(Document context) + { + return evaluate(context.getDocumentElement()); + } + + + /** + * Applies this expression to the specified element, converting the + * resulting NodeList into a java.util.List for ease in iteration. + */ + public List<Node> evaluate(Element context) + { + compileIfNeeded(); + try + { + NodeList result = (NodeList)_compiled.evaluate(context, XPathConstants.NODESET); + List<Node> ret = new ArrayList<Node>(result.getLength()); + for (int ii = 0 ; ii < result.getLength() ; ii++) + { + ret.add(result.item(ii)); + } + return ret; + } + catch (Exception ee) + { + throw new XmlException("unable to evaluate: " + _expr, ee); + } + } + + + /** + * Applies this expression to the root of the specified document, + * requesting the <code>STRING</code> return type. + */ + public String evaluateAsString(Document context) + { + return evaluateAsString(context.getDocumentElement()); + } + + + /** + * Applies this expression to the specified element, requesting the + * <code>STRING</code> return type. + */ + public String evaluateAsString(Element context) + { + compileIfNeeded(); + try + { + return _compiled.evaluate(context); + } + catch (Exception ee) + { + throw new XmlException("unable to evaluate: " + _expr, ee); + } + } + + + /** + * Applies this expression to the root of the specified document, + * requesting the <code>NUMBER</code> return type. + */ + public Double evaluateAsNumber(Document context) + { + return evaluateAsNumber(context.getDocumentElement()); + } + + + /** + * Applies this expression to the specified element, requesting the + * <code>NUMBER</code> return type. + */ + public Double evaluateAsNumber(Element context) + { + compileIfNeeded(); + try + { + return (Double)_compiled.evaluate(context, XPathConstants.NUMBER); + } + catch (Exception ee) + { + throw new XmlException("unable to evaluate: " + _expr, ee); + } + } + + + /** + * Applies this expression to the root of the specified document, + * requesting the <code>BOOLEAN</code> return type. + */ + public Boolean evaluateAsBoolean(Document context) + { + return evaluateAsBoolean(context.getDocumentElement()); + } + + + /** + * Applies this expression to the specified element, requesting the + * <code>BOOLEAN</code> return type. + */ + public Boolean evaluateAsBoolean(Element context) + { + compileIfNeeded(); + try + { + return (Boolean)_compiled.evaluate(context, XPathConstants.BOOLEAN); + } + catch (Exception ee) + { + throw new XmlException("unable to evaluate: " + _expr, ee); + } + } + + +//---------------------------------------------------------------------------- +// Overrides of Object +//---------------------------------------------------------------------------- + + + /** + * Two instances are considered equal if they have the same expression, + * namespace mappings, variable mappings, and function mappings. Note + * that instances with function mappings are all but guaranteed to be + * not-equal, unless you override the <code>equals()</code> method on + * the function implementation class. + */ + @Override + public final boolean equals(Object obj) + { + if (obj instanceof XPathWrapper) + { + XPathWrapper that = (XPathWrapper)obj; + return this._expr.equals(that._expr) + && this._nsResolver.equals(that._nsResolver) + && this._variables.equals(that._variables) + && this._functions.equals(that._functions); + } + return false; + } + + + /** + * Hash code is driven by the expression, ignoring differences between + * namespaces, variables, and functions. + */ + @Override + public int hashCode() + { + return _expr.hashCode(); + } + + + /** + * The string value is the expression. + */ + @Override + public String toString() + { + return _expr; + } + + +//---------------------------------------------------------------------------- +// Internals +//---------------------------------------------------------------------------- + + /** + * Compiles the expression, if it has not already been compiled. This is + * called from the various <code>evaluate</code> methods, and ensures + * that the caller has completely configured the wrapper prior to use. + */ + private void compileIfNeeded() + { + if (_compiled != null) + return; + + try + { + XPath xpath = XPathFactory.newInstance().newXPath(); + xpath.setNamespaceContext(_nsResolver); + xpath.setXPathVariableResolver(new MyVariableResolver()); + xpath.setXPathFunctionResolver(new MyFunctionResolver()); + _compiled = xpath.compile(_expr); + } + catch (XPathExpressionException ee) + { + throw new XmlException("unable to compile: " + _expr, ee); + } + } + + + /** + * Resolver for variable references. + */ + private class MyVariableResolver + implements XPathVariableResolver + { + public Object resolveVariable(QName name) + { + return _variables.get(name); + } + } + + + /** + * Resolver for function references. Note that we ignore the "arity" + * argument, meaning that there's a single binding per name. + */ + private class MyFunctionResolver + implements XPathFunctionResolver + { + public XPathFunction resolveFunction(QName name, int arity) + { + return _functions.get(name); + } + } +} Property changes on: trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java ___________________________________________________________________ Added: svn:executable + * Added: trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java 2008-12-01 00:09:30 UTC (rev 36) @@ -0,0 +1,235 @@ +package net.sf.practicalxml; + +import java.util.List; +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + + +public class TestXPathWrapper +extends AbstractTestCase +{ + public TestXPathWrapper(String name) + { + super(name); + } + + +//---------------------------------------------------------------------------- +// Test data +//---------------------------------------------------------------------------- + + public final static String EL_ROOT = "root"; + public final static String EL_CHILD = "child"; + public final static String NS1 = "ns1"; + public final static String NS2 = "ns2"; + + Document _dom; + Element _root; + Element _child1; + Element _child2; + Element _child3; + + @Override + protected void setUp() + { + _root = DomUtil.newDocument(EL_ROOT); + _child1 = DomUtil.appendChild(_root, EL_CHILD); + _child2 = DomUtil.appendChild(_root, NS1, EL_CHILD); + _child3 = DomUtil.appendChild(_root, NS2, EL_CHILD); + _dom = _root.getOwnerDocument(); + } + + +//---------------------------------------------------------------------------- +// Support Code +//---------------------------------------------------------------------------- + + +//---------------------------------------------------------------------------- +// Test Cases +//---------------------------------------------------------------------------- + + // the basic test to verify we can compile and execute + public void testCurrentElement() + throws Exception + { + XPathWrapper xpath = new XPathWrapper("."); + + List<Node> result1 = xpath.evaluate(_dom); + assertEquals(1, result1.size()); + assertSame(_root, result1.get(0)); + + List<Node> result2 = xpath.evaluate(_root); + assertEquals(1, result2.size()); + assertSame(_root, result2.get(0)); + + List<Node> result3 = xpath.evaluate(_child1); + assertEquals(1, result3.size()); + assertSame(_child1, result3.get(0)); + } + + + public void testEvalAsString() + throws Exception + { + _root.setAttribute("foo", "bar"); + _root.setAttribute("argle", "bargle"); + + XPathWrapper xpath = new XPathWrapper("@foo"); + + assertEquals("bar", xpath.evaluateAsString(_root)); + assertEquals("bar", xpath.evaluateAsString(_dom)); + } + + + public void testEvalAsNumber() + throws Exception + { + _root.setAttribute("foo", "10"); + + XPathWrapper xpath = new XPathWrapper("@foo"); + + assertEquals(Double.valueOf(10.0), xpath.evaluateAsNumber(_root)); + assertEquals(Double.valueOf(10.0), xpath.evaluateAsNumber(_dom)); + } + + + public void testEvalAsBoolean() + throws Exception + { + _root.setAttribute("foo", "10"); + + XPathWrapper xpath1 = new XPathWrapper("@foo=10"); + + assertTrue(xpath1.evaluateAsBoolean(_root).booleanValue()); + assertTrue(xpath1.evaluateAsBoolean(_dom).booleanValue()); + + _root.setAttribute("foo", "20"); + + assertFalse(xpath1.evaluateAsBoolean(_root).booleanValue()); + assertFalse(xpath1.evaluateAsBoolean(_dom).booleanValue()); + } + + + public void testNamespaces() throws Exception + { + XPathWrapper xpath1 = new XPathWrapper("//child"); + List<Node> result1 = xpath1.evaluate(_dom); + assertEquals(1, result1.size()); + assertSame(_child1, result1.get(0)); + + XPathWrapper xpath2 = new XPathWrapper("//ns:child") + .bindNamespace("ns", NS1); + List<Node> result2 = xpath2.evaluate(_dom); + assertEquals(1, result2.size()); + assertSame(_child2, result2.get(0)); + + XPathWrapper xpath3 = new XPathWrapper("//:child") + .bindDefaultNamespace(NS2); + List<Node> result3 = xpath3.evaluate(_dom); + assertEquals(1, result3.size()); + assertSame(_child3, result3.get(0)); + } + + + public void testVariables() throws Exception + { + _child1.setAttribute("bar", "baz"); + _child2.setAttribute("bar", "bargle"); + + XPathWrapper xpath = new XPathWrapper("//*[@bar=$test]") + .bindVariable(new QName("test"), "baz"); + + List<Node> result1 = xpath.evaluate(_dom); + assertEquals(1, result1.size()); + assertSame(_child1, result1.get(0)); + + xpath.bindVariable("test", "bargle"); + + List<Node> result2 = xpath.evaluate(_dom); + assertEquals(1, result2.size()); + assertSame(_child2, result2.get(0)); + } + + + public void testFunctions() throws Exception + { + _child1.setAttribute("bar", "baz"); + _child2.setAttribute("bar", "bargle"); + + XPathFunction myfunc = new XPathFunction() { + public Object evaluate(List args) + throws XPathFunctionException + { + NodeList arg = (NodeList)args.get(0); + Element asElem = (Element)arg.item(0); + return asElem.getAttribute("bar"); + } + }; + + XPathWrapper xpath = new XPathWrapper("ns:myfunc(.)") + .bindNamespace("ns", NS1) + .bindFunction(new QName(NS1, "myfunc"), myfunc); + + assertEquals("baz", xpath.evaluateAsString(_child1)); + assertEquals("bargle", xpath.evaluateAsString(_child2)); + } + + + + public void testEqualsAndHashCode() throws Exception + { + Object obj1a = new XPathWrapper("//foo"); + Object obj1b = new XPathWrapper("//foo"); + Object obj2a = new XPathWrapper("//foo") + .bindDefaultNamespace("zippy"); + Object obj2b = new XPathWrapper("//foo") + .bindDefaultNamespace("zippy"); + Object obj3a = new XPathWrapper("//foo") + .bindNamespace("argle", "bargle"); + Object obj3b = new XPathWrapper("//foo") + .bindNamespace("argle", "bargle"); + Object obj4a = new XPathWrapper("//foo") + .bindVariable("argle", "bargle"); + Object obj4b = new XPathWrapper("//foo") + .bindVariable("argle", "bargle"); + Object obj5a = new XPathWrapper("//foo") + .bindFunction(new QName("foo"), null); + Object obj5b = new XPathWrapper("//foo") + .bindFunction(new QName("foo"), null); + + assertTrue(obj1a.equals(obj1b)); + assertTrue(obj1b.equals(obj1a)); + assertEquals(obj1a.hashCode(), obj1b.hashCode()); + + assertFalse(obj1a.equals(obj2a)); + assertTrue(obj2a.equals(obj2b)); + assertTrue(obj2b.equals(obj2a)); + assertEquals(obj1a.hashCode(), obj2a.hashCode()); + assertEquals(obj2a.hashCode(), obj2b.hashCode()); + + assertFalse(obj1a.equals(obj3a)); + assertTrue(obj3a.equals(obj3b)); + assertTrue(obj3b.equals(obj3a)); + assertEquals(obj1a.hashCode(), obj3a.hashCode()); + assertEquals(obj3a.hashCode(), obj3b.hashCode()); + + assertFalse(obj1a.equals(obj4a)); + assertTrue(obj4a.equals(obj4b)); + assertTrue(obj4b.equals(obj4a)); + assertEquals(obj1a.hashCode(), obj4a.hashCode()); + assertEquals(obj4a.hashCode(), obj4b.hashCode()); + + assertFalse(obj1a.equals(obj5a)); + assertTrue(obj5a.equals(obj5b)); + assertTrue(obj5b.equals(obj5a)); + assertEquals(obj1a.hashCode(), obj5a.hashCode()); + assertEquals(obj5a.hashCode(), obj5b.hashCode()); + } +} Property changes on: trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java ___________________________________________________________________ Added: svn:executable + * This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-01 02:11:14
|
Revision: 37 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=37&view=rev Author: kdgregory Date: 2008-12-01 02:11:10 +0000 (Mon, 01 Dec 2008) Log Message: ----------- NamespaceResolver, SimpleNamespaceResolver: improve test coverage, fix corner cases Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/misc/SimpleNamespaceResolver.java trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java trunk/src/test/java/net/sf/practicalxml/misc/TestSimpleNamespaceResolver.java Modified: trunk/src/main/java/net/sf/practicalxml/misc/SimpleNamespaceResolver.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/misc/SimpleNamespaceResolver.java 2008-12-01 00:09:30 UTC (rev 36) +++ trunk/src/main/java/net/sf/practicalxml/misc/SimpleNamespaceResolver.java 2008-12-01 02:11:10 UTC (rev 37) @@ -23,6 +23,11 @@ public SimpleNamespaceResolver(String prefix, String nsURI) { + if (prefix == null) + throw new IllegalArgumentException("prefix may not be null"); + if (nsURI == null) + throw new IllegalArgumentException("nsURI may not be null"); + _prefix = prefix; _nsURI = nsURI; _prefixes = Arrays.asList(prefix); @@ -48,7 +53,6 @@ return XMLConstants.XML_NS_URI; else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; - else return null; } @@ -84,7 +88,7 @@ String prefix = getPrefix(nsURI); if (_prefix.equals(prefix)) return _prefixes.iterator(); - else if (_prefix == null) + else if (prefix == null) return Collections.<String>emptyList().iterator(); else return Arrays.asList(prefix).iterator(); Modified: trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java 2008-12-01 00:09:30 UTC (rev 36) +++ trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java 2008-12-01 02:11:10 UTC (rev 37) @@ -2,6 +2,7 @@ import java.util.Iterator; import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; import net.sf.practicalxml.AbstractTestCase; @@ -33,7 +34,7 @@ final String prefix2 = "argle"; final String nsURI2 = "bargle"; - NamespaceResolver resolv = new NamespaceResolver() + NamespaceContext resolv = new NamespaceResolver() .addNamespace(prefix1, nsURI1) .addNamespace(prefix2, nsURI2); @@ -59,7 +60,7 @@ final String prefix2 = "argle"; final String nsURI = "bargle"; - NamespaceResolver resolv = new NamespaceResolver() + NamespaceContext resolv = new NamespaceResolver() .addNamespace(prefix1, nsURI) .addNamespace(prefix2, nsURI); @@ -75,10 +76,56 @@ } - public void testStandardMappings() throws Exception + public void testUnboundNamespace() throws Exception { + NamespaceContext resolv = new NamespaceResolver(); + + assertNull(resolv.getNamespaceURI("argle")); + assertNull(resolv.getPrefix("argle")); + assertFalse(resolv.getPrefixes("argle").hasNext()); + } + + + public void testInvalidNamespace() throws Exception + { NamespaceResolver resolv = new NamespaceResolver(); + try + { + resolv.addNamespace(null, "foo"); + fail("accepted null prefix"); + } + catch (IllegalArgumentException e) + { + // success + } + + try + { + resolv.addNamespace("foo", null); + fail("accepted null nsURI"); + } + catch (IllegalArgumentException e) + { + // success + } + + try + { + resolv.setDefaultNamespace(null); + fail("accepted null nsURI"); + } + catch (IllegalArgumentException e) + { + // success + } + } + + + public void testStandardMappings() throws Exception + { + NamespaceContext resolv = new NamespaceResolver(); + assertEquals(XMLConstants.XML_NS_URI, resolv.getNamespaceURI(XMLConstants.XML_NS_PREFIX)); assertEquals(XMLConstants.XML_NS_PREFIX, resolv.getPrefix(XMLConstants.XML_NS_URI)); Iterator<String> itx1 = resolv.getPrefixes(XMLConstants.XML_NS_URI); @@ -145,29 +192,31 @@ public void testEqualsAndHashCode() throws Exception { - NamespaceResolver resolv1 = new NamespaceResolver() - .addNamespace("foo", "bar") - .setDefaultNamespace("zippy"); - NamespaceResolver resolv2 = new NamespaceResolver() - .addNamespace("foo", "bar") - .setDefaultNamespace("zippy"); - NamespaceResolver resolv3 = new NamespaceResolver() - .addNamespace("foo", "bar"); - NamespaceResolver resolv4 = new NamespaceResolver() - .addNamespace("argle", "bargle"); + Object obj1 = new NamespaceResolver() + .addNamespace("foo", "bar") + .setDefaultNamespace("zippy"); + Object obj2 = new NamespaceResolver() + .addNamespace("foo", "bar") + .setDefaultNamespace("zippy"); + Object obj3 = new NamespaceResolver() + .addNamespace("foo", "bar"); + Object obj4 = new NamespaceResolver() + .addNamespace("argle", "bargle"); - assertTrue(resolv1.equals(resolv2)); - assertTrue(resolv2.equals(resolv1)); - assertEquals(resolv1.hashCode(), resolv2.hashCode()); + assertFalse(obj1.equals(new Object())); - assertFalse(resolv1.equals(resolv3)); - assertFalse(resolv3.equals(resolv1)); + assertTrue(obj1.equals(obj2)); + assertTrue(obj2.equals(obj1)); + assertEquals(obj1.hashCode(), obj2.hashCode()); - assertFalse(resolv3.equals(resolv4)); - assertFalse(resolv4.equals(resolv3)); + assertFalse(obj1.equals(obj3)); + assertFalse(obj3.equals(obj1)); + assertFalse(obj3.equals(obj4)); + assertFalse(obj4.equals(obj3)); + // this works today ... assume that the underlying calcs don't change - assertFalse(resolv3.hashCode() == resolv4.hashCode()); + assertFalse(obj3.hashCode() == obj4.hashCode()); } Modified: trunk/src/test/java/net/sf/practicalxml/misc/TestSimpleNamespaceResolver.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/misc/TestSimpleNamespaceResolver.java 2008-12-01 00:09:30 UTC (rev 36) +++ trunk/src/test/java/net/sf/practicalxml/misc/TestSimpleNamespaceResolver.java 2008-12-01 02:11:10 UTC (rev 37) @@ -9,6 +9,30 @@ public class TestSimpleNamespaceResolver extends AbstractTestCase { + public void testInvalidConstruction() throws Exception + { + try + { + new SimpleNamespaceResolver(null, "foo"); + fail("accepted null prefix"); + } + catch (IllegalArgumentException e) + { + // success + } + + try + { + new SimpleNamespaceResolver("foo", null); + fail("accepted null nsURI"); + } + catch (IllegalArgumentException e) + { + // success + } + } + + public void testLookup() throws Exception { final String prefix = "foo"; @@ -25,9 +49,35 @@ } + public void testDefaultNamespace() throws Exception + { + final String prefix = ""; + final String nsURI = "bar"; + + NamespaceContext resolv = new SimpleNamespaceResolver(prefix, nsURI); + + assertEquals(nsURI, resolv.getNamespaceURI(prefix)); + assertEquals(prefix, resolv.getPrefix(nsURI)); + + Iterator<String> itx = resolv.getPrefixes(nsURI); + assertEquals(prefix, itx.next()); + assertFalse(itx.hasNext()); + } + + + public void testUnboundNamespace() throws Exception + { + NamespaceContext resolv = new SimpleNamespaceResolver("foo", "bar"); + + assertNull(resolv.getNamespaceURI("argle")); + assertNull(resolv.getPrefix("argle")); + assertFalse(resolv.getPrefixes("argle").hasNext()); + } + + public void testStandardMappings() throws Exception { - NamespaceResolver resolv = new NamespaceResolver(); + NamespaceContext resolv = new SimpleNamespaceResolver("foo", "bar"); assertEquals(XMLConstants.XML_NS_URI, resolv.getNamespaceURI(XMLConstants.XML_NS_PREFIX)); assertEquals(XMLConstants.XML_NS_PREFIX, resolv.getPrefix(XMLConstants.XML_NS_URI)); @@ -75,18 +125,20 @@ public void testEqualsAndHashCode() throws Exception { - Object resolv1 = new SimpleNamespaceResolver("foo", "bar"); - Object resolv2 = new SimpleNamespaceResolver("foo", "bar"); - Object resolv3 = new SimpleNamespaceResolver("argle", "bargle"); + Object obj1 = new SimpleNamespaceResolver("foo", "bar"); + Object obj2 = new SimpleNamespaceResolver("foo", "bar"); + Object obj3 = new SimpleNamespaceResolver("argle", "bargle"); - assertTrue(resolv1.equals(resolv2)); - assertTrue(resolv2.equals(resolv1)); - assertTrue(resolv1.hashCode() == resolv2.hashCode()); + assertFalse(obj1.equals(new Object())); - assertFalse(resolv1.equals(resolv3)); - assertFalse(resolv3.equals(resolv1)); + assertTrue(obj1.equals(obj2)); + assertTrue(obj2.equals(obj1)); + assertTrue(obj1.hashCode() == obj2.hashCode()); + + assertFalse(obj1.equals(obj3)); + assertFalse(obj3.equals(obj1)); // this works today ... assume that the underlying calcs don't change - assertFalse(resolv1.hashCode() == resolv3.hashCode()); + assertFalse(obj1.hashCode() == obj3.hashCode()); } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-03 14:08:48
|
Revision: 39 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=39&view=rev Author: kdgregory Date: 2008-12-03 14:08:45 +0000 (Wed, 03 Dec 2008) Log Message: ----------- add DomAsserts Added Paths: ----------- trunk/src/main/java/net/sf/practicalxml/junit/ trunk/src/main/java/net/sf/practicalxml/junit/DomAsserts.java trunk/src/test/java/net/sf/practicalxml/junit/ trunk/src/test/java/net/sf/practicalxml/junit/TestDomAsserts.java Added: trunk/src/main/java/net/sf/practicalxml/junit/DomAsserts.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/junit/DomAsserts.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/junit/DomAsserts.java 2008-12-03 14:08:45 UTC (rev 39) @@ -0,0 +1,273 @@ +package net.sf.practicalxml.junit; + +import static junit.framework.Assert.*; + +import java.util.List; + +import junit.framework.Assert; + +import net.sf.practicalxml.DomUtil; +import net.sf.practicalxml.XPathWrapper; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * JUnit assertions for DOM documents. These are defined as static methods, + * so may be statically imported (although in some cases this will clash + * with the standard assertions in <code>junit.framework.Assert</code>). + * <p> + * As with the standard JUnit assertions, there are two forms for each method: + * one that takes an explanatory message, and one that doesn't. + */ +public class DomAsserts +{ + /** + * Asserts that an element has the given name, ignoring namespace. + * + * @param expected The expected name. + * @param elem The element to assert. + */ + public static void assertName(String expected, Element elem) + { + Assert.assertEquals(expected, DomUtil.getLocalName(elem)); + } + + /** + * Asserts that an element has the given name, ignoring namespace. + * + * @param message Message to display if assertion fails. + * @param expected The expected name. + * @param elem The element to assert. + */ + public static void assertName(String message, String expected, Element elem) + { + Assert.assertEquals(message, expected, DomUtil.getLocalName(elem)); + } + + + /** + * Asserts that an element has the given name and namespace URI. + * <p> + * If assertion fails, will display message indicating whether name or + * namespace was invalid. + * + * @param expectedNSUri The expected namespace URI. May be <code>null + * </code> to assert that the element does not + * have a namespace. + * @param expectedName The expected name. + * @param elem The element to assert. + */ + public static void assertNamespaceAndName( + String expectedNSUri, String expectedName, Element elem) + { + Assert.assertEquals("invalid namespace", expectedNSUri, elem.getNamespaceURI()); + Assert.assertEquals("invalid localname", expectedName, DomUtil.getLocalName(elem)); + } + + + /** + * Asserts that an element has the given name and namespace URI. + * + * @param message Message to display if assertion fails. + * @param expectedNSUri The expected namespace URI. May be <code>null + * </code> to assert that the element does not + * have a namespace. + * @param expectedName The expected name. + * @param elem The element to assert. + */ + public static void assertNamespaceAndName( + String message, String expectedNSUri, String expectedName, Element elem) + { + Assert.assertEquals(message, expectedNSUri, elem.getNamespaceURI()); + Assert.assertEquals(message, expectedName, DomUtil.getLocalName(elem)); + } + + + /** + * Asserts that the specified XPath selects at least one node. + * <p> + * Will display the XPath if assertion fails. + * + * @param elem The element to serve as initial context. + * @param xpath The path expression. + */ + public static void assertExists(Element elem, String xpath) + { + assertExists(xpath, elem, xpath); + } + + + /** + * Asserts that the specified XPath selects at least one node. + * + * @param message Message to display if assertion fails. + * @param elem The element to serve as initial context. + * @param xpath The path expression. + */ + public static void assertExists(String message, Element elem, String xpath) + { + assertExists(message, elem, new XPathWrapper(xpath)); + } + + + /** + * Asserts that the specified XPath selects at least one node. Uses the + * <code>XPathWrapper</code> class to allow more complex paths, including + * namespace bindings. + * <p> + * Will display the XPath if assertion fails. + * + * @param elem The element to serve as initial context. + * @param xpath The path expression. + */ + public static void assertExists(Element elem, XPathWrapper xpath) + { + assertExists(xpath.toString(), elem, xpath); + } + + + /** + * Asserts that the specified XPath selects at least one node. Uses the + * <code>XPathWrapper</code> class to allow more complex paths, including + * namespace bindings. + * + * @param message Message to display if assertion fails. + * @param elem The element to serve as initial context. + * @param xpath The path expression. + */ + public static void assertExists(String message, Element elem, XPathWrapper xpath) + { + List<Node> result = xpath.evaluate(elem); + assertTrue(message, result.size() > 0); + } + + + /** + * Asserts that the specified XPath selects a specified number of nodes. + * <p> + * Will display the XPath if assertion fails. + * + * @param expected The expected number of nodes selected. + * @param elem The element to serve as initial context. + * @param xpath The path expression. + */ + public static void assertCount(int expected, Element elem, String xpath) + { + assertCount(xpath, expected, elem, xpath); + } + + + /** + * Asserts that the specified XPath selects a specified number of nodes. + * + * @param message Message to display if assertion fails. + * @param expected The expected number of nodes selected. + * @param elem The element to serve as initial context. + * @param xpath The path expression. + */ + public static void assertCount( + String message, int expected, Element elem, String xpath) + { + assertCount(message, expected, elem, new XPathWrapper(xpath)); + } + + + /** + * Asserts that the specified XPath selects a specified number of nodes. + * Uses the <code>XPathWrapper</code> class to allow more complex paths, + * including namespace bindings. + * <p> + * Will display the XPath if assertion fails. + * + * @param expected The expected number of nodes selected. + * @param elem The element to serve as initial context. + * @param xpath The path expression. + */ + public static void assertCount(int expected, Element elem, XPathWrapper xpath) + { + assertCount(xpath.toString(), expected, elem, xpath); + } + + + /** + * Asserts that the specified XPath selects a specified number of nodes. + * Uses the <code>XPathWrapper</code> class to allow more complex paths, + * including namespace bindings. + * + * @param message Message to display if assertion fails. + * @param expected The expected number of nodes selected. + * @param elem The element to serve as initial context. + * @param xpath The path expression. + */ + public static void assertCount( + String message, int expected, Element elem, XPathWrapper xpath) + { + List<Node> result = xpath.evaluate(elem); + Assert.assertEquals(message, expected, result.size()); + } + + + /** + * Asserts that the specified XPath selects a particular String value. + * <p> + * Will display the XPath if assertion fails. + * + * @param expected The expected value. + * @param elem The element to serve as initial context. + * @param xpath The path expression. + */ + public static void assertEquals(String expected, Element elem, String xpath) + { + assertEquals(xpath, expected, elem, new XPathWrapper(xpath)); + } + + + /** + * Asserts that the specified XPath selects a particular String value. + * + * @param message Message to display if assertion fails. + * @param expected The expected value. + * @param elem The element to serve as initial context. + * @param xpath The path expression. + */ + public static void assertEquals( + String message, String expected, Element elem, String xpath) + { + assertEquals(message, expected, elem, new XPathWrapper(xpath)); + } + + + /** + * Asserts that the specified XPath selects a particular String value. + * This variant uses the <code>XPathWrapper</code> class to allow + * more complex paths, including namespace bindings. + * <p> + * Will display the XPath if assertion fails. + * + * @param expected The expected value. + * @param elem The element to serve as initial context. + * @param xpath The path expression. + */ + public static void assertEquals(String expected, Element elem, XPathWrapper xpath) + { + assertEquals(xpath.toString(), expected, elem, xpath); + } + + + /** + * Asserts that the specified XPath selects a particular String value. + * This variant uses the <code>XPathWrapper</code> class to allow + * more complex paths, including namespace bindings. + * + * @param message Message to display if assertion fails. + * @param expected The expected value. + * @param elem The element to serve as initial context. + * @param xpath The path expression. + */ + public static void assertEquals( + String message, String expected, Element elem, XPathWrapper xpath) + { + Assert.assertEquals(message, expected, xpath.evaluateAsString(elem)); + } +} Added: trunk/src/test/java/net/sf/practicalxml/junit/TestDomAsserts.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/junit/TestDomAsserts.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/junit/TestDomAsserts.java 2008-12-03 14:08:45 UTC (rev 39) @@ -0,0 +1,241 @@ +package net.sf.practicalxml.junit; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import junit.framework.AssertionFailedError; + +import net.sf.practicalxml.AbstractTestCase; +import net.sf.practicalxml.DomUtil; +import net.sf.practicalxml.XPathWrapper; + + +public class TestDomAsserts +extends AbstractTestCase +{ + public TestDomAsserts(String name) + { + super(name); + } + + +//---------------------------------------------------------------------------- +// Test data +//---------------------------------------------------------------------------- + + public final static String MESSAGE = "qwery this is a test asdf"; + + public final static String INVALID_NAME = "slkdfio"; + + public final static String EL_ROOT = "root"; + public final static String EL_CHILD = "child"; + public final static String NS = "ns"; + public final static String NS1 = "ns1"; + public final static String NS2 = "ns2"; + public final static String ATTR1 = "foo"; + public final static String ATTVAL1a = "bar"; + public final static String ATTVAL1b = "10"; + + public final static String XPATH1 = "//" + EL_CHILD; + public final static String XPATH1a = "//" + EL_CHILD + "[@" + ATTR1 + "=\"" + ATTVAL1a + "\"]"; + public final static String XPATH2 = "//" + NS + ":" + EL_CHILD; + public final static String XPATH2a = "//" + NS + ":" + EL_CHILD + "[@" + ATTR1 + "=\"" + ATTVAL1a + "\"]"; + public final static String XPATH3 = "@" + ATTR1; + public final static String XPATH4 = "//" + INVALID_NAME; + + + Document _dom; + Element _root; + Element _child1; + Element _child2; + Element _child3; + Element _child4; + Element _child5; + + + @Override + protected void setUp() + { + _root = DomUtil.newDocument(EL_ROOT); + _child1 = DomUtil.appendChild(_root, EL_CHILD); + _child2 = DomUtil.appendChild(_root, EL_CHILD); + _child3 = DomUtil.appendChild(_root, EL_CHILD); + _child4 = DomUtil.appendChild(_root, NS1, EL_CHILD); + _child5 = DomUtil.appendChild(_root, NS2, EL_CHILD); + + _child2.setAttribute(ATTR1, ATTVAL1a); + _child3.setAttribute(ATTR1, ATTVAL1b); + _child4.setAttribute(ATTR1, ATTVAL1a); + _child5.setAttribute(ATTR1, ATTVAL1b); + + _dom = _root.getOwnerDocument(); + } + + +//---------------------------------------------------------------------------- +// Support Code +//---------------------------------------------------------------------------- + + +//---------------------------------------------------------------------------- +// Test Cases +//---------------------------------------------------------------------------- + + public void testAssertName() throws Exception + { + DomAsserts.assertName(EL_CHILD, _child1); + DomAsserts.assertName(EL_CHILD, _child4); + + AssertionFailedError fail1 = null; + try + { + DomAsserts.assertName(INVALID_NAME, _child1); + } + catch (AssertionFailedError ee) + { + fail1 = ee; + } + assertNotNull("asserted invalid name", fail1); + + + AssertionFailedError fail2 = null; + try + { + DomAsserts.assertName(MESSAGE, INVALID_NAME, _child1); + } + catch (AssertionFailedError ee) + { + fail2 = ee; + } + assertTrue("missing message", fail2.getMessage().contains(MESSAGE)); + } + + + public void testAssertNameAndNamespace() throws Exception + { + DomAsserts.assertNamespaceAndName(null, EL_CHILD, _child1); + DomAsserts.assertNamespaceAndName(NS1, EL_CHILD, _child4); + + AssertionFailedError fail1 = null; + try + { + DomAsserts.assertNamespaceAndName(INVALID_NAME, EL_CHILD, _child1); + } + catch (AssertionFailedError ee) + { + fail1 = ee; + } + assertNotNull("asserted invalid namespace", fail1); + + AssertionFailedError fail2 = null; + try + { + DomAsserts.assertNamespaceAndName(NS1, INVALID_NAME, _child4); + } + catch (AssertionFailedError ee) + { + fail2 = ee; + } + assertNotNull("asserted invalid name", fail2); + + AssertionFailedError fail3 = null; + try + { + DomAsserts.assertNamespaceAndName(MESSAGE, NS1, INVALID_NAME, _child4); + } + catch (AssertionFailedError ee) + { + fail3 = ee; + } + assertTrue("missing message", fail3.getMessage().contains(MESSAGE)); + } + + + public void testAssertExists() throws Exception + { + DomAsserts.assertExists(_root, XPATH1); + DomAsserts.assertExists(_root, XPATH1a); + DomAsserts.assertExists(_root, new XPathWrapper(XPATH2).bindNamespace(NS, NS1)); + + AssertionFailedError fail1 = null; + try + { + DomAsserts.assertExists(_root, XPATH4); + } + catch (AssertionFailedError ee) + { + fail1 = ee; + } + assertNotNull("asserted invalid xpath", fail1); + + AssertionFailedError fail2 = null; + try + { + DomAsserts.assertExists(MESSAGE, _root, XPATH4); + } + catch (AssertionFailedError ee) + { + fail2 = ee; + } + assertTrue("missing message", fail2.getMessage().contains(MESSAGE)); + } + + + public void testAssertCount() throws Exception + { + DomAsserts.assertCount(3, _root, XPATH1); + DomAsserts.assertCount(1, _root, XPATH1a); + DomAsserts.assertCount(1, _root, new XPathWrapper(XPATH2).bindNamespace(NS, NS1)); + DomAsserts.assertCount(0, _root, XPATH4); + + AssertionFailedError fail1 = null; + try + { + DomAsserts.assertCount(2, _root, XPATH1); + } + catch (AssertionFailedError ee) + { + fail1 = ee; + } + assertNotNull("asserted incorrect count", fail1); + + AssertionFailedError fail2 = null; + try + { + DomAsserts.assertCount(MESSAGE, 2, _root, XPATH1); + } + catch (AssertionFailedError ee) + { + fail2 = ee; + } + assertTrue("missing message", fail2.getMessage().contains(MESSAGE)); + } + + + public void testAssertEqualsString() throws Exception + { + DomAsserts.assertEquals(ATTVAL1a, _child2, XPATH3); + + AssertionFailedError fail1 = null; + try + { + DomAsserts.assertEquals(ATTVAL1a, _child1, XPATH3); + } + catch (AssertionFailedError ee) + { + fail1 = ee; + } + assertNotNull("asserted when xpath should have returned nothing", fail1); + + AssertionFailedError fail2 = null; + try + { + DomAsserts.assertEquals(MESSAGE, ATTVAL1a, _child1, XPATH3); + } + catch (AssertionFailedError ee) + { + fail2 = ee; + } + assertTrue("missing message", fail2.getMessage().contains(MESSAGE)); + } +} Property changes on: trunk/src/test/java/net/sf/practicalxml/junit/TestDomAsserts.java ___________________________________________________________________ Added: svn:executable + * This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-08 03:13:57
|
Revision: 40 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=40&view=rev Author: kdgregory Date: 2008-12-08 03:13:51 +0000 (Mon, 08 Dec 2008) Log Message: ----------- add DomUtil.getSiblings(), DomUtil.getAbsolutePath() rename namespace-inheriting variant of DomUtil.appendChild() to appendChildWithNamespace() refactor DomUtil.getChildren() Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/DomUtil.java trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java Modified: trunk/src/main/java/net/sf/practicalxml/DomUtil.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/DomUtil.java 2008-12-03 14:08:45 UTC (rev 39) +++ trunk/src/main/java/net/sf/practicalxml/DomUtil.java 2008-12-08 03:13:51 UTC (rev 40) @@ -2,19 +2,20 @@ import java.util.ArrayList; import java.util.List; - +import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import org.apache.commons.lang.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; +import org.apache.commons.lang.StringUtils; + /** * A collection of static utility methods for working with DOM trees. * Most of these are usability workarounds for the <code>org.w3c.dom</code> @@ -69,6 +70,21 @@ /** + * Appends a child element with the specified name and no namespace, to a + * passed parent element. + * + * @param parent The parent element. + * @param lclName Qualified name for the new element. + * + * @return The newly created child element. + */ + public static Element appendChild(Element parent, String lclName) + { + return appendChild(parent, null, lclName); + } + + + /** * Appends a child element with the specified name and namespace, to a * passed parent element. * @@ -88,9 +104,9 @@ /** - * Appends a child element with the specified name, to a passed parent + * Appends a child element with the specified name to a passed parent * element. The child will inherit the parent's namespace (if any), and - * may inherit the parent's namespace prefix. + * may also inherit the parent's namespace prefix. * * @param parent The parent element. * @param qname Qualified name for the new element. If passed a simple @@ -98,7 +114,7 @@ * * @return The newly created child element. */ - public static Element appendChild(Element parent, String qname) + public static Element appendChildWithNamespace(Element parent, String qname) { String nsUri = parent.getNamespaceURI(); String parentPrefix = parent.getPrefix(); @@ -112,9 +128,58 @@ /** - * Returns all <code>Element</code> children of the passed element, - * in document order. + * Returns all <code>Element</code> children of the passed element's + * parent (ie, the element <em>and</em> its siblings). Result is in + * document order. */ + public static List<Element> getSiblings(Element elem) + { + if (elem.getParentNode() instanceof Element) + return getChildren((Element)elem.getParentNode()); + else + { + List<Element> ret = new ArrayList<Element>(); + ret.add(elem); + return ret; + } + } + + + /** + * Returns all <code>Element</code> children of the passed element's + * parent that have the specified <em>localname</em>, ignoring namespace. + * Result is in document order (and will only contain the passed element + * if it satisfies the name test). + */ + public static List<Element> getSiblings(Element elem, String lclName) + { + if (elem.getParentNode() instanceof Element) + return getChildren((Element)elem.getParentNode(), lclName); + else + return new ArrayList<Element>(); + } + + + /** + * Returns all <code>Element</code> children of the passed element's + * parent that have the specified namespace and local name. Result is + * in document order (note that it may not contain the passed element). + * Specified namespace may be <code>null</code>, in which case selected + * children must not have a namespace. + */ + public static List<Element> getSiblings(Element elem, String nsUri, String lclName) + { + if (elem.getParentNode() instanceof Element) + return getChildren((Element)elem.getParentNode(), nsUri, lclName); + else + return new ArrayList<Element>(); + } + + + /** + * Returns all <code>Element</code> children of the passed element, in + * document order. + */ public static List<Element> getChildren(Element parent) { List<Element> ret = new ArrayList<Element>(); @@ -133,8 +198,7 @@ /** * Returns the children of the passed element that have the given - * <em>localname</em>. This method ignores namespace and name - * prefix -- it's probably the best choice for most needs. + * <em>localname</em>, ignoring namespace. * <p> * Returns the children in document order. Returns an empty list if * there are no children matching the specified name. @@ -158,7 +222,9 @@ /** * Returns the children of the passed element that have the given - * namespace and <em>localname</em> (ignoring prefix). + * namespace and <em>localname</em> (ignoring prefix). Namespace may + * be <code>null</code>, in which case the child element must not + * have a namespace. * <p> * Returns the children in document order. Returns an empty list if * there are no children matching the specified namespace/name. @@ -170,9 +236,7 @@ for (int ii = 0 ; ii < children.getLength() ; ii++) { Node child = children.item(ii); - if ((child instanceof Element) - && (nsUri.equals(child.getNamespaceURI())) - && (lclName.equals(getLocalName((Element)child)))) + if ((child instanceof Element) && isNamed((Element)child, nsUri, lclName)) { ret.add((Element)child); } @@ -352,7 +416,7 @@ /** * Determines whether the passed element has the expected namespace URI * and local name. The expected namespace may be null, in which case - * the the element must not have a namespace. + * the element must not have a namespace. */ public static boolean isNamed(Element elem, String nsUri, String localName) { @@ -384,8 +448,9 @@ * limitations: First, it doesn't handle namespaces, although it does * use qualified names where they appear in the document. Second, it * doesn't properly escape quotes in attribute values. Third, and most - * important, it doesn't differentiate between nodes with the same name - * and attribute values (ie, no positional predicates). + * important, it doesn't differentiate between sibling nodes with the + * same name and attribute values; if that's important, use {@link + * #getAbsolutePath}. */ public static String getPath(Element elem, String... attrNames) { @@ -395,6 +460,43 @@ } + /** + * Returns the path from the root of the document to the specified + * element, as an XPath expression using positional predicates to + * differentiate between nodes with the same local name, ignoring + * namespace. + */ + public static String getAbsolutePath(Element elem) + { + StringBuilder sb = new StringBuilder(); + buildAbsolutePath(elem, sb, null, null); + return sb.toString(); + } + + + /** + * Returns the path from the root of the document to the specified + * element, as an XPath expression using positional predicates to + * differentiate between nodes with the same name and namespace. + * <p> + * The <code>nsLookup</code> parameter is used to retrieve prefixes + * for the passed element and its ancestors. If all namespaces can + * be resolved to a prefix, then the returned path may be evaluated + * against the document to retrieve the element. + * <p> + * If <code>nsLookup</code> does not have a mapping for a given + * namespace, the returned path will contain a dummy prefix of the + * form "NSx", where "x" is incremented for each unknown namespace, + * whether or not the namespace has been seen elsewhere in the path. + */ + public static String getAbsolutePath(Element elem, NamespaceContext nsLookup) + { + StringBuilder sb = new StringBuilder(); + buildAbsolutePath(elem, sb, nsLookup, new int[] {0}); + return sb.toString(); + } + + //---------------------------------------------------------------------------- // Internals //---------------------------------------------------------------------------- @@ -428,8 +530,13 @@ /** - * Implementation code for {@link #getPath}, which recursively works + * Implementation code for {@link #getPath}. Recursively works * its way up the tree and adds information for each node. + * + * @param elem The current element, which is appended to the buffer + * after all parent nodes. + * @param sb A buffer used to build the path. + * @param attrNames Attribute names to include as predicates in path. */ private static void buildPath(Element elem, StringBuilder sb, String[] attrNames) { @@ -448,4 +555,83 @@ } } } + + + /** + * Implementation code for {@link #getAbsolutePath}. Recursively works + * its way up the tree and adds information for each node. + * + * @param elem The current element, which is appended to the buffer + * after all parent nodes. + * @param sb A buffer used to build the path. + * @param nsLookup Used to resolve defined namespaces. May be null, in + * which case returned path will not have any namespaces. + * @param nsCounter Used to generate prefixes for unresolved namespaces: + * contains a single element that is incremented for each + * unmapped namespace. + */ + private static void buildAbsolutePath( + Element elem, StringBuilder sb, + NamespaceContext nsLookup, int[] nsCounter) + { + Node parent = elem.getParentNode(); + if (parent instanceof Element) + { + buildAbsolutePath((Element)parent, sb, nsLookup, nsCounter); + } + + String prefix = getPrefix(elem, nsLookup, nsCounter); + String localName = getLocalName(elem); + List<Element> siblings = (nsLookup == null) + ? getSiblings(elem, getLocalName(elem)) + : getSiblings(elem, elem.getNamespaceURI(), getLocalName(elem)); + + sb.append("/"); + if (prefix != null) + sb.append(prefix).append(":"); + sb.append(localName); + if (siblings.size() > 1) + sb.append("[").append(getIndex(elem, siblings)).append("]"); + } + + + /** + * Helper method for {@link #buildAbsolutePath} that returns the prefix + * for a given element, using the passed namespace resolver. + */ + private static String getPrefix(Element elem, NamespaceContext nsLookup, int[] nsCounter) + { + if (nsLookup == null) + return null; + + String nsUri = elem.getNamespaceURI(); + if (nsUri == null) + return null; + + String prefix = nsLookup.getPrefix(nsUri); + if (prefix == null) + prefix = "NS" + nsCounter[0]++; + return prefix; + } + + + /** + * Helper method for {@link #buildAbsolutePath} that returns the position + * of the specified element in a list of its siblings. This may graduate + * to a public method if it's found generally useful. + * + * @throws IllegalArgumentException if <code>siblings</code> does not + * contain <code>elem</code> ... should never happen. + */ + private static int getIndex(Element elem, List<Element> siblings) + { + int elemPos = 0; + for (Element sibling : siblings) + { + elemPos++; + if (sibling == elem) + return elemPos; + } + throw new IllegalArgumentException("element not amongs its siblings"); + } } Modified: trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java 2008-12-03 14:08:45 UTC (rev 39) +++ trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java 2008-12-08 03:13:51 UTC (rev 40) @@ -2,11 +2,15 @@ import java.util.List; +import javax.xml.namespace.NamespaceContext; + import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; +import net.sf.practicalxml.misc.SimpleNamespaceResolver; + public class TestDomUtil extends AbstractTestCase { @@ -32,7 +36,7 @@ // variant with explicit namespace - Element child1 = DomUtil.appendChild(root, null, "baz"); + Element child1 = DomUtil.appendChild(root, "baz"); assertNotNull(child1); assertSame(root, child1.getParentNode()); assertNull(child1.getNamespaceURI()); @@ -53,23 +57,21 @@ assertEquals("w", child3.getPrefix()); assertEquals("wargle", child3.getLocalName()); - // variant that inherits namespace - - Element grandchild1 = DomUtil.appendChild(child1, "qwe"); + Element grandchild1 = DomUtil.appendChildWithNamespace(child1, "qwe"); assertNotNull(grandchild1); assertSame(child1, grandchild1.getParentNode()); assertEquals(child1.getNamespaceURI(), grandchild1.getNamespaceURI()); assertEquals(child1.getPrefix(), grandchild1.getPrefix()); assertEquals("qwe", grandchild1.getLocalName()); - Element grandchild2 = DomUtil.appendChild(child2, "asd"); + Element grandchild2 = DomUtil.appendChildWithNamespace(child2, "asd"); assertNotNull(grandchild2); assertSame(child2, grandchild2.getParentNode()); assertEquals(child2.getNamespaceURI(), grandchild2.getNamespaceURI()); assertEquals(child2.getPrefix(), grandchild2.getPrefix()); assertEquals("asd", grandchild2.getLocalName()); - Element grandchild3 = DomUtil.appendChild(child3, "zxc"); + Element grandchild3 = DomUtil.appendChildWithNamespace(child3, "zxc"); assertNotNull(grandchild3); assertSame(child3, grandchild3.getParentNode()); assertEquals(child3.getNamespaceURI(), grandchild3.getNamespaceURI()); @@ -85,7 +87,7 @@ { Element root = DomUtil.newDocument("foo"); Element child = DomUtil.appendChild(root, "MyNSURI", "argle:bargle"); - Element grandchild = DomUtil.appendChild(child, "zippy:pinhead"); + Element grandchild = DomUtil.appendChildWithNamespace(child, "zippy:pinhead"); assertEquals("zippy", grandchild.getPrefix()); } @@ -109,7 +111,7 @@ DomUtil.setText(root, t3); assertEquals(t3, DomUtil.getText(root)); - Element child = DomUtil.appendChild(root, "bar"); + Element child = DomUtil.appendChildWithNamespace(root, "bar"); assertNull(DomUtil.getText(child)); DomUtil.appendText(child, t1); @@ -130,11 +132,51 @@ } + public void testGetSiblings() throws Exception + { + Element root = DomUtil.newDocument("foo"); + Element child1 = DomUtil.appendChildWithNamespace(root, "bargle"); + Element child2 = DomUtil.appendChild(root, "argle", "bargle"); + Element child3 = DomUtil.appendChild(root, "argle", "w:wargle"); + DomUtil.appendText(root, "should never be returned"); + + List<Element> ret1 = DomUtil.getSiblings(root); + assertEquals(1, ret1.size()); + assertSame(root, ret1.get(0)); + + List<Element> ret2 = DomUtil.getSiblings(child1); + assertEquals(3, ret2.size()); + assertSame(child1, ret2.get(0)); + assertSame(child2, ret2.get(1)); + assertSame(child3, ret2.get(2)); + + List<Element> ret3 = DomUtil.getSiblings(child1, "bargle"); + assertEquals(2, ret3.size()); + assertSame(child1, ret3.get(0)); + assertSame(child2, ret3.get(1)); + + List<Element> ret4 = DomUtil.getSiblings(child1, "wargle"); + assertEquals(1, ret4.size()); + assertSame(child3, ret4.get(0)); + + List<Element> ret5 = DomUtil.getSiblings(child1, "argle", "bargle"); + assertEquals(1, ret5.size()); + assertSame(child2, ret5.get(0)); + + List<Element> ret6 = DomUtil.getSiblings(child1, null, "bargle"); + assertEquals(1, ret6.size()); + assertSame(child1, ret6.get(0)); + + List<Element> ret7 = DomUtil.getSiblings(child1, "fargle", "bargle"); + assertEquals(0, ret7.size()); + } + + public void testGetChildren() throws Exception { Element root = DomUtil.newDocument("foo"); DomUtil.appendText(root, "bar"); - Element child1 = DomUtil.appendChild(root, "bargle"); + Element child1 = DomUtil.appendChildWithNamespace(root, "bargle"); Element child2 = DomUtil.appendChild(root, "argle", "bargle"); Element child3 = DomUtil.appendChild(root, "argle", "w:wargle"); @@ -208,8 +250,8 @@ final String TEXT2_WS = " "; Element root = DomUtil.newDocument("foo"); - Element child1 = DomUtil.appendChild(root, "foo"); - Element child2 = DomUtil.appendChild(root, "foo"); + Element child1 = DomUtil.appendChildWithNamespace(root, "foo"); + Element child2 = DomUtil.appendChildWithNamespace(root, "foo"); DomUtil.setText(child1, TEXT1_WS); DomUtil.setText(child2, TEXT2_WS); @@ -231,8 +273,8 @@ public void testRemoveEmptyText() throws Exception { Element root = DomUtil.newDocument("foo"); - Element child1 = DomUtil.appendChild(root, "foo"); - Element child2 = DomUtil.appendChild(root, "foo"); + Element child1 = DomUtil.appendChildWithNamespace(root, "foo"); + Element child2 = DomUtil.appendChildWithNamespace(root, "foo"); DomUtil.setText(child1, "foo"); DomUtil.setText(child2, " "); @@ -244,13 +286,38 @@ } + public void testIsNamed() throws Exception + { + Element root = DomUtil.newDocument("foo"); + Element child = DomUtil.appendChild(root, "bar", "argle:bargle"); + + assertTrue(DomUtil.isNamed(root, null, "foo")); + assertTrue(DomUtil.isNamed(child, "bar", "bargle")); + + assertFalse(DomUtil.isNamed(root, null, "blimey")); + assertFalse(DomUtil.isNamed(child, "bar", "blimey")); + assertFalse(DomUtil.isNamed(child, "bar", "argle:bargle")); + assertFalse(DomUtil.isNamed(child, null, "bargle")); + + try + { + assertFalse(DomUtil.isNamed(root, null, null)); + fail("accepted null localName"); + } + catch (IllegalArgumentException e) + { + // success + } + } + + public void testGetPath() throws Exception { Element root = DomUtil.newDocument("foo"); - Element child1 = DomUtil.appendChild(root, "bargle"); + Element child1 = DomUtil.appendChild(root, null, "bargle"); Element child2 = DomUtil.appendChild(root, "argle", "bargle"); Element child3 = DomUtil.appendChild(root, "argle", "w:wargle"); - Element child1a = DomUtil.appendChild(child1, "zargle"); + Element child1a = DomUtil.appendChild(child1, null, "zargle"); child1.setAttribute("poi", "1234"); child2.setAttribute("poi", "5678"); child2.setAttribute("qwe", "asd"); @@ -276,27 +343,71 @@ } - public void testIsNamed() throws Exception + public void testGetAbsolutePath() throws Exception { Element root = DomUtil.newDocument("foo"); - Element child = DomUtil.appendChild(root, "bar", "argle:bargle"); + Element child1 = DomUtil.appendChild(root, null, "bargle"); + Element child2 = DomUtil.appendChild(root, null, "wargle"); + Element child3 = DomUtil.appendChild(root, null, "wargle"); + Element child4 = DomUtil.appendChild(root, "argle", "w:zargle"); + Element child5 = DomUtil.appendChild(root, "argle", "zargle"); + Element child6 = DomUtil.appendChild(root, "qargle", "zargle"); + Element child7 = DomUtil.appendChild(root, "argle", "zargle"); + Element child1a = DomUtil.appendChild(child1, null, "bargle"); + Element child3a = DomUtil.appendChild(child3, null, "zargle"); + Element child4a = DomUtil.appendChild(child4, null, "bargle"); + Element child4b = DomUtil.appendChild(child4, "argle", "bargle"); - assertTrue(DomUtil.isNamed(root, null, "foo")); - assertTrue(DomUtil.isNamed(child, "bar", "bargle")); + assertEquals("/foo", + DomUtil.getAbsolutePath(root)); + assertEquals("/foo/bargle", + DomUtil.getAbsolutePath(child1)); + assertEquals("/foo/bargle/bargle", + DomUtil.getAbsolutePath(child1a)); + assertEquals("/foo/wargle[1]", + DomUtil.getAbsolutePath(child2)); + assertEquals("/foo/wargle[2]", + DomUtil.getAbsolutePath(child3)); + assertEquals("/foo/wargle[2]/zargle", + DomUtil.getAbsolutePath(child3a)); + assertEquals("/foo/zargle[1]", + DomUtil.getAbsolutePath(child4)); + assertEquals("/foo/zargle[1]/bargle[1]", + DomUtil.getAbsolutePath(child4a)); + assertEquals("/foo/zargle[1]/bargle[2]", + DomUtil.getAbsolutePath(child4b)); + assertEquals("/foo/zargle[2]", + DomUtil.getAbsolutePath(child5)); + assertEquals("/foo/zargle[3]", + DomUtil.getAbsolutePath(child6)); + assertEquals("/foo/zargle[4]", + DomUtil.getAbsolutePath(child7)); - assertFalse(DomUtil.isNamed(root, null, "blimey")); - assertFalse(DomUtil.isNamed(child, "bar", "blimey")); - assertFalse(DomUtil.isNamed(child, "bar", "argle:bargle")); - assertFalse(DomUtil.isNamed(child, null, "bargle")); + NamespaceContext nsLookup = new SimpleNamespaceResolver("arg", "argle"); - try - { - assertFalse(DomUtil.isNamed(root, null, null)); - fail("accepted null localName"); - } - catch (IllegalArgumentException e) - { - // success - } + assertEquals("/foo", + DomUtil.getAbsolutePath(root, nsLookup)); + assertEquals("/foo/bargle", + DomUtil.getAbsolutePath(child1, nsLookup)); + assertEquals("/foo/bargle/bargle", + DomUtil.getAbsolutePath(child1a, nsLookup)); + assertEquals("/foo/wargle[1]", + DomUtil.getAbsolutePath(child2, nsLookup)); + assertEquals("/foo/wargle[2]", + DomUtil.getAbsolutePath(child3, nsLookup)); + assertEquals("/foo/wargle[2]/zargle", + DomUtil.getAbsolutePath(child3a, nsLookup)); + assertEquals("/foo/arg:zargle[1]", + DomUtil.getAbsolutePath(child4, nsLookup)); + assertEquals("/foo/arg:zargle[1]/bargle", + DomUtil.getAbsolutePath(child4a, nsLookup)); + assertEquals("/foo/arg:zargle[1]/arg:bargle", + DomUtil.getAbsolutePath(child4b, nsLookup)); + assertEquals("/foo/arg:zargle[2]", + DomUtil.getAbsolutePath(child5, nsLookup)); + assertEquals("/foo/NS0:zargle", + DomUtil.getAbsolutePath(child6, nsLookup)); + assertEquals("/foo/arg:zargle[3]", + DomUtil.getAbsolutePath(child7, nsLookup)); } } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-09 12:41:56
|
Revision: 48 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=48&view=rev Author: kdgregory Date: 2008-12-09 12:41:54 +0000 (Tue, 09 Dec 2008) Log Message: ----------- DomUtil: rename appendChildWithNamespace() to appendChildInheritNamespace() Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/DomUtil.java trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java Modified: trunk/src/main/java/net/sf/practicalxml/DomUtil.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/DomUtil.java 2008-12-09 12:41:16 UTC (rev 47) +++ trunk/src/main/java/net/sf/practicalxml/DomUtil.java 2008-12-09 12:41:54 UTC (rev 48) @@ -114,7 +114,7 @@ * * @return The newly created child element. */ - public static Element appendChildWithNamespace(Element parent, String qname) + public static Element appendChildInheritNamespace(Element parent, String qname) { String nsUri = parent.getNamespaceURI(); String parentPrefix = parent.getPrefix(); Modified: trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java 2008-12-09 12:41:16 UTC (rev 47) +++ trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java 2008-12-09 12:41:54 UTC (rev 48) @@ -57,21 +57,21 @@ assertEquals("w", child3.getPrefix()); assertEquals("wargle", child3.getLocalName()); - Element grandchild1 = DomUtil.appendChildWithNamespace(child1, "qwe"); + Element grandchild1 = DomUtil.appendChildInheritNamespace(child1, "qwe"); assertNotNull(grandchild1); assertSame(child1, grandchild1.getParentNode()); assertEquals(child1.getNamespaceURI(), grandchild1.getNamespaceURI()); assertEquals(child1.getPrefix(), grandchild1.getPrefix()); assertEquals("qwe", grandchild1.getLocalName()); - Element grandchild2 = DomUtil.appendChildWithNamespace(child2, "asd"); + Element grandchild2 = DomUtil.appendChildInheritNamespace(child2, "asd"); assertNotNull(grandchild2); assertSame(child2, grandchild2.getParentNode()); assertEquals(child2.getNamespaceURI(), grandchild2.getNamespaceURI()); assertEquals(child2.getPrefix(), grandchild2.getPrefix()); assertEquals("asd", grandchild2.getLocalName()); - Element grandchild3 = DomUtil.appendChildWithNamespace(child3, "zxc"); + Element grandchild3 = DomUtil.appendChildInheritNamespace(child3, "zxc"); assertNotNull(grandchild3); assertSame(child3, grandchild3.getParentNode()); assertEquals(child3.getNamespaceURI(), grandchild3.getNamespaceURI()); @@ -87,7 +87,7 @@ { Element root = DomUtil.newDocument("foo"); Element child = DomUtil.appendChild(root, "MyNSURI", "argle:bargle"); - Element grandchild = DomUtil.appendChildWithNamespace(child, "zippy:pinhead"); + Element grandchild = DomUtil.appendChildInheritNamespace(child, "zippy:pinhead"); assertEquals("zippy", grandchild.getPrefix()); } @@ -111,7 +111,7 @@ DomUtil.setText(root, t3); assertEquals(t3, DomUtil.getText(root)); - Element child = DomUtil.appendChildWithNamespace(root, "bar"); + Element child = DomUtil.appendChildInheritNamespace(root, "bar"); assertNull(DomUtil.getText(child)); DomUtil.appendText(child, t1); @@ -135,7 +135,7 @@ public void testGetSiblings() throws Exception { Element root = DomUtil.newDocument("foo"); - Element child1 = DomUtil.appendChildWithNamespace(root, "bargle"); + Element child1 = DomUtil.appendChildInheritNamespace(root, "bargle"); Element child2 = DomUtil.appendChild(root, "argle", "bargle"); Element child3 = DomUtil.appendChild(root, "argle", "w:wargle"); DomUtil.appendText(root, "should never be returned"); @@ -176,7 +176,7 @@ { Element root = DomUtil.newDocument("foo"); DomUtil.appendText(root, "bar"); - Element child1 = DomUtil.appendChildWithNamespace(root, "bargle"); + Element child1 = DomUtil.appendChildInheritNamespace(root, "bargle"); Element child2 = DomUtil.appendChild(root, "argle", "bargle"); Element child3 = DomUtil.appendChild(root, "argle", "w:wargle"); @@ -250,8 +250,8 @@ final String TEXT2_WS = " "; Element root = DomUtil.newDocument("foo"); - Element child1 = DomUtil.appendChildWithNamespace(root, "foo"); - Element child2 = DomUtil.appendChildWithNamespace(root, "foo"); + Element child1 = DomUtil.appendChildInheritNamespace(root, "foo"); + Element child2 = DomUtil.appendChildInheritNamespace(root, "foo"); DomUtil.setText(child1, TEXT1_WS); DomUtil.setText(child2, TEXT2_WS); @@ -273,8 +273,8 @@ public void testRemoveEmptyText() throws Exception { Element root = DomUtil.newDocument("foo"); - Element child1 = DomUtil.appendChildWithNamespace(root, "foo"); - Element child2 = DomUtil.appendChildWithNamespace(root, "foo"); + Element child1 = DomUtil.appendChildInheritNamespace(root, "foo"); + Element child2 = DomUtil.appendChildInheritNamespace(root, "foo"); DomUtil.setText(child1, "foo"); DomUtil.setText(child2, " "); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-14 15:15:46
|
Revision: 51 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=51&view=rev Author: kdgregory Date: 2008-12-14 15:15:44 +0000 (Sun, 14 Dec 2008) Log Message: ----------- NamespaceResolver: add getDefaultNamespace(), getAllPrefixes() Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java Modified: trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java 2008-12-09 18:54:12 UTC (rev 50) +++ trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java 2008-12-14 15:15:44 UTC (rev 51) @@ -1,9 +1,12 @@ package net.sf.practicalxml.misc; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.SortedSet; +import java.util.TreeMap; import java.util.TreeSet; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; @@ -39,7 +42,7 @@ XML_NS_ATTR_PREFIXES.add(XMLConstants.XMLNS_ATTRIBUTE); } - private Map<String,String> _prefix2ns = new HashMap<String,String>(); + private TreeMap<String,String> _prefix2ns = new TreeMap<String,String>(); private Map<String,SortedSet<String>> _ns2prefix = new HashMap<String,SortedSet<String>>(); private String _defaultNS = ""; @@ -88,6 +91,26 @@ } + /** + * Returns the default namespace, an empty string if one has not yet + * been set. + */ + public String getDefaultNamespace() + { + return _defaultNS; + } + + + /** + * Returns a list of all prefixes known to this resolver, in alphabetical + * order. + */ + public List<String> getAllPrefixes() + { + return new ArrayList<String>(_prefix2ns.keySet()); + } + + //---------------------------------------------------------------------------- // NamespaceContext implementation //---------------------------------------------------------------------------- Modified: trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java 2008-12-09 18:54:12 UTC (rev 50) +++ trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java 2008-12-14 15:15:44 UTC (rev 51) @@ -1,6 +1,8 @@ package net.sf.practicalxml.misc; import java.util.Iterator; +import java.util.List; + import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; @@ -174,6 +176,7 @@ { NamespaceResolver resolv = new NamespaceResolver(); + assertEquals("", resolv.getDefaultNamespace()); assertEquals("", resolv.getNamespaceURI("")); assertEquals("", resolv.getPrefix("")); Iterator<String> itx1 = resolv.getPrefixes(""); @@ -182,14 +185,42 @@ assertSame(resolv, resolv.setDefaultNamespace("foo")); + assertEquals("foo", resolv.getDefaultNamespace()); assertEquals("foo", resolv.getNamespaceURI("")); assertEquals("", resolv.getPrefix("foo")); Iterator<String> itx2 = resolv.getPrefixes("foo"); assertEquals("", itx2.next()); assertFalse(itx2.hasNext()); + + assertSame(resolv, resolv.setDefaultNamespace("bar")); + + assertEquals("bar", resolv.getDefaultNamespace()); + assertEquals("bar", resolv.getNamespaceURI("")); + assertEquals("", resolv.getPrefix("bar")); + Iterator<String> itx3 = resolv.getPrefixes("bar"); + assertEquals("", itx3.next()); + assertFalse(itx3.hasNext()); } + public void testGetAllPrefixes() throws Exception + { + NamespaceResolver resolv = new NamespaceResolver() + .addNamespace("foo", "bar") + .addNamespace("baz", "bar") + .addNamespace("baz", "biggles") // intentional overwrite + .addNamespace("argle", "bargle"); + + List<String> prefixes = resolv.getAllPrefixes(); + assertEquals(3, prefixes.size()); + + Iterator<String> itx = prefixes.iterator(); + assertEquals("argle", itx.next()); + assertEquals("baz", itx.next()); + assertEquals("foo", itx.next()); + } + + public void testEqualsAndHashCode() throws Exception { Object obj1 = new NamespaceResolver() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-14 17:07:49
|
Revision: 52 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=52&view=rev Author: kdgregory Date: 2008-12-14 17:07:45 +0000 (Sun, 14 Dec 2008) Log Message: ----------- DomUtil: add variant of getAbsolutePath() that takes NamespaceResolver (so path can be used) Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/DomUtil.java trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java Added Paths: ----------- trunk/src/test/java/net/sf/practicalxml/TestDomUtilGetPath.java Modified: trunk/src/main/java/net/sf/practicalxml/DomUtil.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/DomUtil.java 2008-12-14 15:15:44 UTC (rev 51) +++ trunk/src/main/java/net/sf/practicalxml/DomUtil.java 2008-12-14 17:07:45 UTC (rev 52) @@ -13,6 +13,8 @@ import org.w3c.dom.NodeList; import org.w3c.dom.Text; +import net.sf.practicalxml.misc.NamespaceResolver; + import org.apache.commons.lang.StringUtils; @@ -439,9 +441,9 @@ /** * Returns the path from the root of the document to the specified - * element, consisting of each node's nodename separated by slashes. - * Accepts an arbitrary number of attribute names, and inserts these - * as predicates into the path nodes where they apply. + * element, consisting of each node's qualified name, separated by + * slashes. Accepts an arbitrary number of attribute names, and + * inserts these as predicates into the path nodes where they apply. * <p> * This method is meant primarily for logging and debugging. While the * returned path can by passed to an XPath evaluator, it has several @@ -449,8 +451,10 @@ * use qualified names where they appear in the document. Second, it * doesn't properly escape quotes in attribute values. Third, and most * important, it doesn't differentiate between sibling nodes with the - * same name and attribute values; if that's important, use {@link - * #getAbsolutePath}. + * same name and attribute values. + * <p> + * If you want a path that can later be used to select the element, + * see {@link #getAbsolutePath} */ public static String getPath(Element elem, String... attrNames) { @@ -465,11 +469,15 @@ * element, as an XPath expression using positional predicates to * differentiate between nodes with the same local name, ignoring * namespace. + * <p> + * <em>Warning:</em> if your document has namespaces, you will not + * be able to use the returned path to select the same node. Use + * one of the other variants of this method instead. */ public static String getAbsolutePath(Element elem) { StringBuilder sb = new StringBuilder(); - buildAbsolutePath(elem, sb, null, null); + buildAbsolutePath(elem, sb, null, null, null); return sb.toString(); } @@ -486,17 +494,55 @@ * <p> * If <code>nsLookup</code> does not have a mapping for a given * namespace, the returned path will contain a dummy prefix of the - * form "NSx", where "x" is incremented for each unknown namespace, - * whether or not the namespace has been seen elsewhere in the path. + * form "NSx", where "x" is incremented for each unknown namespace. + * In this case, you will not be able to use the returned path to + * select the element, without adding context entries for those + * generated namespaces. + * <p> + * Note that any prefixes in the source document are ignored. If an + * element has a prefix in the source document, but that element's + * namespace is not present in <code>nsLookup</code>, the path will + * contain a generated prefix. Similarly, if <code>nsLookup.getPrefix() + * </code> returns a value for the prefix, that value is used for the + * generated path. */ public static String getAbsolutePath(Element elem, NamespaceContext nsLookup) { StringBuilder sb = new StringBuilder(); - buildAbsolutePath(elem, sb, nsLookup, new int[] {0}); + buildAbsolutePath(elem, sb, nsLookup, new NamespaceResolver(), new int[] {0}); return sb.toString(); } + /** + * Returns the path from the root of the document to the specified + * element, as an XPath expression using positional predicates to + * differentiate between nodes with the same name and namespace. + * <p> + * The <code>nsLookup</code> parameter is used to retrieve prefixes + * for the passed element and its ancestors. If it does not contain + * a mapping for a given namespace, one will be added with a prefix + * of the form "NSx" (where "x" is a number that's incremented for + * each unknown namespace). + * <p> + * Note that any prefixes in the source document are ignored. If an + * element has a prefix in the source document, but that element's + * namespace is not present in <code>nsLookup</code>, the path will + * contain a generated prefix. Similarly, if <code>nsLookup.getPrefix() + * </code> returns a value for the prefix, that value is used for the + * generated path. + * <p> + * The returned path may be used to select the element, provided that + * <code>nsLookup</code> is provided as the namespace context. + */ + public static String getAbsolutePath(Element elem, NamespaceResolver nsLookup) + { + StringBuilder sb = new StringBuilder(); + buildAbsolutePath(elem, sb, nsLookup, nsLookup, new int[] {0}); + return sb.toString(); + } + + //---------------------------------------------------------------------------- // Internals //---------------------------------------------------------------------------- @@ -566,21 +612,22 @@ * @param sb A buffer used to build the path. * @param nsLookup Used to resolve defined namespaces. May be null, in * which case returned path will not have any namespaces. + * @param genLookup Holds generated namespace mappings. * @param nsCounter Used to generate prefixes for unresolved namespaces: * contains a single element that is incremented for each * unmapped namespace. */ private static void buildAbsolutePath( Element elem, StringBuilder sb, - NamespaceContext nsLookup, int[] nsCounter) + NamespaceContext nsLookup, NamespaceResolver genLookup, int[] nsCounter) { Node parent = elem.getParentNode(); if (parent instanceof Element) { - buildAbsolutePath((Element)parent, sb, nsLookup, nsCounter); + buildAbsolutePath((Element)parent, sb, nsLookup, genLookup, nsCounter); } - String prefix = getPrefix(elem, nsLookup, nsCounter); + String prefix = getPrefix(elem, nsLookup, genLookup, nsCounter); String localName = getLocalName(elem); List<Element> siblings = (nsLookup == null) ? getSiblings(elem, getLocalName(elem)) @@ -597,9 +644,14 @@ /** * Helper method for {@link #buildAbsolutePath} that returns the prefix - * for a given element, using the passed namespace resolver. + * for an element. Will first look in <code>nsLookup</code>; if it doesn't + * find the namespace there, will look in <code>genLookup</code>; if still + * unable to resolve the namespace, will use <code>nsCounter</code> to + * generate a new mapping, that's added to <code>genLookup</code>. */ - private static String getPrefix(Element elem, NamespaceContext nsLookup, int[] nsCounter) + private static String getPrefix( + Element elem, NamespaceContext nsLookup, + NamespaceResolver genLookup, int[] nsCounter) { if (nsLookup == null) return null; @@ -609,8 +661,21 @@ return null; String prefix = nsLookup.getPrefix(nsUri); - if (prefix == null) + if (prefix != null) + return prefix; + + prefix = genLookup.getPrefix(nsUri); + if (prefix != null) + return prefix; + + // make sure we don't reuse a prefix + while (prefix == null) + { prefix = "NS" + nsCounter[0]++; + if ((nsLookup.getNamespaceURI(prefix) != null) || (genLookup.getNamespaceURI(prefix) != null)) + prefix = null; + } + genLookup.addNamespace(prefix, nsUri); return prefix; } Modified: trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java 2008-12-14 15:15:44 UTC (rev 51) +++ trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java 2008-12-14 17:07:45 UTC (rev 52) @@ -2,15 +2,15 @@ import java.util.List; -import javax.xml.namespace.NamespaceContext; - import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; -import net.sf.practicalxml.misc.SimpleNamespaceResolver; - +/** + * Tests all DomUtil methods except <code>getPath()</code> and + * <code>getAbsolutePath()</code>. + */ public class TestDomUtil extends AbstractTestCase { @@ -309,105 +309,4 @@ // success } } - - - public void testGetPath() throws Exception - { - Element root = DomUtil.newDocument("foo"); - Element child1 = DomUtil.appendChild(root, null, "bargle"); - Element child2 = DomUtil.appendChild(root, "argle", "bargle"); - Element child3 = DomUtil.appendChild(root, "argle", "w:wargle"); - Element child1a = DomUtil.appendChild(child1, null, "zargle"); - child1.setAttribute("poi", "1234"); - child2.setAttribute("poi", "5678"); - child2.setAttribute("qwe", "asd"); - child1a.setAttribute("qwe", "zxc"); - - assertEquals("/foo", - DomUtil.getPath(root)); - assertEquals("/foo/bargle", - DomUtil.getPath(child1)); - assertEquals("/foo/bargle", - DomUtil.getPath(child2)); - assertEquals("/foo/w:wargle", - DomUtil.getPath(child3)); - assertEquals("/foo/bargle/zargle", - DomUtil.getPath(child1a)); - - assertEquals("/foo", - DomUtil.getPath(root, "poi", "qwe")); - assertEquals("/foo/bargle[poi='1234']", - DomUtil.getPath(child1, "poi", "qwe")); - assertEquals("/foo/bargle[poi='5678'][qwe='asd']", - DomUtil.getPath(child2, "poi", "qwe")); - } - - - public void testGetAbsolutePath() throws Exception - { - Element root = DomUtil.newDocument("foo"); - Element child1 = DomUtil.appendChild(root, null, "bargle"); - Element child2 = DomUtil.appendChild(root, null, "wargle"); - Element child3 = DomUtil.appendChild(root, null, "wargle"); - Element child4 = DomUtil.appendChild(root, "argle", "w:zargle"); - Element child5 = DomUtil.appendChild(root, "argle", "zargle"); - Element child6 = DomUtil.appendChild(root, "qargle", "zargle"); - Element child7 = DomUtil.appendChild(root, "argle", "zargle"); - Element child1a = DomUtil.appendChild(child1, null, "bargle"); - Element child3a = DomUtil.appendChild(child3, null, "zargle"); - Element child4a = DomUtil.appendChild(child4, null, "bargle"); - Element child4b = DomUtil.appendChild(child4, "argle", "bargle"); - - assertEquals("/foo", - DomUtil.getAbsolutePath(root)); - assertEquals("/foo/bargle", - DomUtil.getAbsolutePath(child1)); - assertEquals("/foo/bargle/bargle", - DomUtil.getAbsolutePath(child1a)); - assertEquals("/foo/wargle[1]", - DomUtil.getAbsolutePath(child2)); - assertEquals("/foo/wargle[2]", - DomUtil.getAbsolutePath(child3)); - assertEquals("/foo/wargle[2]/zargle", - DomUtil.getAbsolutePath(child3a)); - assertEquals("/foo/zargle[1]", - DomUtil.getAbsolutePath(child4)); - assertEquals("/foo/zargle[1]/bargle[1]", - DomUtil.getAbsolutePath(child4a)); - assertEquals("/foo/zargle[1]/bargle[2]", - DomUtil.getAbsolutePath(child4b)); - assertEquals("/foo/zargle[2]", - DomUtil.getAbsolutePath(child5)); - assertEquals("/foo/zargle[3]", - DomUtil.getAbsolutePath(child6)); - assertEquals("/foo/zargle[4]", - DomUtil.getAbsolutePath(child7)); - - NamespaceContext nsLookup = new SimpleNamespaceResolver("arg", "argle"); - - assertEquals("/foo", - DomUtil.getAbsolutePath(root, nsLookup)); - assertEquals("/foo/bargle", - DomUtil.getAbsolutePath(child1, nsLookup)); - assertEquals("/foo/bargle/bargle", - DomUtil.getAbsolutePath(child1a, nsLookup)); - assertEquals("/foo/wargle[1]", - DomUtil.getAbsolutePath(child2, nsLookup)); - assertEquals("/foo/wargle[2]", - DomUtil.getAbsolutePath(child3, nsLookup)); - assertEquals("/foo/wargle[2]/zargle", - DomUtil.getAbsolutePath(child3a, nsLookup)); - assertEquals("/foo/arg:zargle[1]", - DomUtil.getAbsolutePath(child4, nsLookup)); - assertEquals("/foo/arg:zargle[1]/bargle", - DomUtil.getAbsolutePath(child4a, nsLookup)); - assertEquals("/foo/arg:zargle[1]/arg:bargle", - DomUtil.getAbsolutePath(child4b, nsLookup)); - assertEquals("/foo/arg:zargle[2]", - DomUtil.getAbsolutePath(child5, nsLookup)); - assertEquals("/foo/NS0:zargle", - DomUtil.getAbsolutePath(child6, nsLookup)); - assertEquals("/foo/arg:zargle[3]", - DomUtil.getAbsolutePath(child7, nsLookup)); - } } Added: trunk/src/test/java/net/sf/practicalxml/TestDomUtilGetPath.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestDomUtilGetPath.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/TestDomUtilGetPath.java 2008-12-14 17:07:45 UTC (rev 52) @@ -0,0 +1,205 @@ +package net.sf.practicalxml; + +import javax.xml.namespace.NamespaceContext; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import net.sf.practicalxml.misc.NamespaceResolver; +import net.sf.practicalxml.misc.SimpleNamespaceResolver; + + +/** + * Tests the methods <code>getPath()</code> and <code>getAbsolutePath()</code>, + * because these have multiple tests that use the same complex DOM tree. + */ +public class TestDomUtilGetPath +extends AbstractTestCase +{ +//---------------------------------------------------------------------------- +// Setup -- no need for a method, we can do everything at initialization +// -- I think I want to keep the literal element names, rather than +// making them all constants, because I want to make sure that +// we're really testing different conditions +//---------------------------------------------------------------------------- + + Element root = DomUtil.newDocument("root"); + Element child1 = DomUtil.appendChild(root, null, "bargle"); + Element child2 = DomUtil.appendChild(root, null, "wargle"); + Element child3 = DomUtil.appendChild(root, null, "wargle"); + Element child4 = DomUtil.appendChild(root, "argle", "w:zargle"); + Element child5 = DomUtil.appendChild(root, "argle", "zargle"); + Element child6 = DomUtil.appendChild(root, "qargle", "zargle"); + Element child7 = DomUtil.appendChild(root, "argle", "zargle"); + Element child8 = DomUtil.appendChild(root, "margle", "m:zargle"); + Element child1a = DomUtil.appendChild(child1, null, "bargle"); + Element child3a = DomUtil.appendChild(child3, null, "zargle"); + Element child4a = DomUtil.appendChild(child4, null, "bargle"); + Element child4b = DomUtil.appendChild(child4, "argle", "bargle"); + Element child6a = DomUtil.appendChild(child6, "qargle", "zargle"); + + Document dom = root.getOwnerDocument(); + + Element[] allElements = new Element[] + { + root, + child1, child2, child3, child4, child5, + child6, child7, child8, + child1a, child3a, child4a, child4b, child6a + }; + + +//---------------------------------------------------------------------------- +// Test Cases +//---------------------------------------------------------------------------- + + public void testGetPath() throws Exception + { + child1.setAttribute("poi", "1234"); + child2.setAttribute("poi", "5678"); + child2.setAttribute("qwe", "asd"); + child1a.setAttribute("qwe", "zxc"); + + assertEquals("/root", + DomUtil.getPath(root)); + assertEquals("/root/bargle", + DomUtil.getPath(child1)); + assertEquals("/root/wargle", + DomUtil.getPath(child2)); + assertEquals("/root/w:zargle", + DomUtil.getPath(child4)); + assertEquals("/root/wargle/zargle", + DomUtil.getPath(child3a)); + + assertEquals("/root", + DomUtil.getPath(root, "poi", "qwe")); + assertEquals("/root/bargle[poi='1234']", + DomUtil.getPath(child1, "poi", "qwe")); + assertEquals("/root/wargle[poi='5678'][qwe='asd']", + DomUtil.getPath(child2, "poi", "qwe")); + } + + + public void testGetAbsolutePathWithoutNamespaces() throws Exception + { + assertEquals("/root", + DomUtil.getAbsolutePath(root)); + assertEquals("/root/bargle", + DomUtil.getAbsolutePath(child1)); + assertEquals("/root/bargle/bargle", + DomUtil.getAbsolutePath(child1a)); + assertEquals("/root/wargle[1]", + DomUtil.getAbsolutePath(child2)); + assertEquals("/root/wargle[2]", + DomUtil.getAbsolutePath(child3)); + assertEquals("/root/wargle[2]/zargle", + DomUtil.getAbsolutePath(child3a)); + assertEquals("/root/zargle[1]", + DomUtil.getAbsolutePath(child4)); + assertEquals("/root/zargle[1]/bargle[1]", + DomUtil.getAbsolutePath(child4a)); + assertEquals("/root/zargle[1]/bargle[2]", + DomUtil.getAbsolutePath(child4b)); + assertEquals("/root/zargle[2]", + DomUtil.getAbsolutePath(child5)); + assertEquals("/root/zargle[3]", + DomUtil.getAbsolutePath(child6)); + assertEquals("/root/zargle[4]", + DomUtil.getAbsolutePath(child7)); + } + + + public void testGetAbsolutePathWithPredefinedNamespaces() throws Exception + { + NamespaceContext nsLookup1 = new SimpleNamespaceResolver("arg", "argle"); + + assertEquals("/root", + DomUtil.getAbsolutePath(root, nsLookup1)); + assertEquals("/root/bargle", + DomUtil.getAbsolutePath(child1, nsLookup1)); + assertEquals("/root/bargle/bargle", + DomUtil.getAbsolutePath(child1a, nsLookup1)); + assertEquals("/root/wargle[1]", + DomUtil.getAbsolutePath(child2, nsLookup1)); + assertEquals("/root/wargle[2]", + DomUtil.getAbsolutePath(child3, nsLookup1)); + assertEquals("/root/wargle[2]/zargle", + DomUtil.getAbsolutePath(child3a, nsLookup1)); + assertEquals("/root/arg:zargle[1]", + DomUtil.getAbsolutePath(child4, nsLookup1)); + assertEquals("/root/arg:zargle[1]/bargle", + DomUtil.getAbsolutePath(child4a, nsLookup1)); + assertEquals("/root/arg:zargle[1]/arg:bargle", + DomUtil.getAbsolutePath(child4b, nsLookup1)); + assertEquals("/root/arg:zargle[2]", + DomUtil.getAbsolutePath(child5, nsLookup1)); + assertEquals("/root/NS0:zargle", + DomUtil.getAbsolutePath(child6, nsLookup1)); + assertEquals("/root/NS0:zargle/NS0:zargle", + DomUtil.getAbsolutePath(child6a, nsLookup1)); + assertEquals("/root/arg:zargle[3]", + DomUtil.getAbsolutePath(child7, nsLookup1)); + assertEquals("/root/NS0:zargle", + DomUtil.getAbsolutePath(child8, nsLookup1)); + } + + + public void testGetAbsolutePathWithUpdatableNamespaces() throws Exception + { + NamespaceResolver nsLookup = new NamespaceResolver() + .addNamespace("arg", "argle") + .addNamespace("NS0", "asdf") + .addNamespace("NS1", "asdf"); + + assertEquals("/root", + DomUtil.getAbsolutePath(root, nsLookup)); + assertEquals("/root/bargle", + DomUtil.getAbsolutePath(child1, nsLookup)); + assertEquals("/root/bargle/bargle", + DomUtil.getAbsolutePath(child1a, nsLookup)); + assertEquals("/root/wargle[1]", + DomUtil.getAbsolutePath(child2, nsLookup)); + assertEquals("/root/wargle[2]", + DomUtil.getAbsolutePath(child3, nsLookup)); + assertEquals("/root/wargle[2]/zargle", + DomUtil.getAbsolutePath(child3a, nsLookup)); + assertEquals("/root/arg:zargle[1]", + DomUtil.getAbsolutePath(child4, nsLookup)); + assertEquals("/root/arg:zargle[1]/bargle", + DomUtil.getAbsolutePath(child4a, nsLookup)); + assertEquals("/root/arg:zargle[1]/arg:bargle", + DomUtil.getAbsolutePath(child4b, nsLookup)); + assertEquals("/root/arg:zargle[2]", + DomUtil.getAbsolutePath(child5, nsLookup)); + assertEquals("/root/NS2:zargle", + DomUtil.getAbsolutePath(child6, nsLookup)); + // note: previous call already added the namespace binding + assertEquals("/root/NS2:zargle/NS2:zargle", + DomUtil.getAbsolutePath(child6a, nsLookup)); + assertEquals("/root/arg:zargle[3]", + DomUtil.getAbsolutePath(child7, nsLookup)); + assertEquals("/root/NS3:zargle", + DomUtil.getAbsolutePath(child8, nsLookup)); + + // verify that the resolver has been updated from all calls + + assertEquals("qargle", nsLookup.getNamespaceURI("NS2")); + assertEquals("margle", nsLookup.getNamespaceURI("NS3")); + } + + + public void testGetAbsolutePathWillSelectElement() throws Exception + { + for (Element elem : allElements) + { + NamespaceResolver nsLookup = new NamespaceResolver(); + XPath xpath = XPathFactory.newInstance().newXPath(); + xpath.setNamespaceContext(nsLookup); + assertSame(elem, xpath.evaluate( + DomUtil.getAbsolutePath(elem, nsLookup), + dom, XPathConstants.NODE)); + } + } +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-14 17:22:32
|
Revision: 53 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=53&view=rev Author: kdgregory Date: 2008-12-14 17:22:13 +0000 (Sun, 14 Dec 2008) Log Message: ----------- NamespaceResolver: add clone() Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java Modified: trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java 2008-12-14 17:07:45 UTC (rev 52) +++ trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java 2008-12-14 17:22:13 UTC (rev 53) @@ -178,6 +178,9 @@ @Override public final boolean equals(Object obj) { + if (this == obj) + return true; + if (obj instanceof NamespaceResolver) { NamespaceResolver that = (NamespaceResolver)obj; @@ -208,7 +211,7 @@ { buf.append("xmlns=\"").append(_defaultNS).append("\""); } - for (String prefix : new TreeSet<String>(_prefix2ns.keySet())) + for (String prefix : getAllPrefixes()) { if (buf.length() > 0) buf.append(" "); @@ -219,10 +222,28 @@ } + /** + * Returns a deep clone of this object, that can then be independently + * manipulated. + */ + @Override + protected NamespaceResolver clone() + { + NamespaceResolver that = new NamespaceResolver() + .setDefaultNamespace(getDefaultNamespace()); + for (String prefix : getAllPrefixes()) + { + that.addNamespace(prefix, getNamespaceURI(prefix)); + } + return that; + } + + //---------------------------------------------------------------------------- // Internals //---------------------------------------------------------------------------- + /** * Returns the set of prefixes for a given namespace, creating a new * entry if one doesn't already exist. Modified: trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java 2008-12-14 17:07:45 UTC (rev 52) +++ trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java 2008-12-14 17:22:13 UTC (rev 53) @@ -275,4 +275,34 @@ assertTrue(str3.contains("xmlns:zippy=\"pinhead\"")); assertEquals(3, str3.split(" +").length); } + + + public void testClone() throws Exception + { + NamespaceResolver resolv1 = new NamespaceResolver() + .setDefaultNamespace("foo") + .addNamespace("argle", "bargle"); + + NamespaceResolver resolv2 = resolv1.clone(); + assertNotSame(resolv1, resolv2); + assertEquals(resolv1, resolv2); + + resolv2.setDefaultNamespace("bar"); + assertFalse(resolv1.equals(resolv2)); + assertEquals("foo", resolv1.getDefaultNamespace()); + assertEquals("bar", resolv2.getDefaultNamespace()); + + resolv2.addNamespace("argle", "zargle"); + assertEquals("bargle", resolv1.getNamespaceURI("argle")); + assertEquals("zargle", resolv2.getNamespaceURI("argle")); + + resolv2.addNamespace("wargle", "qwerty"); + assertNull(resolv1.getPrefix("qwerty")); + assertNull(resolv1.getNamespaceURI("wargle")); + + resolv1.addNamespace("wargle", "asdfg"); + assertNull(resolv2.getPrefix("asdfg")); + assertEquals("qwerty", resolv2.getNamespaceURI("wargle")); + } + } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-16 02:56:50
|
Revision: 54 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=54&view=rev Author: kdgregory Date: 2008-12-16 02:56:39 +0000 (Tue, 16 Dec 2008) Log Message: ----------- Package changes: renamed "misc" to "util" added "xpath", moving XPath support classes from "util" Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/DomUtil.java trunk/src/main/java/net/sf/practicalxml/ParseUtil.java trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java trunk/src/main/java/net/sf/practicalxml/util/ErrorHandlerAdapter.java trunk/src/main/java/net/sf/practicalxml/util/ExceptionErrorHandler.java trunk/src/main/java/net/sf/practicalxml/util/NodeListIterable.java trunk/src/test/java/net/sf/practicalxml/TestDomUtilGetPath.java trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java trunk/src/test/java/net/sf/practicalxml/util/TestNodeListIterable.java Added Paths: ----------- trunk/src/main/java/net/sf/practicalxml/util/ trunk/src/main/java/net/sf/practicalxml/xpath/ trunk/src/main/java/net/sf/practicalxml/xpath/NamespaceResolver.java trunk/src/main/java/net/sf/practicalxml/xpath/SimpleNamespaceResolver.java trunk/src/main/java/net/sf/practicalxml/xpath/package.html trunk/src/test/java/net/sf/practicalxml/util/ trunk/src/test/java/net/sf/practicalxml/xpath/ trunk/src/test/java/net/sf/practicalxml/xpath/TestNamespaceResolver.java trunk/src/test/java/net/sf/practicalxml/xpath/TestSimpleNamespaceResolver.java Removed Paths: ------------- trunk/src/main/java/net/sf/practicalxml/misc/ trunk/src/main/java/net/sf/practicalxml/util/NamespaceResolver.java trunk/src/main/java/net/sf/practicalxml/util/SimpleNamespaceResolver.java trunk/src/test/java/net/sf/practicalxml/misc/ trunk/src/test/java/net/sf/practicalxml/util/TestNamespaceResolver.java trunk/src/test/java/net/sf/practicalxml/util/TestSimpleNamespaceResolver.java Modified: trunk/src/main/java/net/sf/practicalxml/DomUtil.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/DomUtil.java 2008-12-14 17:22:13 UTC (rev 53) +++ trunk/src/main/java/net/sf/practicalxml/DomUtil.java 2008-12-16 02:56:39 UTC (rev 54) @@ -13,7 +13,7 @@ import org.w3c.dom.NodeList; import org.w3c.dom.Text; -import net.sf.practicalxml.misc.NamespaceResolver; +import net.sf.practicalxml.xpath.NamespaceResolver; import org.apache.commons.lang.StringUtils; Modified: trunk/src/main/java/net/sf/practicalxml/ParseUtil.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/ParseUtil.java 2008-12-14 17:22:13 UTC (rev 53) +++ trunk/src/main/java/net/sf/practicalxml/ParseUtil.java 2008-12-16 02:56:39 UTC (rev 54) @@ -8,7 +8,7 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.validation.Schema; -import net.sf.practicalxml.misc.ExceptionErrorHandler; +import net.sf.practicalxml.util.ExceptionErrorHandler; import org.w3c.dom.Document; import org.xml.sax.EntityResolver; Modified: trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java 2008-12-14 17:22:13 UTC (rev 53) +++ trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java 2008-12-16 02:56:39 UTC (rev 54) @@ -19,7 +19,7 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import net.sf.practicalxml.misc.NamespaceResolver; +import net.sf.practicalxml.xpath.NamespaceResolver; Modified: trunk/src/main/java/net/sf/practicalxml/util/ErrorHandlerAdapter.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/misc/ErrorHandlerAdapter.java 2008-12-14 17:22:13 UTC (rev 53) +++ trunk/src/main/java/net/sf/practicalxml/util/ErrorHandlerAdapter.java 2008-12-16 02:56:39 UTC (rev 54) @@ -1,4 +1,4 @@ -package net.sf.practicalxml.misc; +package net.sf.practicalxml.util; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; Modified: trunk/src/main/java/net/sf/practicalxml/util/ExceptionErrorHandler.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/misc/ExceptionErrorHandler.java 2008-12-14 17:22:13 UTC (rev 53) +++ trunk/src/main/java/net/sf/practicalxml/util/ExceptionErrorHandler.java 2008-12-16 02:56:39 UTC (rev 54) @@ -1,4 +1,4 @@ -package net.sf.practicalxml.misc; +package net.sf.practicalxml.util; import java.util.ArrayList; import java.util.List; Deleted: trunk/src/main/java/net/sf/practicalxml/util/NamespaceResolver.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java 2008-12-14 17:22:13 UTC (rev 53) +++ trunk/src/main/java/net/sf/practicalxml/util/NamespaceResolver.java 2008-12-16 02:56:39 UTC (rev 54) @@ -1,261 +0,0 @@ -package net.sf.practicalxml.misc; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.SortedSet; -import java.util.TreeMap; -import java.util.TreeSet; -import javax.xml.XMLConstants; -import javax.xml.namespace.NamespaceContext; - - -/** - * Maintains a bi-directional lookup table for mappings between namespace URIs - * and prefixes. Follows the "builder" pattern, in that the methods to add - * mappings may be chained: a resolver can be created and fully configured in - * a single expression. - * <p> - * Usage note: <code>NamespaceContext</code> allows multiple prefixes per URI, - * in keeping with the Namespace spec. This implementation supports that, but - * it's a bad idea to actually use this feature when writing an XPath. You'll - * be much happier if you limit yourself to a 1:1 mapping. - * <p> - * If you have a single namespace mapping, this implementation is overkill. - * Instead, use {@link SimpleNamespaceResolver}. - */ -public class NamespaceResolver -implements NamespaceContext -{ - private final static SortedSet<String> DEFAULT_PREFIXES = - new TreeSet<String>(); - private final static SortedSet<String> XML_NS_URI_PREFIXES = - new TreeSet<String>(); - private final static SortedSet<String> XML_NS_ATTR_PREFIXES = - new TreeSet<String>(); - static - { - DEFAULT_PREFIXES.add(""); - XML_NS_URI_PREFIXES.add(XMLConstants.XML_NS_PREFIX); - XML_NS_ATTR_PREFIXES.add(XMLConstants.XMLNS_ATTRIBUTE); - } - - private TreeMap<String,String> _prefix2ns = new TreeMap<String,String>(); - private Map<String,SortedSet<String>> _ns2prefix = new HashMap<String,SortedSet<String>>(); - private String _defaultNS = ""; - - -//---------------------------------------------------------------------------- -// Public methods -//---------------------------------------------------------------------------- - - /** - * Adds a namespace to this resolver. - * - * @return The resolver instance, so that calls may be chained. - * - * @throws IllegalArgumentException if either <code>prefix</code> - * or <code>nsURI</code> is <code>null</code>. - */ - public NamespaceResolver addNamespace(String prefix, String nsURI) - { - if (prefix == null) - throw new IllegalArgumentException("prefix may not be null"); - if (nsURI == null) - throw new IllegalArgumentException("nsURI may not be null"); - - _prefix2ns.put(prefix, nsURI); - getPrefixSet(nsURI).add(prefix); - return this; - } - - - /** - * Sets the default namespace -- the namespace that will be returned - * when an empty string is passed to the resolver. - * - * @return The resolver instance, so that calls may be chained. - * - * @throws IllegalArgumentException if <code>nsURI</code> is - * <code>null</code>. - */ - public NamespaceResolver setDefaultNamespace(String nsURI) - { - if (nsURI == null) - throw new IllegalArgumentException("nsURI may not be null"); - - _defaultNS = nsURI; - return this; - } - - - /** - * Returns the default namespace, an empty string if one has not yet - * been set. - */ - public String getDefaultNamespace() - { - return _defaultNS; - } - - - /** - * Returns a list of all prefixes known to this resolver, in alphabetical - * order. - */ - public List<String> getAllPrefixes() - { - return new ArrayList<String>(_prefix2ns.keySet()); - } - - -//---------------------------------------------------------------------------- -// NamespaceContext implementation -//---------------------------------------------------------------------------- - - /** - * Returns the namespace URI bound to a given prefix, <code>null</code> - * if no URI is bound to the specified prefix. See interface doc for - * default bindings. - */ - public String getNamespaceURI(String prefix) - { - if (prefix == null) - throw new IllegalArgumentException("prefix may not be null"); - else if ("".equals(prefix)) - return _defaultNS; - else if (XMLConstants.XML_NS_PREFIX.equals(prefix)) - return XMLConstants.XML_NS_URI; - else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) - return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; - else - return _prefix2ns.get(prefix); - } - - - /** - * Returns the first prefix in alphabetical order bound to this namespace - * URI, <code>null</code> if there is no binding for the namespace. See - * interface doc for default bindings. - */ - public String getPrefix(String nsURI) - { - Iterator<String> itx = getPrefixes(nsURI); - return itx.hasNext() ? itx.next() : null; - } - - - /** - * Returns an iterator over all prefixes bound to this namespace URI, in - * alphabetical order, an empty iterator if there are no bindings. See - * interface doc for default bindings. - */ - public Iterator<String> getPrefixes(String nsURI) - { - if (nsURI == null) - throw new IllegalArgumentException("nsURI may not be null"); - else if (_defaultNS.equals(nsURI)) - return DEFAULT_PREFIXES.iterator(); - else if (XMLConstants.XML_NS_URI.equals(nsURI)) - return XML_NS_URI_PREFIXES.iterator(); - else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(nsURI)) - return XML_NS_ATTR_PREFIXES.iterator(); - else - return getPrefixSet(nsURI).iterator(); - } - - -//---------------------------------------------------------------------------- -// Object overrides -//---------------------------------------------------------------------------- - - /** - * Two instances are considered equal if they have the same mappings, - * including default namespace. - */ - @Override - public final boolean equals(Object obj) - { - if (this == obj) - return true; - - if (obj instanceof NamespaceResolver) - { - NamespaceResolver that = (NamespaceResolver)obj; - return this._prefix2ns.equals(that._prefix2ns) - && this._defaultNS.equals(that._defaultNS); - } - return false; - } - - - @Override - public int hashCode() - { - // rely on these objects caching their hashcode - return _prefix2ns.hashCode() ^ _defaultNS.hashCode(); - } - - - /** - * Returns a string containing the the <code>xmlns</code> attribute - * specifications that would result from this resolver. - */ - @Override - public String toString() - { - StringBuilder buf = new StringBuilder(50 * _prefix2ns.size()); - if (!"".equals(_defaultNS)) - { - buf.append("xmlns=\"").append(_defaultNS).append("\""); - } - for (String prefix : getAllPrefixes()) - { - if (buf.length() > 0) - buf.append(" "); - buf.append("xmlns:").append(prefix).append("=\"") - .append(_prefix2ns.get(prefix)).append("\""); - } - return buf.toString(); - } - - - /** - * Returns a deep clone of this object, that can then be independently - * manipulated. - */ - @Override - protected NamespaceResolver clone() - { - NamespaceResolver that = new NamespaceResolver() - .setDefaultNamespace(getDefaultNamespace()); - for (String prefix : getAllPrefixes()) - { - that.addNamespace(prefix, getNamespaceURI(prefix)); - } - return that; - } - - -//---------------------------------------------------------------------------- -// Internals -//---------------------------------------------------------------------------- - - - /** - * Returns the set of prefixes for a given namespace, creating a new - * entry if one doesn't already exist. - */ - private SortedSet<String> getPrefixSet(String nsURI) - { - SortedSet<String> prefixes = _ns2prefix.get(nsURI); - if (prefixes == null) - { - prefixes = new TreeSet<String>(); - _ns2prefix.put(nsURI, prefixes); - } - return prefixes; - } -} Modified: trunk/src/main/java/net/sf/practicalxml/util/NodeListIterable.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/misc/NodeListIterable.java 2008-12-14 17:22:13 UTC (rev 53) +++ trunk/src/main/java/net/sf/practicalxml/util/NodeListIterable.java 2008-12-16 02:56:39 UTC (rev 54) @@ -1,4 +1,4 @@ -package net.sf.practicalxml.misc; +package net.sf.practicalxml.util; import java.util.Iterator; import java.util.NoSuchElementException; Deleted: trunk/src/main/java/net/sf/practicalxml/util/SimpleNamespaceResolver.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/misc/SimpleNamespaceResolver.java 2008-12-14 17:22:13 UTC (rev 53) +++ trunk/src/main/java/net/sf/practicalxml/util/SimpleNamespaceResolver.java 2008-12-16 02:56:39 UTC (rev 54) @@ -1,145 +0,0 @@ -package net.sf.practicalxml.misc; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -import javax.xml.XMLConstants; -import javax.xml.namespace.NamespaceContext; - - -/** - * Implements a bidirectional lookup between a single namespace URI and its - * prefix, for use with XPath expressions. Does not support the "default" - * namespace, unless explicitly created with a prefix "". - */ -public class SimpleNamespaceResolver -implements NamespaceContext -{ - private final String _prefix; - private final String _nsURI; - private final List<String> _prefixes; - - public SimpleNamespaceResolver(String prefix, String nsURI) - { - if (prefix == null) - throw new IllegalArgumentException("prefix may not be null"); - if (nsURI == null) - throw new IllegalArgumentException("nsURI may not be null"); - - _prefix = prefix; - _nsURI = nsURI; - _prefixes = Arrays.asList(prefix); - } - - -//---------------------------------------------------------------------------- -// NamespaceContext implementation -//---------------------------------------------------------------------------- - - /** - * Returns the namespace URI bound to the passed prefix, <code>null</code> - * if the prefix does not correspond to the binding defined by this - * instance. Also supports "standard" bindings; see JDK doc for details. - */ - public String getNamespaceURI(String prefix) - { - if (prefix == null) - throw new IllegalArgumentException("prefix may not be null"); - else if (_prefix.equals(prefix)) - return _nsURI; - else if (XMLConstants.XML_NS_PREFIX.equals(prefix)) - return XMLConstants.XML_NS_URI; - else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) - return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; - else - return null; - } - - - /** - * Returns the prefix bound to the passed namespace URI, <code>null</code> - * if the URI does not correspond to the binding defined by this instance. - * Also supports "standard" bindings; see JDK doc for details. - */ - public String getPrefix(String nsURI) - { - if (nsURI == null) - throw new IllegalArgumentException("nsURI may not be null"); - else if (nsURI.equals(_nsURI)) - return _prefix; - else if (nsURI.equals(XMLConstants.XML_NS_URI)) - return XMLConstants.XML_NS_PREFIX; - else if (nsURI.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) - return XMLConstants.XMLNS_ATTRIBUTE; - else - return null; - } - - - /** - * Returns an iterator over prefixes for the passed URI, an empty iterator - * if the URI does not correspond to the binding defined by this instance. - * Also supports "standard" bindings; see JDK doc for details. - */ - public Iterator<String> getPrefixes(String nsURI) - { - String prefix = getPrefix(nsURI); - if (_prefix.equals(prefix)) - return _prefixes.iterator(); - else if (prefix == null) - return Collections.<String>emptyList().iterator(); - else - return Arrays.asList(prefix).iterator(); - } - - -//---------------------------------------------------------------------------- -// Object overrides -//---------------------------------------------------------------------------- - - /** - * Two instances are considered equal if they have the same mappings, - * including default namespace. - */ - @Override - public final boolean equals(Object obj) - { - if (obj instanceof SimpleNamespaceResolver) - { - SimpleNamespaceResolver that = (SimpleNamespaceResolver)obj; - return this._prefix.equals(that._prefix) - && this._nsURI.equals(that._nsURI); - } - return false; - } - - - @Override - public int hashCode() - { - return _prefix.hashCode() ^ _nsURI.hashCode(); - } - - - /** - * Returns a string containing the the <code>xmlns</code> attribute - * specifications that would result from this resolver. - */ - @Override - public String toString() - { - StringBuilder buf = new StringBuilder(_prefix.length() + _nsURI.length() + 10); - if ("".equals(_prefix)) - { - buf.append("xmlns=\"").append(_nsURI).append("\""); - } - else - { - buf.append("xmlns:").append(_prefix).append("=\"") - .append(_nsURI).append("\""); - } - return buf.toString(); - } -} Copied: trunk/src/main/java/net/sf/practicalxml/xpath/NamespaceResolver.java (from rev 53, trunk/src/main/java/net/sf/practicalxml/misc/NamespaceResolver.java) =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/NamespaceResolver.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/xpath/NamespaceResolver.java 2008-12-16 02:56:39 UTC (rev 54) @@ -0,0 +1,261 @@ +package net.sf.practicalxml.xpath; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; + + +/** + * Maintains a bi-directional lookup table for mappings between namespace URIs + * and prefixes. Follows the "builder" pattern, in that the methods to add + * mappings may be chained: a resolver can be created and fully configured in + * a single expression. + * <p> + * Usage note: <code>NamespaceContext</code> allows multiple prefixes per URI, + * in keeping with the Namespace spec. This implementation supports that, but + * it's a bad idea to actually use this feature when writing an XPath. You'll + * be much happier if you limit yourself to a 1:1 mapping. + * <p> + * If you have a single namespace mapping, this implementation is overkill. + * Instead, use {@link SimpleNamespaceResolver}. + */ +public class NamespaceResolver +implements NamespaceContext +{ + private final static SortedSet<String> DEFAULT_PREFIXES = + new TreeSet<String>(); + private final static SortedSet<String> XML_NS_URI_PREFIXES = + new TreeSet<String>(); + private final static SortedSet<String> XML_NS_ATTR_PREFIXES = + new TreeSet<String>(); + static + { + DEFAULT_PREFIXES.add(""); + XML_NS_URI_PREFIXES.add(XMLConstants.XML_NS_PREFIX); + XML_NS_ATTR_PREFIXES.add(XMLConstants.XMLNS_ATTRIBUTE); + } + + private TreeMap<String,String> _prefix2ns = new TreeMap<String,String>(); + private Map<String,SortedSet<String>> _ns2prefix = new HashMap<String,SortedSet<String>>(); + private String _defaultNS = ""; + + +//---------------------------------------------------------------------------- +// Public methods +//---------------------------------------------------------------------------- + + /** + * Adds a namespace to this resolver. + * + * @return The resolver instance, so that calls may be chained. + * + * @throws IllegalArgumentException if either <code>prefix</code> + * or <code>nsURI</code> is <code>null</code>. + */ + public NamespaceResolver addNamespace(String prefix, String nsURI) + { + if (prefix == null) + throw new IllegalArgumentException("prefix may not be null"); + if (nsURI == null) + throw new IllegalArgumentException("nsURI may not be null"); + + _prefix2ns.put(prefix, nsURI); + getPrefixSet(nsURI).add(prefix); + return this; + } + + + /** + * Sets the default namespace -- the namespace that will be returned + * when an empty string is passed to the resolver. + * + * @return The resolver instance, so that calls may be chained. + * + * @throws IllegalArgumentException if <code>nsURI</code> is + * <code>null</code>. + */ + public NamespaceResolver setDefaultNamespace(String nsURI) + { + if (nsURI == null) + throw new IllegalArgumentException("nsURI may not be null"); + + _defaultNS = nsURI; + return this; + } + + + /** + * Returns the default namespace, an empty string if one has not yet + * been set. + */ + public String getDefaultNamespace() + { + return _defaultNS; + } + + + /** + * Returns a list of all prefixes known to this resolver, in alphabetical + * order. + */ + public List<String> getAllPrefixes() + { + return new ArrayList<String>(_prefix2ns.keySet()); + } + + +//---------------------------------------------------------------------------- +// NamespaceContext implementation +//---------------------------------------------------------------------------- + + /** + * Returns the namespace URI bound to a given prefix, <code>null</code> + * if no URI is bound to the specified prefix. See interface doc for + * default bindings. + */ + public String getNamespaceURI(String prefix) + { + if (prefix == null) + throw new IllegalArgumentException("prefix may not be null"); + else if ("".equals(prefix)) + return _defaultNS; + else if (XMLConstants.XML_NS_PREFIX.equals(prefix)) + return XMLConstants.XML_NS_URI; + else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) + return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; + else + return _prefix2ns.get(prefix); + } + + + /** + * Returns the first prefix in alphabetical order bound to this namespace + * URI, <code>null</code> if there is no binding for the namespace. See + * interface doc for default bindings. + */ + public String getPrefix(String nsURI) + { + Iterator<String> itx = getPrefixes(nsURI); + return itx.hasNext() ? itx.next() : null; + } + + + /** + * Returns an iterator over all prefixes bound to this namespace URI, in + * alphabetical order, an empty iterator if there are no bindings. See + * interface doc for default bindings. + */ + public Iterator<String> getPrefixes(String nsURI) + { + if (nsURI == null) + throw new IllegalArgumentException("nsURI may not be null"); + else if (_defaultNS.equals(nsURI)) + return DEFAULT_PREFIXES.iterator(); + else if (XMLConstants.XML_NS_URI.equals(nsURI)) + return XML_NS_URI_PREFIXES.iterator(); + else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(nsURI)) + return XML_NS_ATTR_PREFIXES.iterator(); + else + return getPrefixSet(nsURI).iterator(); + } + + +//---------------------------------------------------------------------------- +// Object overrides +//---------------------------------------------------------------------------- + + /** + * Two instances are considered equal if they have the same mappings, + * including default namespace. + */ + @Override + public final boolean equals(Object obj) + { + if (this == obj) + return true; + + if (obj instanceof NamespaceResolver) + { + NamespaceResolver that = (NamespaceResolver)obj; + return this._prefix2ns.equals(that._prefix2ns) + && this._defaultNS.equals(that._defaultNS); + } + return false; + } + + + @Override + public int hashCode() + { + // rely on these objects caching their hashcode + return _prefix2ns.hashCode() ^ _defaultNS.hashCode(); + } + + + /** + * Returns a string containing the the <code>xmlns</code> attribute + * specifications that would result from this resolver. + */ + @Override + public String toString() + { + StringBuilder buf = new StringBuilder(50 * _prefix2ns.size()); + if (!"".equals(_defaultNS)) + { + buf.append("xmlns=\"").append(_defaultNS).append("\""); + } + for (String prefix : getAllPrefixes()) + { + if (buf.length() > 0) + buf.append(" "); + buf.append("xmlns:").append(prefix).append("=\"") + .append(_prefix2ns.get(prefix)).append("\""); + } + return buf.toString(); + } + + + /** + * Returns a deep clone of this object, that can then be independently + * manipulated. + */ + @Override + protected NamespaceResolver clone() + { + NamespaceResolver that = new NamespaceResolver() + .setDefaultNamespace(getDefaultNamespace()); + for (String prefix : getAllPrefixes()) + { + that.addNamespace(prefix, getNamespaceURI(prefix)); + } + return that; + } + + +//---------------------------------------------------------------------------- +// Internals +//---------------------------------------------------------------------------- + + + /** + * Returns the set of prefixes for a given namespace, creating a new + * entry if one doesn't already exist. + */ + private SortedSet<String> getPrefixSet(String nsURI) + { + SortedSet<String> prefixes = _ns2prefix.get(nsURI); + if (prefixes == null) + { + prefixes = new TreeSet<String>(); + _ns2prefix.put(nsURI, prefixes); + } + return prefixes; + } +} Copied: trunk/src/main/java/net/sf/practicalxml/xpath/SimpleNamespaceResolver.java (from rev 53, trunk/src/main/java/net/sf/practicalxml/misc/SimpleNamespaceResolver.java) =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/SimpleNamespaceResolver.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/xpath/SimpleNamespaceResolver.java 2008-12-16 02:56:39 UTC (rev 54) @@ -0,0 +1,145 @@ +package net.sf.practicalxml.xpath; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; + + +/** + * Implements a bidirectional lookup between a single namespace URI and its + * prefix, for use with XPath expressions. Does not support the "default" + * namespace, unless explicitly created with a prefix "". + */ +public class SimpleNamespaceResolver +implements NamespaceContext +{ + private final String _prefix; + private final String _nsURI; + private final List<String> _prefixes; + + public SimpleNamespaceResolver(String prefix, String nsURI) + { + if (prefix == null) + throw new IllegalArgumentException("prefix may not be null"); + if (nsURI == null) + throw new IllegalArgumentException("nsURI may not be null"); + + _prefix = prefix; + _nsURI = nsURI; + _prefixes = Arrays.asList(prefix); + } + + +//---------------------------------------------------------------------------- +// NamespaceContext implementation +//---------------------------------------------------------------------------- + + /** + * Returns the namespace URI bound to the passed prefix, <code>null</code> + * if the prefix does not correspond to the binding defined by this + * instance. Also supports "standard" bindings; see JDK doc for details. + */ + public String getNamespaceURI(String prefix) + { + if (prefix == null) + throw new IllegalArgumentException("prefix may not be null"); + else if (_prefix.equals(prefix)) + return _nsURI; + else if (XMLConstants.XML_NS_PREFIX.equals(prefix)) + return XMLConstants.XML_NS_URI; + else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) + return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; + else + return null; + } + + + /** + * Returns the prefix bound to the passed namespace URI, <code>null</code> + * if the URI does not correspond to the binding defined by this instance. + * Also supports "standard" bindings; see JDK doc for details. + */ + public String getPrefix(String nsURI) + { + if (nsURI == null) + throw new IllegalArgumentException("nsURI may not be null"); + else if (nsURI.equals(_nsURI)) + return _prefix; + else if (nsURI.equals(XMLConstants.XML_NS_URI)) + return XMLConstants.XML_NS_PREFIX; + else if (nsURI.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) + return XMLConstants.XMLNS_ATTRIBUTE; + else + return null; + } + + + /** + * Returns an iterator over prefixes for the passed URI, an empty iterator + * if the URI does not correspond to the binding defined by this instance. + * Also supports "standard" bindings; see JDK doc for details. + */ + public Iterator<String> getPrefixes(String nsURI) + { + String prefix = getPrefix(nsURI); + if (_prefix.equals(prefix)) + return _prefixes.iterator(); + else if (prefix == null) + return Collections.<String>emptyList().iterator(); + else + return Arrays.asList(prefix).iterator(); + } + + +//---------------------------------------------------------------------------- +// Object overrides +//---------------------------------------------------------------------------- + + /** + * Two instances are considered equal if they have the same mappings, + * including default namespace. + */ + @Override + public final boolean equals(Object obj) + { + if (obj instanceof SimpleNamespaceResolver) + { + SimpleNamespaceResolver that = (SimpleNamespaceResolver)obj; + return this._prefix.equals(that._prefix) + && this._nsURI.equals(that._nsURI); + } + return false; + } + + + @Override + public int hashCode() + { + return _prefix.hashCode() ^ _nsURI.hashCode(); + } + + + /** + * Returns a string containing the the <code>xmlns</code> attribute + * specifications that would result from this resolver. + */ + @Override + public String toString() + { + StringBuilder buf = new StringBuilder(_prefix.length() + _nsURI.length() + 10); + if ("".equals(_prefix)) + { + buf.append("xmlns=\"").append(_nsURI).append("\""); + } + else + { + buf.append("xmlns:").append(_prefix).append("=\"") + .append(_nsURI).append("\""); + } + return buf.toString(); + } +} Added: trunk/src/main/java/net/sf/practicalxml/xpath/package.html =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/package.html (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/xpath/package.html 2008-12-16 02:56:39 UTC (rev 54) @@ -0,0 +1,5 @@ +<html> +<body> + This package support classes that are primarily used for XPath evaluation. +</body> +</html> \ No newline at end of file Modified: trunk/src/test/java/net/sf/practicalxml/TestDomUtilGetPath.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestDomUtilGetPath.java 2008-12-14 17:22:13 UTC (rev 53) +++ trunk/src/test/java/net/sf/practicalxml/TestDomUtilGetPath.java 2008-12-16 02:56:39 UTC (rev 54) @@ -7,8 +7,8 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; -import net.sf.practicalxml.misc.NamespaceResolver; -import net.sf.practicalxml.misc.SimpleNamespaceResolver; +import net.sf.practicalxml.xpath.NamespaceResolver; +import net.sf.practicalxml.xpath.SimpleNamespaceResolver; /** Modified: trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java 2008-12-14 17:22:13 UTC (rev 53) +++ trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java 2008-12-16 02:56:39 UTC (rev 54) @@ -6,7 +6,7 @@ import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; -import net.sf.practicalxml.misc.ExceptionErrorHandler; +import net.sf.practicalxml.util.ExceptionErrorHandler; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; Deleted: trunk/src/test/java/net/sf/practicalxml/util/TestNamespaceResolver.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java 2008-12-14 17:22:13 UTC (rev 53) +++ trunk/src/test/java/net/sf/practicalxml/util/TestNamespaceResolver.java 2008-12-16 02:56:39 UTC (rev 54) @@ -1,308 +0,0 @@ -package net.sf.practicalxml.misc; - -import java.util.Iterator; -import java.util.List; - -import javax.xml.XMLConstants; -import javax.xml.namespace.NamespaceContext; - -import net.sf.practicalxml.AbstractTestCase; - - -public class TestNamespaceResolver extends AbstractTestCase -{ - public void testSingleNamespace() throws Exception - { - final String prefix = "foo"; - final String nsURI = "bar"; - - NamespaceResolver resolv = new NamespaceResolver(); - - assertSame(resolv, resolv.addNamespace(prefix, nsURI)); - - assertEquals(nsURI, resolv.getNamespaceURI(prefix)); - assertEquals(prefix, resolv.getPrefix(nsURI)); - - Iterator<String> itx = resolv.getPrefixes(nsURI); - assertEquals(prefix, itx.next()); - assertFalse(itx.hasNext()); - } - - - public void testTwoNamespaces() throws Exception - { - final String prefix1 = "foo"; - final String nsURI1 = "bar"; - final String prefix2 = "argle"; - final String nsURI2 = "bargle"; - - NamespaceContext resolv = new NamespaceResolver() - .addNamespace(prefix1, nsURI1) - .addNamespace(prefix2, nsURI2); - - assertEquals(nsURI1, resolv.getNamespaceURI(prefix1)); - assertEquals(nsURI2, resolv.getNamespaceURI(prefix2)); - - assertEquals(prefix1, resolv.getPrefix(nsURI1)); - assertEquals(prefix2, resolv.getPrefix(nsURI2)); - - Iterator<String> itx1 = resolv.getPrefixes(nsURI1); - assertEquals(prefix1, itx1.next()); - assertFalse(itx1.hasNext()); - - Iterator<String> itx2 = resolv.getPrefixes(nsURI2); - assertEquals(prefix2, itx2.next()); - assertFalse(itx2.hasNext()); - } - - - public void testOneNamespaceTwoPrefixes() throws Exception - { - final String prefix1 = "foo"; - final String prefix2 = "argle"; - final String nsURI = "bargle"; - - NamespaceContext resolv = new NamespaceResolver() - .addNamespace(prefix1, nsURI) - .addNamespace(prefix2, nsURI); - - assertEquals(nsURI, resolv.getNamespaceURI(prefix1)); - assertEquals(nsURI, resolv.getNamespaceURI(prefix2)); - - assertEquals(prefix2, resolv.getPrefix(nsURI)); - - Iterator<String> itx1 = resolv.getPrefixes(nsURI); - assertEquals(prefix2, itx1.next()); - assertEquals(prefix1, itx1.next()); - assertFalse(itx1.hasNext()); - } - - - public void testUnboundNamespace() throws Exception - { - NamespaceContext resolv = new NamespaceResolver(); - - assertNull(resolv.getNamespaceURI("argle")); - assertNull(resolv.getPrefix("argle")); - assertFalse(resolv.getPrefixes("argle").hasNext()); - } - - - public void testInvalidNamespace() throws Exception - { - NamespaceResolver resolv = new NamespaceResolver(); - - try - { - resolv.addNamespace(null, "foo"); - fail("accepted null prefix"); - } - catch (IllegalArgumentException e) - { - // success - } - - try - { - resolv.addNamespace("foo", null); - fail("accepted null nsURI"); - } - catch (IllegalArgumentException e) - { - // success - } - - try - { - resolv.setDefaultNamespace(null); - fail("accepted null nsURI"); - } - catch (IllegalArgumentException e) - { - // success - } - } - - - public void testStandardMappings() throws Exception - { - NamespaceContext resolv = new NamespaceResolver(); - - assertEquals(XMLConstants.XML_NS_URI, resolv.getNamespaceURI(XMLConstants.XML_NS_PREFIX)); - assertEquals(XMLConstants.XML_NS_PREFIX, resolv.getPrefix(XMLConstants.XML_NS_URI)); - Iterator<String> itx1 = resolv.getPrefixes(XMLConstants.XML_NS_URI); - assertEquals(XMLConstants.XML_NS_PREFIX, itx1.next()); - assertFalse(itx1.hasNext()); - - assertEquals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, resolv.getNamespaceURI(XMLConstants.XMLNS_ATTRIBUTE)); - assertEquals(XMLConstants.XMLNS_ATTRIBUTE, resolv.getPrefix(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)); - Iterator<String> itx2 = resolv.getPrefixes(XMLConstants.XMLNS_ATTRIBUTE_NS_URI); - assertEquals(XMLConstants.XMLNS_ATTRIBUTE, itx2.next()); - assertFalse(itx2.hasNext()); - - try - { - resolv.getNamespaceURI(null); - fail("should throw IllegalArgumentException"); - } - catch (IllegalArgumentException e) - { - // success - } - - try - { - resolv.getPrefix(null); - fail("should throw IllegalArgumentException"); - } - catch (IllegalArgumentException e) - { - // success - } - - try - { - resolv.getPrefixes(null); - fail("should throw IllegalArgumentException"); - } - catch (IllegalArgumentException e) - { - // success - } - } - - - public void testDefaultNamespace() throws Exception - { - NamespaceResolver resolv = new NamespaceResolver(); - - assertEquals("", resolv.getDefaultNamespace()); - assertEquals("", resolv.getNamespaceURI("")); - assertEquals("", resolv.getPrefix("")); - Iterator<String> itx1 = resolv.getPrefixes(""); - assertEquals("", itx1.next()); - assertFalse(itx1.hasNext()); - - assertSame(resolv, resolv.setDefaultNamespace("foo")); - - assertEquals("foo", resolv.getDefaultNamespace()); - assertEquals("foo", resolv.getNamespaceURI("")); - assertEquals("", resolv.getPrefix("foo")); - Iterator<String> itx2 = resolv.getPrefixes("foo"); - assertEquals("", itx2.next()); - assertFalse(itx2.hasNext()); - - assertSame(resolv, resolv.setDefaultNamespace("bar")); - - assertEquals("bar", resolv.getDefaultNamespace()); - assertEquals("bar", resolv.getNamespaceURI("")); - assertEquals("", resolv.getPrefix("bar")); - Iterator<String> itx3 = resolv.getPrefixes("bar"); - assertEquals("", itx3.next()); - assertFalse(itx3.hasNext()); - } - - - public void testGetAllPrefixes() throws Exception - { - NamespaceResolver resolv = new NamespaceResolver() - .addNamespace("foo", "bar") - .addNamespace("baz", "bar") - .addNamespace("baz", "biggles") // intentional overwrite - .addNamespace("argle", "bargle"); - - List<String> prefixes = resolv.getAllPrefixes(); - assertEquals(3, prefixes.size()); - - Iterator<String> itx = prefixes.iterator(); - assertEquals("argle", itx.next()); - assertEquals("baz", itx.next()); - assertEquals("foo", itx.next()); - } - - - public void testEqualsAndHashCode() throws Exception - { - Object obj1 = new NamespaceResolver() - .addNamespace("foo", "bar") - .setDefaultNamespace("zippy"); - Object obj2 = new NamespaceResolver() - .addNamespace("foo", "bar") - .setDefaultNamespace("zippy"); - Object obj3 = new NamespaceResolver() - .addNamespace("foo", "bar"); - Object obj4 = new NamespaceResolver() - .addNamespace("argle", "bargle"); - - assertFalse(obj1.equals(new Object())); - - assertTrue(obj1.equals(obj2)); - assertTrue(obj2.equals(obj1)); - assertEquals(obj1.hashCode(), obj2.hashCode()); - - assertFalse(obj1.equals(obj3)); - assertFalse(obj3.equals(obj1)); - - assertFalse(obj3.equals(obj4)); - assertFalse(obj4.equals(obj3)); - - // this works today ... assume that the underlying calcs don't change - assertFalse(obj3.hashCode() == obj4.hashCode()); - } - - - public void testToString() throws Exception - { - NamespaceResolver resolv = new NamespaceResolver(); - String str0 = resolv.toString(); - assertEquals(0, str0.length()); - - resolv.setDefaultNamespace("foo"); - String str1 = resolv.toString(); - assertTrue(str1.contains("xmlns=\"foo\"")); - assertEquals(1, str1.split(" +").length); - - resolv.addNamespace("argle", "bargle"); - String str2 = resolv.toString(); - assertTrue(str2.contains("xmlns=\"foo\"")); - assertTrue(str2.contains("xmlns:argle=\"bargle\"")); - assertEquals(2, str2.split(" +").length); - - resolv.addNamespace("zippy", "pinhead"); - String str3 = resolv.toString(); - assertTrue(str3.contains("xmlns=\"foo\"")); - assertTrue(str3.contains("xmlns:argle=\"bargle\"")); - assertTrue(str3.contains("xmlns:zippy=\"pinhead\"")); - assertEquals(3, str3.split(" +").length); - } - - - public void testClone() throws Exception - { - NamespaceResolver resolv1 = new NamespaceResolver() - .setDefaultNamespace("foo") - .addNamespace("argle", "bargle"); - - NamespaceResolver resolv2 = resolv1.clone(); - assertNotSame(resolv1, resolv2); - assertEquals(resolv1, resolv2); - - resolv2.setDefaultNamespace("bar"); - assertFalse(resolv1.equals(resolv2)); - assertEquals("foo", resolv1.getDefaultNamespace()); - assertEquals("bar", resolv2.getDefaultNamespace()); - - resolv2.addNamespace("argle", "zargle"); - assertEquals("bargle", resolv1.getNamespaceURI("argle")); - assertEquals("zargle", resolv2.getNamespaceURI("argle")); - - resolv2.addNamespace("wargle", "qwerty"); - assertNull(resolv1.getPrefix("qwerty")); - assertNull(resolv1.getNamespaceURI("wargle")); - - resolv1.addNamespace("wargle", "asdfg"); - assertNull(resolv2.getPrefix("asdfg")); - assertEquals("qwerty", resolv2.getNamespaceURI("wargle")); - } - -} Modified: trunk/src/test/java/net/sf/practicalxml/util/TestNodeListIterable.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/misc/TestNodeListIterable.java 2008-12-14 17:22:13 UTC (rev 53) +++ trunk/src/test/java/net/sf/practicalxml/util/TestNodeListIterable.java 2008-12-16 02:56:39 UTC (rev 54) @@ -1,11 +1,10 @@ -package net.sf.practicalxml.misc; +package net.sf.practicalxml.util; import java.util.Iterator; import java.util.NoSuchElementException; import net.sf.practicalxml.AbstractTestCase; import net.sf.practicalxml.DomUtil; -import net.sf.practicalxml.misc.NodeListIterable; import org.w3c.dom.Element; import org.w3c.dom.Node; Deleted: trunk/src/test/java/net/sf/practicalxml/util/TestSimpleNamespaceResolver.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/misc/TestSimpleNamespaceResolver.java 2008-12-14 17:22:13 UTC (rev 53) +++ trunk/src/test/java/net/sf/practicalxml/util/TestSimpleNamespaceResolver.java 2008-12-16 02:56:39 UTC (rev 54) @@ -1,153 +0,0 @@ -package net.sf.practicalxml.misc; - -import java.util.Iterator; -import javax.xml.XMLConstants; -import javax.xml.namespace.NamespaceContext; - -import net.sf.practicalxml.AbstractTestCase; - - -public class TestSimpleNamespaceResolver extends AbstractTestCase -{ - public void testInvalidConstruction() throws Exception - { - try - { - new SimpleNamespaceResolver(null, "foo"); - fail("accepted null prefix"); - } - catch (IllegalArgumentException e) - { - // success - } - - try - { - new SimpleNamespaceResolver("foo", null); - fail("accepted null nsURI"); - } - catch (IllegalArgumentException e) - { - // success - } - } - - - public void testLookup() throws Exception - { - final String prefix = "foo"; - final String nsURI = "bar"; - - NamespaceContext resolv = new SimpleNamespaceResolver(prefix, nsURI); - - assertEquals(nsURI, resolv.getNamespaceURI(prefix)); - assertEquals(prefix, resolv.getPrefix(nsURI)); - - Iterator<String> itx = resolv.getPrefixes(nsURI); - assertEquals(prefix, itx.next()); - assertFalse(itx.hasNext()); - } - - - public void testDefaultNamespace() throws Exception - { - final String prefix = ""; - final String nsURI = "bar"; - - NamespaceContext resolv = new SimpleNamespaceResolver(prefix, nsURI); - - assertEquals(nsURI, resolv.getNamespaceURI(prefix)); - assertEquals(prefix, resolv.getPrefix(nsURI)); - - Iterator<String> itx = resolv.getPrefixes(nsURI); - assertEquals(prefix, itx.next()); - assertFalse(itx.hasNext()); - } - - - public void testUnboundNamespace() throws Exception - { - NamespaceContext resolv = new SimpleNamespaceResolver("foo", "bar"); - - assertNull(resolv.getNamespaceURI("argle")); - assertNull(resolv.getPrefix("argle")); - assertFalse(resolv.getPrefixes("argle").hasNext()); - } - - - public void testStandardMappings() throws Exception - { - NamespaceContext resolv = new SimpleNamespaceResolver("foo", "bar"); - - assertEquals(XMLConstants.XML_NS_URI, resolv.getNamespaceURI(XMLConstants.XML_NS_PREFIX)); - assertEquals(XMLConstants.XML_NS_PREFIX, resolv.getPrefix(XMLConstants.XML_NS_URI)); - Iterator<String> itx1 = resolv.getPrefixes(XMLConstants.XML_NS_URI); - assertEquals(XMLConstants.XML_NS_PREFIX, itx1.next()); - assertFalse(itx1.hasNext()); - - assertEquals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, resolv.getNamespaceURI(XMLConstants.XMLNS_ATTRIBUTE)); - assertEquals(XMLConstants.XMLNS_ATTRIBUTE, resolv.getPrefix(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)); - Iterator<String> itx2 = resolv.getPrefixes(XMLConstants.XMLNS_ATTRIBUTE_NS_URI); - assertEquals(XMLConstants.XMLNS_ATTRIBUTE, itx2.next()); - assertFalse(itx2.hasNext()); - - try - { - resolv.getNamespaceURI(null); - fail("should throw IllegalArgumentException"); - } - catch (IllegalArgumentException e) - { - // success - } - - try - { - resolv.getPrefix(null); - fail("should throw IllegalArgumentException"); - } - catch (IllegalArgumentException e) - { - // success - } - - try - { - resolv.getPrefixes(null); - fail("should throw IllegalArgumentException"); - } - catch (IllegalArgumentException e) - { - // success - } - } - - - public void testEqualsAndHashCode() throws Exception - { - Object obj1 = new SimpleNamespaceResolver("foo", "bar"); - Object obj2 = new SimpleNamespaceResolver("foo", "bar"); - Object obj3 = new SimpleNamespaceResolver("argle", "bargle"); - - assertFalse(obj1.equals(new Object())); - - assertTrue(obj1.equals(obj2)); - assertTrue(obj2.equals(obj1)); - assertTrue(obj1.hashCode() == obj2.hashCode()); - - assertFalse(obj1.equals(obj3)); - assertFalse(obj3.equals(obj1)); - // this works today ... assume that the underlying calcs don't change - assertFalse(obj1.hashCode() == obj3.hashCode()); - } - - - public void testToString() throws Exception - { - String str1 = new SimpleNamespaceResolver("", "foo").toString(); - assertEquals("xmlns=\"foo\"", str1); - - String str2 = new SimpleNamespaceResolver("foo", "bar").toString(); - assertEquals("xmlns:foo=\"bar\"", str2); - } -} Copied: trunk/src/test/java/net/sf/practicalxml/xpath/TestNamespaceResolver.java (from rev 53, trunk/src/test/java/net/sf/practicalxml/misc/TestNamespaceResolver.java) =================================================================== --- trunk/src/test/java/net/sf/practicalxml/xpath/TestNamespaceResolver.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/xpath/TestNamespaceResolver.java 2008-12-16 02:56:39 UTC (rev 54) @@ -0,0 +1,308 @@ +package net.sf.practicalxml.xpath; + +import java.util.Iterator; +import java.util.List; + +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; + +import net.sf.practicalxml.AbstractTestCase; + + +public class TestNamespaceResolver extends AbstractTestCase +{ + public void testSingleNamespace() throws Exception + { + final String prefix = "foo"; + final String nsURI = "bar"; + + NamespaceResolver resolv = new NamespaceResolver(); + + assertSame(resolv, resolv.addNamespace(prefix, nsURI)); + + assertEquals(nsURI, resolv.getNamespaceURI(prefix)); + assertEquals(prefix, resolv.getPrefix(nsURI)); + + Iterator<String> itx = resolv.getPrefixes(nsURI); + assertEquals(prefix, itx.next()); + assertFalse(itx.hasNext()); + } + + + public void testTwoNamespaces() throws Exception + { + final String prefix1 = "foo"; + final String nsURI1 = "bar"; + final String prefix2 = "argle"; + final String nsURI2 = "bargle"; + + NamespaceContext resolv = new NamespaceResolver() + .addNamespace(prefix1, nsURI1) + .addNamespace(prefix2, nsURI2); + + assertEquals(nsURI1, resolv.getNamespaceURI(prefix1)); + assertEquals(nsURI2, resolv.getNamespaceURI(prefix2)); + + assertEquals(prefix1, resolv.getPrefix(nsURI1)); + assertEquals(prefix2, resolv.getPrefix(nsURI2)); + + Iterator<String> itx1 = resolv.getPrefixes(nsURI1); + assertEquals(prefix1, itx1.next()); + assertFalse(itx1.hasNext()); + + Iterator<String> itx2 = resolv.getPrefixes(nsURI2); + assertEquals(prefix2, itx2.next()); + assertFalse(itx2.hasNext()); + } + + + public void testOneNamespaceTwoPrefixes() throws Exception + { + final String prefix1 = "foo"; + final String prefix2 = "argle"; + final String nsURI = "bargle"; + + NamespaceContext resolv = new NamespaceResolver() + .addNamespace(prefix1, nsURI) + .addNamespace(prefix2, nsURI); + + assertEquals(nsURI, resolv.getNamespaceURI(prefix1)); + assertEquals(nsURI, resolv.getNamespaceURI(prefix2)); + + assertEquals(prefix2, resolv.getPrefix(nsURI)); + + Iterator<String> itx1 = resolv.getPrefixes(nsURI); + assertEquals(prefix2, itx1.next()); + assertEquals(prefix1, itx1.next()); + assertFalse(itx1.hasNext()); + } + + + public void testUnboundNamespace() throws Exception + { + NamespaceContext resolv = new NamespaceResolver(); + + assertNull(resolv.getNamespaceURI("argle")); + assertNull(resolv.getPrefix("argle")); + assertFalse(resolv.getPrefixes("argle").hasNext()); + } + + + public void testInvalidNamespace() throws Exception + { + NamespaceResolver resolv = new NamespaceResolver(); + + try + { + resolv.addNamespace(null, "foo"); + fail("accepted null prefix"); + } + catch (IllegalArgumentException e) + { + // success + } + + try + { + resolv.addNamespace("foo", null); + fail("accepted null nsURI"); + } + catch (IllegalArgumentException e) + { + // success + } + + try + { + resolv.setDefaultNamespace(null); + fail("accepted null nsURI"); + } + catch (IllegalArgumentException e) + { + // success + } + } + + + public void testStandardMappings() throws Exception + { + NamespaceContext resolv = new NamespaceResolver(); + + assertEquals(XMLConstants.XML_NS_URI, resolv.getNamespaceURI(XMLConstants.XML_NS_PREFIX)); + assertEquals(XMLConstants.XML_NS_PREFIX, resolv.getPrefix(XMLConstants.XML_NS_URI)); + Iterator<String> itx1 = resolv.getPrefixes(XMLConstants.XML_NS_URI); + assertEquals(XMLConstants.XML_NS_PREFIX, itx1.next()); + assertFalse(itx1.hasNext()); + + assertEquals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, resolv.getNamespaceURI(XMLConstants.XMLNS_ATTRIBUTE)); + assertEquals(XMLConstants.XMLNS_ATTRIBUTE, resolv.getPrefix(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)); + Iterator<String> itx2 = resolv.getPrefixes(XMLConstants.XMLNS_ATTRIBUTE_NS_URI); + assertEquals(XMLConstants.XMLNS_ATTRIBUTE, itx2.next()); + assertFalse(itx2.hasNext()); + + try + { + resolv.getNamespaceURI(null); + fail("should throw IllegalArgumentException"); + } + catch (IllegalArgumentException e) + { + // success + } + + try + { + resolv.getPrefix(null); + fail("should throw IllegalArgumentException"); + } + catch (IllegalArgumentException e) + { + // success + } + + try + { + resolv.getPrefixes(null); + fail("should throw IllegalArgumentException"); + } + catch (IllegalArgumentException e) + { + // success + } + } + + + public void testDefaultNamespace() throws Exception + { + NamespaceResolver resolv = new NamespaceResolver(); + + assertEquals("", resolv.getDefaultNamespace()); + assertEquals("", resolv.getNamespaceURI("")); + assertEquals("", resolv.getPrefix("")); + Iterator<String> itx1 = resolv.getPrefixes(""); + assertEquals("", itx1.next()); + assertFalse(itx1.hasNext()); + + assertSame(resolv, resolv.setDefaultNamespace("foo")); + + assertEquals("foo", resolv.getDefaultNamespace()); + assertEquals("foo", resolv.getNamespaceURI("")); + assertEquals("", resolv.getPrefix("foo")); + Iterator<String> itx2 = resolv.getPrefixes("foo"); + assertEquals("", itx2.next()); + assertFalse(itx2.hasNext()); + + assertSame(resolv, resolv.setDefaultNamespace("bar")); + + assertEquals("bar", resolv.getDefaultNamespace()); + assertEquals("bar", resolv.getNamespaceURI("")); + assertEquals("", resolv.getPrefix("bar")); + Iterator<String> itx3 = resolv.getPrefixes("bar"); + assertEquals("", itx3.next()); + assertFalse(itx3.hasNext()); + } + + + public void testGetAllPrefixes() throws Exception + { + NamespaceResolver resolv = new NamespaceResolver() + .addNamespace("foo", "bar") + .addNamespace("baz", "bar") + .addNamespace("baz", "biggles") // intentional overwrite + .addNamespace("argle", "bargle"); + + List<String> prefixes = resolv.getAllPrefixes(); + assertEquals(3, prefixes.size()); + + Iterator<String> itx = prefixes.iterator(); + assertEquals("argle", itx.next()); + assertEquals("baz", itx.next()); + assertEquals("foo", itx.next()); + } + + + public void testEqualsAndHashCode() throws Exception + { + Object obj1 = new NamespaceResolver() + .addNamespace("foo", "bar") + .setDefaultNamespace("zippy"); + Object obj2 = new NamespaceResolver() + .addNamespace("foo", "bar") + .setDefaultNamespace("zippy"); + Object obj3 = new NamespaceResolver() + .addNamespace("foo", "bar"); + Object obj4 = new NamespaceResolver() + .addNamespace("argle", "bargle"); + + assertFalse(obj1.equals(new Object())); + + assertTrue(obj1.equals(obj2)); + assertTrue(obj2.equals(obj1)); + assertEquals(obj1.hashCode(), obj2.hashCode()); + + assertFalse(obj1.equals(obj3)); + assertFalse(obj3.equals(obj1)); + + assertFalse(obj3.equals(obj4)); + assertFalse(obj4.equals(obj3)); + + // this works today ... assume that the underlying calcs don't change + assertFalse(obj3.hashCode() == obj4.hashCode()); + } + + + public void testToString() throws Exception + { + NamespaceResolver resolv = new NamespaceResolver(); + String str0 = resolv.toString(); + assertEquals(0, str0.length()); + + resolv.setDefaultNamespace("foo"... [truncated message content] |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-17 03:35:38
|
Revision: 55 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=55&view=rev Author: kdgregory Date: 2008-12-17 03:35:33 +0000 (Wed, 17 Dec 2008) Log Message: ----------- add AbstractFunction Added Paths: ----------- trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java Added: trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java 2008-12-17 03:35:33 UTC (rev 55) @@ -0,0 +1,396 @@ +package net.sf.practicalxml.xpath; + +import java.util.Collections; +import java.util.List; + +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + + +/** + * A base class for writing XPath functions that replaces much of the + * boilerplate code using a "template method" approach. Execution goes + * through the following three stages: + * <ol> + * <li> Initialization: the subclass method {@link #init} is invoked. + * <li> Input: for each argument, the type-appropriate {@link #processArg} + * method is called. + * <li> Completion: the subclass method {@link #getResult} is invoked, and + * its return value is returned from {@link #evaluate}. + * </ol> + * <p> + * Subclasses define their name, namespace, and the number of arguments + * they accept (which may be undefined). + * <p> + * To support thread safety, subclasses are expected to make use of a helper + * object that holds intermediate results. The helper object is created by + * <code>init()</code>, is an argument to and return from <code>processArg() + * </code>, and is an argument to <code>getResult()</code>. Subclasses are + * parameterized with the helper object type, and the default behavior of + * <code>getResult()</code> is to return the helper (this makes life easier + * for a function that takes zero or one arguments). + * <p> + * <em>Note:</em> in JDK 1.5, DOM objects implement both <code>Node</code> + * and <code>NodeList</code>. While this means that <code>processArg(Node) + * </code> would never be called as part of the normal dispatch loop (unless + * the implementation changes), it <em>is</em> called as part of the default + * behavior of <code>processArg(NodeList)</code>, and is given the first + * element in the list. + */ +public class AbstractFunction<T> +implements XPathFunction +{ + /** + * Qualified name of this function. + */ + final private QName _qname; + + + /** + * Minimum number of arguments, 0 if no minimum. + */ + final private int _minArgCount; + + + /** + * Maximum number of arguments, Integer.MAX_VALUE if no maximum. + */ + final private int _maxArgCount; + + + /** + * Constructor for a function that can take any number of arguments. + */ + protected AbstractFunction(String nsUri, String localName) + { + _qname = new QName(nsUri, localName); + _minArgCount = 0; + _maxArgCount = Integer.MAX_VALUE; + } + + + /** + * Constructor for a function that has a fixed number of arguments. + */ + protected AbstractFunction(String nsUri, String localName, int numArgs) + { + _qname = new QName(nsUri, localName); + _minArgCount = numArgs; + _maxArgCount = numArgs; + } + + + /** + * Constructor for a function that has a variable number of arguments. + */ + protected AbstractFunction(String nsUri, String localName, int minArgs, int maxArgs) + { + _qname = new QName(nsUri, localName); + _minArgCount = minArgs; + _maxArgCount = maxArgs; + } + + +//---------------------------------------------------------------------------- +// Public methods +//---------------------------------------------------------------------------- + + /** + * Returns the qualified name of this function, consisting of name and + * namespace (but not prefix). + */ + public QName getQName() + { + return _qname; + } + + + /** + * Returns the namespace URI for this function. + */ + public String getNamespaceUri() + { + return _qname.getNamespaceURI(); + } + + + /** + * Returns the name of this function. + */ + public String getName() + { + return _qname.getLocalPart(); + } + + + /** + * Returns the minimum number of arguments handled by this function. + */ + public int getMinArgCount() + { + return _minArgCount; + } + + + /** + * Returns the maximum number of arguments handled by this function. + */ + public int getMaxArgCount() + { + return _maxArgCount; + } + + + /** + * Determines whether this function is a match for a call to + * <code>XPathFunctionResolver.resolveFunction()</code>. + */ + public boolean isMatch(QName qname, int arity) + { + return _qname.equals(qname) + && (arity >= _minArgCount) + && (arity <= _maxArgCount); + } + + +//---------------------------------------------------------------------------- +// Implementation of XPathFunction +//---------------------------------------------------------------------------- + + /** + * Invokes methods defined by the subclasses to evaluate the function. + * Will invoke the appropriate <code>processArg()</code> method in turn + * for each argument, then invokes <code>getResult()</code> to retrieve + * the function's result. + * + * @throws XPathFunctionException if the supplied argument list does + * not match the min/max for this function, or if any called + * method throws an exception. + */ + public Object evaluate(List args) + throws XPathFunctionException + { + if (args == null) + args = Collections.EMPTY_LIST; + + if ((args.size() < _minArgCount) || (args.size() > _maxArgCount)) + throw new XPathFunctionException("illegal argument count: " + args.size()); + + try + { + T helper = init(); + int idx = 0; + for (Object arg : args) + { + if (arg instanceof String) + helper = processArg(idx, (String)arg, helper); + else if (arg instanceof NodeList) + helper = processArg(idx, (NodeList)arg, helper); + else if (arg instanceof Node) + helper = processArg(idx, (Node)arg, helper); + else if (arg == null) + helper = processNullArg(idx, helper); + else + helper = processUnexpectedArg(idx, arg, helper); + idx++; + } + return getResult(helper); + } + catch (Exception e) + { + throw new XPathFunctionException(e); + } + } + + +//---------------------------------------------------------------------------- +// Subclasses override these methods +//---------------------------------------------------------------------------- + + /** + * Creates a helper object to preserve intermediate results. This object + * is passed to <code>processArg()</code> and <code>getResult()</code>. + * <p> + * The default implementation returns <code>null</code>. + * <p> + * Subclasses are permitted to throw any exception; it will be wrapped in + * an <code>XPathFunctionException</code>. + */ + protected T init() + throws Exception + { + return null; + } + + + /** + * Processes a String argument. + * + * @param index Index of the argument, numbered from 0. + * @param value Value of the argument. + * @param helper Helper object to preserve intermediate results. + * + * @return The helper object (subclasses may return a replacement). + * + * @throws Subclasses are permitted to throw any exception. It will be + * wrapped in <code>XPathFunctionException</code> and cause + * function processing to abort. + */ + protected T processArg(int index, String value, T helper) + throws Exception + { + return helper; + } + + + /** + * Processes a Number argument. Per the XPath spec, this should be a + * <code>Double</code> + * + * @param index Index of the argument, numbered from 0. + * @param value Value of the argument. + * @param helper Helper object to preserve intermediate results. + * + * @return The helper object (subclasses may return a replacement). + * + * @throws Subclasses are permitted to throw any exception. It will be + * wrapped in <code>XPathFunctionException</code> and cause + * function processing to abort. + */ + protected T processArg(int index, Number value, T helper) + throws Exception + { + return helper; + } + + + /** + * Processes a Boolean argument. + * + * @param index Index of the argument, numbered from 0. + * @param value Value of the argument. + * @param helper Helper object to preserve intermediate results. + * + * @return The helper object (subclasses may return a replacement). + * + * @throws Subclasses are permitted to throw any exception. It will be + * wrapped in <code>XPathFunctionException</code> and cause + * function processing to abort. + */ + protected T processArg(int index, Boolean value, T helper) + throws Exception + { + return helper; + } + + + /** + * Processes a Node argument. This function will be invoked by the + * default implementation of <code>processArg(NodeList)</code>. + * + * @param index Index of the argument, numbered from 0. + * @param value Value of the argument. May be <code>null</code>. + * @param helper Helper object to preserve intermediate results. + * + * @return The helper object (subclasses may return a replacement). + * + * @throws Subclasses are permitted to throw any exception. It will be + * wrapped in <code>XPathFunctionException</code> and cause + * function processing to abort. + */ + protected T processArg(int index, Node value, T helper) + throws Exception + { + return helper; + } + + + /** + * Processes a NodeList argument. + * <p> + * The default implementation calls <code>processArg(Node)</code> with + * the first element in the list (<code>null</code> if the list is empty). + * + * @param index Index of the argument, numbered from 0. + * @param value Value of the argument. + * @param helper Helper object to preserve intermediate results. + * + * @return The helper object (subclasses may return a replacement). + * + * @throws Subclasses are permitted to throw any exception. It will be + * wrapped in <code>XPathFunctionException</code> and cause + * function processing to abort. + */ + protected T processArg(int index, NodeList value, T helper) + throws Exception + { + return processArg(index, value.item(0), helper); + } + + + /** + * Processes a <code>null</code> argument — it's unclear whether + * this can ever happen. The default implementation throws <code> + * IllegalArgumentException</code>. + * + * @param index Index of the argument, numbered from 0. + * @param helper Helper object to preserve intermediate results. + * + * @return The helper object (subclasses may return a replacement). + * + * @throws Subclasses are permitted to throw any exception. It will be + * wrapped in <code>XPathFunctionException</code> and cause + * function processing to abort. + */ + protected T processNullArg(int index, T helper) + throws Exception + { + throw new IllegalArgumentException("null argument: " + index); + } + + + /** + * Processes an argument that is not one of the defined types for XPath + * evaluation — it is unclear whether this can actually happen. + * The default implementation throws <code>IllegalArgumentException</code>. + * + * @param index Index of the argument, numbered from 0. + * @param value The argument value + * @param helper Helper object to preserve intermediate results. + * + * @return The helper object (subclasses may return a replacement). + * + * @throws Subclasses are permitted to throw any exception. It will be + * wrapped in <code>XPathFunctionException</code> and cause + * function processing to abort. + */ + protected T processUnexpectedArg(int index, Object value, T helper) + throws Exception + { + throw new IllegalArgumentException( + "unexpected argument: " + index + + " (" + value.getClass().getName() + ")"); + } + + + /** + * Returns the result of this invocation. + * + * @param helper Helper object to preserve intermediate results. + * + * @return The helper object (subclasses may return whatever they want). + * + * @throws Subclasses are permitted to throw any exception. It will be + * wrapped in <code>XPathFunctionException</code> and cause + * function processing to abort. + */ + protected Object getResult(T helper) + throws Exception + { + return helper; + } +} Added: trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java 2008-12-17 03:35:33 UTC (rev 55) @@ -0,0 +1,320 @@ +package net.sf.practicalxml.xpath; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathFunctionException; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import net.sf.practicalxml.AbstractTestCase; +import net.sf.practicalxml.DomUtil; + + +public class TestAbstractFunction +extends AbstractTestCase +{ +//---------------------------------------------------------------------------- +// Support code +//---------------------------------------------------------------------------- + + /** + * A mock function implementation that records its invocations, and + * passes a dummy helper object through the call chain (returning it + * as the invocation result). + */ + private static class MyMockFunction + extends AbstractFunction<Object> + { + public Object _helper; + public int _initCalls; + public int _processStringCalls; + public int _processNumberCalls; + public int _processBooleanCalls; + public int _procesNodeCalls; + public int _procesNodeListCalls; + public int _getResultCalls; + + public MyMockFunction(Object helper) + { + super("foo", "bar"); + _helper = helper; + } + + @Override + protected Object init() throws Exception + { + _initCalls++; + return _helper; + } + + @Override + protected Object processArg(int index, String value, Object helper) + throws Exception + { + _processStringCalls++; + return helper; + } + + @Override + protected Object processArg(int index, Number value, Object helper) + throws Exception + { + _processNumberCalls++; + return helper; + } + + @Override + protected Object processArg(int index, Boolean value, Object helper) + throws Exception + { + _processBooleanCalls++; + return helper; + } + + @Override + protected Object processArg(int index, Node value, Object helper) + throws Exception + { + _procesNodeCalls++; + return helper; + } + + @Override + protected Object processArg(int index, NodeList value, Object helper) + throws Exception + { + _procesNodeListCalls++; + return helper; + } + + @Override + protected Object getResult(Object helper) + { + _getResultCalls++; + assertSame(_helper, helper); + return _helper; + } + } + + +//---------------------------------------------------------------------------- +// Test cases +//---------------------------------------------------------------------------- + + public void testConstructionAndAccessors() throws Exception + { + QName testName = new QName("foo", "bar"); + + AbstractFunction<Object> fn1 = new AbstractFunction<Object>("foo", "bar"); + assertEquals(testName, fn1.getQName()); + assertEquals("foo", fn1.getNamespaceUri()); + assertEquals("bar", fn1.getName()); + assertEquals(0, fn1.getMinArgCount()); + assertEquals(Integer.MAX_VALUE, fn1.getMaxArgCount()); + + assertTrue(fn1.isMatch(testName, 0)); + assertTrue(fn1.isMatch(testName, 1)); + assertTrue(fn1.isMatch(testName, Integer.MAX_VALUE)); + + AbstractFunction<Object> fn2 = new AbstractFunction<Object>("foo", "bar", 5); + assertEquals(testName, fn2.getQName()); + assertEquals("foo", fn2.getNamespaceUri()); + assertEquals("bar", fn2.getName()); + assertEquals(5, fn2.getMinArgCount()); + assertEquals(5, fn2.getMaxArgCount()); + + assertFalse(fn2.isMatch(testName, 0)); + assertFalse(fn2.isMatch(testName, 1)); + assertTrue(fn2.isMatch(testName, 5)); + assertFalse(fn2.isMatch(testName, 6)); + assertFalse(fn2.isMatch(testName, Integer.MAX_VALUE)); + + AbstractFunction<Object> fn3 = new AbstractFunction<Object>("foo", "bar", 1, 5); + assertEquals(testName, fn3.getQName()); + assertEquals("foo", fn3.getNamespaceUri()); + assertEquals("bar", fn3.getName()); + assertEquals(1, fn3.getMinArgCount()); + assertEquals(5, fn3.getMaxArgCount()); + + assertFalse(fn3.isMatch(testName, 0)); + assertTrue(fn3.isMatch(testName, 1)); + assertTrue(fn3.isMatch(testName, 5)); + assertFalse(fn3.isMatch(testName, 6)); + assertFalse(fn3.isMatch(testName, Integer.MAX_VALUE)); + } + + + public void testEvaluateInvalidArgCount() throws Exception + { + try + { + new AbstractFunction<Object>("foo", "bar", 5).evaluate( + Arrays.asList(new String[] {"foo", "bar"})); + fail("invalid argument count"); + } + catch (XPathFunctionException e) + { + // success + } + } + + + public void testEvaluateNullArglist() throws Exception + { + MyMockFunction fn = new MyMockFunction("zippy"); + assertEquals("zippy", fn.evaluate(null)); + assertEquals(1, fn._initCalls); + assertEquals(0, fn._processStringCalls); + assertEquals(0, fn._processNumberCalls); + assertEquals(0, fn._processBooleanCalls); + assertEquals(0, fn._procesNodeCalls); + assertEquals(0, fn._procesNodeListCalls); + assertEquals(1, fn._getResultCalls); + } + + + public void testEvaluateNullArgument() throws Exception + { + List args = Arrays.asList(new Object[] {null}); + MyMockFunction fn = new MyMockFunction("zippy"); + try + { + fn.evaluate(Arrays.asList(args)); + fail("evaluated null argument"); + } + catch (XPathFunctionException e) + { + // success + } + } + + + public void testEvaluateUnsupportedArgumentType() throws Exception + { + List args = Arrays.asList(new Object[] {new Exception()}); + MyMockFunction fn = new MyMockFunction("zippy"); + try + { + fn.evaluate(args); + fail("evaluated unsupported argument type"); + } + catch (XPathFunctionException e) + { + // success + } + } + + + public void testEvaluateString() throws Exception + { + List args = Arrays.asList(new Object[] {"foo", "bar"}); + MyMockFunction fn = new MyMockFunction("zippy"); + + assertEquals("zippy", fn.evaluate(args)); + assertEquals(1, fn._initCalls); + assertEquals(2, fn._processStringCalls); + assertEquals(0, fn._processNumberCalls); + assertEquals(0, fn._processBooleanCalls); + assertEquals(0, fn._procesNodeCalls); + assertEquals(0, fn._procesNodeListCalls); + assertEquals(1, fn._getResultCalls); + } + + + public void testEvaluateNode() throws Exception + { + // since any JDK Node implementation is also a NodeList, we have to + // get tricky + Node node = (Node)Proxy.newProxyInstance( + Node.class.getClassLoader(), + new Class[] {Node.class}, + new InvocationHandler() + { + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable + { + return null; + } + }); + + List args = Arrays.asList(new Object[] {node}); + MyMockFunction fn = new MyMockFunction("zippy"); + + assertEquals("zippy", fn.evaluate(args)); + assertEquals(1, fn._initCalls); + assertEquals(0, fn._processStringCalls); + assertEquals(0, fn._processNumberCalls); + assertEquals(0, fn._processBooleanCalls); + assertEquals(1, fn._procesNodeCalls); + assertEquals(0, fn._procesNodeListCalls); + assertEquals(1, fn._getResultCalls); + } + + + public void testEvaluateNodeList() throws Exception + { + List args = Arrays.asList(new Object[] {DomUtil.newDocument("foo").getChildNodes()}); + MyMockFunction fn = new MyMockFunction("zippy"); + + assertEquals("zippy", fn.evaluate(args)); + assertEquals(1, fn._initCalls); + assertEquals(0, fn._processStringCalls); + assertEquals(0, fn._processNumberCalls); + assertEquals(0, fn._processBooleanCalls); + assertEquals(0, fn._procesNodeCalls); + assertEquals(1, fn._procesNodeListCalls); + assertEquals(1, fn._getResultCalls); + } + + + public void testDefaultNodeListBehavior() + throws Exception + { + final Element root = DomUtil.newDocument("foo"); + final Element child1 = DomUtil.appendChild(root, "bar"); + final Element child2 = DomUtil.appendChild(root, "baz"); + + new AbstractFunction<Object>("argle", "bargle") + { + @Override + protected Object processArg(int index, NodeList value, Object helper) + throws Exception + { + assertEquals(2, value.getLength()); + return super.processArg(index, value, helper); + } + + @Override + protected Object processArg(int index, Node value, Object helper) + throws Exception + { + assertSame(child1, value); + return null; + } + }.evaluate(Arrays.asList(new Object[] {root.getChildNodes()})); + } + + + public void testDefaultGetResultBehavior() + throws Exception + { + final Object helper = new Object(); + AbstractFunction<Object> fn = new AbstractFunction<Object>("argle", "bargle") + { + @Override + protected Object init() throws Exception + { + return helper; + } + }; + + assertSame(helper, fn.evaluate(Collections.<String>emptyList())); + } +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-17 03:36:46
|
Revision: 56 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=56&view=rev Author: kdgregory Date: 2008-12-17 03:36:43 +0000 (Wed, 17 Dec 2008) Log Message: ----------- add package xpath.function add Uppercase, Lowercase Added Paths: ----------- trunk/src/main/java/net/sf/practicalxml/xpath/function/ trunk/src/main/java/net/sf/practicalxml/xpath/function/Constants.java trunk/src/main/java/net/sf/practicalxml/xpath/function/Lowercase.java trunk/src/main/java/net/sf/practicalxml/xpath/function/Uppercase.java trunk/src/main/java/net/sf/practicalxml/xpath/function/package.html trunk/src/test/java/net/sf/practicalxml/xpath/function/ trunk/src/test/java/net/sf/practicalxml/xpath/function/TestLowercase.java trunk/src/test/java/net/sf/practicalxml/xpath/function/TestUppercase.java Added: trunk/src/main/java/net/sf/practicalxml/xpath/function/Constants.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/function/Constants.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/xpath/function/Constants.java 2008-12-17 03:36:43 UTC (rev 56) @@ -0,0 +1,12 @@ +package net.sf.practicalxml.xpath.function; + +/** + * Constants for use in this package. + */ +public class Constants +{ + /** + * All functions in this package use this as their namespace URI. + */ + public final static String COMMON_NS_URI = "http://practicalxml.sourceforge.net/"; +} Property changes on: trunk/src/main/java/net/sf/practicalxml/xpath/function/Constants.java ___________________________________________________________________ Added: svn:executable + * Added: trunk/src/main/java/net/sf/practicalxml/xpath/function/Lowercase.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/function/Lowercase.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/xpath/function/Lowercase.java 2008-12-17 03:36:43 UTC (rev 56) @@ -0,0 +1,44 @@ +package net.sf.practicalxml.xpath.function; + +import org.w3c.dom.Node; + +import net.sf.practicalxml.xpath.AbstractFunction; + + +/** + * Converts the string value of its argument — which must be + * either a literal string or a node/nodeset — to uppercase, + * using <code>java.lang.String.toUppercase()</code>. + */ +public class Lowercase +extends AbstractFunction<String> +{ + public Lowercase() + { + super(Constants.COMMON_NS_URI, "lowercase", 1); + } + + @Override + protected String processArg(int index, Node value, String helper) + throws Exception + { + return (value != null) + ? processArg(index, value.getTextContent(), helper) + : ""; + } + + @Override + protected String processArg(int index, String value, String helper) + throws Exception + { + return value.toLowerCase(); + } + + + @Override + protected String processNullArg(int index, String helper) + throws Exception + { + return ""; + } +} Property changes on: trunk/src/main/java/net/sf/practicalxml/xpath/function/Lowercase.java ___________________________________________________________________ Added: svn:executable + * Added: trunk/src/main/java/net/sf/practicalxml/xpath/function/Uppercase.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/function/Uppercase.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/xpath/function/Uppercase.java 2008-12-17 03:36:43 UTC (rev 56) @@ -0,0 +1,44 @@ +package net.sf.practicalxml.xpath.function; + +import org.w3c.dom.Node; + +import net.sf.practicalxml.xpath.AbstractFunction; + + +/** + * Converts the string value of its argument — which must be + * either a literal string or a node/nodeset — to uppercase, + * using <code>java.lang.String.toUppercase()</code>. + */ +public class Uppercase +extends AbstractFunction<String> +{ + public Uppercase() + { + super(Constants.COMMON_NS_URI, "uppercase", 1); + } + + @Override + protected String processArg(int index, Node value, String helper) + throws Exception + { + return (value != null) + ? processArg(index, value.getTextContent(), helper) + : ""; + } + + @Override + protected String processArg(int index, String value, String helper) + throws Exception + { + return value.toUpperCase(); + } + + + @Override + protected String processNullArg(int index, String helper) + throws Exception + { + return ""; + } +} Property changes on: trunk/src/main/java/net/sf/practicalxml/xpath/function/Uppercase.java ___________________________________________________________________ Added: svn:executable + * Added: trunk/src/main/java/net/sf/practicalxml/xpath/function/package.html =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/function/package.html (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/xpath/function/package.html 2008-12-17 03:36:43 UTC (rev 56) @@ -0,0 +1,7 @@ +<html> +<body> + This package contains reusable XPath functions, implemented using {@link + net.sf.practicalxml.xpath.AbstractFunction}. All such functions belong + to the namepspace "http://practicalxml.sourceforge.net/". +</body> +</html> \ No newline at end of file Property changes on: trunk/src/main/java/net/sf/practicalxml/xpath/function/package.html ___________________________________________________________________ Added: svn:executable + * Added: trunk/src/test/java/net/sf/practicalxml/xpath/function/TestLowercase.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/xpath/function/TestLowercase.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/xpath/function/TestLowercase.java 2008-12-17 03:36:43 UTC (rev 56) @@ -0,0 +1,80 @@ +package net.sf.practicalxml.xpath.function; + +import java.util.Arrays; +import java.util.Collections; + +import javax.xml.xpath.XPathFunctionException; + +import org.w3c.dom.Element; + +import net.sf.practicalxml.AbstractTestCase; +import net.sf.practicalxml.DomUtil; + + +public class TestLowercase +extends AbstractTestCase +{ + public void testConstruction() throws Exception + { + Lowercase fn = new Lowercase(); + assertEquals(Constants.COMMON_NS_URI, fn.getNamespaceUri()); + assertEquals("lowercase", fn.getName()); + assertEquals(1, fn.getMinArgCount()); + assertEquals(1, fn.getMaxArgCount()); + } + + public void testLiteralString() throws Exception + { + assertEquals( + "test", + new Lowercase().evaluate(Arrays.asList("Test"))); + } + + + public void testNodeList() throws Exception + { + Element root = DomUtil.newDocument("foo"); + Element child1 = DomUtil.appendChild(root, "bar"); + Element child2 = DomUtil.appendChild(root, "baz"); + DomUtil.setText(root, "Test"); + DomUtil.setText(child1, "Test2"); + DomUtil.setText(child2, "Test3"); + + assertEquals( + "test2", + new Lowercase().evaluate(Arrays.asList(root.getChildNodes()))); + } + + + public void testEmptyNodeList() throws Exception + { + Element root = DomUtil.newDocument("foo"); + + assertEquals( + "", + new Lowercase().evaluate(Arrays.asList(root.getChildNodes()))); + } + + + public void testNull() throws Exception + { + assertEquals( + "", + new Lowercase().evaluate(Arrays.asList((String)null))); + } + + + public void testEmptyArglist() throws Exception + { + try + { + new Lowercase().evaluate(Collections.<String>emptyList()); + fail("didn't throw on empty list"); + } + catch (XPathFunctionException e) + { + // success + } + } + +} Property changes on: trunk/src/test/java/net/sf/practicalxml/xpath/function/TestLowercase.java ___________________________________________________________________ Added: svn:executable + * Added: trunk/src/test/java/net/sf/practicalxml/xpath/function/TestUppercase.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/xpath/function/TestUppercase.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/xpath/function/TestUppercase.java 2008-12-17 03:36:43 UTC (rev 56) @@ -0,0 +1,80 @@ +package net.sf.practicalxml.xpath.function; + +import java.util.Arrays; +import java.util.Collections; + +import javax.xml.xpath.XPathFunctionException; + +import org.w3c.dom.Element; + +import net.sf.practicalxml.AbstractTestCase; +import net.sf.practicalxml.DomUtil; + + +public class TestUppercase +extends AbstractTestCase +{ + public void testConstruction() throws Exception + { + Uppercase fn = new Uppercase(); + assertEquals(Constants.COMMON_NS_URI, fn.getNamespaceUri()); + assertEquals("uppercase", fn.getName()); + assertEquals(1, fn.getMinArgCount()); + assertEquals(1, fn.getMaxArgCount()); + } + + public void testLiteralString() throws Exception + { + assertEquals( + "TEST", + new Uppercase().evaluate(Arrays.asList("Test"))); + } + + + public void testNodeList() throws Exception + { + Element root = DomUtil.newDocument("foo"); + Element child1 = DomUtil.appendChild(root, "bar"); + Element child2 = DomUtil.appendChild(root, "baz"); + DomUtil.setText(root, "Test"); + DomUtil.setText(child1, "Test2"); + DomUtil.setText(child2, "Test3"); + + assertEquals( + "TEST2", + new Uppercase().evaluate(Arrays.asList(root.getChildNodes()))); + } + + + public void testEmptyNodeList() throws Exception + { + Element root = DomUtil.newDocument("foo"); + + assertEquals( + "", + new Lowercase().evaluate(Arrays.asList(root.getChildNodes()))); + } + + + public void testNull() throws Exception + { + assertEquals( + "", + new Uppercase().evaluate(Arrays.asList((String)null))); + } + + + public void testEmptyArglist() throws Exception + { + try + { + new Uppercase().evaluate(Collections.<String>emptyList()); + fail("didn't throw on empty list"); + } + catch (XPathFunctionException e) + { + // success + } + } + +} Property changes on: trunk/src/test/java/net/sf/practicalxml/xpath/function/TestUppercase.java ___________________________________________________________________ Added: svn:executable + * This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-17 14:03:23
|
Revision: 57 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=57&view=rev Author: kdgregory Date: 2008-12-17 14:03:16 +0000 (Wed, 17 Dec 2008) Log Message: ----------- AbstractFunction: implement Comparable (will support FunctionResolver) Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java Modified: trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java 2008-12-17 03:36:43 UTC (rev 56) +++ trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java 2008-12-17 14:03:16 UTC (rev 57) @@ -42,7 +42,7 @@ * element in the list. */ public class AbstractFunction<T> -implements XPathFunction +implements XPathFunction, Comparable<AbstractFunction<?>> { /** * Qualified name of this function. @@ -156,7 +156,47 @@ && (arity <= _maxArgCount); } +//---------------------------------------------------------------------------- +// Implementation of Comparable +//---------------------------------------------------------------------------- + /** + * Instances of this class implement <code>Comparable</code> in order to + * support a function resolver picking the most appropriate instance for + * a particular invocation. Comparison starts with namespace and name, + * using <code>String.compareTo()</code>. If two instances have the same + * namespace and name, they are compared based on the number of arguments + * they accept: + * <ul> + * <li> Instances that accept fewer arguments are considered less-than + * instances that accept more. + * <li> If two instances accept the same number of arguments, the instance + * with the lower minimum argument count is less-than that with the + * higher count. + * </ul> + */ + public int compareTo(AbstractFunction<?> that) + { + int result = this._qname.getNamespaceURI().compareTo(that._qname.getNamespaceURI()); + if (result != 0) + return (result < 0) ? -1 : 1; + + result = this._qname.getLocalPart().compareTo(that._qname.getLocalPart()); + if (result != 0) + return (result < 0) ? -1 : 1; + + result = (this._maxArgCount - this._minArgCount) - (that._maxArgCount - that._minArgCount); + if (result != 0) + return (result < 0) ? -1 : 1; + + result = this._minArgCount - that._minArgCount; + if (result != 0) + return (result < 0) ? -1 : 1; + + return result; + } + + //---------------------------------------------------------------------------- // Implementation of XPathFunction //---------------------------------------------------------------------------- Modified: trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java 2008-12-17 03:36:43 UTC (rev 56) +++ trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java 2008-12-17 14:03:16 UTC (rev 57) @@ -317,4 +317,46 @@ assertSame(helper, fn.evaluate(Collections.<String>emptyList())); } + + + public void testComparable() + throws Exception + { + AbstractFunction<Object> fn1 = new AbstractFunction<Object>("foo", "bar"); + AbstractFunction<Object> fn2 = new AbstractFunction<Object>("foo", "bar", 1, 5); + AbstractFunction<Object> fn3 = new AbstractFunction<Object>("foo", "bar", 2, 5); + AbstractFunction<Object> fn4 = new AbstractFunction<Object>("foo", "bar", 1, 4); + AbstractFunction<Object> fn5 = new AbstractFunction<Object>("foo", "bar", 3); + AbstractFunction<Object> fn6 = new AbstractFunction<Object>("foo", "baz", 3); + AbstractFunction<Object> fn7 = new AbstractFunction<Object>("fzz", "bar", 3); + + // always equal to self + assertEquals(0, fn1.compareTo(fn1)); + assertEquals(0, fn2.compareTo(fn2)); + assertEquals(0, fn3.compareTo(fn3)); + assertEquals(0, fn4.compareTo(fn4)); + assertEquals(0, fn5.compareTo(fn5)); + assertEquals(0, fn6.compareTo(fn6)); + assertEquals(0, fn7.compareTo(fn7)); + + // differing name/namespace + assertEquals(-1, fn5.compareTo(fn7)); + assertEquals(1, fn7.compareTo(fn5)); + assertEquals(-1, fn5.compareTo(fn6)); + assertEquals(1, fn6.compareTo(fn5)); + assertEquals(-1, fn1.compareTo(fn6)); + assertEquals(1, fn6.compareTo(fn1)); + + // differing number of arguments + assertEquals(1, fn1.compareTo(fn2)); + assertEquals(-1, fn2.compareTo(fn1)); + assertEquals(1, fn2.compareTo(fn3)); + assertEquals(-1, fn3.compareTo(fn2)); + assertEquals(1, fn3.compareTo(fn5)); + assertEquals(-1, fn5.compareTo(fn3)); + + // same number of arguments, differing range + assertEquals(1, fn3.compareTo(fn4)); + assertEquals(-1, fn4.compareTo(fn3)); + } } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-20 11:29:20
|
Revision: 58 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=58&view=rev Author: kdgregory Date: 2008-12-20 11:29:17 +0000 (Sat, 20 Dec 2008) Log Message: ----------- add FunctionResolver update XPathWrapper to use it Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java Added Paths: ----------- trunk/src/main/java/net/sf/practicalxml/xpath/FunctionResolver.java trunk/src/test/java/net/sf/practicalxml/xpath/TestFunctionResolver.java Modified: trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java 2008-12-17 14:03:16 UTC (rev 57) +++ trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java 2008-12-20 11:29:17 UTC (rev 58) @@ -11,7 +11,6 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFunction; -import javax.xml.xpath.XPathFunctionResolver; import javax.xml.xpath.XPathVariableResolver; import org.w3c.dom.Document; @@ -19,6 +18,8 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import net.sf.practicalxml.xpath.AbstractFunction; +import net.sf.practicalxml.xpath.FunctionResolver; import net.sf.practicalxml.xpath.NamespaceResolver; @@ -34,7 +35,7 @@ private final String _expr; private final NamespaceResolver _nsResolver = new NamespaceResolver(); private Map<QName,Object> _variables = new HashMap<QName,Object>(); - private Map<QName,XPathFunction> _functions = new HashMap<QName,XPathFunction>(); + private FunctionResolver _functions = new FunctionResolver(); private XPathExpression _compiled; @@ -129,22 +130,127 @@ /** - * Binds a function to this expression, replacing any previous binding - * for the specified name. Note that <code>XPathFunctionResolver</code> - * can support multiple functions with the same name; we don't. + * Binds an {@link net.sf.practicalxml.xpath.AbstractFunction} to this + * expression. Subsequent calls to this method with the same name will + * silently replace the binding. * <p> * Per the JDK documentation, user-defined functions must occupy their - * own namespace. You will need to add a namespace binding along with - * your function binding. + * own namespace. You must bind a namespace for this function, and use + * the bound prefix to reference it in your expression. Alternatively, + * you can call the variant of <code>bindFunction()</code> that binds + * a prefix to the function's namespace. * - * @param name The fully-qualified name for this binding. + * @param func The function. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindFunction(AbstractFunction func) + { + _functions.addFunction(func); + return this; + } + + + /** + * Binds an {@link net.sf.practicalxml.xpath.AbstractFunction} to this + * expression, along with the prefix used to access that function. + * Subsequent calls to this method with the same name will silently + * replace the binding. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. This method will retrieve the namespace from the + * function, and bind it to the passed prefix. + * + * @param func The function. + * @param prefix The prefix to bind to this function's namespace. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindFunction(AbstractFunction func, String prefix) + { + _functions.addFunction(func); + return bindNamespace(prefix, func.getNamespaceUri()); + } + + + /** + * Binds a standard <code>XPathFunction</code> to this expression, + * handling any number of arguments. Subsequent calls to this method + * with the same name will silently replace the binding. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. If the qualified name that you pass to this method + * includes a prefix, the associated namespace will be bound to that + * prefix. If not, you must bind the namespace explicitly. In either + * case, you must refer to the function in your expression using a + * bound prefix. + * + * @param name The qualified name for this function. Must contain + * a name and namespace, may contain a prefix. * @param func The function to bind to this name. * * @return The wrapper, so that calls may be chained. */ public XPathWrapper bindFunction(QName name, XPathFunction func) { - _functions.put(name, func); + return bindFunction(name, func, 0, Integer.MAX_VALUE); + } + + + /** + * Binds a standard <code>XPathFunction</code> to this expression, + * handling a specific number of arguments. Subsequent calls to this + * method with the same name and arity will silently replace the binding. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. If the qualified name that you pass to this method + * includes a prefix, the associated namespace will be bound to that + * prefix. If not, you must bind the namespace explicitly. In either + * case, you must refer to the function in your expression using a + * bound prefix. + * + * @param name The qualified name for this function. Must contain + * a name and namespace, may contain a prefix. + * @param func The function to bind to this name. + * @param arity The number of arguments accepted by this function. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindFunction(QName name, XPathFunction func, int arity) + { + return bindFunction(name, func, arity, arity); + } + + + /** + * Binds a standard <code>XPathFunction</code> to this expression, + * handling a specific range of arguments. Subsequent calls to this + * method with the same name and range of arguments will silently + * replace the binding. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. If the qualified name that you pass to this method + * includes a prefix, the associated namespace will be bound to that + * prefix. If not, you must bind the namespace explicitly. In either + * case, you must refer to the function in your expression using a + * bound prefix. + * + * @param name The qualified name for this function. Must contain + * a name and namespace, may contain a prefix. + * @param func The function to bind to this name. + * @param minArity The minimum number of arguments accepted by this + * function. + * @param maxArity The maximum number of arguments accepted by this + * function. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindFunction(QName name, XPathFunction func, + int minArity, int maxArity) + { + _functions.addFunction(func, name, minArity, maxArity); + if (!"".equals(name.getPrefix())) + bindNamespace(name.getPrefix(), name.getNamespaceURI()); return this; } @@ -335,7 +441,7 @@ XPath xpath = XPathFactory.newInstance().newXPath(); xpath.setNamespaceContext(_nsResolver); xpath.setXPathVariableResolver(new MyVariableResolver()); - xpath.setXPathFunctionResolver(new MyFunctionResolver()); + xpath.setXPathFunctionResolver(_functions); _compiled = xpath.compile(_expr); } catch (XPathExpressionException ee) @@ -356,18 +462,4 @@ return _variables.get(name); } } - - - /** - * Resolver for function references. Note that we ignore the "arity" - * argument, meaning that there's a single binding per name. - */ - private class MyFunctionResolver - implements XPathFunctionResolver - { - public XPathFunction resolveFunction(QName name, int arity) - { - return _functions.get(name); - } - } } Modified: trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java 2008-12-17 14:03:16 UTC (rev 57) +++ trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java 2008-12-20 11:29:17 UTC (rev 58) @@ -67,9 +67,7 @@ */ protected AbstractFunction(String nsUri, String localName) { - _qname = new QName(nsUri, localName); - _minArgCount = 0; - _maxArgCount = Integer.MAX_VALUE; + this(nsUri, localName, 0, Integer.MAX_VALUE); } @@ -78,9 +76,7 @@ */ protected AbstractFunction(String nsUri, String localName, int numArgs) { - _qname = new QName(nsUri, localName); - _minArgCount = numArgs; - _maxArgCount = numArgs; + this(nsUri, localName, numArgs, numArgs); } @@ -89,7 +85,16 @@ */ protected AbstractFunction(String nsUri, String localName, int minArgs, int maxArgs) { - _qname = new QName(nsUri, localName); + this(new QName(nsUri, localName), minArgs, maxArgs); + } + + + /** + * Base constructor. + */ + protected AbstractFunction(QName qname, int minArgs, int maxArgs) + { + _qname = qname; _minArgCount = minArgs; _maxArgCount = maxArgs; } @@ -152,12 +157,25 @@ public boolean isMatch(QName qname, int arity) { return _qname.equals(qname) - && (arity >= _minArgCount) - && (arity <= _maxArgCount); + && (arity >= _minArgCount) + && (arity <= _maxArgCount); } + + /** + * Determines whether this function can handle the specified number of + * arguments. This is used by {@link FunctionResolver}, which already + * knows that the QName matches. + */ + public boolean isArityMatch(int arity) + { + return (arity >= _minArgCount) + && (arity <= _maxArgCount); + } + + //---------------------------------------------------------------------------- -// Implementation of Comparable +// Implementation of Comparable, Object overrides //---------------------------------------------------------------------------- /** @@ -197,6 +215,40 @@ } + /** + * Two instances are considered equal if they have the same qualified + * name and accept the same range of arguments. This supports use with + * the collections framework, which is the only place that you should + * be checking equality. + */ + @Override + public final boolean equals(Object obj) + { + if (this == obj) + return true; + else if (obj instanceof AbstractFunction<?>) + { + AbstractFunction<?> that = (AbstractFunction<?>)obj; + return this._qname.equals(that._qname) + && this._minArgCount == that._minArgCount + && this._maxArgCount == that._maxArgCount; + } + else + return false; + } + + + /** + * Hashcode is based on the qualified name, does not consider argument + * count. + */ + @Override + public final int hashCode() + { + return _qname.hashCode(); + } + + //---------------------------------------------------------------------------- // Implementation of XPathFunction //---------------------------------------------------------------------------- @@ -376,6 +428,22 @@ * Processes a <code>null</code> argument — it's unclear whether * this can ever happen. The default implementation throws <code> * IllegalArgumentException</code>. + + + @Override + public boolean equals(Object obj) + { + // FIXME - implement + return super.equals(obj); + } + + + @Override + public int hashCode() + { + // FIXME - implement + return super.hashCode(); + } * * @param index Index of the argument, numbered from 0. * @param helper Helper object to preserve intermediate results. Added: trunk/src/main/java/net/sf/practicalxml/xpath/FunctionResolver.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/FunctionResolver.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/xpath/FunctionResolver.java 2008-12-20 11:29:17 UTC (rev 58) @@ -0,0 +1,266 @@ +package net.sf.practicalxml.xpath; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; + +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; +import javax.xml.xpath.XPathFunctionResolver; + + +/** + * An <code>XPathFunctionResolver</code> designed for use with subclasses of + * {@link AbstractFunction} (but will work with any <code>XPathFunction</code>). + * Functions are resolved based on name, namespace, and arity. Where multiple + * eligible functions have the same name and namespace, the comparison rules of + * <code>AbstractFunction</code> are used to pick the most appropriate function: + * smaller range of accepted arguments first, followed by lower minimum argument + * count. + * <p> + * This object follows the builder pattern: <code>addFunction()</code> returns + * the object itself, so that calls may be chained. + */ +public class FunctionResolver +implements XPathFunctionResolver +{ + private Map<QName,FunctionHolder> _table = new HashMap<QName,FunctionHolder>(); + +//---------------------------------------------------------------------------- +// Public Methods +//---------------------------------------------------------------------------- + + /** + * Adds a subclass of {@link AbstractFunction} to this resolver. This + * function provides its own information about name, namespace, and + * supported number of arguments. + * <p> + * If the same function has already been added, will silently replace it. + * + * @return The resolver, so that calls may be chained. + */ + public FunctionResolver addFunction(AbstractFunction<?> func) + { + FunctionHolder holder = _table.get(func.getQName()); + if (holder == null) + { + holder = new FunctionHolder(func); + _table.put(func.getQName(), holder); + } + else + { + holder.put(func); + } + return this; + } + + + /** + * Adds a normal <code>XPathFunction</code> to this resolver, without + * specifying argument count. This function will be chosen for any number + * of arguments, provided that there is not a more-specific binding). + * <p> + * If a function has already been added with the same name and argument + * range, this call will silently replace it. + * <p> + * Note: the resolver wraps the passed function with a decorator that + * allows it to be managed. The resolver will return this decorator + * object rather than the original function. + * + * @return The resolver, so that calls may be chained. + */ + public FunctionResolver addFunction(XPathFunction func, QName name) + { + return addFunction(func, name, 0, Integer.MAX_VALUE); + } + + + /** + * Adds a normal <code>XPathFunction</code> to this resolver. Specifies + * the exact number of arguments for which this function will be chosen. + * <p> + * If a function has already been added with the same name and argument + * range, this call will silently replace it. + * <p> + * Note: the resolver wraps the passed function with a decorator that + * allows it to be managed. The resolver will return this decorator + * object rather than the original function. + * + * @return The resolver, so that calls may be chained. + */ + public FunctionResolver addFunction(XPathFunction func, QName name, int argCount) + { + return addFunction(func, name, argCount, argCount); + } + + + /** + * Adds a normal <code>XPathFunction</code> to this resolver. Specifies + * the minimum and maximum number of arguments for which this function + * will be chosen. + * <p> + * If a function has already been added with the same name and argument + * range, this call will silently replace it. + * <p> + * Note: the resolver wraps the passed function with a decorator that + * allows it to be managed. The resolver will return this decorator + * object rather than the original function. + * + * @return The resolver, so that calls may be chained. + */ + public FunctionResolver addFunction(XPathFunction func, QName name, int minArgCount, int maxArgCount) + { + return addFunction(new StandardFunctionAdapter(func, name, minArgCount, maxArgCount)); + } + + +//---------------------------------------------------------------------------- +// XPathFunctionResolver +//---------------------------------------------------------------------------- + + /** + * Picks the "most eligble" function of those bound to this resolver. + * If unable to find any function that matches, returns <code>null</code>. + */ + public XPathFunction resolveFunction(QName functionName, int arity) + { + FunctionHolder holder = _table.get(functionName); + return (holder != null) ? holder.get(arity) + : null; + } + + +//---------------------------------------------------------------------------- +// Object overrides +//---------------------------------------------------------------------------- + + /** + * Two instances are equal if they have the same number of functions, + * and each has a counterpart with the same QName and arity range (but + * not the same implementation). + */ + @Override + public final boolean equals(Object obj) + { + if (this == obj) + return true; + else if (obj instanceof FunctionResolver) + return this._table.equals(((FunctionResolver)obj)._table); + else + return false; + } + + + @Override + public final int hashCode() + { + // I suspect this is a very inefficient way to compute hashcode, + // but this object should never be stored in a hashing structure + // -- if you need to do that, come up with a better implementation + return _table.keySet().hashCode(); + } + + +//---------------------------------------------------------------------------- +// Internals +//---------------------------------------------------------------------------- + + /** + * Holder for AbstractFunction instances. Can deal with 1 or N, and + * pick the appropriate one for a particular evaluation. + */ + private static class FunctionHolder + { + private AbstractFunction<?> _onlyOne; + private TreeSet<AbstractFunction<?>> _hasMany; + + public FunctionHolder(AbstractFunction<?> initial) + { + _onlyOne = initial; + } + + public void put(AbstractFunction<?> func) + { + if (_hasMany != null) + { + // remove old implementation if it exists + _hasMany.remove(func); + _hasMany.add(func); + } + else if (_onlyOne.equals(func)) + { + _onlyOne = func; + } + else + { + _hasMany = new TreeSet<AbstractFunction<?>>(); + _hasMany.add(func); + _hasMany.add(_onlyOne); + _onlyOne = null; + } + } + + public AbstractFunction<?> get(int arity) + { + if (_onlyOne != null) + { + return (_onlyOne.isArityMatch(arity)) + ? _onlyOne + : null; + } + + for (AbstractFunction<?> func : _hasMany) + { + if (func.isArityMatch(arity)) + return func; + } + + return null; + } + + @Override + public boolean equals(Object obj) + { + // if this ever fails, someone needs to fix their code + FunctionHolder that = (FunctionHolder)obj; + + return (_onlyOne != null) + ? this._onlyOne.equals(that._onlyOne) + : this._hasMany.equals(that._hasMany); + } + + @Override + public int hashCode() + { + // I know this will never be stored as the key of a hash-table, + // but I want to keep static analysis tools quiet + return 0; + } + } + + + /** + * Adapter for standard XPathFunction instances, allowing them to be + * managed by this resolver. + */ + private static class StandardFunctionAdapter + extends AbstractFunction<Object> + { + final XPathFunction _func; + + public StandardFunctionAdapter(XPathFunction func, QName name, int minArgCount, int maxArgCount) + { + super(name, minArgCount, maxArgCount); + _func = func; + } + + @Override + public Object evaluate(List args) + throws XPathFunctionException + { + return _func.evaluate(args); + } + } +} Modified: trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java 2008-12-17 14:03:16 UTC (rev 57) +++ trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java 2008-12-20 11:29:17 UTC (rev 58) @@ -5,6 +5,8 @@ import javax.xml.xpath.XPathFunction; import javax.xml.xpath.XPathFunctionException; +import net.sf.practicalxml.xpath.AbstractFunction; + import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -50,7 +52,42 @@ // Support Code //---------------------------------------------------------------------------- + /** + * A standard XPath function implementation that returns the namespace + * of the first selected node. + */ + private static class MyStandardFunction + implements XPathFunction + { + public Object evaluate(List args) + throws XPathFunctionException + { + NodeList arg = (NodeList)args.get(0); + return arg.item(0).getNamespaceURI(); + } + } + + /** + * An <code>AbstractFunction</code> implementation that returns the + * namespace of the first selected node. + */ + private static class MyAbstractFunction + extends AbstractFunction<String> + { + public MyAbstractFunction(String nsUri, String name) + { + super(nsUri, name); + } + + @Override + protected String processArg(int index, Node value, String helper) + throws Exception + { + return value.getNamespaceURI(); + } + } + //---------------------------------------------------------------------------- // Test Cases //---------------------------------------------------------------------------- @@ -158,31 +195,62 @@ } - public void testFunctions() throws Exception + public void testAbstractFunctions() throws Exception { - _child1.setAttribute("bar", "baz"); - _child2.setAttribute("bar", "bargle"); + XPathWrapper xpath1 = new XPathWrapper("ns:myfunc(.)") + .bindNamespace("ns", NS1) + .bindFunction(new MyAbstractFunction(NS1, "myfunc")); - XPathFunction myfunc = new XPathFunction() { - public Object evaluate(List args) - throws XPathFunctionException - { - NodeList arg = (NodeList)args.get(0); - Element asElem = (Element)arg.item(0); - return asElem.getAttribute("bar"); - } - }; + assertEquals("", xpath1.evaluateAsString(_child1)); + assertEquals(NS1, xpath1.evaluateAsString(_child2)); - XPathWrapper xpath = new XPathWrapper("ns:myfunc(.)") - .bindNamespace("ns", NS1) - .bindFunction(new QName(NS1, "myfunc"), myfunc); + XPathWrapper xpath2 = new XPathWrapper("ns:myfunc(.)") + .bindFunction(new MyAbstractFunction(NS1, "myfunc"), "ns"); - assertEquals("baz", xpath.evaluateAsString(_child1)); - assertEquals("bargle", xpath.evaluateAsString(_child2)); + assertEquals("", xpath2.evaluateAsString(_child1)); + assertEquals(NS1, xpath2.evaluateAsString(_child2)); } + public void testStandardFunctions() throws Exception + { + XPathWrapper xpath1 = new XPathWrapper("ns:myfunc(.)") + .bindFunction(new QName(NS1, "myfunc", "ns"), + new MyStandardFunction()); + assertEquals("", xpath1.evaluateAsString(_child1)); + assertEquals(NS1, xpath1.evaluateAsString(_child2)); + + XPathWrapper xpath2 = new XPathWrapper("ns:myfunc(.,.)") + .bindFunction(new QName(NS2, "myfunc", "ns"), + new MyStandardFunction(), + 2); + + assertEquals("", xpath2.evaluateAsString(_child1)); + assertEquals(NS1, xpath2.evaluateAsString(_child2)); + } + + + public void testUnresolvableFunction() throws Exception + { + // we call with two arguments, it only gets resolved for one + XPathWrapper xpath1 = new XPathWrapper("ns:myfunc(.,.)") + .bindFunction(new QName(NS2, "myfunc", "ns"), + new MyStandardFunction(), + 1); + try + { + xpath1.evaluateAsString(_child1); + fail("didn't throw even though arity was wrong"); + } + catch (XmlException ee) + { + // success + } + } + + + public void testEqualsAndHashCode() throws Exception { Object obj1a = new XPathWrapper("//foo"); Modified: trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java 2008-12-17 14:03:16 UTC (rev 57) +++ trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java 2008-12-20 11:29:17 UTC (rev 58) @@ -359,4 +359,31 @@ assertEquals(1, fn3.compareTo(fn4)); assertEquals(-1, fn4.compareTo(fn3)); } + + public void testEqualsAndHashcode() throws Exception + { + AbstractFunction<Object> fn1 = new AbstractFunction<Object>("foo", "bar", 1, 4); + AbstractFunction<Object> fn2 = new AbstractFunction<Object>("foo", "bar", 3); + AbstractFunction<Object> fn3 = new AbstractFunction<Object>("foo", "bar", 3); + AbstractFunction<Object> fn4 = new AbstractFunction<Object>("foo", "baz", 3); + AbstractFunction<Object> fn5 = new AbstractFunction<Object>("fzz", "bar", 3); + + assertFalse(fn1.equals(fn2)); + assertFalse(fn2.equals(fn1)); + + assertTrue(fn2.equals(fn3)); + assertTrue(fn3.equals(fn2)); + + assertFalse(fn2.equals(fn4)); + assertFalse(fn4.equals(fn2)); + + assertFalse(fn2.equals(fn5)); + assertFalse(fn5.equals(fn2)); + + assertEquals(fn1.hashCode(), fn2.hashCode()); + assertEquals(fn2.hashCode(), fn3.hashCode()); + + // as of JDK 1.5, this is known to be true; it shouldn't change + assertTrue(fn1.hashCode() != fn4.hashCode()); + } } Added: trunk/src/test/java/net/sf/practicalxml/xpath/TestFunctionResolver.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/xpath/TestFunctionResolver.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/xpath/TestFunctionResolver.java 2008-12-20 11:29:17 UTC (rev 58) @@ -0,0 +1,227 @@ +package net.sf.practicalxml.xpath; + +import java.util.Collections; +import java.util.List; + +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; + +import net.sf.practicalxml.AbstractTestCase; + + +public class TestFunctionResolver +extends AbstractTestCase +{ +//---------------------------------------------------------------------------- +// Support Code +//---------------------------------------------------------------------------- + + private static class MockStandardFunction + implements XPathFunction + { + public int _calls; + + public Object evaluate(List args) throws XPathFunctionException + { + _calls++; + return null; + } + } + + +//---------------------------------------------------------------------------- +// Test Cases +//---------------------------------------------------------------------------- + + public void testSingleAbstractFunctionMaxRange() throws Exception + { + AbstractFunction<Object> fn = new AbstractFunction<Object>("foo", "bar"); + + FunctionResolver resolver = new FunctionResolver(); + assertSame(resolver, resolver.addFunction(fn)); + + assertSame(fn, resolver.resolveFunction(new QName("foo", "bar"), 0)); + assertSame(fn, resolver.resolveFunction(new QName("foo", "bar"), 1)); + assertSame(fn, resolver.resolveFunction(new QName("foo", "bar"), 10)); + } + + + public void testMultipleAbstractFunctionMaxRange() throws Exception + { + AbstractFunction<Object> fn1 = new AbstractFunction<Object>("foo", "bar"); + AbstractFunction<Object> fn2 = new AbstractFunction<Object>("foo", "baz"); + + FunctionResolver resolver = new FunctionResolver() + .addFunction(fn1) + .addFunction(fn2); + + assertSame(fn1, resolver.resolveFunction(new QName("foo", "bar"), 1)); + assertSame(fn2, resolver.resolveFunction(new QName("foo", "baz"), 1)); + } + + + public void testSingleAbstractFunctionMultiRange() throws Exception + { + AbstractFunction<Object> fn1 = new AbstractFunction<Object>("foo", "bar"); + AbstractFunction<Object> fn2 = new AbstractFunction<Object>("foo", "bar", 1); + AbstractFunction<Object> fn3 = new AbstractFunction<Object>("foo", "bar", 2, 5); + + FunctionResolver resolver = new FunctionResolver() + .addFunction(fn1) + .addFunction(fn2) + .addFunction(fn3); + + assertSame(fn1, resolver.resolveFunction(new QName("foo", "bar"), 0)); + assertSame(fn2, resolver.resolveFunction(new QName("foo", "bar"), 1)); + assertSame(fn3, resolver.resolveFunction(new QName("foo", "bar"), 2)); + assertSame(fn3, resolver.resolveFunction(new QName("foo", "bar"), 5)); + assertSame(fn1, resolver.resolveFunction(new QName("foo", "bar"), 6)); + } + + + public void testOverwriteOnAdd() throws Exception + { + AbstractFunction<Object> fn1 = new AbstractFunction<Object>("foo", "bar", 1); + AbstractFunction<Object> fn2 = new AbstractFunction<Object>("foo", "bar", 1); + AbstractFunction<Object> fn3 = new AbstractFunction<Object>("foo", "bar", 1); + + FunctionResolver resolver = new FunctionResolver() + .addFunction(fn1) + .addFunction(fn2); + + assertSame(fn2, resolver.resolveFunction(new QName("foo", "bar"), 1)); + + resolver.addFunction(fn3); + assertSame(fn3, resolver.resolveFunction(new QName("foo", "bar"), 1)); + } + + + public void testSingleStandardFunctionMaxRange() throws Exception + { + MockStandardFunction xfn = new MockStandardFunction(); + + FunctionResolver resolver = new FunctionResolver() + .addFunction(xfn, new QName("foo", "bar")); + + XPathFunction ret1 = resolver.resolveFunction(new QName("foo", "bar"), 0); + XPathFunction ret2 = resolver.resolveFunction(new QName("foo", "bar"), 0); + XPathFunction ret3 = resolver.resolveFunction(new QName("foo", "bar"), 0); + + assertNotNull(ret1); + assertSame(ret1, ret2); + assertSame(ret1, ret3); + + ret1.evaluate(Collections.EMPTY_LIST); + assertEquals(1, xfn._calls); + } + + + public void testSingleStandardFunctionMultiRange() throws Exception + { + MockStandardFunction xfn1 = new MockStandardFunction(); + MockStandardFunction xfn2 = new MockStandardFunction(); + MockStandardFunction xfn3 = new MockStandardFunction(); + + FunctionResolver resolver = new FunctionResolver() + .addFunction(xfn1, new QName("foo", "bar")) + .addFunction(xfn2, new QName("foo", "bar"), 1) + .addFunction(xfn3, new QName("foo", "bar"), 2, 5); + + resolver.resolveFunction(new QName("foo", "bar"), 0).evaluate(Collections.EMPTY_LIST); + assertEquals(1, xfn1._calls); + assertEquals(0, xfn2._calls); + assertEquals(0, xfn3._calls); + + resolver.resolveFunction(new QName("foo", "bar"), 1).evaluate(Collections.EMPTY_LIST); + assertEquals(1, xfn1._calls); + assertEquals(1, xfn2._calls); + assertEquals(0, xfn3._calls); + + resolver.resolveFunction(new QName("foo", "bar"), 2).evaluate(Collections.EMPTY_LIST); + assertEquals(1, xfn1._calls); + assertEquals(1, xfn2._calls); + assertEquals(1, xfn3._calls); + + resolver.resolveFunction(new QName("foo", "bar"), 5).evaluate(Collections.EMPTY_LIST); + assertEquals(1, xfn1._calls); + assertEquals(1, xfn2._calls); + assertEquals(2, xfn3._calls); + + resolver.resolveFunction(new QName("foo", "bar"), 6).evaluate(Collections.EMPTY_LIST); + assertEquals(2, xfn1._calls); + assertEquals(1, xfn2._calls); + assertEquals(2, xfn3._calls); + } + + + public void testUnresolvedFunctions() throws Exception + { + FunctionResolver resolver = new FunctionResolver() + .addFunction(new AbstractFunction<Object>("foo", "bar", 1)) + .addFunction(new AbstractFunction<Object>("fizz", "buzz", 1)) + .addFunction(new AbstractFunction<Object>("fizz", "buzz", 2)); + + assertNull(resolver.resolveFunction(new QName("f", "b"), 1)); + assertNull(resolver.resolveFunction(new QName("foo", "bar"), 2)); + assertNull(resolver.resolveFunction(new QName("fizz", "buzz"), 3)); + } + + + public void testEqualsAndHashcode() throws Exception + { + AbstractFunction<Object> fn1 = new AbstractFunction<Object>("foo", "bar", 1); + AbstractFunction<Object> fn2 = new AbstractFunction<Object>("foo", "bar", 1, 5); + AbstractFunction<Object> fn3 = new AbstractFunction<Object>("foo", "baz"); + + FunctionResolver resolv1 = new FunctionResolver() + .addFunction(fn1); + FunctionResolver resolv2 = new FunctionResolver() + .addFunction(fn1); + FunctionResolver resolv3 = new FunctionResolver() + .addFunction(fn2); + FunctionResolver resolv4 = new FunctionResolver() + .addFunction(fn1) + .addFunction(fn2); + FunctionResolver resolv5 = new FunctionResolver() + .addFunction(fn1) + .addFunction(fn2); + FunctionResolver resolv6 = new FunctionResolver() + .addFunction(fn1) + .addFunction(fn2) + .addFunction(fn3); + FunctionResolver resolv7 = new FunctionResolver() + .addFunction(fn1) + .addFunction(fn2) + .addFunction(fn3); + + // self and bogus + assertTrue(resolv1.equals(resolv1)); + assertFalse(resolv1.equals(new Object())); + assertFalse(resolv1.equals(null)); + + // single definition + assertTrue(resolv1.equals(resolv2)); + assertTrue(resolv2.equals(resolv1)); + + // multiple definitions, same QName + assertTrue(resolv4.equals(resolv5)); + assertTrue(resolv5.equals(resolv4)); + + // multiple definitions, multiple QNames + assertTrue(resolv6.equals(resolv7)); + assertTrue(resolv7.equals(resolv6)); + + // different arity ranges + assertFalse(resolv1.equals(resolv3)); + assertFalse(resolv3.equals(resolv1)); + + // partial overlap + assertFalse(resolv1.equals(resolv4)); + assertFalse(resolv4.equals(resolv1)); + + // hashcode - the different hashcodes are per test + assertTrue(resolv1.hashCode() == resolv2.hashCode()); + assertTrue(resolv1.hashCode() != resolv7.hashCode()); + } +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
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. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-28 00:46:56
|
Revision: 61 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=61&view=rev Author: kdgregory Date: 2008-12-28 00:46:35 +0000 (Sun, 28 Dec 2008) Log Message: ----------- OutputUtil: add elementToString(), treeToString() Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/OutputUtil.java trunk/src/test/java/net/sf/practicalxml/TestOutputUtil.java Modified: trunk/src/main/java/net/sf/practicalxml/OutputUtil.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/OutputUtil.java 2008-12-24 00:31:26 UTC (rev 60) +++ trunk/src/main/java/net/sf/practicalxml/OutputUtil.java 2008-12-28 00:46:35 UTC (rev 61) @@ -14,6 +14,7 @@ import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; +import org.w3c.dom.Element; /** @@ -25,6 +26,31 @@ public class OutputUtil { /** + * A simple <code>toString()</code> for an element, using the format + * "<code>{<i>NSURI</i>}<i>LOCALNAME</i></code>"; if the element has no + * namespace, the brackets remain but are empty. + */ + public static String elementToString(Element elem) + { + return appendElementString(new StringBuilder(256), elem).toString(); + } + + + /** + * Debug dump of the tree rooted at the specified element. Each line holds + * one element, + * + * + * @param elem + * @param indent + */ + public static String treeToString(Element elem, int indent) + { + return appendTreeString(new StringBuilder(1024), elem, indent, 0).toString(); + } + + + /** * Writes a DOM document to a simple string format, without a prologue or * whitespace between elements. * <p> @@ -206,6 +232,41 @@ //---------------------------------------------------------------------------- /** + * The actual implementation of {@link #elementToString}, which appends + * the string format to a passed buffer. Returns the buffer as a + * convenience. + */ + private static StringBuilder appendElementString(StringBuilder buf, Element elem) + { + String namespaceURI = elem.getNamespaceURI(); + String localName = DomUtil.getLocalName(elem); + + return buf.append("{") + .append((namespaceURI != null) ? namespaceURI : "") + .append("}") + .append(localName); + } + + /** + * Actual implementation of <code>dumpTree</code>, using a passed buffer + * so that we're not doing lots of string concats + */ + private static StringBuilder appendTreeString(StringBuilder buf, Element elem, int indent, int curIndent) + { + if (buf.length() > 0) + buf.append("\n"); + for (int ii = 0 ; ii < curIndent ; ii++) + buf.append(" "); + appendElementString(buf, elem); + for (Element child : DomUtil.getChildren(elem)) + { + appendTreeString(buf, child, indent, curIndent + indent); + } + return buf; + } + + + /** * Flushes an <code>OutputStream</code>, wrapping exceptions. */ private static void flushStream(OutputStream stream) Modified: trunk/src/test/java/net/sf/practicalxml/TestOutputUtil.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestOutputUtil.java 2008-12-24 00:31:26 UTC (rev 60) +++ trunk/src/test/java/net/sf/practicalxml/TestOutputUtil.java 2008-12-28 00:46:35 UTC (rev 61) @@ -31,10 +31,30 @@ //---------------------------------------------------------------------------- -// Test Cases -- we're looking for overall structure, assume that the output -// transform will do the right thing with the details +// Test Cases -- in most of these tests we look for overall structure, assume +// that the output transform will do the right thing //---------------------------------------------------------------------------- + public void testElementToString() throws Exception + { + Element root = DomUtil.newDocument("foo"); + Element child1 = DomUtil.appendChild(root, "argle", "bargle"); + + assertEquals("{}foo", OutputUtil.elementToString(root)); + assertEquals("{argle}bargle", OutputUtil.elementToString(child1)); + } + + + public void testTreeToString() throws Exception + { + Element root = DomUtil.newDocument("foo"); + Element child1 = DomUtil.appendChild(root, "argle", "bargle"); + + assertEquals("{}foo\n {argle}bargle", OutputUtil.treeToString(root, 2)); + assertEquals("{argle}bargle", OutputUtil.treeToString(child1, 2)); + } + + public void testCompactStringSingleElement() throws Exception { Element root = DomUtil.newDocument(EL_ROOT); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
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. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-29 00:35:20
|
Revision: 63 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=63&view=rev Author: kdgregory Date: 2008-12-29 00:35:16 +0000 (Mon, 29 Dec 2008) Log Message: ----------- SchemaUtil: combineSchema() renamed, now returns Document[] not Schema 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 15:56:10 UTC (rev 62) +++ trunk/src/main/java/net/sf/practicalxml/SchemaUtil.java 2008-12-29 00:35:16 UTC (rev 63) @@ -94,12 +94,33 @@ */ public static Schema newSchema(SchemaFactory factory, InputSource... sources) { - Document[] parsed = parseSources(sources); + return newSchema(factory, parseSources(sources)); + } + + + /** + * Compiles one or more DOM documents 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 documents 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. + * + * @throws IllegalArgumentException if invoked without any sources. + * @throws XmlException if unable to compile the schema. + */ + public static Schema newSchema(SchemaFactory factory, Document... sources) + { try { synchronized (factory) { - return factory.newSchema(toDOMSources(parsed)); + return factory.newSchema(toDOMSources(sources)); } } catch (SAXException e) @@ -110,13 +131,14 @@ /** - * 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. + * Parses and combines one or more input sources to produce an array + * of DOM documents containing schema components. Will properly order + * the output documents based on dependencies, remove any location + * references from imports that are satisfied by the sources, and + * provides a workaround for the + * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6198705"> + * JDK 1.5 bug</a> that prevents combination of source documents with + * the same namespace. * <p> * When combining schema documents with the same namespace, all top-level * attributes (eg, <code>elementFormDefault</code>) come from the first @@ -129,9 +151,6 @@ * 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. Note that these are * <code>org.xml.sax.InputSource</code> objects for * consistency with other classes in this library; @@ -141,23 +160,12 @@ * @throws IllegalArgumentException if invoked without any sources, or if * 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). + * @throws XmlException on any parse error, or if the sources do not + * appear to be valid XSD documents. */ - public static Schema combineSchema(SchemaFactory factory, InputSource... sources) + public static Document[] combineSchemas(InputSource... sources) { - SchemaManager manager = new SchemaManager(parseSources(sources)); - try - { - synchronized (factory) - { - return factory.newSchema(manager.toDOMSources()); - } - } - catch (SAXException e) - { - throw new XmlException("unable to generate schema", e); - } + return new SchemaManager(parseSources(sources)).buildOutput(); } @@ -245,7 +253,7 @@ /** * Returns the ordered set of sources for this manager. */ - public DOMSource[] toDOMSources() + public Document[] buildOutput() { TreeSet<Document> ordered = new TreeSet<Document>(new SchemaComparator()); for (Document doc : _documents.values()) @@ -253,7 +261,7 @@ ordered.add(rebuildImports(doc)); } - return SchemaUtil.toDOMSources(ordered.toArray(new Document[ordered.size()])); + return ordered.toArray(new Document[ordered.size()]); } /** Modified: trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java 2008-12-28 15:56:10 UTC (rev 62) +++ trunk/src/test/java/net/sf/practicalxml/TestSchemaUtil.java 2008-12-29 00:35:16 UTC (rev 63) @@ -208,11 +208,12 @@ + "<bargle>test</bargle>" + "</foo>"; - Schema schema = SchemaUtil.combineSchema( + Schema schema = SchemaUtil.newSchema( SchemaUtil.newFactory(new ExceptionErrorHandler()), - new InputSource(new StringReader(xsd1)), - new InputSource(new StringReader(xsd2)), - new InputSource(new StringReader(xsd3))); + SchemaUtil.combineSchemas( + new InputSource(new StringReader(xsd1)), + new InputSource(new StringReader(xsd2)), + new InputSource(new StringReader(xsd3)))); assertNotNull(schema); ParseUtil.validatingParse(new InputSource(new StringReader(xml)), @@ -245,10 +246,12 @@ + "<argle>12</argle>" + "</foo>"; - Schema schema = SchemaUtil.combineSchema( + + Schema schema = SchemaUtil.newSchema( SchemaUtil.newFactory(new ExceptionErrorHandler()), - new InputSource(new StringReader(xsd1)), - new InputSource(new StringReader(xsd2))); + SchemaUtil.combineSchemas( + new InputSource(new StringReader(xsd1)), + new InputSource(new StringReader(xsd2)))); assertNotNull(schema); ParseUtil.validatingParse(new InputSource(new StringReader(xml)), @@ -296,11 +299,13 @@ + "</foo>"; // note: these sources are intentionally out-of-order - Schema schema = SchemaUtil.combineSchema( + + Schema schema = SchemaUtil.newSchema( SchemaUtil.newFactory(new ExceptionErrorHandler()), - new InputSource(new StringReader(xsd1)), - new InputSource(new StringReader(xsd2)), - new InputSource(new StringReader(xsd3))); + SchemaUtil.combineSchemas( + new InputSource(new StringReader(xsd1)), + new InputSource(new StringReader(xsd2)), + new InputSource(new StringReader(xsd3)))); assertNotNull(schema); ParseUtil.validatingParse(new InputSource(new StringReader(xml)), This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-29 00:42:29
|
Revision: 64 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=64&view=rev Author: kdgregory Date: 2008-12-29 00:42:26 +0000 (Mon, 29 Dec 2008) Log Message: ----------- XPathWrapper: move to xpath package Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/junit/DomAsserts.java trunk/src/test/java/net/sf/practicalxml/junit/TestDomAsserts.java trunk/src/test/java/net/sf/practicalxml/xpath/function/TestLowercase.java trunk/src/test/java/net/sf/practicalxml/xpath/function/TestUppercase.java Added Paths: ----------- trunk/src/main/java/net/sf/practicalxml/xpath/XPathWrapper.java trunk/src/test/java/net/sf/practicalxml/xpath/TestXPathWrapper.java Removed Paths: ------------- trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java Deleted: trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java 2008-12-29 00:35:16 UTC (rev 63) +++ trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java 2008-12-29 00:42:26 UTC (rev 64) @@ -1,465 +0,0 @@ -package net.sf.practicalxml; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.xml.namespace.QName; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; -import javax.xml.xpath.XPathFunction; -import javax.xml.xpath.XPathVariableResolver; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import net.sf.practicalxml.xpath.AbstractFunction; -import net.sf.practicalxml.xpath.FunctionResolver; -import net.sf.practicalxml.xpath.NamespaceResolver; - - - -/** - * This class simplifies the use of XPath expressions, hiding the factory and - * return types, and providing a simple builder-style interface for adding - * resolvers. It also maintains the expression in a compiled form, improving - * reuse performance. - */ -public class XPathWrapper -{ - private final String _expr; - private final NamespaceResolver _nsResolver = new NamespaceResolver(); - private Map<QName,Object> _variables = new HashMap<QName,Object>(); - private FunctionResolver _functions = new FunctionResolver(); - - private XPathExpression _compiled; - - - /** - * Creates a new instance, which may then be customized with various - * resolvers, and used to evaluate expressions. - */ - public XPathWrapper(String expr) - { - _expr = expr; - } - - -//---------------------------------------------------------------------------- -// Public methods -//---------------------------------------------------------------------------- - - /** - * Adds a namespace binding to this expression. All bindings must be - * added prior to the first call to <code>evaluate()</code>. - * - * @param prefix The prefix used to reference this namespace in the - * XPath expression. Note that this does <em>not</em> - * need to be the same prefix used by the document. - * @param nsURI The namespace URI to associate with this prefix. - * - * @return The wrapper, so that calls may be chained. - */ - public XPathWrapper bindNamespace(String prefix, String nsURI) - { - _nsResolver.addNamespace(prefix, nsURI); - return this; - } - - - /** - * Sets the default namespace binding: this will be applied to all - * expressions that do not explicitly specify a prefix (although - * they must use the colon separating the non-existent prefix from - * the element name). - * - * @param nsURI The default namespace for this document. - * - * @return The wrapper, so that calls may be chained. - */ - public XPathWrapper bindDefaultNamespace(String nsURI) - { - _nsResolver.setDefaultNamespace(nsURI); - return this; - } - - - /** - * Binds a value to a variable, replacing any previous value for that - * variable. Unlike other configuration methods, this may be called - * after calling <code>evaluate()</code>; the new values will be used - * for subsequent evaluations. - * - * @param name The name of the variable; this is turned into a - * <code>QName</code> without namespace. - * @param value The value of the variable; the XPath evaluator must - * be able to convert this value into a type usable in - * an XPath expression. - * - * @return The wrapper, so that calls may be chained. - */ - public XPathWrapper bindVariable(String name, Object value) - { - return bindVariable(new QName(name), value); - } - - - /** - * Binds a value to a variable, replacing any previous value for that - * variable. Unlike other configuration methods, this may be called - * after calling <code>evaluate()</code>; the new values will be used - * for subsequent evaluations. - * - * @param name The fully-qualified name of the variable. - * @param value The value of the variable; the XPath evaluator must - * be able to convert this value into a type usable in - * an XPath expression. - * - * @return The wrapper, so that calls may be chained. - */ - public XPathWrapper bindVariable(QName name, Object value) - { - _variables.put(name, value); - return this; - } - - - /** - * Binds an {@link net.sf.practicalxml.xpath.AbstractFunction} to this - * expression. Subsequent calls to this method with the same name will - * silently replace the binding. - * <p> - * Per the JDK documentation, user-defined functions must occupy their - * own namespace. You must bind a namespace for this function, and use - * the bound prefix to reference it in your expression. Alternatively, - * you can call the variant of <code>bindFunction()</code> that binds - * a prefix to the function's namespace. - * - * @param func The function. - * - * @return The wrapper, so that calls may be chained. - */ - public XPathWrapper bindFunction(AbstractFunction func) - { - _functions.addFunction(func); - return this; - } - - - /** - * Binds an {@link net.sf.practicalxml.xpath.AbstractFunction} to this - * expression, along with the prefix used to access that function. - * Subsequent calls to this method with the same name will silently - * replace the binding. - * <p> - * Per the JDK documentation, user-defined functions must occupy their - * own namespace. This method will retrieve the namespace from the - * function, and bind it to the passed prefix. - * - * @param func The function. - * @param prefix The prefix to bind to this function's namespace. - * - * @return The wrapper, so that calls may be chained. - */ - public XPathWrapper bindFunction(AbstractFunction func, String prefix) - { - _functions.addFunction(func); - return bindNamespace(prefix, func.getNamespaceUri()); - } - - - /** - * Binds a standard <code>XPathFunction</code> to this expression, - * handling any number of arguments. Subsequent calls to this method - * with the same name will silently replace the binding. - * <p> - * Per the JDK documentation, user-defined functions must occupy their - * own namespace. If the qualified name that you pass to this method - * includes a prefix, the associated namespace will be bound to that - * prefix. If not, you must bind the namespace explicitly. In either - * case, you must refer to the function in your expression using a - * bound prefix. - * - * @param name The qualified name for this function. Must contain - * a name and namespace, may contain a prefix. - * @param func The function to bind to this name. - * - * @return The wrapper, so that calls may be chained. - */ - public XPathWrapper bindFunction(QName name, XPathFunction func) - { - return bindFunction(name, func, 0, Integer.MAX_VALUE); - } - - - /** - * Binds a standard <code>XPathFunction</code> to this expression, - * handling a specific number of arguments. Subsequent calls to this - * method with the same name and arity will silently replace the binding. - * <p> - * Per the JDK documentation, user-defined functions must occupy their - * own namespace. If the qualified name that you pass to this method - * includes a prefix, the associated namespace will be bound to that - * prefix. If not, you must bind the namespace explicitly. In either - * case, you must refer to the function in your expression using a - * bound prefix. - * - * @param name The qualified name for this function. Must contain - * a name and namespace, may contain a prefix. - * @param func The function to bind to this name. - * @param arity The number of arguments accepted by this function. - * - * @return The wrapper, so that calls may be chained. - */ - public XPathWrapper bindFunction(QName name, XPathFunction func, int arity) - { - return bindFunction(name, func, arity, arity); - } - - - /** - * Binds a standard <code>XPathFunction</code> to this expression, - * handling a specific range of arguments. Subsequent calls to this - * method with the same name and range of arguments will silently - * replace the binding. - * <p> - * Per the JDK documentation, user-defined functions must occupy their - * own namespace. If the qualified name that you pass to this method - * includes a prefix, the associated namespace will be bound to that - * prefix. If not, you must bind the namespace explicitly. In either - * case, you must refer to the function in your expression using a - * bound prefix. - * - * @param name The qualified name for this function. Must contain - * a name and namespace, may contain a prefix. - * @param func The function to bind to this name. - * @param minArity The minimum number of arguments accepted by this - * function. - * @param maxArity The maximum number of arguments accepted by this - * function. - * - * @return The wrapper, so that calls may be chained. - */ - public XPathWrapper bindFunction(QName name, XPathFunction func, - int minArity, int maxArity) - { - _functions.addFunction(func, name, minArity, maxArity); - if (!"".equals(name.getPrefix())) - bindNamespace(name.getPrefix(), name.getNamespaceURI()); - return this; - } - - - /** - * Applies this expression to the root of the specified document, - * converting the resulting NodeList into a java.util.List for ease - * in iteration. - */ - public List<Node> evaluate(Document context) - { - return evaluate(context.getDocumentElement()); - } - - - /** - * Applies this expression to the specified element, converting the - * resulting NodeList into a java.util.List for ease in iteration. - */ - public List<Node> evaluate(Element context) - { - compileIfNeeded(); - try - { - NodeList result = (NodeList)_compiled.evaluate(context, XPathConstants.NODESET); - List<Node> ret = new ArrayList<Node>(result.getLength()); - for (int ii = 0 ; ii < result.getLength() ; ii++) - { - ret.add(result.item(ii)); - } - return ret; - } - catch (Exception ee) - { - throw new XmlException("unable to evaluate: " + _expr, ee); - } - } - - - /** - * Applies this expression to the root of the specified document, - * requesting the <code>STRING</code> return type. - */ - public String evaluateAsString(Document context) - { - return evaluateAsString(context.getDocumentElement()); - } - - - /** - * Applies this expression to the specified element, requesting the - * <code>STRING</code> return type. - */ - public String evaluateAsString(Element context) - { - compileIfNeeded(); - try - { - return _compiled.evaluate(context); - } - catch (Exception ee) - { - throw new XmlException("unable to evaluate: " + _expr, ee); - } - } - - - /** - * Applies this expression to the root of the specified document, - * requesting the <code>NUMBER</code> return type. - */ - public Double evaluateAsNumber(Document context) - { - return evaluateAsNumber(context.getDocumentElement()); - } - - - /** - * Applies this expression to the specified element, requesting the - * <code>NUMBER</code> return type. - */ - public Double evaluateAsNumber(Element context) - { - compileIfNeeded(); - try - { - return (Double)_compiled.evaluate(context, XPathConstants.NUMBER); - } - catch (Exception ee) - { - throw new XmlException("unable to evaluate: " + _expr, ee); - } - } - - - /** - * Applies this expression to the root of the specified document, - * requesting the <code>BOOLEAN</code> return type. - */ - public Boolean evaluateAsBoolean(Document context) - { - return evaluateAsBoolean(context.getDocumentElement()); - } - - - /** - * Applies this expression to the specified element, requesting the - * <code>BOOLEAN</code> return type. - */ - public Boolean evaluateAsBoolean(Element context) - { - compileIfNeeded(); - try - { - return (Boolean)_compiled.evaluate(context, XPathConstants.BOOLEAN); - } - catch (Exception ee) - { - throw new XmlException("unable to evaluate: " + _expr, ee); - } - } - - -//---------------------------------------------------------------------------- -// Overrides of Object -//---------------------------------------------------------------------------- - - - /** - * Two instances are considered equal if they have the same expression, - * namespace mappings, variable mappings, and function mappings. Note - * that instances with function mappings are all but guaranteed to be - * not-equal, unless you override the <code>equals()</code> method on - * the function implementation class. - */ - @Override - public final boolean equals(Object obj) - { - if (obj instanceof XPathWrapper) - { - XPathWrapper that = (XPathWrapper)obj; - return this._expr.equals(that._expr) - && this._nsResolver.equals(that._nsResolver) - && this._variables.equals(that._variables) - && this._functions.equals(that._functions); - } - return false; - } - - - /** - * Hash code is driven by the expression, ignoring differences between - * namespaces, variables, and functions. - */ - @Override - public int hashCode() - { - return _expr.hashCode(); - } - - - /** - * The string value is the expression. - */ - @Override - public String toString() - { - return _expr; - } - - -//---------------------------------------------------------------------------- -// Internals -//---------------------------------------------------------------------------- - - /** - * Compiles the expression, if it has not already been compiled. This is - * called from the various <code>evaluate</code> methods, and ensures - * that the caller has completely configured the wrapper prior to use. - */ - private void compileIfNeeded() - { - if (_compiled != null) - return; - - try - { - XPath xpath = XPathFactory.newInstance().newXPath(); - xpath.setNamespaceContext(_nsResolver); - xpath.setXPathVariableResolver(new MyVariableResolver()); - xpath.setXPathFunctionResolver(_functions); - _compiled = xpath.compile(_expr); - } - catch (XPathExpressionException ee) - { - throw new XmlException("unable to compile: " + _expr, ee); - } - } - - - /** - * Resolver for variable references. - */ - private class MyVariableResolver - implements XPathVariableResolver - { - public Object resolveVariable(QName name) - { - return _variables.get(name); - } - } -} Modified: trunk/src/main/java/net/sf/practicalxml/junit/DomAsserts.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/junit/DomAsserts.java 2008-12-29 00:35:16 UTC (rev 63) +++ trunk/src/main/java/net/sf/practicalxml/junit/DomAsserts.java 2008-12-29 00:42:26 UTC (rev 64) @@ -7,7 +7,7 @@ import junit.framework.Assert; import net.sf.practicalxml.DomUtil; -import net.sf.practicalxml.XPathWrapper; +import net.sf.practicalxml.xpath.XPathWrapper; import org.w3c.dom.Element; import org.w3c.dom.Node; Copied: trunk/src/main/java/net/sf/practicalxml/xpath/XPathWrapper.java (from rev 63, trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java) =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/XPathWrapper.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/xpath/XPathWrapper.java 2008-12-29 00:42:26 UTC (rev 64) @@ -0,0 +1,463 @@ +package net.sf.practicalxml.xpath; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.xml.namespace.QName; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathVariableResolver; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import net.sf.practicalxml.XmlException; + + + +/** + * This class simplifies the use of XPath expressions, hiding the factory and + * return types, and providing a simple builder-style interface for adding + * resolvers. It also maintains the expression in a compiled form, improving + * reuse performance. + */ +public class XPathWrapper +{ + private final String _expr; + private final NamespaceResolver _nsResolver = new NamespaceResolver(); + private Map<QName,Object> _variables = new HashMap<QName,Object>(); + private FunctionResolver _functions = new FunctionResolver(); + + private XPathExpression _compiled; + + + /** + * Creates a new instance, which may then be customized with various + * resolvers, and used to evaluate expressions. + */ + public XPathWrapper(String expr) + { + _expr = expr; + } + + +//---------------------------------------------------------------------------- +// Public methods +//---------------------------------------------------------------------------- + + /** + * Adds a namespace binding to this expression. All bindings must be + * added prior to the first call to <code>evaluate()</code>. + * + * @param prefix The prefix used to reference this namespace in the + * XPath expression. Note that this does <em>not</em> + * need to be the same prefix used by the document. + * @param nsURI The namespace URI to associate with this prefix. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindNamespace(String prefix, String nsURI) + { + _nsResolver.addNamespace(prefix, nsURI); + return this; + } + + + /** + * Sets the default namespace binding: this will be applied to all + * expressions that do not explicitly specify a prefix (although + * they must use the colon separating the non-existent prefix from + * the element name). + * + * @param nsURI The default namespace for this document. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindDefaultNamespace(String nsURI) + { + _nsResolver.setDefaultNamespace(nsURI); + return this; + } + + + /** + * Binds a value to a variable, replacing any previous value for that + * variable. Unlike other configuration methods, this may be called + * after calling <code>evaluate()</code>; the new values will be used + * for subsequent evaluations. + * + * @param name The name of the variable; this is turned into a + * <code>QName</code> without namespace. + * @param value The value of the variable; the XPath evaluator must + * be able to convert this value into a type usable in + * an XPath expression. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindVariable(String name, Object value) + { + return bindVariable(new QName(name), value); + } + + + /** + * Binds a value to a variable, replacing any previous value for that + * variable. Unlike other configuration methods, this may be called + * after calling <code>evaluate()</code>; the new values will be used + * for subsequent evaluations. + * + * @param name The fully-qualified name of the variable. + * @param value The value of the variable; the XPath evaluator must + * be able to convert this value into a type usable in + * an XPath expression. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindVariable(QName name, Object value) + { + _variables.put(name, value); + return this; + } + + + /** + * Binds an {@link net.sf.practicalxml.xpath.AbstractFunction} to this + * expression. Subsequent calls to this method with the same name will + * silently replace the binding. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. You must bind a namespace for this function, and use + * the bound prefix to reference it in your expression. Alternatively, + * you can call the variant of <code>bindFunction()</code> that binds + * a prefix to the function's namespace. + * + * @param func The function. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindFunction(AbstractFunction func) + { + _functions.addFunction(func); + return this; + } + + + /** + * Binds an {@link net.sf.practicalxml.xpath.AbstractFunction} to this + * expression, along with the prefix used to access that function. + * Subsequent calls to this method with the same name will silently + * replace the binding. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. This method will retrieve the namespace from the + * function, and bind it to the passed prefix. + * + * @param func The function. + * @param prefix The prefix to bind to this function's namespace. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindFunction(AbstractFunction func, String prefix) + { + _functions.addFunction(func); + return bindNamespace(prefix, func.getNamespaceUri()); + } + + + /** + * Binds a standard <code>XPathFunction</code> to this expression, + * handling any number of arguments. Subsequent calls to this method + * with the same name will silently replace the binding. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. If the qualified name that you pass to this method + * includes a prefix, the associated namespace will be bound to that + * prefix. If not, you must bind the namespace explicitly. In either + * case, you must refer to the function in your expression using a + * bound prefix. + * + * @param name The qualified name for this function. Must contain + * a name and namespace, may contain a prefix. + * @param func The function to bind to this name. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindFunction(QName name, XPathFunction func) + { + return bindFunction(name, func, 0, Integer.MAX_VALUE); + } + + + /** + * Binds a standard <code>XPathFunction</code> to this expression, + * handling a specific number of arguments. Subsequent calls to this + * method with the same name and arity will silently replace the binding. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. If the qualified name that you pass to this method + * includes a prefix, the associated namespace will be bound to that + * prefix. If not, you must bind the namespace explicitly. In either + * case, you must refer to the function in your expression using a + * bound prefix. + * + * @param name The qualified name for this function. Must contain + * a name and namespace, may contain a prefix. + * @param func The function to bind to this name. + * @param arity The number of arguments accepted by this function. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindFunction(QName name, XPathFunction func, int arity) + { + return bindFunction(name, func, arity, arity); + } + + + /** + * Binds a standard <code>XPathFunction</code> to this expression, + * handling a specific range of arguments. Subsequent calls to this + * method with the same name and range of arguments will silently + * replace the binding. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. If the qualified name that you pass to this method + * includes a prefix, the associated namespace will be bound to that + * prefix. If not, you must bind the namespace explicitly. In either + * case, you must refer to the function in your expression using a + * bound prefix. + * + * @param name The qualified name for this function. Must contain + * a name and namespace, may contain a prefix. + * @param func The function to bind to this name. + * @param minArity The minimum number of arguments accepted by this + * function. + * @param maxArity The maximum number of arguments accepted by this + * function. + * + * @return The wrapper, so that calls may be chained. + */ + public XPathWrapper bindFunction(QName name, XPathFunction func, + int minArity, int maxArity) + { + _functions.addFunction(func, name, minArity, maxArity); + if (!"".equals(name.getPrefix())) + bindNamespace(name.getPrefix(), name.getNamespaceURI()); + return this; + } + + + /** + * Applies this expression to the root of the specified document, + * converting the resulting NodeList into a java.util.List for ease + * in iteration. + */ + public List<Node> evaluate(Document context) + { + return evaluate(context.getDocumentElement()); + } + + + /** + * Applies this expression to the specified element, converting the + * resulting NodeList into a java.util.List for ease in iteration. + */ + public List<Node> evaluate(Element context) + { + compileIfNeeded(); + try + { + NodeList result = (NodeList)_compiled.evaluate(context, XPathConstants.NODESET); + List<Node> ret = new ArrayList<Node>(result.getLength()); + for (int ii = 0 ; ii < result.getLength() ; ii++) + { + ret.add(result.item(ii)); + } + return ret; + } + catch (Exception ee) + { + throw new XmlException("unable to evaluate: " + _expr, ee); + } + } + + + /** + * Applies this expression to the root of the specified document, + * requesting the <code>STRING</code> return type. + */ + public String evaluateAsString(Document context) + { + return evaluateAsString(context.getDocumentElement()); + } + + + /** + * Applies this expression to the specified element, requesting the + * <code>STRING</code> return type. + */ + public String evaluateAsString(Element context) + { + compileIfNeeded(); + try + { + return _compiled.evaluate(context); + } + catch (Exception ee) + { + throw new XmlException("unable to evaluate: " + _expr, ee); + } + } + + + /** + * Applies this expression to the root of the specified document, + * requesting the <code>NUMBER</code> return type. + */ + public Double evaluateAsNumber(Document context) + { + return evaluateAsNumber(context.getDocumentElement()); + } + + + /** + * Applies this expression to the specified element, requesting the + * <code>NUMBER</code> return type. + */ + public Double evaluateAsNumber(Element context) + { + compileIfNeeded(); + try + { + return (Double)_compiled.evaluate(context, XPathConstants.NUMBER); + } + catch (Exception ee) + { + throw new XmlException("unable to evaluate: " + _expr, ee); + } + } + + + /** + * Applies this expression to the root of the specified document, + * requesting the <code>BOOLEAN</code> return type. + */ + public Boolean evaluateAsBoolean(Document context) + { + return evaluateAsBoolean(context.getDocumentElement()); + } + + + /** + * Applies this expression to the specified element, requesting the + * <code>BOOLEAN</code> return type. + */ + public Boolean evaluateAsBoolean(Element context) + { + compileIfNeeded(); + try + { + return (Boolean)_compiled.evaluate(context, XPathConstants.BOOLEAN); + } + catch (Exception ee) + { + throw new XmlException("unable to evaluate: " + _expr, ee); + } + } + + +//---------------------------------------------------------------------------- +// Overrides of Object +//---------------------------------------------------------------------------- + + + /** + * Two instances are considered equal if they have the same expression, + * namespace mappings, variable mappings, and function mappings. Note + * that instances with function mappings are all but guaranteed to be + * not-equal, unless you override the <code>equals()</code> method on + * the function implementation class. + */ + @Override + public final boolean equals(Object obj) + { + if (obj instanceof XPathWrapper) + { + XPathWrapper that = (XPathWrapper)obj; + return this._expr.equals(that._expr) + && this._nsResolver.equals(that._nsResolver) + && this._variables.equals(that._variables) + && this._functions.equals(that._functions); + } + return false; + } + + + /** + * Hash code is driven by the expression, ignoring differences between + * namespaces, variables, and functions. + */ + @Override + public int hashCode() + { + return _expr.hashCode(); + } + + + /** + * The string value is the expression. + */ + @Override + public String toString() + { + return _expr; + } + + +//---------------------------------------------------------------------------- +// Internals +//---------------------------------------------------------------------------- + + /** + * Compiles the expression, if it has not already been compiled. This is + * called from the various <code>evaluate</code> methods, and ensures + * that the caller has completely configured the wrapper prior to use. + */ + private void compileIfNeeded() + { + if (_compiled != null) + return; + + try + { + XPath xpath = XPathFactory.newInstance().newXPath(); + xpath.setNamespaceContext(_nsResolver); + xpath.setXPathVariableResolver(new MyVariableResolver()); + xpath.setXPathFunctionResolver(_functions); + _compiled = xpath.compile(_expr); + } + catch (XPathExpressionException ee) + { + throw new XmlException("unable to compile: " + _expr, ee); + } + } + + + /** + * Resolver for variable references. + */ + private class MyVariableResolver + implements XPathVariableResolver + { + public Object resolveVariable(QName name) + { + return _variables.get(name); + } + } +} Deleted: trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java 2008-12-29 00:35:16 UTC (rev 63) +++ trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java 2008-12-29 00:42:26 UTC (rev 64) @@ -1,339 +0,0 @@ -package net.sf.practicalxml; - -import java.util.List; -import javax.xml.namespace.QName; -import javax.xml.xpath.XPathFunction; -import javax.xml.xpath.XPathFunctionException; - -import net.sf.practicalxml.xpath.AbstractFunction; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - - -public class TestXPathWrapper -extends AbstractTestCase -{ - public TestXPathWrapper(String name) - { - super(name); - } - - -//---------------------------------------------------------------------------- -// Test data -//---------------------------------------------------------------------------- - - public final static String EL_ROOT = "root"; - public final static String EL_CHILD = "child"; - public final static String NS1 = "ns1"; - public final static String NS2 = "ns2"; - - Document _dom; - Element _root; - Element _child1; - Element _child2; - Element _child3; - - @Override - protected void setUp() - { - _root = DomUtil.newDocument(EL_ROOT); - _child1 = DomUtil.appendChild(_root, EL_CHILD); - _child2 = DomUtil.appendChild(_root, NS1, EL_CHILD); - _child3 = DomUtil.appendChild(_root, NS2, EL_CHILD); - _dom = _root.getOwnerDocument(); - } - - -//---------------------------------------------------------------------------- -// Support Code -//---------------------------------------------------------------------------- - - /** - * A standard XPath function implementation that returns the namespace - * of the first selected node. - */ - private static class MyStandardFunction - implements XPathFunction - { - public Object evaluate(List args) - throws XPathFunctionException - { - NodeList arg = (NodeList)args.get(0); - return arg.item(0).getNamespaceURI(); - } - } - - - /** - * An <code>AbstractFunction</code> implementation that returns the - * namespace of the first selected node. - */ - private static class MyAbstractFunction - extends AbstractFunction<String> - { - public MyAbstractFunction(String nsUri, String name) - { - super(nsUri, name); - } - - @Override - protected String processArg(int index, Node value, String helper) - throws Exception - { - return value.getNamespaceURI(); - } - } - -//---------------------------------------------------------------------------- -// Test Cases -//---------------------------------------------------------------------------- - - // the basic test to verify we can compile and execute - public void testCurrentElement() - throws Exception - { - XPathWrapper xpath = new XPathWrapper("."); - - List<Node> result1 = xpath.evaluate(_dom); - assertEquals(1, result1.size()); - assertSame(_root, result1.get(0)); - - List<Node> result2 = xpath.evaluate(_root); - assertEquals(1, result2.size()); - assertSame(_root, result2.get(0)); - - List<Node> result3 = xpath.evaluate(_child1); - assertEquals(1, result3.size()); - assertSame(_child1, result3.get(0)); - } - - - public void testEvalAsString() - throws Exception - { - _root.setAttribute("foo", "bar"); - _root.setAttribute("argle", "bargle"); - - XPathWrapper xpath = new XPathWrapper("@foo"); - - assertEquals("bar", xpath.evaluateAsString(_root)); - assertEquals("bar", xpath.evaluateAsString(_dom)); - } - - - public void testEvalAsNumber() - throws Exception - { - _root.setAttribute("foo", "10"); - - XPathWrapper xpath = new XPathWrapper("@foo"); - - assertEquals(Double.valueOf(10.0), xpath.evaluateAsNumber(_root)); - assertEquals(Double.valueOf(10.0), xpath.evaluateAsNumber(_dom)); - } - - - public void testEvalAsBoolean() - throws Exception - { - _root.setAttribute("foo", "10"); - - XPathWrapper xpath1 = new XPathWrapper("@foo=10"); - - assertTrue(xpath1.evaluateAsBoolean(_root).booleanValue()); - assertTrue(xpath1.evaluateAsBoolean(_dom).booleanValue()); - - _root.setAttribute("foo", "20"); - - assertFalse(xpath1.evaluateAsBoolean(_root).booleanValue()); - assertFalse(xpath1.evaluateAsBoolean(_dom).booleanValue()); - } - - - public void testNamespaces() throws Exception - { - XPathWrapper xpath1 = new XPathWrapper("//child"); - List<Node> result1 = xpath1.evaluate(_dom); - assertEquals(1, result1.size()); - assertSame(_child1, result1.get(0)); - - XPathWrapper xpath2 = new XPathWrapper("//ns:child") - .bindNamespace("ns", NS1); - List<Node> result2 = xpath2.evaluate(_dom); - assertEquals(1, result2.size()); - assertSame(_child2, result2.get(0)); - - XPathWrapper xpath3 = new XPathWrapper("//:child") - .bindDefaultNamespace(NS2); - List<Node> result3 = xpath3.evaluate(_dom); - assertEquals(1, result3.size()); - assertSame(_child3, result3.get(0)); - } - - - public void testVariables() throws Exception - { - _child1.setAttribute("bar", "baz"); - _child2.setAttribute("bar", "bargle"); - - XPathWrapper xpath = new XPathWrapper("//*[@bar=$test]") - .bindVariable(new QName("test"), "baz"); - - List<Node> result1 = xpath.evaluate(_dom); - assertEquals(1, result1.size()); - assertSame(_child1, result1.get(0)); - - xpath.bindVariable("test", "bargle"); - - List<Node> result2 = xpath.evaluate(_dom); - assertEquals(1, result2.size()); - assertSame(_child2, result2.get(0)); - } - - - public void testAbstractFunctions() throws Exception - { - XPathWrapper xpath1 = new XPathWrapper("ns:myfunc(.)") - .bindNamespace("ns", NS1) - .bindFunction(new MyAbstractFunction(NS1, "myfunc")); - - assertEquals("", xpath1.evaluateAsString(_child1)); - assertEquals(NS1, xpath1.evaluateAsString(_child2)); - - XPathWrapper xpath2 = new XPathWrapper("ns:myfunc(.)") - .bindFunction(new MyAbstractFunction(NS1, "myfunc"), "ns"); - - assertEquals("", xpath2.evaluateAsString(_child1)); - assertEquals(NS1, xpath2.evaluateAsString(_child2)); - } - - - public void testStandardFunctions() throws Exception - { - XPathWrapper xpath1 = new XPathWrapper("ns:myfunc(.)") - .bindFunction(new QName(NS1, "myfunc", "ns"), - new MyStandardFunction()); - - assertEquals("", xpath1.evaluateAsString(_child1)); - assertEquals(NS1, xpath1.evaluateAsString(_child2)); - - XPathWrapper xpath2 = new XPathWrapper("ns:myfunc(.,.)") - .bindFunction(new QName(NS2, "myfunc", "ns"), - new MyStandardFunction(), - 2); - - assertEquals("", xpath2.evaluateAsString(_child1)); - assertEquals(NS1, xpath2.evaluateAsString(_child2)); - } - - - public void testUnresolvableFunction() throws Exception - { - // we call with two arguments, it only gets resolved for one - XPathWrapper xpath1 = new XPathWrapper("ns:myfunc(.,.)") - .bindFunction(new QName(NS2, "myfunc", "ns"), - new MyStandardFunction(), - 1); - try - { - xpath1.evaluateAsString(_child1); - fail("didn't throw even though arity was wrong"); - } - catch (XmlException ee) - { - // success - } - } - - - - public void testEqualsAndHashCode() throws Exception - { - Object obj1a = new XPathWrapper("//foo"); - Object obj1b = new XPathWrapper("//foo"); - Object obj2a = new XPathWrapper("//foo") - .bindDefaultNamespace("zippy"); - Object obj2b = new XPathWrapper("//foo") - .bindDefaultNamespace("zippy"); - Object obj3a = new XPathWrapper("//foo") - .bindNamespace("argle", "bargle"); - Object obj3b = new XPathWrapper("//foo") - .bindNamespace("argle", "bargle"); - Object obj4a = new XPathWrapper("//foo") - .bindVariable("argle", "bargle"); - Object obj4b = new XPathWrapper("//foo") - .bindVariable("argle", "bargle"); - Object obj5a = new XPathWrapper("//foo") - .bindFunction(new QName("foo"), null); - Object obj5b = new XPathWrapper("//foo") - .bindFunction(new QName("foo"), null); - - assertFalse(obj1a.equals(null)); - assertFalse(obj1a.equals(new Object())); - - assertTrue(obj1a.equals(obj1b)); - assertTrue(obj1b.equals(obj1a)); - assertEquals(obj1a.hashCode(), obj1b.hashCode()); - - assertFalse(obj1a.equals(obj2a)); - assertTrue(obj2a.equals(obj2b)); - assertTrue(obj2b.equals(obj2a)); - assertEquals(obj1a.hashCode(), obj2a.hashCode()); - assertEquals(obj2a.hashCode(), obj2b.hashCode()); - - assertFalse(obj1a.equals(obj3a)); - assertTrue(obj3a.equals(obj3b)); - assertTrue(obj3b.equals(obj3a)); - assertEquals(obj1a.hashCode(), obj3a.hashCode()); - assertEquals(obj3a.hashCode(), obj3b.hashCode()); - - assertFalse(obj1a.equals(obj4a)); - assertTrue(obj4a.equals(obj4b)); - assertTrue(obj4b.equals(obj4a)); - assertEquals(obj1a.hashCode(), obj4a.hashCode()); - assertEquals(obj4a.hashCode(), obj4b.hashCode()); - - assertFalse(obj1a.equals(obj5a)); - assertTrue(obj5a.equals(obj5b)); - assertTrue(obj5b.equals(obj5a)); - assertEquals(obj1a.hashCode(), obj5a.hashCode()); - assertEquals(obj5a.hashCode(), obj5b.hashCode()); - } - - - public void testToString() throws Exception - { - final String expr = "//foo"; - assertEquals(expr, new XPathWrapper(expr).toString()); - assertEquals(expr, new XPathWrapper(expr).bindNamespace("foo", "bar").toString()); - } - - - public void testFailures() throws Exception - { - try - { - new XPathWrapper(".foo.").evaluate(_dom); - fail("compiled invalid expression"); - } - catch (XmlException ee) - { - // success - } - - try - { - new XPathWrapper("@foo=$bar").evaluate(_dom); - fail("evaluated expression with unbound variable"); - } - catch (XmlException ee) - { - // success - } - } - -} Modified: trunk/src/test/java/net/sf/practicalxml/junit/TestDomAsserts.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/junit/TestDomAsserts.java 2008-12-29 00:35:16 UTC (rev 63) +++ trunk/src/test/java/net/sf/practicalxml/junit/TestDomAsserts.java 2008-12-29 00:42:26 UTC (rev 64) @@ -7,7 +7,7 @@ import net.sf.practicalxml.AbstractTestCase; import net.sf.practicalxml.DomUtil; -import net.sf.practicalxml.XPathWrapper; +import net.sf.practicalxml.xpath.XPathWrapper; public class TestDomAsserts Copied: trunk/src/test/java/net/sf/practicalxml/xpath/TestXPathWrapper.java (from rev 63, trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java) =================================================================== --- trunk/src/test/java/net/sf/practicalxml/xpath/TestXPathWrapper.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/xpath/TestXPathWrapper.java 2008-12-29 00:42:26 UTC (rev 64) @@ -0,0 +1,343 @@ +package net.sf.practicalxml.xpath; + +import java.util.List; +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; + +import net.sf.practicalxml.AbstractTestCase; +import net.sf.practicalxml.DomUtil; +import net.sf.practicalxml.XmlException; +import net.sf.practicalxml.xpath.AbstractFunction; +import net.sf.practicalxml.xpath.XPathWrapper; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + + +public class TestXPathWrapper +extends AbstractTestCase +{ + public TestXPathWrapper(String name) + { + super(name); + } + + +//---------------------------------------------------------------------------- +// Test data +//---------------------------------------------------------------------------- + + public final static String EL_ROOT = "root"; + public final static String EL_CHILD = "child"; + public final static String NS1 = "ns1"; + public final static String NS2 = "ns2"; + + Document _dom; + Element _root; + Element _child1; + Element _child2; + Element _child3; + + @Override + protected void setUp() + { + _root = DomUtil.newDocument(EL_ROOT); + _child1 = DomUtil.appendChild(_root, EL_CHILD); + _child2 = DomUtil.appendChild(_root, NS1, EL_CHILD); + _child3 = DomUtil.appendChild(_root, NS2, EL_CHILD); + _dom = _root.getOwnerDocument(); + } + + +//---------------------------------------------------------------------------- +// Support Code +//---------------------------------------------------------------------------- + + /** + * A standard XPath function implementation that returns the namespace + * of the first selected node. + */ + private static class MyStandardFunction + implements XPathFunction + { + public Object evaluate(List args) + throws XPathFunctionException + { + NodeList arg = (NodeList)args.get(0); + return arg.item(0).getNamespaceURI(); + } + } + + + /** + * An <code>AbstractFunction</code> implementation that returns the + * namespace of the first selected node. + */ + private static class MyAbstractFunction + extends AbstractFunction<String> + { + public MyAbstractFunction(String nsUri, String name) + { + super(nsUri, name); + } + + @Override + protected String processArg(int index, Node value, String helper) + throws Exception + { + return value.getNamespaceURI(); + } + } + +//---------------------------------------------------------------------------- +// Test Cases +//---------------------------------------------------------------------------- + + // the basic test to verify we can compile and execute + public void testCurrentElement() + throws Exception + { + XPathWrapper xpath = new XPathWrapper("."); + + List<Node> result1 = xpath.evaluate(_dom); + assertEquals(1, result1.size()); + assertSame(_root, result1.get(0)); + + List<Node> result2 = xpath.evaluate(_root); + assertEquals(1, result2.size()); + assertSame(_root, result2.get(0)); + + List<Node> result3 = xpath.evaluate(_child1); + assertEquals(1, result3.size()); + assertSame(_child1, result3.get(0)); + } + + + public void testEvalAsString() + throws Exception + { + _root.setAttribute("foo", "bar"); + _root.setAttribute("argle", "bargle"); + + XPathWrapper xpath = new XPathWrapper("@foo"); + + assertEquals("bar", xpath.evaluateAsString(_root)); + assertEquals("bar", xpath.evaluateAsString(_dom)); + } + + + public void testEvalAsNumber() + throws Exception + { + _root.setAttribute("foo", "10"); + + XPathWrapper xpath = new XPathWrapper("@foo"); + + assertEquals(Double.valueOf(10.0), xpath.evaluateAsNumber(_root)); + assertEquals(Double.valueOf(10.0), xpath.evaluateAsNumber(_dom)); + } + + + public void testEvalAsBoolean() + throws Exception + { + _root.setAttribute("foo", "10"); + + XPathWrapper xpath1 = new XPathWrapper("@foo=10"); + + assertTrue(xpath1.evaluateAsBoolean(_root).booleanValue()); + assertTrue(xpath1.evaluateAsBoolean(_dom).booleanValue()); + + _root.setAttribute("foo", "20"); + + assertFalse(xpath1.evaluateAsBoolean(_root).booleanValue()); + assertFalse(xpath1.evaluateAsBoolean(_dom).booleanValue()); + } + + + public void testNamespaces() throws Exception + { + XPathWrapper xpath1 = new XPathWrapper("//child"); + List<Node> result1 = xpath1.evaluate(_dom); + assertEquals(1, result1.size()); + assertSame(_child1, result1.get(0)); + + XPathWrapper xpath2 = new XPathWrapper("//ns:child") + .bindNamespace("ns", NS1); + List<Node> result2 = xpath2.evaluate(_dom); + assertEquals(1, result2.size()); + assertSame(_child2, result2.get(0)); + + XPathWrapper xpath3 = new XPathWrapper("//:child") + .bindDefaultNamespace(NS2); + List<Node> result3 = xpath3.evaluate(_dom); + assertEquals(1, result3.size()); + assertSame(_child3, result3.get(0)); + } + + + public void testVariables() throws Exception + { + _child1.setAttribute("bar", "baz"); + _child2.setAttribute("bar", "bargle"); + + XPathWrapper xpath = new XPathWrapper("//*[@bar=$test]") + .bindVariable(new QName("test"), "baz"); + + List<Node> result1 = xpath.evaluate(_dom); + assertEquals(1, result1.size()); + assertSame(_child1, result1.get(0)); + + xpath.bindVariable("test", "bargle"); + + List<Node> result2 = xpath.evaluate(_dom); + assertEquals(1, result2.size()); + assertSame(_child2, result2.get(0)); + } + + + public void testAbstractFunctions() throws Exception + { + XPathWrapper xpath1 = new XPathWrapper("ns:myfunc(.)") + .bindNamespace("ns", NS1) + .bindFunction(new MyAbstractFunction(NS1, "myfunc")); + + assertEquals("", xpath1.evaluateAsString(_child1)); + assertEquals(NS1, xpath1.evaluateAsString(_child2)); + + XPathWrapper xpath2 = new XPathWrapper("ns:myfunc(.)") + .bindFunction(new MyAbstractFunction(NS1, "myfunc"), "ns"); + + assertEquals("", xpath2.evaluateAsString(_child1)); + assertEquals(NS1, xpath2.evaluateAsString(_child2)); + } + + + public void testStandardFunctions() throws Exception + { + XPathWrapper xpath1 = new XPathWrapper("ns:myfunc(.)") + .bindFunction(new QName(NS1, "myfunc", "ns"), + new MyStandardFunction()); + + assertEquals("", xpath1.evaluateAsString(_child1)); + assertEquals(NS1, xpath1.evaluateAsString(_child2)); + + XPathWrapper xpath2 = new XPathWrapper("ns:myfunc(.,.)") + .bindFunction(new QName(NS2, "myfunc", "ns"), + new MyStandardFunction(), + 2); + + assertEquals("", xpath2.evaluateAsString(_child1)); + assertEquals(NS1, xpath2.evaluateAsString(_child2)); + } + + + public void testUnresolvableFunction() throws Exception + { + // we call with two arguments, it only gets resolved for one + XPathWrapper xpath1 = new XPathWrapper("ns:myfunc(.,.)") + .bindFunction(new QName(NS2, "myfunc", "ns"), + new MyStandardFunction(), + 1); + try + { + xpath1.evaluateAsString(_child1); + fail("didn't throw even though arity was wrong"); + } + catch (XmlException ee) + { + // success + } + } + + + + public void testEqualsAndHashCode() throws Exception + { + Object obj1a = new XPathWrapper("//foo"); + Object obj1b = new XPathWrapper("//foo"); + Object obj2a = new XPathWrapper("//foo") + .bindDefaultNamespace("zippy"); + Object obj2b = new XPathWrapper("//foo") + .bindDefaultNamespace("zippy"); + Object obj3a = new XPathWrapper("//foo") + .bindNamespace("argle", "bargle"); + Object obj3b = new XPathWrapper("//foo") + .bindNamespace("argle", "bargle"); + Object obj4a = new XPathWrapper("//foo") + .bindVariable("argle", "bargle"); + Object obj4b = new XPathWrapper("//foo") + .bindVariable("argle", "bargle"); + Object obj5a = new XPathWrapper("//foo") + .bindFunction(new QName("foo"), null); + Object obj5b = new XPathWrapper("//foo") + .bindFunction(new QName("foo"), null); + + assertFalse(obj1a.equals(null)); + assertFalse(obj1a.equals(new Object())); + + assertTrue(obj1a.equals(obj1b)); + assertTrue(obj1b.equals(obj1a)); + assertEquals(obj1a.hashCode(), obj1b.hashCode()); + + assertFalse(obj1a.equals(obj2a)); + assertTrue(obj2a.equals(obj2b)); + assertTrue(obj2b.equals(obj2a)); + assertEquals(obj1a.hashCode(), obj2a.hashCode()); + assertEquals(obj2a.hashCode(), obj2b.hashCode()); + + assertFalse(obj1a.equals(obj3a)); + assertTrue(obj3a.equals(obj3b)); + assertTrue(obj3b.equals(obj3a)); + assertEquals(obj1a.hashCode(), obj3a.hashCode()); + assertEquals(obj3a.hashCode(), obj3b.hashCode()); + + assertFalse(obj1a.equals(obj4a)); + assertTrue(obj4a.equals(obj4b)); + assertTrue(obj4b.equals(obj4a)); + assertEquals(obj1a.hashCode(), obj4a.hashCode()); + assertEquals(obj4a.hashCode(), obj4b.hashCode()); + + assertFalse(obj1a.equals(obj5a)); + assertTrue(obj5a.equals(obj5b)); + assertTrue(obj5b.equals(obj5a)); + assertEquals(obj1a.hashCode(), obj5a.hashCode()); + assertEquals(obj5a.hashCode(), obj5b.hashCode()); + } + + + public void testToString() throws Exception + { + final String expr = "//foo"; + assertEquals(expr, new XPathWrapper(expr).toString()); + assertEquals(expr, new XPathWrapper(expr).bindNamespace("foo", "bar").toString()); + } + + + public void testFailures() throws Exception + { + try + { + new XPathWrapper(".foo.").evaluate(_dom); + fail("compiled invalid expression"); + } + catch (XmlException ee) + { + // success + } + + try + { + new XPathWrapper("@foo=$bar").evaluate(_dom); + fail("evaluated expression with unbound variable"); + } + catch (XmlException ee) + { + // success + } + } + +} Modified: trunk/src/test/java/net/sf/practicalxml/xpath/function/TestLowercase.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/xpath/function/TestLowercase.java 2008-12-29 00:35:16 UTC (rev 63) +++ trunk/src/test/java/net/sf/practicalxml/xpath/function/TestLowercase.java 2008-12-29 00:42:26 UTC (rev 64) @@ -10,7 +10,7 @@ import net.sf.practicalxml.AbstractTestCase; import net.sf.practicalxml.DomUtil; -import net.sf.practicalxml.XPathWrapper; +import net.sf.practicalxml.xpath.XPathWrapper; public class TestLowercase Modified: trunk/src/test/java/net/sf/practicalxml/xpath/function/TestUppercase.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/xpath/function/TestUppercase.java 2008-12-29 00:35:16 UTC (rev 63) +++ trunk/src/test/java/net/sf/practicalxml/xpath/function/TestUppercase.java 2008-12-29 00:42:26 UTC (rev 64) @@ -10,7 +10,7 @@ import net.sf.practicalxml.AbstractTestCase; import net.sf.practicalxml.DomUtil; -import net.sf.practicalxml.XPathWrapper; +import net.sf.practicalxml.xpath.XPathWrapper; public class TestUppercase This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-29 01:56:42
|
Revision: 65 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=65&view=rev Author: kdgregory Date: 2008-12-29 01:31:37 +0000 (Mon, 29 Dec 2008) Log Message: ----------- AbstractFunction: wasn't handling Number arguments Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java Modified: trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java 2008-12-29 00:42:26 UTC (rev 64) +++ trunk/src/main/java/net/sf/practicalxml/xpath/AbstractFunction.java 2008-12-29 01:31:37 UTC (rev 65) @@ -280,6 +280,8 @@ { if (arg instanceof String) helper = processArg(idx, (String)arg, helper); + else if (arg instanceof Number) + helper = processArg(idx, (Number)arg, helper); else if (arg instanceof NodeList) helper = processArg(idx, (NodeList)arg, helper); else if (arg instanceof Node) Modified: trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java 2008-12-29 00:42:26 UTC (rev 64) +++ trunk/src/test/java/net/sf/practicalxml/xpath/TestAbstractFunction.java 2008-12-29 01:31:37 UTC (rev 65) @@ -228,6 +228,22 @@ } + public void testEvaluateNumber() throws Exception + { + List args = Arrays.asList(new Object[] {Integer.valueOf(10)}); + MyMockFunction fn = new MyMockFunction("zippy"); + + assertEquals("zippy", fn.evaluate(args)); + assertEquals(1, fn._initCalls); + assertEquals(0, fn._processStringCalls); + assertEquals(1, fn._processNumberCalls); + assertEquals(0, fn._processBooleanCalls); + assertEquals(0, fn._procesNodeCalls); + assertEquals(0, fn._procesNodeListCalls); + assertEquals(1, fn._getResultCalls); + } + + public void testEvaluateNode() throws Exception { // since any JDK Node implementation is also a NodeList, we have to This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-29 02:52:08
|
Revision: 66 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=66&view=rev Author: kdgregory Date: 2008-12-29 02:52:04 +0000 (Mon, 29 Dec 2008) Log Message: ----------- XsiBoolean Added Paths: ----------- trunk/src/main/java/net/sf/practicalxml/xpath/function/XsiBoolean.java trunk/src/test/java/net/sf/practicalxml/xpath/function/TestXsiBoolean.java Added: trunk/src/main/java/net/sf/practicalxml/xpath/function/XsiBoolean.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/xpath/function/XsiBoolean.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/xpath/function/XsiBoolean.java 2008-12-29 02:52:04 UTC (rev 66) @@ -0,0 +1,69 @@ +package net.sf.practicalxml.xpath.function; + +import org.w3c.dom.Node; + +import net.sf.practicalxml.xpath.AbstractFunction; + + +/** + * Converts its argument to a boolean value, using a modification of the rules + * for Schema instances: true is represented by the literal values "true" or + * 1, ignoring case, while false is everything else. This is very different + * from the XPath function <code>boolean()</code>, in which any non-zero value + * or non-empty string/nodeset is true. + * <p> + * Note: the name of this class is <code>XsiBoolean</code>, but it's name in + * an XPath expression is "<code>boolean</code>". This is to prevent name + * collision with <code>java.lang.Boolean</code>. + */ +public class XsiBoolean +extends AbstractFunction<Boolean> +{ + public XsiBoolean() + { + super(Constants.COMMON_NS_URI, "boolean", 1); + } + + + @Override + protected Boolean processArg(int index, Node value, Boolean helper) + throws Exception + { + return (value != null) + ? processArg(index, value.getTextContent(), helper) + : Boolean.FALSE; + } + + + @Override + protected Boolean processArg(int index, String value, Boolean helper) + throws Exception + { + return "true".equals(value.toLowerCase()) + || "1".equals(value); + } + + + @Override + protected Boolean processArg(int index, Boolean value, Boolean helper) + throws Exception + { + return value; + } + + + @Override + protected Boolean processArg(int index, Number value, Boolean helper) + throws Exception + { + return Boolean.valueOf(value.intValue() == 1); + } + + + @Override + protected Boolean processNullArg(int index, Boolean helper) + throws Exception + { + return Boolean.FALSE; + } +} \ No newline at end of file Added: trunk/src/test/java/net/sf/practicalxml/xpath/function/TestXsiBoolean.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/xpath/function/TestXsiBoolean.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/xpath/function/TestXsiBoolean.java 2008-12-29 02:52:04 UTC (rev 66) @@ -0,0 +1,183 @@ +package net.sf.practicalxml.xpath.function; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.xml.xpath.XPathFunctionException; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import net.sf.practicalxml.AbstractTestCase; +import net.sf.practicalxml.DomUtil; +import net.sf.practicalxml.xpath.XPathWrapper; + + +public class TestXsiBoolean +extends AbstractTestCase +{ + public TestXsiBoolean(String name) + { + super(name); + } + + +//---------------------------------------------------------------------------- +// Setup +//---------------------------------------------------------------------------- + + private Element _root; + private Element _child1; + private Element _child2; + private Element _child3; + private Element _child4; + private Element _child5; + + @Override + protected void setUp() throws Exception + { + _root = DomUtil.newDocument("foo"); + _child1 = DomUtil.appendChild(_root, "bar"); + _child2 = DomUtil.appendChild(_root, "baz"); + _child3 = DomUtil.appendChild(_root, "false"); + _child4 = DomUtil.appendChild(_root, "true"); + _child5 = DomUtil.appendChild(_root, "x"); + + DomUtil.setText(_child1, "true"); + DomUtil.setText(_child2, "false"); + DomUtil.setText(_child3, "1"); + DomUtil.setText(_child4, "0"); + + _child1.setAttribute("attr", "False"); + _child2.setAttribute("attr", "True"); + _child3.setAttribute("attr", "0"); + _child4.setAttribute("attr", "1"); + } + + +//---------------------------------------------------------------------------- +// Test Cases +//---------------------------------------------------------------------------- + + public void testConstruction() throws Exception + { + XsiBoolean fn = new XsiBoolean(); + assertEquals(Constants.COMMON_NS_URI, fn.getNamespaceUri()); + assertEquals("boolean", fn.getName()); + assertEquals(1, fn.getMinArgCount()); + assertEquals(1, fn.getMaxArgCount()); + } + + + public void testLiteralString() throws Exception + { + assertEquals( + Boolean.TRUE, + new XsiBoolean().evaluate(Arrays.asList("true"))); + assertEquals( + Boolean.FALSE, + new XsiBoolean().evaluate(Arrays.asList("false"))); + assertEquals( + Boolean.TRUE, + new XsiBoolean().evaluate(Arrays.asList("TrUe"))); + assertEquals( + Boolean.FALSE, + new XsiBoolean().evaluate(Arrays.asList("FaLsE"))); + assertEquals( + Boolean.TRUE, + new XsiBoolean().evaluate(Arrays.asList("1"))); + assertEquals( + Boolean.FALSE, + new XsiBoolean().evaluate(Arrays.asList("0"))); + assertEquals( + Boolean.FALSE, + new XsiBoolean().evaluate(Arrays.asList("zippy"))); + } + + + public void testLiteralNumber() throws Exception + { + assertEquals( + Boolean.TRUE, + new XsiBoolean().evaluate(Arrays.asList(Double.valueOf(1)))); + assertEquals( + Boolean.FALSE, + new XsiBoolean().evaluate(Arrays.asList(Double.valueOf(0)))); + assertEquals( + Boolean.FALSE, + new XsiBoolean().evaluate(Arrays.asList(Double.valueOf(10)))); + } + + + public void testNodeList() throws Exception + { + assertEquals( + Boolean.TRUE, + new XsiBoolean().evaluate(Arrays.asList(_root.getChildNodes()))); + } + + + public void testNode() throws Exception + { + assertEquals( + Boolean.TRUE, + new XsiBoolean().evaluate(Arrays.asList(_child1))); + assertEquals( + Boolean.FALSE, + new XsiBoolean().evaluate(Arrays.asList(_child2))); + assertEquals( + Boolean.TRUE, + new XsiBoolean().evaluate(Arrays.asList(_child3))); + assertEquals( + Boolean.FALSE, + new XsiBoolean().evaluate(Arrays.asList(_child4))); + } + + + public void testEmptyNodeList() throws Exception + { + assertEquals( + Boolean.FALSE, + new XsiBoolean().evaluate(Arrays.asList(_child5.getChildNodes()))); + } + + + public void testNull() throws Exception + { + assertEquals( + Boolean.FALSE, + new XsiBoolean().evaluate(Arrays.asList((String)null))); + } + + + public void testEmptyArglist() throws Exception + { + try + { + new XsiBoolean().evaluate(Collections.<String>emptyList()); + fail("didn't throw on empty list"); + } + catch (XPathFunctionException e) + { + // success + } + } + + + public void testInSitu() throws Exception + { + XPathWrapper xpath1 = new XPathWrapper("//*[ns:boolean(text())]") + .bindFunction(new XsiBoolean(), "ns"); + List<Node> result1 = xpath1.evaluate(_root); + assertEquals(2, result1.size()); + assertSame(_child1, result1.get(0)); + assertSame(_child3, result1.get(1)); + + XPathWrapper xpath2 = new XPathWrapper("//*[ns:boolean(@attr)]") + .bindFunction(new XsiBoolean(), "ns"); + List<Node> result2 = xpath2.evaluate(_root); + assertEquals(2, result2.size()); + assertSame(_child2, result2.get(0)); + assertSame(_child4, result2.get(1)); + } +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-30 13:50:02
|
Revision: 68 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=68&view=rev Author: kdgregory Date: 2008-12-30 13:49:58 +0000 (Tue, 30 Dec 2008) Log Message: ----------- DomUtil.toList() Modified Paths: -------------- trunk/src/main/java/net/sf/practicalxml/DomUtil.java trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java Modified: trunk/src/main/java/net/sf/practicalxml/DomUtil.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/DomUtil.java 2008-12-29 02:57:43 UTC (rev 67) +++ trunk/src/main/java/net/sf/practicalxml/DomUtil.java 2008-12-30 13:49:58 UTC (rev 68) @@ -440,6 +440,28 @@ /** + * Creates a paramaterized list from a <code>NodeList</code>, making it + * usable within the Java coding idiom. + * + * @param nodelist The list of nodes to convert. + * @param ofClass The type of the nodes being converted. + * + * @throws ClassCastException if any node in the list is not the expected + * type. + */ + public static <T> List<T> toList(NodeList nodelist, Class<T> ofClass) + { + int size = nodelist.getLength(); + List<T> result = new ArrayList<T>(size); + for (int ii = 0 ; ii < size ; ii++) + { + result.add(ofClass.cast(nodelist.item(ii))); + } + return result; + } + + + /** * Returns the path from the root of the document to the specified * element, consisting of each node's qualified name, separated by * slashes. Accepts an arbitrary number of attribute names, and @@ -697,6 +719,6 @@ if (sibling == elem) return elemPos; } - throw new IllegalArgumentException("element not amongs its siblings"); + throw new IllegalArgumentException("element not amongst its siblings"); } } Modified: trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java 2008-12-29 02:57:43 UTC (rev 67) +++ trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java 2008-12-30 13:49:58 UTC (rev 68) @@ -309,4 +309,18 @@ // success } } + + + public void testToList() throws Exception + { + Element root = DomUtil.newDocument("foo"); + Element child1 = DomUtil.appendChild(root, "foo"); + DomUtil.setText(root, "blah blah blah"); + Element child2 = DomUtil.appendChild(root, "foo"); + + List<Element> result = DomUtil.toList(root.getElementsByTagName("*"), Element.class); + assertEquals(2, result.size()); + assertSame(child1, result.get(0)); + assertSame(child2, result.get(1)); + } } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |