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))
+ ...
[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 AbstractFunct...
[truncated message content] |
|
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'"
+ ...
[truncated message content] |
|
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 spe...
[truncated message content] |
|
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.
|