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