/******************************************************************************* * $Id$ * $Author$ * $Date$ * * Copyright 2002-2003 YAJUL Developers, Joshua Davis, Kent Vogel. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * ******************************************************************************/ package org.yajul.xml; import org.w3c.dom.*; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.yajul.collections.CollectionUtil; import org.yajul.util.StringUtil; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Map; import static org.yajul.collections.CollectionUtil.newArrayList; /** * Provides commonly used DOM operations in convenient methods. * Good for replacing many lines of DOM code with a nice one-liner. * Works with any JAXP 1.1 implementation (e.g. XERCES/XALAN). * * @author Joshua Davis */ public class DOMUtil { /** * The default URI used for parsing XML documents. * */ private static final String DEFAULT_URI = "file://"; /** * Creates a new DOM document with the specified root element * as the 'document element'. * * @param rootElementTag The tag name for the root element. * @return Document - The new document. * @throws ParserConfigurationException - If the document could not * be created. */ public static Document createDocument(String rootElementTag) throws ParserConfigurationException { Document document = createDocument(); Element root = document.createElement(rootElementTag); document.appendChild(root); return document; } /** * Creates a new DOM document with no root element. * * @return Document - The new document. * @throws ParserConfigurationException - If the document could not * be created. */ public static Document createDocument() throws ParserConfigurationException { DocumentBuilder builder = getDocumentBuilder(); return builder.newDocument(); } /** * Adds a child element to the specified parent element. * * @param document The document that contains the parent. * @param parent The parent element. * @param childTag The tag name for the new child. * @return Element - The new child element. */ public static Element addChild(Document document, Element parent, String childTag) { Element child = document.createElement(childTag); parent.appendChild(child); return child; } /** * Adds a child element to the specified parent element containing * some text. * * @param document The document that contains the parent. * @param parent The parent element. * @param childTag The tag name for the new child. * @param text The text that will be inside the new child. Note: If 'text' is null, a * zero length string will be used in order to avoid NPE's (in XALAN2) later on. * @return Element - The new child element. */ public static Element addChildWithText(Document document, Element parent, String childTag, String text) { Element child = addChild(document, parent, childTag); Text t = document.createTextNode(text == null ? "" : text); child.appendChild(t); return child; } /** * Returns a new document builder. * * @return DocumentBuilder - A new document builder. * @throws ParserConfigurationException - If the JAXP * implementation is configured incorrectly */ public static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); return factory.newDocumentBuilder(); } // --- Navigation methods --- /** * Returns an array of elements, given a node list. * * @param nodeList The node list. * @return a list of elements */ public static List<Element> toElementList(NodeList nodeList) { int size = nodeList.getLength(); List<Element> list = newArrayList(size); Node n; for (int i = 0; i < size; i++) { n = nodeList.item(i); if (n.getNodeType() != Node.ELEMENT_NODE) continue; list.add((Element) n); } return list; } /** * Returns the list of child elements in a parent element * * @param parent The parent element. * @return the list of elements in the parent */ public static List<Element> getChildElements(Element parent) { NodeList children = parent.getChildNodes(); return toElementList(children); } /** * Returns a list of child elements. * * @param parent The parent document. * @return the list of elements in the parent */ public static List<Element> getChildElements(Document parent) { return getChildElements(parent.getDocumentElement()); } /** * Returns a list of ALL child elements in this element (either directly * or inside a descendant) with the specified tag name. * * @param parent The parent element. * @param tag The child element tag name. * @return The list of ALL elements in parent that have the * specified tag name. */ public static List<Element> getChildElements(Element parent, String tag) { NodeList children = parent.getElementsByTagName(tag); return toElementList(children); } /** * Returns an array of child elements with the specified tag name. * * @param parent The parent element. * @param tag - The child element tag name. * @return The list of ALL elements in parent that have the * specified tag name. */ public static List<Element> getChildElements(Document parent, String tag) { return getChildElements(parent.getDocumentElement(), tag); } /** * Returns the text inside an element as a string. If there are multiple * text nodes, they will be concantenated. * * @param element The element containing the text. * @return String - The text in the element. */ public static String getChildText(Element element) { // Iterate through the children and append text nodes. StringBuilder sb = new StringBuilder(); Node n = element.getFirstChild(); Text t; while (n != null) { if (n.getNodeType() == Node.TEXT_NODE) { t = (Text) n; sb.append(t.getData()); } n = n.getNextSibling(); } // for return sb.toString(); } /** * Returns the names of all the attributes in the element as an array of strings. * * @param elem The element. * @return the names of all the attributes in the element as an array of strings. */ public static List<String> getAttributeNames(Element elem) { NamedNodeMap map = elem.getAttributes(); List<String> names = newArrayList(map.getLength()); for (int i = 0; i < map.getLength(); i++) names.add(((Attr) map.item(i)).getName()); return names; } /** * Returns a map of all the attributes in the element. The keys will be the attribute names and * the values will be the attribute values. * * @param elem The element. * @return a map of all the attributes in the element. The keys will be the attribute names and * the values will be the attribute values. */ public static Map<String, String> getAttributeMap(Element elem) { NamedNodeMap nodeMap = elem.getAttributes(); Map<String, String> map = CollectionUtil.newHashMap(nodeMap.getLength()); for (int i = 0; i < nodeMap.getLength(); i++) { Attr attr = ((Attr) nodeMap.item(i)); map.put(attr.getName(), attr.getValue()); } return map; } /** * Returns the value of the specified attribute as a boolean primitive. * * @param elem The element. * @param attributeName The attribute name in the element. * @param defaultValue The default value. * @return the value of the specified attribute as a boolean primitive. */ public static boolean getBooleanAttribute(Element elem, String attributeName, boolean defaultValue) { String str = elem.getAttribute(attributeName); boolean val = defaultValue; if (!StringUtil.isEmpty(str)) val = "true".equals(str); return val; } /** * Returns the value of the specified attribute as an int primitive. * * @param elem The element. * @param attributeName The attribute name in the element. * @param defaultValue The default value. * @return the value of the specified attribute as an int primitive. */ public static int getIntAttribute(Element elem, String attributeName, int defaultValue) { String str = elem.getAttribute(attributeName); int val = defaultValue; if (!StringUtil.isEmpty(str)) val = Integer.parseInt(str); return val; } // --- Parsing methods --- /** * Parses the input stream and returns a DOM document. * * @param input - The input stream to parse. * @return Document - The DOM Document. * @throws javax.xml.parsers.ParserConfigurationException * - If the JAXP * implementation is configured incorrectly * @throws org.xml.sax.SAXException - If the document could not be parsed * @throws java.io.IOException - If there was something wrong with the * input stream. */ public static Document parse(InputStream input) throws javax.xml.parsers.ParserConfigurationException, org.xml.sax.SAXException, java.io.IOException { DocumentBuilder builder = getDocumentBuilder(); return builder.parse(input, DEFAULT_URI); } /** * Parse a file using it's name. * * @param fileName The name of the file to parse. * @return A DOM document. * @throws javax.xml.parsers.ParserConfigurationException * - If the JAXP * implementation is configured incorrectly * @throws org.xml.sax.SAXException - If the document could not be parsed * @throws java.io.IOException - If there was something wrong with the * input stream. */ public static Document parseFile(String fileName) throws javax.xml.parsers.ParserConfigurationException, org.xml.sax.SAXException, java.io.IOException { return parse( new java.io.BufferedInputStream( new java.io.FileInputStream( fileName))); } /** * Parse an XML file. * * @param file The file to parse. * @return A DOM document. * @throws javax.xml.parsers.ParserConfigurationException * - If the JAXP * implementation is configured incorrectly * @throws org.xml.sax.SAXException - If the document could not be parsed * @throws java.io.IOException - If there was something wrong with the * input stream. */ public static Document parseFile(File file) throws javax.xml.parsers.ParserConfigurationException, org.xml.sax.SAXException, java.io.IOException { return parse( new java.io.BufferedInputStream( new java.io.FileInputStream( file))); } /** * Parse an XML resource. * * @param resourceName The resource to parse. * @return A DOM document. * @throws javax.xml.parsers.ParserConfigurationException * If the JAXP * implementation is configured incorrectly * @throws org.xml.sax.SAXException If the document could not be parsed * @throws java.io.IOException If there was something wrong with the * input stream. */ public static Document parseResource(String resourceName) throws javax.xml.parsers.ParserConfigurationException, org.xml.sax.SAXException, java.io.IOException { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); InputStream is = classLoader.getResourceAsStream(resourceName); if (is == null) throw new java.io.IOException("Resource not found: '" + resourceName + "'"); return parse(is); } /** * Parse the input with the specified reader, producing a DOM Document. * * @param reader The reader, which will produce SAX2 events. * @param input The input source * @return The DOM document. * @throws TransformerConfigurationException * if the transformer doesn't support this operation. * @throws IOException if the input cannot be read * @throws SAXException if the input cannot be parsed */ public static Document parse(XMLReader reader, InputSource input) throws TransformerConfigurationException, IOException, SAXException { // Use the transformer factory to create a content handler that will build a DOM. TransformerFactory factory = TransformerFactory.newInstance(); if (!factory.getFeature(SAXTransformerFactory.FEATURE)) throw new TransformerConfigurationException("The transformer factory does not support SAX transformation!"); TransformerHandler handler = ((SAXTransformerFactory) factory).newTransformerHandler(); // Create a DOM result for the transformation. DOMResult domResult = new DOMResult(); handler.setResult(domResult); // Register the content handler with the reader, and parse the input. reader.setContentHandler(handler); reader.parse(input); // Return the resulting document. return (Document) domResult.getNode(); } }