/* * Sample XML Helper routines. * * This sample is provided on as "as is" basis. Use it at your own risk. * * The purpose of this sample is to illustrate the type of processing that has been found * useful in XML helper routines, and suggest some of the DOM API calls that you might like * to use. * * The sample evolved as a series of routines written to help a single application. * It has not been designed as a generic XML helper. Some aspects are probably not best * practice, and some are certainly not efficient. Some of the functions implemented * here are probably available in the DOM. * * The exception handling and logging are based on what was required for the application. * You are likely to need something different. * * There are better ways of converting XML documents to strings than those illustrated in * this code. The application for which this was written only uses these routines during * testing, where the routines' simple output is useful. * * The code is entirely procedural, because that suited the application that used this. * * For large XML documents, consider using the streaming API for XML (StAX), rather than * the DOM. * */ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilder; import org.w3c.dom.*; import javax.xml.transform.TransformerFactory; import javax.xml.transform.Transformer; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.stream.StreamSource; import javax.xml.transform.stream.StreamResult; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import java.io.StringReader; import java.io.StringWriter; import java.util.logging.Logger; import java.util.logging.Level; /** *

A series of useful routines for accessing XML.

* *

These have been collected into a single place * for a variety of reasons:

* *

This is basically a series of static methods that do just * what their method names suggest.

* *

All unusual exceptions are caught, logged, and rethrown as * Exceptions.

* */ public class XMLHelper { /** * Create a new empty document. */ public static Document newDocument() throws Exception { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); return builder.newDocument(); } catch (Exception e) { String message = "Internal XML error: can not create empty XML document."; Logger.getLogger("XMLHelper").log(Level.SEVERE,message,e); throw new Exception(message); } } /** * Create a document with the given element name as the * high level element. This element can be accessed using * doc.getDocumentElement(); * * @param rootElementName Name of root element to create. */ public static Document newDocumentWithRoot(String rootElementName) throws Exception { Document output = newDocument(); Element topLevel = output.createElement(rootElementName); output.appendChild(topLevel); return output; } /** * Create a new document from a file name. * NB This isn't suitable if you want to continue if there is a parse error. * * @param fileName Name of file from which to create the XML document. */ public static Document newDocumentFromFile(String fileName) throws Exception { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // Set the factory to namespace aware, so that it can be used for XSLT documents factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); return builder.parse(fileName); } catch (Exception e) { String message = "Can not create XML document from file " + fileName + "."; Logger.getLogger("XMLHelper").log(Level.SEVERE,message,e); throw new Exception(message); } } /** * Return the root element name, or null if there isn't one. * * @param document Document for which root element name is to be returned. */ public static String getRootName(Document document) { if ( document == null ) { return null; } Node n = document.getDocumentElement(); if ( n == null ) { return null; } else { return n.getNodeName(); } } /** * Add a child with a given name. This adds unconditionally, i.e. * whether or not there is already a child with that name. * * @param parentElement Element to which child is to be added. * @param childName Name of child element to add. */ public static Element addNamedChild(Element parentElement, String childName) { Element newChild = parentElement.getOwnerDocument().createElement(childName); parentElement.appendChild(newChild); return newChild; } /** * Retrieve a child with a given name, creating it if necessary. * * @param parentElement Element from which child is to be retrieved or created. * @param childName Name of child element to retrieve or creatae */ public static Element setNamedChild(Element parentElement, String childName) { Element child = getNamedChild(parentElement,childName); if ( child == null ) { child = addNamedChild(parentElement,childName); } return child; } /** * Get the (first) child element with a given name. * If there are no child elements, return null. * If you want all of them, use element.getElementsByTagName. * * @param parentElement Element from which child is to be retrieved. * @param childName Name of child element to retrieve. */ public static Element getNamedChild(Element parentElement,String childName) { NodeList children = parentElement.getElementsByTagName(childName); if ( children.getLength() > 0 ) { return (Element) children.item(0); } else { return null ; } } /** * Remove the child element with a given name. * If there are more than one, all are removed. * * @param parentElement Element from which child is to be removed. * @param childName Name of child element to remove. */ public static void removeNamedChild(Element parentElement, String childName) { NodeList children = parentElement.getElementsByTagName(childName); for ( int i = 0 ; i < children.getLength() ; i++ ) { parentElement.removeChild(children.item(i)); } } /** * Set the text of an element. All existing content of the element is removed. * * @param element Element for which text will be set. * @param text The text. */ public static void setElementText(Element element, String text) { while ( element.getFirstChild() != null ) { element.removeChild(element.getFirstChild()); } if ( text != null ) { Text theText = element.getOwnerDocument().createTextNode(text); element.appendChild(theText); } } /** * Get the text of an element. This returns the value of the first child * of the element. This will retrieve text that has been set with setElementText. * * @param element Element for which text will be returned. */ public static String getElementText(Element element) { Node firstChild = element.getFirstChild(); if ( firstChild == null ) { return null ; } else if (firstChild.getNodeType() == Node.TEXT_NODE || firstChild.getNodeType() == Node.CDATA_SECTION_NODE ) { return firstChild.getNodeValue(); } else { return null ; } } /** * Set the text of a child element, given its name. * If necessary, create the child element. * Text can be retrieved using getNamedChildText. * * @param parentElement Parent element of child which will have text set. * @param childName Name of child element which will have text set. * @param text The text. */ public static void setNamedChildText(Element parentElement, String childName , String text) { Element child = setNamedChild(parentElement,childName); setElementText(child,text); } /** * Get the text of a child element given its name. If there is no such child, * or the child value is null, return null. * * @param parentElement Parent element of child which will have text returned. * @param childName Name of child element which will have text returned. */ public static String getNamedChildText(Element parentElement, String childName) { Element firstChild = getNamedChild(parentElement, childName); if ( firstChild == null ) { return null ; } else { return getElementText(firstChild); } } /** * Get the text of a child element, given its name, and default if not present. * This works just like getNamedChildText (except that it returns the default * rather than null if the item is not present). * * @param parentElement Parent element of child which will have text returned. * @param childName Name of child element which will have text returned. * @param defaultText Text to be returned if there is no such child or no child value. */ public static String getNamedChildText(Element parentElement, String childName, String defaultText) { String t = getNamedChildText(parentElement,childName); if ( t == null ) { t = defaultText; } return t; } /** * Get the text of a child element, and raise an Exception if it is not * present. Only use this for reading mandatory configuration parameters. * * @param parentElement Parent element of child which will have text returned. * @param childName Name of child element which will have text returned. */ public static String getNamedChildTextMandatory(Element parentElement, String childName) throws Exception { String t = getNamedChildText(parentElement,childName); if ( t == null ) { throw new Exception("Can not find text for XML element <" + childName + ">."); } return t; } /** *

Convert an XML string to a Document.

*

Throw an Exception if this is not possible.

*

NB This isn't suitable if you want to continue if there is a parse error.

* * @param text String to be used to create a document. */ public static Document stringToDocument(String text) throws Exception { DocumentBuilderFactory factory; DocumentBuilder builder; try { factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); builder = factory.newDocumentBuilder(); InputSource in = new InputSource(new StringReader(text)); return builder.parse(in); } catch ( Exception e ) { String message = "Can not create XML document from string."; Logger.getLogger("XMLHelper").log(Level.SEVERE,message,e); throw new Exception(message); } } /** *

Convert a document to an XML string.

*

This only copies over the most usual * things, i.e. element, attributes and data. It doesn't copy over * processing instructions and comments.

* * @param doc Document to be converted to a string. */ public static String documentToString(Document doc) { StringBuffer sb = new StringBuffer(10000); return nodeToString(sb,doc.getDocumentElement()).toString(); // String s = nodeToString(doc.getDocumentElement()); // System.out.println(s); // return s; } /** *

Append a string representation of a node to a string buffer.

*

This only converts elements, attributes and data.

* * @param node Node to be converted to a string. */ public static StringBuffer nodeToString(StringBuffer sb, Node node) { switch (node.getNodeType()) { case Node.ELEMENT_NODE: sb.append('<'); sb.append(node.getNodeName()); if ( node.hasAttributes() ) { // Process attributes NamedNodeMap nnm = node.getAttributes(); for ( int i = 0 ; i < nnm.getLength(); i++ ) { nodeToString(sb,nnm.item(i)); } } if ( node.hasChildNodes() ) { sb.append('>'); // Now process all the other children that aren't attributes NodeList nl = node.getChildNodes(); for ( int i = 0 ; i < nl.getLength(); i++ ) { // I don't think children are ever attributes if ( nl.item(i).getNodeType() != Node.ATTRIBUTE_NODE ) { nodeToString(sb,nl.item(i)); } } sb.append("'); } else { sb.append("/>"); } break; case Node.ATTRIBUTE_NODE: sb.append(' '); sb.append(node.getNodeName()); sb.append("=\""); escapeXML(sb,node.getNodeValue()); sb.append('\"'); break; case Node.TEXT_NODE: case Node.CDATA_SECTION_NODE: escapeXML(sb,node.getNodeValue()); break; default: // Do nothing - not interested break; } return sb; } /** *

Convert a node to an XML string.

*

This only converts elements, attributes and data.

*

Use documentToString to convert a whole document to an XML string.

* * @param node Node to be converted to a string. */ public static String nodeToString(Node node) { StringBuffer sb = new StringBuffer(); return nodeToString(sb,node).toString(); } /** * Append an escaped XML string to a StringBuffer. */ public static StringBuffer escapeXML(StringBuffer sb, String s){ int p; char c; if ( s == null ) { return sb; } for ( p = 0 ; p < s.length() ; p++ ) { c = s.charAt(p); if ( c == '<' ) { sb.append("<"); } else if ( c == '>' ) { sb.append(">"); } else if ( c == '&' ) { sb.append("&"); } else if ( c == '\'' ) { sb.append("'"); } else if ( c == '\"' ) { sb.append("""); } else if ( c > 127 ) { sb.append("&#" + (int)c + ";"); } else { sb.append(c); } } return sb; } /** * Convert string so that all characters are correctly escaped in XML. */ public static String escapeXML(String s){ StringBuffer sb = new StringBuffer(s.length() + 20); return escapeXML(sb,s).toString(); } /** *

Transform a document using an XSLT stylesheet.

*

This only works for XML to XML transformations.

*

XSLT parameters are not supported.

*

Warning: This is not performant because it does not pool transformers. * For high-performance applications, use an alternative method.

* * @param source Document to be transformed * @param stylesheet The XSLT stylesheet. */ public static Document transform (Document source, Document stylesheet) throws Exception { TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(new DOMSource(stylesheet)); Document output = XMLHelper.newDocument(); DOMSource domSource = new DOMSource(source); DOMResult domResult = new DOMResult(output); transformer.transform(domSource, domResult); return output; } /** *

Create a document from an individual element.

*/ public static Document elementToDocument(Element element) throws Exception { Document document = XMLHelper.newDocument(); Node targetRootElement = document.importNode(element,true); document.appendChild(targetRootElement); return document; } /** *

Return the first element within another element.

*

Return null if there isn't one.

*/ public static Element getFirstElement(Element element) throws Exception { NodeList childNodes = element.getChildNodes(); for ( int i = 0 ; i < childNodes.getLength(); i++ ) { if ( childNodes.item(i).getNodeType() == Node.ELEMENT_NODE ) { return (Element) childNodes.item(i); } } return null; } }