[Practicalxml-commits] SF.net SVN: practicalxml:[79] trunk
Brought to you by:
kdgregory
From: Auto-Generated S. C. M. <pra...@li...> - 2009-04-27 12:41:56
|
Revision: 79 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=79&view=rev Author: kdgregory Date: 2009-04-27 12:41:46 +0000 (Mon, 27 Apr 2009) Log Message: ----------- OutputUtil: added support for XMLReader as input source XmlBuilder: use SAX for output (lowers footprint, discards comments) support Processing Instruction nodes (not that anyone uses them) added better package comment Modified Paths: -------------- trunk/pom.xml trunk/src/main/java/net/sf/practicalxml/OutputUtil.java trunk/src/main/java/net/sf/practicalxml/builder/AttributeNode.java trunk/src/main/java/net/sf/practicalxml/builder/CommentNode.java trunk/src/main/java/net/sf/practicalxml/builder/ElementNode.java trunk/src/main/java/net/sf/practicalxml/builder/Node.java trunk/src/main/java/net/sf/practicalxml/builder/TextNode.java trunk/src/main/java/net/sf/practicalxml/builder/XmlBuilder.java trunk/src/main/java/net/sf/practicalxml/builder/package.html trunk/src/test/java/net/sf/practicalxml/TestOutputUtil.java trunk/src/test/java/net/sf/practicalxml/builder/TestXmlBuilder.java Added Paths: ----------- trunk/src/main/java/net/sf/practicalxml/builder/PINode.java Modified: trunk/pom.xml =================================================================== --- trunk/pom.xml 2009-04-25 13:59:44 UTC (rev 78) +++ trunk/pom.xml 2009-04-27 12:41:46 UTC (rev 79) @@ -5,7 +5,7 @@ <groupId>net.sf.practicalxml</groupId> <artifactId>practicalxml</artifactId> <packaging>jar</packaging> - <version>1.0.1</version> + <version>1.0.2</version> <name>practicalxml</name> <url>http://sourceforge.net/projects/practicalxml/</url> Modified: trunk/src/main/java/net/sf/practicalxml/OutputUtil.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/OutputUtil.java 2009-04-25 13:59:44 UTC (rev 78) +++ trunk/src/main/java/net/sf/practicalxml/OutputUtil.java 2009-04-27 12:41:46 UTC (rev 79) @@ -25,10 +25,12 @@ import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.xml.sax.XMLReader; /** @@ -42,7 +44,8 @@ /** * 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. + * namespace, the brackets remain but are empty. This is typically used + * for debugging. */ public static String elementToString(Element elem) { @@ -51,12 +54,8 @@ /** - * Debug dump of the tree rooted at the specified element. Each line holds - * one element, - * - * - * @param elem - * @param indent + * Debug dump of the e rooted at the specified element. Each line holds + * one element, and elements are formatted per {@link #elementToString}. */ public static String treeToString(Element elem, int indent) { @@ -68,7 +67,6 @@ * Writes a DOM document to a simple string format, without a prologue or * whitespace between elements. * <p> - * * Do not simply write this string to a file unless you use UTF-8 encoding * or attach a prologue that specifies your actual encoding. * @@ -84,10 +82,38 @@ /** + * Writes XML in a simple string format, without prologue or whitespace + * between elements, using the passed <code>XMLReader</code> to generate + * a stream of SAX events. + * <p> + * The transformer will call the reader's <code>setContentHandler()</code> + * method, followed by <code>parse()</code>. In the latter method, the + * reader must invoke the content handler's event methods in the correct + * order: at the very least, <code>startDocument() </code>, followed by + * <code>startElement()</code> and <code>endElement()</code> for the root + * element, finishing with <code>endDocument()</code>. Note that SAX does + * not support all DOM node types: in particular, there are no comments. + * <p> + * Do not simply write this string to a file unless you use UTF-8 encoding + * or attach a prologue that specifies your actual encoding. + * + * @param reader Provides a source of SAX events for the transformer. + */ + public static String compactString(XMLReader reader) + { + StringWriter out = new StringWriter(); + new TransformHelper() + .transform(new SAXSource(reader, null), new StreamResult(out)); + return out.toString(); + } + + + /** * Writes a DOM document to a string format, with indenting between - * elements but without a prologue. Do not simply write this string - * to a file unless you use UTF-8 encoding or attach a prologue that - * specifies the encoding. + * elements but without a prologue. + * <p> + * Do not simply write this string to a file unless you use UTF-8 encoding + * or attach a prologue that specifies the encoding. * * @param dom The DOM tree to be output. * @param indentSize The number of spaces to indent each level of the @@ -110,9 +136,45 @@ /** - * Writes a DOM document to a stream, without a prologue or whitespace - * between elements, and using UTF-8 encoding. + * Writes XML in a simple string format, without prologue or whitespace + * between elements, using the passed <code>XMLReader</code> to generate + * a stream of SAX events. + * <p> + * The transformer will call the reader's <code>setContentHandler()</code> + * method, followed by <code>parse()</code>. In the latter method, the + * reader must invoke the content handler's event methods in the correct + * order: at the very least, <code>startDocument() </code>, followed by + * <code>startElement()</code> and <code>endElement()</code> for the root + * element, finishing with <code>endDocument()</code>. Note that SAX does + * not support all DOM node types: in particular, there are no comments. + * <p> + * Do not simply write this string to a file unless you use UTF-8 encoding + * or attach a prologue that specifies the encoding. * + * @param reader Provides a source of SAX events for the transformer. + * @param indentSize The number of spaces to indent each level of the + * tree. Indentation is <em>best effort</em>: the + * <code>javax.transform</code> API does not provide + * any way to set indent level, so we use JDK-specific + * features to achieve this, <em>where available</em>. + * Note also that indenting will cause problems with + * elements that contain mixed content, particularly + * if the text elements cannot be trimmed. + */ + public static String indentedString(XMLReader reader, int indentSize) + { + StringWriter out = new StringWriter(); + new TransformHelper() + .setIndent(indentSize) + .transform(new SAXSource(reader, null), new StreamResult(out)); + return out.toString(); + } + + + /** + * Writes a DOM document to a stream using UTF-8 encoding, without a prologue + * or whitespace between elements. + * * @param dom The DOM tree to be output. * @param stream The output stream. This stream will be flushed by * this method, but will <em>not</em> be closed. @@ -126,6 +188,31 @@ /** + * Writes XML to a stream using UTF-8 encoding, without prologue or + * whitespace between elements, using the passed <code>XMLReader</code> + * to generate a stream of SAX events. + * <p> + * The transformer will call the reader's <code>setContentHandler()</code> + * method, followed by <code>parse()</code>. In the latter method, the + * reader must invoke the content handler's event methods in the correct + * order: at the very least, <code>startDocument() </code>, followed by + * <code>startElement()</code> and <code>endElement()</code> for the root + * element, finishing with <code>endDocument()</code>. Note that SAX does + * not support all DOM node types: in particular, there are no comments. + * + * @param reader Provides a source of SAX events for the transformer. + * @param stream The output stream. This stream will be flushed by + * this method, but will <em>not</em> be closed. + */ + public static void compactStream(XMLReader reader, OutputStream stream) + { + new TransformHelper() + .transform(new SAXSource(reader, null), new StreamResult(stream)); + flushStream(stream); + } + + + /** * Writes a DOM document to a stream using the specified encoding, without * whitespace between elements, but <em>with</em> a prologue that specifes * the encoding. @@ -144,6 +231,32 @@ /** + * Writes XML to a stream using the specified encoding, without prologue or + * whitespace between elements, using the passed <code>XMLReader</code> + * to generate a stream of SAX events. + * <p> + * The transformer will call the reader's <code>setContentHandler()</code> + * method, followed by <code>parse()</code>. In the latter method, the + * reader must invoke the content handler's event methods in the correct + * order: at the very least, <code>startDocument() </code>, followed by + * <code>startElement()</code> and <code>endElement()</code> for the root + * element, finishing with <code>endDocument()</code>. Note that SAX does + * not support all DOM node types: in particular, there are no comments. + * + * @param reader Provides a source of SAX events for the transformer. + * @param stream The output stream. This stream will be flushed by + * this method, but will <em>not</em> be closed. + */ + public static void compactStream(XMLReader reader, OutputStream stream, String encoding) + { + new TransformHelper() + .setPrologue(encoding) + .transform(new SAXSource(reader, null), new StreamResult(stream)); + flushStream(stream); + } + + + /** * This object does the actual transformation work; the exposed static * methods create and configure an instance to do their job. If you * need finer control over output, you can do the same: call the various Modified: trunk/src/main/java/net/sf/practicalxml/builder/AttributeNode.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/builder/AttributeNode.java 2009-04-25 13:59:44 UTC (rev 78) +++ trunk/src/main/java/net/sf/practicalxml/builder/AttributeNode.java 2009-04-27 12:41:46 UTC (rev 79) @@ -15,6 +15,7 @@ package net.sf.practicalxml.builder; import org.w3c.dom.Element; +import org.xml.sax.helpers.AttributesImpl; /** @@ -24,16 +25,18 @@ extends Node implements java.io.Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; private String _nsUri; private String _qname; + private String _lclName; private String _value; public AttributeNode(String nsUri, String qname, String value) { _nsUri = nsUri; _qname = qname; + _lclName = getLocalName(qname); _value = value; } @@ -46,4 +49,14 @@ else parent.setAttributeNS(_nsUri, _qname, _value); } + + + /** + * Helper method to produce a SAX <code>Attributes</code> object. This + * is called by ElementNode. + */ + protected void appendToAttributes(AttributesImpl attrs) + { + attrs.addAttribute(_nsUri, _lclName, _qname, "", _value); + } } Modified: trunk/src/main/java/net/sf/practicalxml/builder/CommentNode.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/builder/CommentNode.java 2009-04-25 13:59:44 UTC (rev 78) +++ trunk/src/main/java/net/sf/practicalxml/builder/CommentNode.java 2009-04-27 12:41:46 UTC (rev 79) @@ -18,6 +18,9 @@ import org.w3c.dom.Element; +/** + * Holds a comment. + */ public class CommentNode extends Node { Modified: trunk/src/main/java/net/sf/practicalxml/builder/ElementNode.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/builder/ElementNode.java 2009-04-25 13:59:44 UTC (rev 78) +++ trunk/src/main/java/net/sf/practicalxml/builder/ElementNode.java 2009-04-27 12:41:46 UTC (rev 79) @@ -14,71 +14,130 @@ package net.sf.practicalxml.builder; +import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.XMLFilterImpl; import net.sf.practicalxml.DomUtil; import net.sf.practicalxml.OutputUtil; +/** + * The primary class for building XML trees and converting them to different + * JAXP-centric forms. Callers should not create instances of this class + * directly; instead use the static factory methods in {@link XmlBuilder}. + */ public final class ElementNode extends Node { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; - private String _nsUri; - private String _qname; - private List<Node> _children; + private String _nsUri; + private String _qname; + private String _lclName; + private List<AttributeNode> _attribs = new ArrayList<AttributeNode>(); + private List<Node> _children = new ArrayList<Node>(); + ElementNode(String nsUri, String qname, Node... children) { _nsUri = nsUri; _qname = qname; - _children = new ArrayList<Node>(Arrays.asList(children)); + _lclName = getLocalName(qname); + for (Node child : children) + addChild(child); } /** - * Adds a child node -- of any type -- to this element. + * Adds a child node -- of any type -- to this element. Returns this as + * a convenience to caller, allowing calls to be chained. */ public ElementNode addChild(Node child) { - if (child != null) + if (child instanceof AttributeNode) + _attribs.add((AttributeNode)child); + else if (child != null) _children.add(child); return this; } /** + * Generates a new DOM document with this element as the root. + */ + public Document toDOM() + { + Element root = DomUtil.newDocument(_nsUri, _qname); + appendChildren(root); + return root.getOwnerDocument(); + } + + + /** + * Invokes the passed <code>ContentHandler</code> for this element + * and its children. + */ + @Override + protected void toSAX(ContentHandler handler) + throws SAXException + { + handler.startElement(_nsUri, _lclName, _qname, getAttributes()); + for (Node child : _children) + { + child.toSAX(handler); + } + handler.endElement(_nsUri, _lclName, _qname); + } + + + /** * Generates an XML string, where this node is the root element. Does - * not insert whitespace between elements. + * not insert whitespace between elements. Note that you <em>must</em> + * use UTF-8 encoding or add a prologue that specifies encoding when + * writing this string to a stream. * <p> - * If you write this string to a file, you <em>must</em> use UTF-8 - * encoding or attach a prologue that specifies the encoding used. + * <em>Warning:</em> + * This method uses a SAX transformer, to minimize footprint. However, + * SAX does not support comment modes, so they will be silently dropped. + * If they are important to you, call {@link #toDOM} and use {@link + * net.sf.practicalxml.OutputUtil#compactString} to generate output. */ @Override public String toString() { - return OutputUtil.compactString(toDOM()); + return OutputUtil.compactString(new SerializationHelper()); } /** * Generates an XML string, where this node is the root element. Inserts - * whitespace between nodes, with the specified indent size. + * whitespace between nodes, along with newlines and the specified indent + * between elements. * <p> * This is the best choice for writing log output. If you write this string * to a stream, you <em>must</em> use UTF-8 encoding or attach a prologue * that specifies the encoding used. + * <p> + * <em>Warning:</em> + * This method uses a SAX transformer, to minimize footprint. However, + * SAX does not support comment modes, so they will be silently dropped. + * If they are important to you, call {@link #toDOM} and use {@link + * net.sf.practicalxml.OutputUtil#indentedString} to generate output. */ public String toString(int indentSize) { - return OutputUtil.indentedString(toDOM(), indentSize); + return OutputUtil.indentedString(new SerializationHelper(), indentSize); } @@ -88,10 +147,16 @@ * <p> * This is the best choice for writing XML that will be read by another * party. + * <p> + * <em>Warning:</em> + * This method uses a SAX transformer, to minimize footprint. However, + * SAX does not support comment modes, so they will be silently dropped. + * If they are important to you, call {@link #toDOM} and use {@link + * net.sf.practicalxml.OutputUtil#compactStream} to generate output. */ public void toStream(OutputStream out) { - OutputUtil.compactStream(toDOM(), out); + OutputUtil.compactStream(new SerializationHelper(), out); } @@ -99,6 +164,12 @@ * Writes the tree rooted at this element to an <code>OutputStream</code>, * using a specified encoding, without a prologue or whitepspace between * nodes. + * <p> + * <em>Warning:</em> + * This method uses a SAX transformer, to minimize footprint. However, + * SAX does not support comment modes, so they will be silently dropped. + * If they are important to you, call {@link #toDOM} and use {@link + * net.sf.practicalxml.OutputUtil#compactStream} to generate output. */ public void toStream(OutputStream out, String encoding) { @@ -106,27 +177,51 @@ } - /** - * Generates a new DOM document, with this element as the root. - */ - public Document toDOM() +//---------------------------------------------------------------------------- +// Internals +//---------------------------------------------------------------------------- + + @Override + protected void appendToElement(Element parent) { - Element root = DomUtil.newDocument(_nsUri, _qname); + appendChildren(DomUtil.appendChild(parent, _nsUri, _qname)); + } + + + private void appendChildren(Element elem) + { + for (Node child : _attribs) + { + child.appendToElement(elem); + } for (Node child : _children) { - child.appendToElement(root); + child.appendToElement(elem); } - return root.getOwnerDocument(); } - @Override - protected void appendToElement(Element parent) + private Attributes getAttributes() { - Element elem = DomUtil.appendChild(parent, _nsUri, _qname); - for (Node child : _children) + AttributesImpl result = new AttributesImpl(); + for (AttributeNode attr : _attribs) { - child.appendToElement(elem); + attr.appendToAttributes(result); } + return result; } + + + private class SerializationHelper + extends XMLFilterImpl + { + @Override + public void parse(InputSource input) + throws SAXException, IOException + { + startDocument(); + toSAX(getContentHandler()); + endDocument(); + } + } } Modified: trunk/src/main/java/net/sf/practicalxml/builder/Node.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/builder/Node.java 2009-04-25 13:59:44 UTC (rev 78) +++ trunk/src/main/java/net/sf/practicalxml/builder/Node.java 2009-04-27 12:41:46 UTC (rev 79) @@ -15,6 +15,8 @@ package net.sf.practicalxml.builder; import org.w3c.dom.Element; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; /** @@ -23,15 +25,40 @@ * are immutable, and require their parent to provide context (ie, no back- * pointers). * <p> - * <code>Node</code> is defined as an abstract class because all methods are - * internal. Only <code>ElementNode</code> should define public methods. + * <code>Node</code> is defined as an abstract class (rather than an interface) + * to allow declaration of protected methods and to provide helper methods. */ public abstract class Node implements java.io.Serializable { /** * This method is called internally by {@link ElementNode}, to append - * its children. + * its children to the DOM subtree rooted at the specified element. */ protected abstract void appendToElement(Element elem); + + + /** + * Invokes the passed <code>ContentHandler</code> for this element + * and its children. Default implementation does nothing (appropriate + * for attributes only). + */ + protected void toSAX(ContentHandler handler) + throws SAXException + { + // nothing happening here ... but almost everyone should override + } + + + /** + * Utility method to return a local name from either a qualified or + * non-qualified name. + */ + protected static String getLocalName(String qname) + { + int sepIdx = qname.indexOf(':'); + return (sepIdx < 0) + ? qname + : qname.substring(sepIdx + 1); + } } Added: trunk/src/main/java/net/sf/practicalxml/builder/PINode.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/builder/PINode.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/builder/PINode.java 2009-04-27 12:41:46 UTC (rev 79) @@ -0,0 +1,54 @@ +// Copyright 2008-2009 severally by the contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package net.sf.practicalxml.builder; + +import org.w3c.dom.Element; +import org.w3c.dom.ProcessingInstruction; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + + +/** + * Holds a processing instruction. + */ +public class PINode extends Node +{ + private static final long serialVersionUID = 1L; + + private String _target; + private String _data; + + public PINode(String target, String data) + { + _target = target; + _data = data; + } + + + @Override + protected void appendToElement(Element parent) + { + ProcessingInstruction pi = parent.getOwnerDocument() + .createProcessingInstruction(_target, _data); + parent.appendChild(pi); + } + + + @Override + protected void toSAX(ContentHandler handler) throws SAXException + { + handler.processingInstruction(_target, _data); + } +} Modified: trunk/src/main/java/net/sf/practicalxml/builder/TextNode.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/builder/TextNode.java 2009-04-25 13:59:44 UTC (rev 78) +++ trunk/src/main/java/net/sf/practicalxml/builder/TextNode.java 2009-04-27 12:41:46 UTC (rev 79) @@ -15,10 +15,16 @@ package net.sf.practicalxml.builder; import org.w3c.dom.Element; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; import net.sf.practicalxml.DomUtil; +/** + * Holds element content. Will be converted to DOM as <code>Text</code> + * (not <code>CDATASection</code>). + */ public class TextNode extends Node { private static final long serialVersionUID = 1L; @@ -36,4 +42,11 @@ { DomUtil.appendText(parent, _content); } + + + @Override + protected void toSAX(ContentHandler handler) throws SAXException + { + handler.characters(_content.toCharArray(), 0, _content.length()); + } } Modified: trunk/src/main/java/net/sf/practicalxml/builder/XmlBuilder.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/builder/XmlBuilder.java 2009-04-25 13:59:44 UTC (rev 78) +++ trunk/src/main/java/net/sf/practicalxml/builder/XmlBuilder.java 2009-04-27 12:41:46 UTC (rev 79) @@ -96,9 +96,22 @@ /** * Creates a comment node. + * <p> + * <em>Warning</em>: + * Comment nodes are not reported by SAX sources. If comments are + * important to you, convert to DOM before serialization. */ public static Node comment(String text) { return new CommentNode(text); } + + + /** + * Creates a processing instruction node. + */ + public static Node processingInstruction(String target, String data) + { + return new PINode(target, data); + } } Modified: trunk/src/main/java/net/sf/practicalxml/builder/package.html =================================================================== --- trunk/src/main/java/net/sf/practicalxml/builder/package.html 2009-04-25 13:59:44 UTC (rev 78) +++ trunk/src/main/java/net/sf/practicalxml/builder/package.html 2009-04-27 12:41:46 UTC (rev 79) @@ -1,5 +1,31 @@ <html> <body> - This package contains a tool for declarative creation of XML documents. + <code>XmlBuilder</code> is a tool for declarative construction of XML. + It was originally created to generate XML for unit tests, but is useful + wherever you want to construct XML documents with minimal code and a + low memory footprint. + <p> + The two classes of interest are {@link net.sf.practicalxml.builder.ElementNode} + and {@link net.sf.practicalxml.builder.XmlBuilder}: the former contains public + methods for transforming a tree into various representations, while the latter + contains static methods for building such a tree: + + <pre> + import static net.sf.practicalxml.builder.XmlBuilder.*; + + // ... + + ElementNode root = + element("root", + element("child1", + text("this is some <text>")), + element("child2", + attribute("foo", "bar"), + attribute("baz", "biff")), + element("http::www.example.com/foo", "ns:child3")); + + Document dom = root.toDOM(); + String out = root.toString(); + </pre> </body> </html> \ No newline at end of file Modified: trunk/src/test/java/net/sf/practicalxml/TestOutputUtil.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestOutputUtil.java 2009-04-25 13:59:44 UTC (rev 78) +++ trunk/src/test/java/net/sf/practicalxml/TestOutputUtil.java 2009-04-27 12:41:46 UTC (rev 79) @@ -15,7 +15,13 @@ package net.sf.practicalxml; import java.io.ByteArrayOutputStream; +import java.io.IOException; + import org.w3c.dom.Element; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLFilterImpl; public class TestOutputUtil @@ -44,6 +50,33 @@ public final static String SOME_TEXT = "blah"; + /** + * An XMLReader that emits a specified series of nested tags. + */ + private static class MyXMLReader + extends XMLFilterImpl + { + private String[] _elems; + + public MyXMLReader(String... elems) + { + _elems = elems; + } + + @Override + public void parse(InputSource input) + throws SAXException, IOException + { + getContentHandler().startDocument(); + for (int ii = 0 ; ii < _elems.length ; ii++) + getContentHandler().startElement(null, _elems[ii], _elems[ii], null); + for (int ii = _elems.length -1 ; ii >= 0 ; ii--) + getContentHandler().endElement(null, _elems[ii], _elems[ii]); + getContentHandler().endDocument(); + } + } + + //---------------------------------------------------------------------------- // Test Cases -- in most of these tests we look for overall structure, assume // that the output transform will do the right thing @@ -69,7 +102,7 @@ } - public void testCompactStringSingleElement() throws Exception + public void testCompactStringSingleElementDOM() throws Exception { Element root = DomUtil.newDocument(EL_ROOT); @@ -78,7 +111,7 @@ } - public void testCompactStringParentChild() throws Exception + public void testCompactStringParentChildDOM() throws Exception { Element root = DomUtil.newDocument(EL_ROOT); DomUtil.appendChild(root, EL_CHILD); @@ -88,6 +121,22 @@ } + public void testCompactStringSingleElementSAX() throws Exception + { + XMLReader reader = new MyXMLReader(EL_ROOT); + String s = OutputUtil.compactString(reader); + assertEquals(EL_ROOT_SOLO, s); + } + + + public void testCompactStringParentChildSAX() throws Exception + { + XMLReader reader = new MyXMLReader(EL_ROOT, EL_CHILD); + String s = OutputUtil.compactString(reader); + assertEquals(EL_ROOT_START + EL_CHILD_SOLO + EL_ROOT_END, s); + } + + public void testCompactStringWithText() throws Exception { Element root = DomUtil.newDocument(EL_ROOT); @@ -98,6 +147,29 @@ } + public void testIndentedStringParentChildDOM() throws Exception + { + Element root = DomUtil.newDocument(EL_ROOT); + Element child = DomUtil.appendChild(root, EL_CHILD); + DomUtil.setText(child, SOME_TEXT); + + String s = OutputUtil.indentedString(root.getOwnerDocument(), 4); + assertMultiline(EL_ROOT_START + + "\n " + EL_CHILD_START + SOME_TEXT + EL_CHILD_END + + "\n" + EL_ROOT_END + "\n", s); + } + + + public void testIndentedStringParentChildSAX() throws Exception + { + XMLReader reader = new MyXMLReader(EL_ROOT, EL_CHILD); + String s = OutputUtil.indentedString(reader, 4); + assertMultiline(EL_ROOT_START + + "\n " + EL_CHILD_SOLO + + "\n" + EL_ROOT_END + "\n", s); + } + + public void testIndentedStringParentChildText() throws Exception { Element root = DomUtil.newDocument(EL_ROOT); @@ -111,7 +183,7 @@ } - public void testCompactStreamAsciiContent() throws Exception + public void testCompactStreamAsciiContentDOM() throws Exception { Element root = DomUtil.newDocument("foo"); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -128,6 +200,23 @@ } + public void testCompactStreamAsciiContentSAX() throws Exception + { + XMLReader reader = new MyXMLReader("foo"); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + OutputUtil.compactStream(reader, out); + byte[] data = out.toByteArray(); + assertEquals(6, data.length); + assertEquals('<', data[0]); + assertEquals('f', data[1]); + assertEquals('o', data[2]); + assertEquals('o', data[3]); + assertEquals('/', data[4]); + assertEquals('>', data[5]); + } + + public void testCompactStreamNonAsciiDefaultEncoding() throws Exception { Element root = DomUtil.newDocument("\u00C0\u00C1"); @@ -147,7 +236,7 @@ } - public void testCompactStreamNonAsciiISO8859Encoding() throws Exception + public void testCompactStreamNonAsciiISO8859EncodingDOM() throws Exception { Element root = DomUtil.newDocument("\u00C0\u00C1"); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -169,6 +258,30 @@ assertTrue("no encoding", s.indexOf("encoding") > 0); assertTrue("incorrect encoding", s.indexOf("iso-8859-1") > 0 || s.indexOf("ISO-8859-1") > 0); + } + + public void testCompactStreamNonAsciiISO8859EncodingSAX() throws Exception + { + XMLReader reader = new MyXMLReader("\u00C0\u00C1"); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + OutputUtil.compactStream(reader, out, "ISO-8859-1"); + byte[] data = out.toByteArray(); + + // look for specific bytes for the element ... note reverse order + int idx = data.length - 1; + assertEquals('>', data[idx--]); + assertEquals('/', data[idx--]); + assertEquals(0xC1, data[idx--] & 0xFF); + assertEquals(0xC0, data[idx--] & 0xFF); + assertEquals('<', data[idx]); + + // convert to string to check for prologue + String s = new String(data, "ISO-8859-1"); + assertTrue("no prologue", s.startsWith("<?xml")); + assertTrue("no encoding", s.indexOf("encoding") > 0); + assertTrue("incorrect encoding", + s.indexOf("iso-8859-1") > 0 || s.indexOf("ISO-8859-1") > 0); } } Modified: trunk/src/test/java/net/sf/practicalxml/builder/TestXmlBuilder.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/builder/TestXmlBuilder.java 2009-04-25 13:59:44 UTC (rev 78) +++ trunk/src/test/java/net/sf/practicalxml/builder/TestXmlBuilder.java 2009-04-27 12:41:46 UTC (rev 79) @@ -15,6 +15,12 @@ package net.sf.practicalxml.builder; import java.io.ByteArrayOutputStream; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; @@ -23,6 +29,9 @@ import org.w3c.dom.Comment; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.ProcessingInstruction; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; import net.sf.practicalxml.AbstractTestCase; import net.sf.practicalxml.DomUtil; @@ -55,6 +64,71 @@ } + private static class MockContentHandler + implements InvocationHandler + { + public ContentHandler getHandler() + throws Exception + { + return (ContentHandler)Proxy.newProxyInstance( + ContentHandler.class.getClassLoader(), + new Class[] { ContentHandler.class }, + this); + } + + private ArrayList<String> _names = new ArrayList<String>(); + private ArrayList<Object[]> _args = new ArrayList<Object[]>(); + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable + { + // this is a hack for characters + for (int ii = 0 ; ii < args.length ; ii++) + { + if (args[ii] instanceof char[]) + args[ii] = new String((char[])args[ii]); + } + + _names.add(method.getName()); + _args.add(args); + return null; + } + + /** + * Asserts that the specific sequence of methods was called on this + * handler. + */ + public void assertInvocationSequence(String... methodNames) + { + List<String> expected = Arrays.asList(methodNames); + assertEquals(expected, _names); + } + + /** + * Asserts the name and argument values for a specific call to this + * handler. For convenience, ignores any arguments past the expected + * list. + */ + public void assertInvocation(int callNum, String methodName, Object... args) + { + assertEquals(methodName, _names.get(callNum)); + for (int ii = 0 ; ii < args.length ; ii++) + { + assertEquals("argument " + ii, args[ii], _args.get(callNum)[ii]); + } + } + + /** + * Returns a specific argument passed to a specific invocation. This + * allows the caller to make test-specific assertions. + */ + public Object getInvocationArgument(int callNum, int argNum) + { + return _args.get(callNum)[argNum]; + } + } + + //---------------------------------------------------------------------------- // Test Cases //---------------------------------------------------------------------------- @@ -62,114 +136,212 @@ public void testSingleElement() throws Exception { ElementNode node = element("foo"); - assertEquals("<foo/>", node.toString()); Document dom = node.toDOM(); assertRootElement(dom, null, "foo", 0); + + MockContentHandler handler = new MockContentHandler(); + node.toSAX(handler.getHandler()); + handler.assertInvocationSequence("startElement", "endElement"); + handler.assertInvocation(0, "startElement", null, "foo", "foo"); + + assertEquals("<foo/>", node.toString()); } - public void testNamespacedSingleElement() throws Exception + public void testSingleElementDefaultNamespace() throws Exception { ElementNode node = element("foo", "bar"); + + Document dom = node.toDOM(); + assertRootElement(dom, "foo", "bar", 0); + + MockContentHandler handler = new MockContentHandler(); + node.toSAX(handler.getHandler()); + handler.assertInvocationSequence("startElement", "endElement"); + handler.assertInvocation(0, "startElement", "foo", "bar", "bar"); + assertEquals("<bar xmlns=\"foo\"/>", node.toString()); + } + + public void testSingleElementQualifiedNamespace() throws Exception + { + ElementNode node = element("foo", "bar:baz"); + Document dom = node.toDOM(); - assertRootElement(dom, "foo", "bar", 0); } + assertRootElement(dom, "foo", "bar:baz", 0); + MockContentHandler handler = new MockContentHandler(); + node.toSAX(handler.getHandler()); + handler.assertInvocationSequence("startElement", "endElement"); + handler.assertInvocation(0, "startElement", "foo", "baz", "bar:baz"); + assertEquals("<bar:baz xmlns:bar=\"foo\"/>", node.toString()); + } + + public void testNestedElement() throws Exception { ElementNode node = element("foo", element("bar")); - assertEquals("<foo><bar/></foo>", node.toString()); Document dom = node.toDOM(); assertRootElement(dom, null, "foo", 1); assertElement(dom, "/foo/bar", null, "bar", 0); + + MockContentHandler handler = new MockContentHandler(); + node.toSAX(handler.getHandler()); + handler.assertInvocationSequence("startElement", + "startElement", "endElement", + "endElement"); + handler.assertInvocation(0, "startElement", null, "foo", "foo"); + handler.assertInvocation(1, "startElement", null, "bar", "bar"); + + assertEquals("<foo><bar/></foo>", node.toString()); } public void testAttribute() throws Exception { - Document dom = element("foo", + ElementNode node = element("foo", attribute("argle", "bargle", "wargle"), - attribute("bar", "baz")) - .toDOM(); + attribute("bar", "baz")); + Document dom = node.toDOM(); Element root = dom.getDocumentElement(); assertEquals("wargle", root.getAttributeNS("argle", "bargle")); assertEquals("baz", root.getAttribute("bar")); + + MockContentHandler handler = new MockContentHandler(); + node.toSAX(handler.getHandler()); + handler.assertInvocationSequence("startElement", "endElement"); + handler.assertInvocation(0, "startElement", null, "foo", "foo"); + + Attributes attrs = (Attributes)handler.getInvocationArgument(0, 3); + assertEquals(2, attrs.getLength()); } public void testTextElement() throws Exception { - Document dom = element("foo", text("bar")) - .toDOM(); + ElementNode node = element("foo", text("bar")); + Document dom = node.toDOM(); Element root = dom.getDocumentElement(); assertEquals("bar", DomUtil.getText(root)); + + MockContentHandler handler = new MockContentHandler(); + node.toSAX(handler.getHandler()); + handler.assertInvocationSequence("startElement", + "characters", + "endElement"); + handler.assertInvocation(0, "startElement", null, "foo", "foo"); + handler.assertInvocation(1, "characters", "bar", 0, 3); + + assertEquals("<foo>bar</foo>", node.toString()); } public void testConsecutiveTextElements() throws Exception { - Document dom = element("foo", text("bar"), text("baz")) - .toDOM(); + ElementNode node = element("foo", text("bar"), text("baz")); + Document dom = node.toDOM(); Element root = dom.getDocumentElement(); assertEquals("barbaz", DomUtil.getText(root)); + + MockContentHandler handler = new MockContentHandler(); + node.toSAX(handler.getHandler()); + handler.assertInvocationSequence("startElement", + "characters", "characters", + "endElement"); + handler.assertInvocation(0, "startElement", null, "foo", "foo"); + handler.assertInvocation(1, "characters", "bar", 0, 3); + handler.assertInvocation(2, "characters", "baz", 0, 3); + + assertEquals("<foo>barbaz</foo>", node.toString()); } public void testComment() throws Exception { - Document dom = element("foo", comment("bar")) - .toDOM(); + ElementNode node = element("foo", comment("bar")); + Document dom = node.toDOM(); Element root = dom.getDocumentElement(); assertEquals(1, root.getChildNodes().getLength()); - Comment child = (Comment)root.getChildNodes().item(0); assertEquals("bar", child.getNodeValue()); + + // note: ContentHandler knows nothing of comments + MockContentHandler handler = new MockContentHandler(); + node.toSAX(handler.getHandler()); + handler.assertInvocationSequence("startElement", "endElement"); + handler.assertInvocation(0, "startElement", null, "foo", "foo"); + + +// assertEquals("<foo><!--bar--></foo>", node.toString()); } + public void testPI() throws Exception + { + ElementNode node = element("foo", processingInstruction("argle", "bargle")); + + Document dom = node.toDOM(); + Element root = dom.getDocumentElement(); + assertEquals(1, root.getChildNodes().getLength()); + ProcessingInstruction child = (ProcessingInstruction)root.getChildNodes().item(0); + assertEquals("argle", child.getTarget()); + assertEquals("bargle", child.getData()); + + MockContentHandler handler = new MockContentHandler(); + node.toSAX(handler.getHandler()); + handler.assertInvocationSequence( + "startElement", + "processingInstruction", + "endElement"); + handler.assertInvocation(1, "processingInstruction", "argle", "bargle"); + + assertEquals("<foo><?argle bargle?></foo>", node.toString()); + } + + public void testAddChild() throws Exception { ElementNode root = element("foo"); assertSame(root, root.addChild(element("bar"))); - Element eRoot = root.toDOM().getDocumentElement(); - assertEquals("foo", eRoot.getNodeName()); - assertEquals(1, eRoot.getChildNodes().getLength()); - assertEquals("bar", eRoot.getChildNodes().item(0).getNodeName()); - } + Document dom = root.toDOM(); + assertRootElement(dom, null, "foo", 1); + MockContentHandler handler = new MockContentHandler(); + root.toSAX(handler.getHandler()); + handler.assertInvocationSequence("startElement", + "startElement", "endElement", + "endElement"); + handler.assertInvocation(0, "startElement", null, "foo", "foo"); + handler.assertInvocation(1, "startElement", null, "bar", "bar"); - public void testToString() throws Exception - { - String s = element("foo", - attribute("argle", "bar&gle"), - element("bar", text("baz"))) - .toString(); - assertEquals("<foo argle=\"bar&gle\"><bar>baz</bar></foo>", s); + assertEquals("<foo><bar/></foo>", root.toString()); } public void testToStringIndented() throws Exception { - String s = element("foo", element("bar", text("baz"))) - .toString(3); + ElementNode root = element("foo", element("bar", text("baz"))); + + String s = root.toString(3); assertMultiline("<foo>\n <bar>baz</bar>\n</foo>\n", s); } public void testToStream() throws Exception { + ElementNode root = element("foo", element("b\u00e2r", text("baz"))); + ByteArrayOutputStream out = new ByteArrayOutputStream(); - element("foo", element("b\u00e2r", text("baz"))) - .toStream(out); + root.toStream(out); String s = new String(out.toByteArray(), "UTF-8"); assertEquals("<foo><b\u00e2r>baz</b\u00e2r></foo>", s); @@ -178,9 +350,10 @@ public void testToStreamWithPrologue() throws Exception { + ElementNode root = element("f\u00f6o", element("bar", text("baz"))); + ByteArrayOutputStream out = new ByteArrayOutputStream(); - element("f\u00f6o", element("bar", text("baz"))) - .toStream(out, "ISO-8859-1"); + root.toStream(out, "ISO-8859-1"); byte[] b = out.toByteArray(); assertEquals(0xF6, b[b.length-3] & 0xFF); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |