/*
* Copyright 2000-2013 Enonic AS
* http://www.enonic.com/license
*/
package com.enonic.esl.xml;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.zip.InflaterInputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.fileupload.FileItem;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.w3c.tidy.Tidy;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.google.common.base.Preconditions;
import com.google.common.io.Closeables;
import com.enonic.esl.containers.ExtendedMap;
import com.enonic.esl.util.StringUtil;
/**
* This class implements xml utility functions.
*/
public final class XMLTool
{
private final static Tidy TIDY = new Tidy();
private final static DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY =
DocumentBuilderFactory.newInstance();
private final static TransformerFactory TRANSFORMER_FACTORY =
TransformerFactory.newInstance();
private final static XPathFactory XPATH_FACTORY =
XPathFactory.newInstance();
static {
loadTidyProperties();
}
private static class ElementComparator
implements Comparator
{
private final String orderByAttribute;
private final boolean descending;
public ElementComparator(String orderByAttribute, boolean descending)
{
this.orderByAttribute = orderByAttribute;
this.descending = descending;
}
public int compare( Object a, Object b )
{
final String valueA = ( (Element) a ).getAttribute( orderByAttribute );
final String valueB = ( (Element) b ).getAttribute( orderByAttribute );
int result = Integer.parseInt( valueA ) - Integer.parseInt( valueB );
if ( descending )
{
return -result;
}
else
{
return result;
}
}
}
private XMLTool()
{
}
private static DocumentBuilder getDocumentBuilder()
{
try {
return DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
} catch (Exception e) {
throw new RuntimeException( e );
}
}
/**
* Create a CDATA section in a XML DOM.
*
* @param doc The owner document.
* @param root The element the CDATA should be created below.
* @param cdata The CDATA content.
* @return CDATASection
*/
public static CDATASection createCDATASection( Document doc, Element root, String cdata )
{
CDATASection cdataSection;
if ( cdata != null )
{
cdataSection = doc.createCDATASection( StringUtil.replaceECC( cdata ) );
root.appendChild( cdataSection );
}
else
{
cdataSection = doc.createCDATASection( "" );
root.appendChild( cdataSection );
}
return cdataSection;
}
/**
* Create XML/XHTML nodes in a XML DOM from a HTML string. All
* nodes inside the body tag are then inserted in the document passed as parameter.
*
* @param doc The owner document.
* @param root The element the nodes should be created below.
* @param html The html code that should be inserted as nodes.
* @param tidyFirst Tidy up the document before creating the nodes.
*/
public static void createXHTMLNodes( Document doc, Element root, String html, boolean tidyFirst )
{
if ( html == null )
{
return;
}
if ( tidyFirst )
{
html = tidy( html );
}
html = html.trim();
if ( html.length() == 0 )
{
return;
}
Document xhtml = domparse( html );
NodeList xhtmlTags = xhtml.getElementsByTagName( "body" ).item( 0 ).getChildNodes();
for ( int i = 0; i < xhtmlTags.getLength(); i++ )
{
root.appendChild( doc.importNode( xhtmlTags.item( i ), true ) );
}
}
private static String tidy( String html )
{
try
{
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = new ByteArrayInputStream( html.getBytes( "UTF-8" ) );
// Run Tidy on the input
Document tidied = HTMLtoXML( in, out );
html = XMLTool.documentToString( tidied, 0 );
in.close();
out.close();
return html;
}
catch ( UnsupportedEncodingException e )
{
throw new RuntimeException( e );
}
catch ( IOException e )
{
throw new RuntimeException( "Could not close temporary streams used by Tidy", e );
}
}
/**
* Convert a HTML stream to a XML stream
*
* @param in The HTML input stream
* @param out The XML output stream
* @return Document A Document containing the XML
*/
public static org.w3c.dom.Document HTMLtoXML( InputStream in, OutputStream out )
{
return TIDY.parseDOM(in, out);
}
/**
* Create an empty document.
*
* @return Document
*/
public static Document createDocument()
{
return getDocumentBuilder().newDocument();
}
/**
* Create a document with a top element.
*
* @param qualifiedName The name of the top element.
* @return Document
*/
public static Document createDocument( String qualifiedName )
{
Document doc = createDocument();
doc.appendChild(doc.createElement(qualifiedName));
return doc;
}
/**
* Create a DOM element.
*
* @param doc Document
* @param name The name of the element.
* @return Element
*/
public static Element createElement( Document doc, String name )
{
Preconditions.checkNotNull(name, "Element name cannot be null" );
Preconditions.checkArgument( name.trim().length() != 0, "Element name has to contain at least one character" );
return doc.createElement(name);
}
public static Element createElement( Element root, String name )
{
return createElement( root.getOwnerDocument(), root, name, null );
}
/**
* Create a DOM element below the specified element.
*
* @param doc Document
* @param root The parent element.
* @param name The name of the new element.
* @return Element
*/
public static Element createElement( Document doc, Element root, String name )
{
return createElement( doc, root, name, null );
}
/**
* Create a DOM element below the specified element, if the element is not already present
*
* @param doc Document
* @param root The parent element.
* @param name The name of the new element.
* @return Element
*/
public static Element createElementIfNotPresent( Document doc, Element root, String name )
{
Element elem = XMLTool.getElement( root, name );
if ( elem != null )
{
return elem;
}
else
{
return createElement( doc, root, name, null );
}
}
public static Element createElement( Document doc, Element root, String name, String text, String sortAttribute, String sortValue )
{
Preconditions.checkNotNull(name, "Element name cannot be null");
Preconditions.checkArgument(name.trim().length() != 0, "Element name has to contain at least one character");
Element elem = doc.createElement( name );
if ( text != null )
{
Text textNode = doc.createTextNode( StringUtil.getXMLSafeString( text ) );
elem.appendChild( textNode );
}
if ( sortAttribute == null || sortValue == null )
{
root.appendChild( elem );
}
else
{
Element[] childElems = getElements( root );
if ( childElems.length == 0 )
{
root.appendChild( elem );
}
else
{
int i = 0;
for (; i < childElems.length; i++ )
{
String childValue = childElems[i].getAttribute( sortAttribute );
if ( childValue != null && childValue.compareToIgnoreCase( sortValue ) >= 0 )
{
break;
}
}
if ( i < childElems.length )
{
root.insertBefore( elem, childElems[i] );
}
else
{
root.appendChild( elem );
}
}
}
return elem;
}
public static void makeTree( Element[] elems )
{
HashMap<String, Element> superKeys = new HashMap<String, Element>();
String superKeyName = "superkey";
// Put all elements in a hashmap
for ( Element elem : elems )
{
superKeys.put( elem.getAttribute( "key" ), elem );
}
// Go through all elements and move them to the correct parent
for ( Element elem : elems )
{
String superKey = elem.getAttribute( superKeyName );
if ( superKey.length() > 0 )
{
XMLTool.moveNode( elem, superKeys.get( superKey ) );
}
}
}
public static Element createElement( Document doc, Element root, String name, String text )
{
return createElement( doc, root, name, text, null, null );
}
public static Element createRootElement( Document doc, String name )
{
Preconditions.checkNotNull(name, "Root element name cannot be null!" );
Preconditions.checkArgument(name.trim().length() != 0, "Root element name has to contain at least one character!" );
// remove old root
NodeList nodes = doc.getChildNodes();
Node[] element = filterNodes( nodes, Node.ELEMENT_NODE );
for ( int i = 0; i < element.length; i++ )
{
doc.removeChild( element[i] );
}
// create and append the new root
Element root = doc.createElement( name );
doc.appendChild( root );
return root;
}
public static Text createTextNode( Document doc, Element root, String text )
{
Text textNode;
if ( text != null )
{
textNode = doc.createTextNode( text );
root.appendChild( textNode );
}
else
{
textNode = doc.createTextNode( "" );
root.appendChild( textNode );
}
return textNode;
}
public static byte[] documentToBytes( Document doc, String enc )
{
try
{
String xml = documentToString( doc, 0 );
if ( xml != null )
{
return xml.getBytes( enc );
}
else
{
return null;
}
}
catch ( UnsupportedEncodingException uee )
{
return null;
}
}
static public String documentToString( org.jdom.Document doc )
{
XMLOutputter outputter = new XMLOutputter( Format.getPrettyFormat() );
return outputter.outputString( doc );
}
static public String documentToString( Document doc )
{
java.io.StringWriter swriter = new java.io.StringWriter();
printDocument( swriter, doc );
return swriter.toString();
}
static public String documentToString( Document doc, int indent )
{
java.io.StringWriter swriter = new java.io.StringWriter();
printDocument( swriter, doc, indent );
return swriter.toString();
}
public static Document domparse( InputStream in )
{
Document doc;
InputSource inputSource = new InputSource( in );
doc = domparse( inputSource, null );
try
{
in.close();
}
catch ( IOException e )
{
throw new RuntimeException( "Failed to close input stream", e );
}
return doc;
}
/**
* Read an XML document from the reader and parse it into a DOM document.
*
* @param reader Reader
* @return Document
*/
public static Document domparse( Reader reader )
{
InputSource inputSource = new InputSource( reader );
return domparse( inputSource, null );
}
/**
* Get an element's sub-elements as an Element array. Returns an empty array if root element is null or the element does not have any
* sub-elements.
*
* @param root Element
* @return Element[]
*/
public static Element[] getElements( Element root )
{
ArrayList<Element> elements = getElementsAsList( root );
if ( elements.size() > 0 )
{
return elements.toArray( new Element[elements.size()] );
}
else
{
return new Element[0];
}
}
/**
* Get an element's sub-elements as a list of Element objects. Returns an empty array if root element is null or the element does not
* have any sub-elements.
*
* @param root Element
* @return ArrayList
*/
public static ArrayList<Element> getElementsAsList( Element root )
{
if ( root == null )
{
return new ArrayList<Element>();
}
ArrayList<Element> elements = new ArrayList<Element>();
NodeList nodeList = root.getChildNodes();
for ( int i = 0; i < nodeList.getLength(); i++ )
{
Node n = nodeList.item( i );
if ( n.getNodeType() == Node.ELEMENT_NODE )
{
elements.add( (Element) n );
}
}
return elements;
}
public static Element selectElement( Element elem, String xpath )
{
return (Element) selectNode( elem, xpath );
}
private static NodeList selectNodeList( Node node, String xpath )
{
try {
final XPath xp = XPATH_FACTORY.newXPath();
return (NodeList)xp.evaluate( xpath, node, XPathConstants.NODESET );
} catch (Exception e) {
throw new RuntimeException( e );
}
}
private static Node selectSingleNode( Node node, String xpath )
{
try {
final XPath xp = XPATH_FACTORY.newXPath();
return (Node)xp.evaluate( xpath, node, XPathConstants.NODE );
} catch (Exception e) {
throw new RuntimeException( e );
}
}
public static Element[] selectElements( Element elem, String xpath )
{
NodeList nl = selectNodeList(elem, xpath);
Node[] elementNodes = filterNodes( nl, Node.ELEMENT_NODE );
Element[] elements = new Element[elementNodes.length];
for ( int i = 0; i < elements.length; i++ )
{
elements[i] = (Element) elementNodes[i];
}
return elements;
}
public static Element[] getElements( Document doc, String xpath )
{
return selectElements( doc.getDocumentElement(), xpath );
}
/**
* Get an element's named sub-elements as an Element array. Returns an empty array if root element is null or the element does not have
* any sub-elements or sub-elements with the specified name.
*
* @param root Element
* @param elementName String
* @return Element[]
*/
public static Element[] getElements( Element root, String elementName )
{
ArrayList<Element> elements = getElementsAsList( root, elementName );
if ( elements.size() > 0 )
{
return elements.toArray( new Element[elements.size()] );
}
else
{
return new Element[0];
}
}
/**
* Get an element's named sub-elements as a list of Element objects. Returns an empty array if root element is null or the element does
* not have any sub-elements or sub-elements with the specified name.
*
* @param root Element
* @param elementName The name of the element to look for inside the root.
* @return All found elements.
*/
private static ArrayList<Element> getElementsAsList( Element root, String elementName )
{
if ( root == null )
{
return new ArrayList<Element>();
}
ArrayList<Element> elements = new ArrayList<Element>();
NodeList nodeList = root.getChildNodes();
for ( int i = 0; i < nodeList.getLength(); i++ )
{
Node n = nodeList.item( i );
if ( n != null && n.getNodeType() == Node.ELEMENT_NODE && elementName.equals( n.getNodeName() ) )
{
elements.add( (Element) n );
}
}
return elements;
}
/**
* Parse the string into a DOM document.
*
* @param xmlData A string containing an XML document.
* @return Document
*/
public static Document domparse( String xmlData )
{
InputSource inputSource = new InputSource( new StringReader( xmlData ) );
return domparse( inputSource, null );
}
public static Document domparse( String xmlData, String rootName )
{
InputSource inputSource = new InputSource( new StringReader( xmlData ) );
return domparse( inputSource, new String[]{rootName} );
}
public static Document domparse( String xmlData, String[] rootNames )
{
InputSource inputSource = new InputSource( new StringReader( xmlData ) );
return domparse( inputSource, rootNames );
}
private static Document domparse( InputSource inputSource, String[] rootNames )
{
Document doc;
try
{
doc = getDocumentBuilder().parse( inputSource );
}
catch ( IOException e )
{
throw new RuntimeException( "Failed to retrieve XML source document on the given URL", e );
}
catch ( SAXException e )
{
throw new RuntimeException( "Failed to parse xml document", e );
}
if ( rootNames != null )
{
Element root = doc.getDocumentElement();
if ( root == null )
{
throw new RuntimeException( "No root element in XML document" );
}
Arrays.sort( rootNames );
if ( Arrays.binarySearch( rootNames, root.getTagName() ) < 0 )
{
throw new RuntimeException( "Wrong root element name: " + root.getTagName() );
}
}
return doc;
}
/**
* Create a map using the element names as keys.
*
* @param nodeList NodeList
* @return Map
*/
public static Map<String, Element> filterElements( NodeList nodeList )
{
Map<String, Element> nodes = new HashMap<String, Element>();
if ( nodeList == null )
{
return nodes;
}
for ( int i = 0; i < nodeList.getLength(); i++ )
{
Node n = nodeList.item( i );
if ( n.getNodeType() == Node.ELEMENT_NODE )
{
Element e = (Element) n;
nodes.put( e.getTagName(), e );
}
}
return nodes;
}
/**
* Each element in the list is entered into a map, with a given attribute as key.
*
* @param nodeList The nodes that should be filtered.
* @param attribute The name of the attribute that should be used as a key.
* @return The resulting map.
*/
public static Map<String, Element> filterElementsWithAttributeAsKey( NodeList nodeList, String attribute )
{
Map<String, Element> nodes = new HashMap<String, Element>();
if ( nodeList == null )
{
return nodes;
}
for ( int i = 0; i < nodeList.getLength(); i++ )
{
Node n = nodeList.item( i );
if ( n.getNodeType() == Node.ELEMENT_NODE )
{
Element e = (Element) n;
nodes.put(e.getAttribute(attribute), e);
}
}
return nodes;
}
/**
* Filter a nodelist on a specific node type.
*
* @param nodeList NodeList
* @param nodeType short
* @return Node[]
*/
public static Node[] filterNodes( NodeList nodeList, short nodeType )
{
List<Node> nodes = new ArrayList<Node>();
if ( nodeList == null )
{
return nodes.toArray( new Node[nodes.size()] );
}
for ( int i = 0; i < nodeList.getLength(); i++ )
{
Node n = nodeList.item( i );
if ( n != null && n.getNodeType() == nodeType )
{
nodes.add( n );
}
}
return nodes.toArray( new Node[nodes.size()] );
}
/**
* Get an element's sub-element by name. Will return null if root is null, sub-element's name is null or empty and if the element is not
* found. If more than one sub-element with the same name, the first match is returned.
*
* @param root Element the root element to search in
* @param elementName String name of the sub-element to find
* @return Element the sub-element
*/
public static Element getElement( Element root, String elementName )
{
if ( root == null || elementName == null || elementName.trim().length() == 0 )
{
return null;
}
Node[] element = filterNodes(root.getChildNodes(), Node.ELEMENT_NODE);
for ( Node anElement : element )
{
String tagName = ( (Element) anElement ).getTagName();
if ( elementName.equals( tagName ) )
{
return (Element) anElement;
}
}
return null;
}
/**
* Get an element's first sub-element. Will return null if root is null, root does not have any sub-elements.
*
* @param root Element the root element to search in
* @return Element the first sub-element, or null if none found
*/
public static Element getFirstElement( Element root )
{
if ( root == null )
{
return null;
}
Node n = root.getFirstChild();
while ( n != null && n.getNodeType() != Node.ELEMENT_NODE )
{
n = n.getNextSibling();
}
if ( n != null )
{
return (Element) n;
}
return null;
}
public static void removeChildNodes( Element root, boolean keepAttributeNodes )
{
if ( root == null )
{
return;
}
NodeList nodeList = root.getChildNodes();
for ( int i = 0; i < nodeList.getLength(); )
{
Node n = nodeList.item( i );
if ( ( !keepAttributeNodes && n.getNodeType() == Node.ATTRIBUTE_NODE ) || n.getNodeType() != Node.ATTRIBUTE_NODE )
{
root.removeChild( n );
}
else
{
i++;
}
}
}
public static void removeChildNodes( Node root )
{
if ( root == null )
{
return;
}
NodeList nodeList = root.getChildNodes();
for ( int i = 0; i < nodeList.getLength(); )
{
root.removeChild( nodeList.item( i ) );
}
}
/**
* Retrieve a text from an element or attribute using an xpath expression.
*
* @param doc The source document that the text is to be retrieved from.
* @param xpath The xpath that selects the element or attribute containing the text.
* @return The element text.
*/
public static String getElementText( Document doc, String xpath )
{
Node node = selectNode( doc.getDocumentElement(), xpath );
return getNodeText( node );
}
public static String getNodeText( Node node )
{
if ( node == null )
{
return null;
}
else if ( node.getNodeType() == Node.TEXT_NODE )
{
return ( (Text) node ).getData();
}
else if ( node.getNodeType() == Node.ATTRIBUTE_NODE )
{
return ( (Attr) node ).getValue();
}
else
{
return getElementText( (Element) node );
}
}
/**
* Retrieve a text from an element or attribute using an xpath expression.
*
* @param contextElement The root element that the xpath expression is to be applied on.
* @param xpath The xpath that selects the element or attribute containing the text.
* @return The element text.
*/
public static String getElementText( Element contextElement, String xpath )
{
if ( contextElement == null )
{
return null;
}
Node node = selectNode( contextElement, xpath );
if ( node != null )
{
if ( node.getNodeType() == Node.TEXT_NODE )
{
return ( (Text) node ).getData();
}
else if ( node.getNodeType() == Node.ATTRIBUTE_NODE )
{
return ( (Attr) node ).getValue();
}
else
{
return getElementText( (Element) node );
}
}
return null;
}
public static String getElementText( Element element )
{
if ( element == null )
{
return null;
}
else
{
String value = DomUtils.getTextValue( element );
if ( value == null )
{
return null;
}
else if ( value.trim().length() == 0 )
{
return null;
}
else
{
return value;
}
}
}
/**
* Print a document to a specific stream.
*
* @param out The output stream.
* @param doc The document
* @param indent Use the specified indentation level.
*/
public static void printDocument( OutputStream out, Document doc, int indent )
{
Preconditions.checkNotNull(doc, "The supplied document is null (doc==" + doc + ")" );
serialize( out, doc, indent );
}
static public String elementToString( Element elem )
{
if ( elem == null )
{
return null;
}
return serialize(elem, 0);
}
private static String serialize( Node node, int indent )
{
final StringWriter writer = new StringWriter();
try {
serialize( writer, node, indent );
} finally {
Closeables.closeQuietly( writer );
}
return writer.toString();
}
private static void serialize( Writer out, Node node, int indent )
{
final StreamResult result = new StreamResult(out);
serialize(result, node, indent);
}
private static void serialize( OutputStream out, Node node, int indent )
{
final StreamResult result = new StreamResult(out);
serialize(result, node, indent);
}
private static void serialize(Result result, Node node, int indent)
{
try {
final Transformer transformer = TRANSFORMER_FACTORY.newTransformer();
transformer.setOutputProperty( OutputKeys.INDENT, indent > 0 ? "yes" : "no" );
transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" );
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setParameter("encoding", "UTF-8");
transformer.transform( new DOMSource(node), result );
} catch (Exception e) {
throw new RuntimeException( e );
}
}
public static void printDocument( Writer out, Document doc )
{
printDocument(out, doc, 0);
}
public static void printDocument( Writer out, Document doc, int indent )
{
Preconditions.checkNotNull(doc, "The supplied document is null (doc==" + doc + ")" );
serialize(out, doc, indent);
}
/**
* Use an XPath string to select a single node.
*
* @param contextNode The node to start searching from.
* @param xpath A valid XPath string.
* @return The first node matching the xpath, or null.
*/
public static Node selectNode( Node contextNode, String xpath )
{
return selectSingleNode(contextNode, xpath);
}
/**
* Use an XPath string to select all nodes matching the xpath. A nodelist is returned.
*
* @param contextNode The node to start searching from.
* @param xpath A valid XPath string.
* @return A nodelist containing the matching nodes.
*/
public static NodeList selectNodes( Node contextNode, String xpath )
{
return selectNodeList( contextNode, xpath );
}
/**
* Removes a child element from a parent element, and returns the next element to the removed one.
*
* @param parent The parent element to the child to be removed.
* @param child The child element to remove.
* @return The next element to the removed child.
*/
public static Element removeChildFromParent( Element parent, Element child )
{
Element previousChild = (Element) child.getPreviousSibling();
// If the child to remove is the first child
if ( previousChild == null )
{
parent.removeChild( child );
// Return the first child as the next child
return (Element) parent.getFirstChild();
}
// If the child is not the first child
else
{
parent.removeChild( child );
return (Element) previousChild.getNextSibling();
}
}
public static Node moveNode( Node node, Node toParent )
{
Node fromParent = node.getParentNode();
fromParent.removeChild( node );
return toParent.appendChild(node);
}
public static String serialize( Node n )
{
return serialize( n, false );
}
public static String serialize( Node n, boolean includeSelf )
{
DocumentFragment df = XMLTool.createDocument().createDocumentFragment();
NodeList children = n.getChildNodes();
// Check whether the child is a CDATA node
Node firstChild = n.getFirstChild();
if ( firstChild != null && firstChild.getNodeType() == Node.CDATA_SECTION_NODE )
{
return null;
}
if ( includeSelf )
{
df.appendChild( df.getOwnerDocument().importNode( n, true ) );
}
else
{
if ( children == null || children.getLength() == 0 )
{
return null;
}
// If only one node is found and it is a CDATA section, there is no need for serialization
if ( children.getLength() == 1 && children.item( 0 ).getNodeType() == Node.CDATA_SECTION_NODE )
{
return null;
}
for ( int i = 0; i < children.getLength(); i++ )
{
df.appendChild( df.getOwnerDocument().importNode( children.item( i ), true ) );
}
}
return serialize( df, 4 );
}
/**
* <p/> A generic method that builds the xml block of arbitrary depth. All fields starting with a specified prefix and an '_' character
* followed element names separated by '_' characters are translated to nested xml elements where the prefix names the top specified
* with the root element. The last element can be specified with one of the following prefix and suffixes: <ul> <li>@ (prefix) -
* element text will be set as an attribute to the parent element <li>_CDATA (suffix) - element text will be wrapped in a CDATA element
* <li>_XHTML (suffix) - element text will be turned into XHTML elements </ul> When reaching one of the prefix and suffixes, the element
* creating process will terminate for this field. </p> <p/> <p>Elements already created with the same path will be reused.</p> <p/>
* <p>Example:<br> The key 'contentdata_foo_bar_zot_CDATA' with the value '<b>alpha</b>' will transform into the following xml:
* <pre>
* <contentdata>
* <foo>
* <bar>
* <zot>
* <[!CDATA[<b><b>alpha</b></b>]]>
* </zot>
* </bar>
* </foo>
* </contentdata>
* </pre>
* </p>
*/
public static void buildSubTree( Document doc, Element rootElement, String prefix, ExtendedMap items )
{
for ( Object o : items.keySet() )
{
String key = (String) o;
String[] values = items.getStringArray( key );
StringTokenizer keyTokenizer = new StringTokenizer( key, "_" );
if ( prefix.equals( keyTokenizer.nextToken() ) )
{
Element tmpRoot = rootElement;
while ( keyTokenizer.hasMoreTokens() )
{
String keyToken = keyTokenizer.nextToken();
if ( "CDATA".equals( keyToken ) )
{
String data = values[0];
data = StringUtil.replaceAll( data, "\r\n", "\n" );
createCDATASection( doc, tmpRoot, data );
break;
}
else if ( "XML".equals( keyToken ) )
{
String xmlDoc = values[0];
Document tempDoc = domparse( xmlDoc );
tmpRoot.appendChild( doc.importNode( tempDoc.getDocumentElement(), true ) );
break;
}
else if ( "XHTML".equals( keyToken ) )
{
createXHTMLNodes( doc, tmpRoot, values[0], true );
break;
}
else if ( "FILEITEM".equals( keyToken ) )
{
FileItem fileItem = items.getFileItem( key );
tmpRoot.setAttribute( "field", fileItem.getFieldName() );
}
else if ( keyToken.charAt( 0 ) == '@' )
{
if ( values.length == 1 )
{
tmpRoot.setAttribute( keyToken.substring( 1 ), items.getString( key ) );
}
else
{
Element parentElem = (Element) tmpRoot.getParentNode();
String elementName = tmpRoot.getTagName();
Element[] elements = getElements( parentElem, elementName );
for ( int i = 0; i < elements.length && i < values.length; i++ )
{
elements[i].setAttribute( keyToken.substring( 1 ), values[i] );
}
if ( elements.length < values.length )
{
for ( int i = elements.length; i < values.length; i++ )
{
Element element = createElement( doc, parentElem, elementName );
element.setAttribute( keyToken.substring( 1 ), values[i] );
}
}
}
break;
}
else
{
if ( !keyTokenizer.hasMoreTokens() && values.length > 1 )
{
Element[] elements = getElements( tmpRoot, keyToken );
for ( int i = 0; i < elements.length && i < values.length; i++ )
{
createTextNode( doc, elements[i], values[i] );
}
if ( elements.length < values.length )
{
for ( int i = elements.length; i < values.length; i++ )
{
createElement( doc, tmpRoot, keyToken, values[i] );
}
}
}
else
{
Element elem = getElement( tmpRoot, keyToken );
if ( elem == null )
{
tmpRoot = createElement( doc, tmpRoot, keyToken );
}
else
{
tmpRoot = elem;
}
if ( !keyTokenizer.hasMoreTokens() )
{
createTextNode( doc, tmpRoot, items.getString( key ) );
break;
}
}
}
}
}
}
}
public static void mergeDocuments( Document destDoc, Document srcDoc )
{
mergeDocuments( destDoc, srcDoc, false );
}
public static void mergeDocuments( Document destDoc, String xmlDoc, boolean copyRoot )
{
Document srcDoc = XMLTool.domparse( xmlDoc );
mergeDocuments( destDoc, srcDoc, copyRoot );
}
public static void mergeDocuments( Element destDocElem, Document srcDoc, boolean copyRoot )
{
if ( copyRoot )
{
Element element = srcDoc.getDocumentElement();
destDocElem.appendChild( destDocElem.getOwnerDocument().importNode( element, true ) );
}
else
{
NodeList nodes = srcDoc.getDocumentElement().getChildNodes();
for ( int i = 0; i < nodes.getLength(); i++ )
{
destDocElem.appendChild( destDocElem.getOwnerDocument().importNode( nodes.item( i ), true ) );
}
}
}
public static void mergeDocuments( Document destDoc, Document srcDoc, boolean copyRoot )
{
Element destDocElem = destDoc.getDocumentElement();
if ( copyRoot )
{
Element element = srcDoc.getDocumentElement();
destDocElem.appendChild( destDoc.importNode( element, true ) );
}
else
{
NodeList nodes = srcDoc.getDocumentElement().getChildNodes();
for ( int i = 0; i < nodes.getLength(); i++ )
{
destDocElem.appendChild( destDoc.importNode( nodes.item( i ), true ) );
}
}
}
public static void replaceElement( Element oldElem, Element newElem )
{
Document doc = oldElem.getOwnerDocument();
Node importedNew = doc.importNode( newElem, true );
oldElem.getParentNode().replaceChild( importedNew, oldElem );
}
public static int getElementIndex( Element elem )
{
Element[] elems = XMLTool.getElements( (Element) elem.getParentNode(), elem.getNodeName() );
for ( int i = 0; i < elems.length; i++ )
{
if ( elems[i] == elem )
{
return i;
}
}
return 0;
}
public static Document deflatedBytesToDocument( byte[] bytes )
{
ByteArrayInputStream in = new ByteArrayInputStream( bytes );
InflaterInputStream iis = new InflaterInputStream( in );
return domparse( iis );
}
public static void sortChildElements( Element elem, String orderByAttribute, boolean descending, boolean recursive )
{
Element[] childElems = XMLTool.getElements( elem );
if ( childElems == null )
{
return;
}
Arrays.sort( childElems, new ElementComparator( orderByAttribute, descending ) );
for ( Element childElem : childElems )
{
elem.appendChild( childElem );
if ( recursive )
{
sortChildElements( childElem, orderByAttribute, descending, recursive );
}
}
}
public static Document createDocument( Element root )
{
Document doc = createDocument();
doc.appendChild( doc.importNode( root, true ) );
return doc;
}
public static Element renameElement( Element elem, String newName )
{
Document doc = elem.getOwnerDocument();
// Create an element with the new name
Element elem2 = doc.createElement( newName );
// Copy the attributes to the new element
NamedNodeMap attrs = elem.getAttributes();
for ( int i = 0; i < attrs.getLength(); i++ )
{
Attr attr2 = (Attr) doc.importNode( attrs.item( i ), true );
elem2.getAttributes().setNamedItem( attr2 );
}
// Move all the children
while ( elem.hasChildNodes() )
{
elem2.appendChild( elem.getFirstChild() );
}
// Replace the old node with the new node
elem.getParentNode().replaceChild( elem2, elem );
return elem2;
}
private static void loadTidyProperties()
{
try {
final Properties props = new Properties();
props.load( XMLTool.class.getResourceAsStream( "tidy.properties" ) );
TIDY.setConfigurationFromProps( props );
} catch (Exception e) {
throw new Error("Failed to read tidy.properties", e);
}
}
}