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