/*
* Copyright 2013 Red Hat Inc. and/or its affiliates and other 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 org.switchyard.common.xml;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.stream.events.StartDocument;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.Validator;
import org.jboss.logging.Logger;
import org.switchyard.common.lang.Strings;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Helper class for manipulating XML documents.
*
* @author <a href='mailto:kevin.conner@jboss.com'>Kevin Conner</a>
* @author Magesh Kumar B <mageshbk@jboss.com> (C) 2011 Red Hat Inc.
*/
public final class XMLHelper {
private static final Logger LOGGER = Logger.getLogger(XMLHelper.class);
/**
* The XML input factory.
*/
private static final XMLInputFactory XML_INPUT_FACTORY;
/**
* The XML output factory.
*/
private static final XMLOutputFactory XML_OUTPUT_FACTORY;
/**
* The Document builder factory.
*/
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY;
/**
* The Document builder for document creation (not parsing).
*/
private static final AtomicReference<DocumentBuilder> DOCUMENT_BUILDER = new AtomicReference<DocumentBuilder>();
/**
* The event writer creator for DOM documents.
*/
private static final EventWriterCreator EVENT_WRITER_CREATOR;
/**
* The event reader creator for DOM nodes.
*/
private static final EventReaderCreator EVENT_READER_CREATOR;
/**
* Default output keys to use for writing DOM nodes.
*/
private static final Map<String, String> DEFAULT_OUTPUT_PROPERTIES;
/**
* Hint to pretty-print Nodes.
*/
public static final String PRETTY_PRINT_HINT = "pretty-print";
/**
* XSL to pretty-print Nodes. (Hard-coded here so as to be faster than reading in a resource.)
*/
private static final String PRETTY_PRINT_XSL =
"<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:xalan='http://xml.apache.org/xslt' version='1.0'>"
+ "<xsl:output method='xml' encoding='UTF-8' indent='yes' xalan:indent-amount='4'/>"
+ "<xsl:strip-space elements='*'/>"
+ "<xsl:template match='@*|node()'>"
+ "<xsl:copy>"
+ "<xsl:apply-templates select='@*|node()'/>"
+ "</xsl:copy>"
+ "</xsl:template>"
+ "</xsl:stylesheet>";
private XMLHelper() {
}
/**
* Get the XML stream reader.
* @param reader The input reader.
* @return The XML stream reader.
* @throws XMLStreamException For errors obtaining an XML stream reader.
*/
public static XMLStreamReader getXMLStreamReader(final Reader reader)
throws XMLStreamException {
return XML_INPUT_FACTORY.createXMLStreamReader(reader);
}
/**
* Get the XML stream reader.
* @param is The input stream.
* @return The XML stream reader.
* @throws XMLStreamException For errors obtaining an XML stream reader.
*/
public static XMLStreamReader getXMLStreamReader(final InputStream is)
throws XMLStreamException {
return XML_INPUT_FACTORY.createXMLStreamReader(is);
}
/**
* Get the XML stream reader.
* @param is The input stream.
* @param encoding The input stream encoding.
* @return The XML stream reader.
* @throws XMLStreamException For errors obtaining an XML stream reader.
*/
public static XMLStreamReader getXMLStreamReader(final InputStream is, final String encoding)
throws XMLStreamException {
return XML_INPUT_FACTORY.createXMLStreamReader(is, encoding);
}
/**
* Get the XML stream reader.
* @param source The source.
* @return The XML stream reader.
* @throws XMLStreamException For errors obtaining an XML stream reader.
*/
public static XMLStreamReader getXMLStreamReader(final Source source)
throws XMLStreamException {
return XML_INPUT_FACTORY.createXMLStreamReader(source);
}
/**
* Get the XML event reader.
* @param reader The input reader.
* @return The XML event reader.
* @throws XMLStreamException For errors obtaining an XML event reader.
*/
public static XMLEventReader getXMLEventReader(final Reader reader)
throws XMLStreamException {
return XML_INPUT_FACTORY.createXMLEventReader(reader);
}
/**
* Get the XML event reader.
* @param is The input stream.
* @return The XML event reader.
* @throws XMLStreamException For errors obtaining an XML event reader.
*/
public static XMLEventReader getXMLEventReader(final InputStream is)
throws XMLStreamException {
return XML_INPUT_FACTORY.createXMLEventReader(is);
}
/**
* Get the XML event reader.
* @param is The input stream.
* @param encoding The input stream encoding.
* @return The XML event reader.
* @throws XMLStreamException For errors obtaining an XML event reader.
*/
public static XMLEventReader getXMLEventReader(final InputStream is, final String encoding)
throws XMLStreamException {
return XML_INPUT_FACTORY.createXMLEventReader(is, encoding);
}
/**
* Get the XML event reader.
* @param source The source.
* @return The XML event reader.
* @throws XMLStreamException For errors obtaining an XML event reader.
*/
public static XMLEventReader getXMLEventReader(final Source source)
throws XMLStreamException {
return XML_INPUT_FACTORY.createXMLEventReader(source);
}
/**
* Get the XML stream writer.
* @param writer The output writer.
* @return The XML stream writer.
* @throws XMLStreamException For errors obtaining an XML stream writer.
*/
public static XMLStreamWriter getXMLStreamWriter(final Writer writer)
throws XMLStreamException {
return XML_OUTPUT_FACTORY.createXMLStreamWriter(writer);
}
/**
* Get the XML stream writer.
* @param os The output stream.
* @return The XML stream writer.
* @throws XMLStreamException For errors obtaining an XML stream writer.
*/
public static XMLStreamWriter getXMLStreamWriter(final OutputStream os)
throws XMLStreamException {
return XML_OUTPUT_FACTORY.createXMLStreamWriter(os);
}
/**
* Get the XML stream writer.
* @param os The output stream.
* @param encoding The output stream encoding.
* @return The XML stream writer.
* @throws XMLStreamException For errors obtaining an XML stream writer.
*/
public static XMLStreamWriter getXMLStreamWriter(final OutputStream os, final String encoding)
throws XMLStreamException {
return XML_OUTPUT_FACTORY.createXMLStreamWriter(os, encoding);
}
/**
* Get the XML stream writer.
* @param result The output result.
* @return The XML stream writer.
* @throws XMLStreamException For errors obtaining an XML stream writer.
*/
public static XMLStreamWriter getXMLStreamWriter(final Result result)
throws XMLStreamException {
return XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
}
/**
* Get the XML event writer.
* @param writer The output writer.
* @return The XML event writer.
* @throws XMLStreamException For errors obtaining an XML event writer.
*/
public static XMLEventWriter getXMLEventWriter(final Writer writer)
throws XMLStreamException {
return XML_OUTPUT_FACTORY.createXMLEventWriter(writer);
}
/**
* Get the XML event writer.
* @param os The output stream.
* @return The XML event writer.
* @throws XMLStreamException For errors obtaining an XML event writer.
*/
public static XMLEventWriter getXMLEventWriter(final OutputStream os)
throws XMLStreamException {
return XML_OUTPUT_FACTORY.createXMLEventWriter(os);
}
/**
* Get the XML event writer.
* @param os The output stream.
* @param encoding The output stream encoding.
* @return The XML event writer.
* @throws XMLStreamException For errors obtaining an XML event writer.
*/
public static XMLEventWriter getXMLEventWriter(final OutputStream os, final String encoding)
throws XMLStreamException {
return XML_OUTPUT_FACTORY.createXMLEventWriter(os, encoding);
}
/**
* Get the XML event writer.
* @param result The output result.
* @return The XML event writer.
* @throws XMLStreamException For errors obtaining an XML event writer.
*/
public static XMLEventWriter getXMLEventWriter(final Result result)
throws XMLStreamException {
return XML_OUTPUT_FACTORY.createXMLEventWriter(result);
}
/**
* Copy an XML event stream.
* @param reader The event reader.
* @param writer The event writer.
* @throws XMLStreamException For errors writing to the XML event writer.
*/
public static void copyXMLEventStream(final XMLEventReader reader, final XMLEventWriter writer)
throws XMLStreamException {
copyXMLEventStream(reader, writer, false);
}
/**
* Copy an XML event stream.
* @param reader The event reader.
* @param writer The event writer.
* @param omitDoc if true, ignore start/end document events.
* @throws XMLStreamException For errors writing to the XML event writer.
*/
public static void copyXMLEventStream(final XMLEventReader reader, final XMLEventWriter writer, final boolean omitDoc)
throws XMLStreamException {
if (omitDoc) {
while (reader.hasNext()) {
final XMLEvent event = reader.nextEvent();
final int type = event.getEventType();
if ((type != XMLStreamConstants.START_DOCUMENT)
&& (type != XMLStreamConstants.END_DOCUMENT)) {
writer.add(event);
}
}
} else {
writer.add(reader);
}
writer.flush();
}
/**
* Validate the specified xml against the schema.
* @param schema The resource schema for validation.
* @param xml The XML to validate.
* @return true if valid, false otherwise.
*/
public static boolean validate(final Schema schema, final String xml) {
final Validator validator = schema.newValidator();
try {
validator.validate(new StreamSource(new StringReader(xml)));
return true;
} catch (final IOException ioe) {
LOGGER.debug(ioe.getMessage(), ioe);
} catch (final SAXException saxe) {
LOGGER.debug(saxe.getMessage(), saxe);
}
return false;
}
/**
* Compare the specified contents as XML.
* @param content1 The first content.
* @param content2 The second content.
* @return true if equals, false otherwise.
* @throws ParserConfigurationException Parser confiuration exception
* @throws SAXException SAX exception
* @throws IOException If unable to read the stream
*/
public static boolean compareXMLContent(final InputStream content1, final InputStream content2)
throws ParserConfigurationException, SAXException, IOException {
return compareXMLContent(new InputSource(content1), new InputSource(content2));
}
/**
* Compare the specified contents as XML.
* @param content1 The first content.
* @param content2 The second content.
* @return true if equals, false otherwise.
* @throws ParserConfigurationException Parser confiuration exception
* @throws SAXException SAX exception
* @throws IOException If unable to read the stream
*/
public static boolean compareXMLContent(final String content1, final String content2)
throws ParserConfigurationException, SAXException, IOException {
return compareXMLContent(new StringReader(content1), new StringReader(content2));
}
/**
* Compare the specified contents as XML.
* @param content1 The first content.
* @param content2 The second content.
* @return true if equals, false otherwise.
* @throws ParserConfigurationException Parser confiuration exception
* @throws SAXException SAX exception
* @throws IOException If unable to read the stream
*/
public static boolean compareXMLContent(final Reader content1, final Reader content2)
throws ParserConfigurationException, SAXException, IOException {
return compareXMLContent(new InputSource(content1), new InputSource(content2));
}
/**
* Compare the specified contents as XML.
* @param content1 The first content.
* @param content2 The second content.
* @return true if equals, false otherwise.
* @throws ParserConfigurationException Parser confiuration exception
* @throws SAXException SAX exception
* @throws IOException If unable to read the stream
*/
public static boolean compareXMLContent(final InputSource content1, final InputSource content2)
throws ParserConfigurationException, SAXException, IOException {
final SAXParserFactory parserFactory = SAXParserFactory.newInstance();
parserFactory.setNamespaceAware(true);
final SAXParser parser = parserFactory.newSAXParser();
final IdentitySAXHandler handler1 = new IdentitySAXHandler();
parser.parse(content1, handler1);
final IdentitySAXHandler handler2 = new IdentitySAXHandler();
parser.parse(content2, handler2);
return (handler1.getRootElement().equals(handler2.getRootElement()));
}
/**
* Compare two DOM Nodes.
* @param node1 The first Node.
* @param node2 The second Node.
* @return true if equals, false otherwise.
* @throws ParserConfigurationException Parser confiuration exception
* @throws TransformerException Transformer exception
* @throws SAXException SAX exception
* @throws IOException If unable to read the stream
*/
public static boolean compareXMLContent(final Node node1, final Node node2)
throws ParserConfigurationException, TransformerException, SAXException, IOException {
TransformerFactory transFactory = TransformerFactory.newInstance();
Transformer transformer = transFactory.newTransformer();
StringWriter writer1 = new StringWriter();
StringWriter writer2 = new StringWriter();
DOMSource source = new DOMSource(node1);
StreamResult result = new StreamResult(writer1);
transformer.transform(source, result);
source = new DOMSource(node2);
result = new StreamResult(writer2);
transformer.transform(source, result);
return compareXMLContent(writer1.toString(), writer2.toString());
}
/**
* Create a document from the specified reader.
* @param reader The XMLEvent reader.
* @return The Document.
* @throws ParserConfigurationException For errors creating the document.
* @throws XMLStreamException For errors reading the event reader.
*/
public static Document createDocument(final XMLEventReader reader)
throws ParserConfigurationException, XMLStreamException {
final Document doc = getNewDocument();
final XMLEventWriter writer = EVENT_WRITER_CREATOR.createXMLEventWriter(doc);
final XMLEvent event = reader.peek();
int type = event.getEventType();
boolean omitDoc = false;
if (type == XMLStreamConstants.START_DOCUMENT) {
StartDocument startDocument = (StartDocument) event;
if (startDocument.getVersion() == null) {
omitDoc = true;
}
}
XMLHelper.copyXMLEventStream(reader, writer, omitDoc);
return doc;
}
/**
* Read from a DOM node, output to a writer.
* @param node The DOM node.
* @param writer The specified writer.
* @param omitDoc if true, ignore start/end document events.
* @throws XMLStreamException For errors writing to the event writer.
*/
public static void readDomNode(final Node node, final XMLEventWriter writer, final boolean omitDoc)
throws XMLStreamException {
final XMLEventReader reader = EVENT_READER_CREATOR.createXMLEventReader(node);
XMLHelper.copyXMLEventStream(reader, writer, omitDoc);
}
/**
* Create a new document.
* @return the new document
* @throws ParserConfigurationException for errors during creation
*/
public static Document getNewDocument()
throws ParserConfigurationException {
final DocumentBuilder builder = getCreationDocumentBuilder();
synchronized (builder) {
// synchronized as it is not guaranteed to be thread safe
return builder.newDocument();
}
}
/**
* Create a document from bytes.
*
* @return the created document
* @param xml the input string
* @throws ParserConfigurationException for errors during creation
* @throws IOException if the source could not be read
* @throws SAXException if any parser error occurs
*/
public static Document getDocumentFromString(final String xml)
throws ParserConfigurationException, IOException, SAXException {
final DocumentBuilder builder = getCreationDocumentBuilder();
synchronized (builder) {
// synchronized as it is not guaranteed to be thread safe
return builder.parse(new InputSource(new StringReader(xml)));
}
}
/**
* Create a document from InputSource.
*
* @return the created document
* @param source the input source
* @throws ParserConfigurationException for errors during creation
* @throws IOException if the source could not be read
* @throws SAXException if any parser error occurs
*/
public static Document getDocument(final InputSource source)
throws ParserConfigurationException, IOException, SAXException {
final DocumentBuilder builder = getCreationDocumentBuilder();
synchronized (builder) {
// synchronized as it is not guaranteed to be thread safe
return builder.parse(source);
}
}
/**
* Get the first child Element of the supplied node that matches a given tag name.
*
* @param node The DOM Node.
* @param name The name of the child node to search for.
* @return The first child element with the matching tag name.
*/
public static Element getFirstChildElementByName(Node node, String name) {
NodeList children = node.getChildNodes();
int childCount = children.getLength();
for (int i = 0; i < childCount; i++) {
Node child = children.item(i);
if (child != null
&& child.getNodeType() == Node.ELEMENT_NODE
&& child.getLocalName() != null
&& child.getLocalName().equals(name)) {
return (Element) child;
}
}
return null;
}
/**
* Get the sibling Element of the supplied node that matches a given tag name.
*
* @param node The DOM Node.
* @param name The name of the sibling node to search for.
* @return The sibling element with the matching tag name or null otherwise.
*/
public static Element getNextSiblingElementByName(Node node, String name) {
Node sibling = node.getNextSibling();
while (sibling != null) {
if (sibling.getNodeType() == Node.ELEMENT_NODE
&& sibling.getLocalName() != null
&& sibling.getLocalName().equals(name)) {
return (Element) sibling;
}
sibling = sibling.getNextSibling();
}
return null;
}
/**
* Get the first child Element of the supplied node.
*
* @param node The DOM Node.
* @return The first child element
*/
public static Element getFirstChildElement(Node node) {
NodeList children = node.getChildNodes();
int childCount = children.getLength();
for (int i = 0; i < childCount; i++) {
Node child = children.item(i);
if (child != null
&& child.getNodeType() == Node.ELEMENT_NODE) {
return (Element) child;
}
}
return null;
}
/**
* Get the next sibling Element of the supplied node.
*
* @param node The DOM Node.
* @return The next sibling element
*/
public static Element getNextSiblingElement(Node node) {
Node sibling = node.getNextSibling();
while (sibling != null && sibling.getNodeType() != Node.ELEMENT_NODE) {
sibling = sibling.getNextSibling();
}
return (Element) sibling;
}
/**
* Get the attribute value of an Element.
*
* @param element the element.
* @param namespace the Attribute namespace.
* @param name the Attribute name.
* @return The value of the attribute
*/
public static String getAttribute(Element element, String namespace, String name) {
String value = null;
if (element.hasAttributeNS(namespace, name)) {
value = element.getAttributeNS(namespace, name);
} else if (element.hasAttribute(name)) {
value = element.getAttribute(name);
}
return value;
}
/**
* Check if an Element has an attribute.
*
* @param element the element.
* @param namespace the Attribute namespace.
* @param name the Attribute name.
* @return true if attribute exists, false otherwise
*/
public static Boolean hasAttribute(Element element, String namespace, String name) {
String value = getAttribute(element, namespace, name);
return (value != null);
}
/**
* Get the document builder for creation
* @return The document builder
* @throws ParserConfigurationException for errors during creation
*/
private static DocumentBuilder getCreationDocumentBuilder()
throws ParserConfigurationException {
final DocumentBuilder current = DOCUMENT_BUILDER.get();
if (current != null) {
return current;
}
final DocumentBuilder newBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
if (DOCUMENT_BUILDER.compareAndSet(null, newBuilder)) {
return newBuilder;
} else {
return DOCUMENT_BUILDER.get();
}
}
/**
* Gets the name of the node.
* @param node the node
* @return the name, first trying the local name (getLocalName()), but if null or zero-length, the node name (getNodeName())
*/
public static String nameOf(Node node) {
if (node != null) {
String name = node.getLocalName();
if (name == null || name.length() == 0) {
name = node.getNodeName();
}
return name;
}
return null;
}
/**
* Gets the value of the node.
* @param node the node
* @return the value of the node if the node is not null, otherwise null
*/
public static String valueOf(Node node) {
if (node != null) {
return node.getNodeValue();
}
return null;
}
/**
* Safely creates a QName based on a DOM Document's root element.
* @param document the document
* @return the QName
*/
public static QName createQName(Document document) {
return createQName(document.getDocumentElement());
}
/**
* Safely creates a QName based on a DOM Element.
* @param element the element
* @return the QName
*/
public static QName createQName(Element element) {
return createQName(element.getNamespaceURI(), nameOf(element), element.getPrefix());
}
/**
* Safely creates a QName based on a name.
* @param name will turn into the local name
* @return the QName
*/
public static QName createQName(String name) {
name = Strings.trimToNull(name);
if (name != null) {
return QName.valueOf(name);
}
return null;
}
/**
* Safely creates a QName based on a namespace and a name.
* @param namespace the namespace
* @param localName the local name
* @return the QName
*/
public static QName createQName(String namespace, String localName) {
return createQName(namespace, localName, null);
}
/**
* Safely creates a QName based on a namespace, a name and a prefix.
* @param namespace the namespace
* @param localName the local name
* @param prefix the prefix
* @return the QName
*/
public static QName createQName(String namespace, String localName, String prefix) {
localName = Strings.trimToNull(localName);
if (localName != null) {
namespace = Strings.trimToNull(namespace);
prefix = Strings.trimToNull(prefix);
if (namespace != null) {
if (prefix != null) {
return new QName(namespace, localName, prefix);
}
return new QName(namespace, localName);
}
return QName.valueOf(localName);
}
return null;
}
/**
* Splits a String into multiple QNames with a delimiter of '/'.
* @param str the String to split
* @return the multiple QNames
*/
public static QName[] splitQNames(String str) {
return splitQNames(str, '/');
}
/**
* Splits a String into multiple QNames per the specified delimiter.
* @param str the String to split
* @param delim the specified delimiter
* @return the multiple QNames
*/
public static QName[] splitQNames(String str, char delim) {
List<QName> qnames = new ArrayList<QName>();
if (str != null) {
List<Integer> indexes = new ArrayList<Integer>();
boolean withinNamespaceURI = false;
char[] ca = str.toCharArray();
for (int i=0; i < ca.length; i++) {
char c = ca[i];
if (c == '{') {
withinNamespaceURI = true;
} else if (withinNamespaceURI && c == '}') {
withinNamespaceURI = false;
} else if (!withinNamespaceURI && c == delim) {
indexes.add(Integer.valueOf(i));
}
}
for (Integer i : indexes) {
QName qname = createQName(str.substring(0, i.intValue()));
if (qname != null) {
qnames.add(qname);
}
str = str.substring(i.intValue()+1, str.length());
}
if (str.length() > 0) {
QName qname = createQName(str);
if (qname != null) {
qnames.add(qname);
}
}
}
return qnames.toArray(new QName[qnames.size()]);
}
/**
* Converts a Node to a String.
* @param node the Node
* @return the String
*/
public static String toString(Node node) {
return toString(node, DEFAULT_OUTPUT_PROPERTIES);
}
/**
* Converts a Node to a String, using the specified output properties.
* @param node the Node
* @param outputProperties the specified output properties
* @return the String
*/
public static String toString(Node node, Map<String, String> outputProperties) {
StringWriter writer = new StringWriter();
try {
write(node, writer, outputProperties);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
return writer.toString();
}
/**
* Converts a Node to a pretty-print String.
* @param node the Node
* @return the pretty-print String
*/
public static String toPretty(Node node) {
Map<String, String> outputProperties = new HashMap<String, String>();
outputProperties.putAll(DEFAULT_OUTPUT_PROPERTIES);
outputProperties.put(PRETTY_PRINT_HINT, "yes");
return toString(node, outputProperties);
}
/**
* Writes a Node to a Writer.
* @param node the Node
* @param writer the Writer
* @throws IOException if a problem occurs while writing
*/
public static void write(Node node, Writer writer) throws IOException {
write(node, writer, DEFAULT_OUTPUT_PROPERTIES);
}
/**
* Writes a Node to a Writer, using the specified output properties.
* @param node the Node
* @param writer the Writer
* @param outputProperties the specified output properties
* @throws IOException if a problem occurs while writing
*/
public static void write(Node node, Writer writer, Map<String, String> outputProperties) throws IOException {
try {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t;
if (isPrettyPrint(outputProperties)) {
t = tf.newTransformer(new StreamSource(new StringReader(PRETTY_PRINT_XSL)));
} else {
t = tf.newTransformer();
}
for (Map.Entry<String, String> entry : outputProperties.entrySet()) {
if (!entry.getKey().equals(PRETTY_PRINT_HINT)) {
t.setOutputProperty(entry.getKey(), entry.getValue());
}
}
t.transform(new DOMSource(node), new StreamResult(writer));
} catch (TransformerException te) {
throw new IOException(te);
}
}
private static boolean isPrettyPrint(Map<String, String> outputProperties) {
String pp = Strings.trimToNull(outputProperties.get(PRETTY_PRINT_HINT));
if (pp != null) {
pp = pp.toLowerCase();
return pp.equals("yes") || pp.equals("true");
}
return false;
}
/**
* Logs a Node.
* @param node the Node
*/
public static void log(Node node) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info(toString(node));
}
}
static {
final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
xmlInputFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
XML_INPUT_FACTORY = xmlInputFactory;
EVENT_READER_CREATOR = new DefaultEventReaderCreator();
XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
EVENT_WRITER_CREATOR = new DefaultEventWriterCreator();
final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true);
DOCUMENT_BUILDER_FACTORY = docBuilderFactory;
DEFAULT_OUTPUT_PROPERTIES = new HashMap<String, String>();
DEFAULT_OUTPUT_PROPERTIES.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
}
/**
* Interface for the event writer creator.
* @author kevin
*/
private interface EventWriterCreator {
/**
* Create the event writer.
* @param doc The associated document.
* @return The XML event writer.
* @throws XMLStreamException for errors constructing the writer.
*/
XMLEventWriter createXMLEventWriter(final Document doc)
throws XMLStreamException;
}
/**
* Interface for the event reader creator.
* @author kevin
*/
private interface EventReaderCreator {
/**
* Create the event reader.
* @param node The associated node.
* @return The XML event reader.
* @throws XMLStreamException for errors constructing the reader.
*/
XMLEventReader createXMLEventReader(final Node node)
throws XMLStreamException;
}
/**
* The default event writer creator
* @author kevin
*/
private static final class DefaultEventWriterCreator implements EventWriterCreator {
/**
* Create the event writer.
* @param doc The associated document.
* @return The XML event writer.
* @throws XMLStreamException for errors constructing the writer.
*/
public XMLEventWriter createXMLEventWriter(final Document doc)
throws XMLStreamException {
return getXMLEventWriter(new DOMResult(doc));
}
}
/**
* The default event reader creator
* @author kevin
*/
private static final class DefaultEventReaderCreator implements EventReaderCreator {
/**
* Create the event reader.
* @param node The associated node.
* @return The XML event reader.
* @throws XMLStreamException for errors constructing the reader.
*/
public XMLEventReader createXMLEventReader(final Node node)
throws XMLStreamException {
return getXMLEventReader(new DOMSource(node));
}
}
}