/******************************************************************************* * Copyright (c) 2008, 2009 Bug Labs, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - Neither the name of Bug Labs, Inc. nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ package com.buglabs.util; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * This class represents an XML Node. Any DOM document is a tree of <code>XMLNode</code>s. * * @author kgilmer * */ public class XmlNode { /** * Name of node. */ private String tagName = null; /** * Content of node. */ private String text = null; private Map<String, String> attributes; private List<XmlNode> childElements; private XmlNode parentNode = null; /** * Create an empty node. * @param tagName */ public XmlNode(String tagName) { guardInvalidName(tagName); this.tagName = tagName; attributes = new HashMap<String, String>(); } /** * Create a node with a String value. * @param tagName * @param value */ public XmlNode(String tagName, String value) { this(tagName); if (value.length() > 0) { this.text = value; } } /** * Create a node with children. * @param tagName * @param children */ public XmlNode(String tagName, List<XmlNode> children) { this(tagName); this.childElements = children; } /** * Create a node with a parent. * @param parent * @param tagName */ public XmlNode(XmlNode parent, String tagName) { this(tagName); try { parent.addChildElement(this); } catch (SelfReferenceException e) { // this should never happen...new object creation. e.printStackTrace(); } this.parentNode = parent; } /** * Create a node with a parent and children. * @param parent * @param tagName * @param children */ public XmlNode(XmlNode parent, String tagName, List<XmlNode> children) { this(tagName); try { parent.addChildElement(this); } catch (SelfReferenceException e) { // this should never happen...new object creation. e.printStackTrace(); } this.parentNode = parent; this.childElements = children; } /** * Create a node with a parent and a String value. * @param parent * @param tagName * @param value */ public XmlNode(XmlNode parent, String tagName, String value) { this(parent, tagName); if (value != null && value.length() > 0) { this.text = value; } } /** * @return true if the node has childern, false otherwise. */ public boolean hasChildren() { return childElements != null && childElements.size() > 0; } /** * @return Name of this node. */ public String getName() { return tagName; } /** * @param name * @param value * @return instance of self */ public XmlNode addAttribute(String name, String value) { this.getAttributes().put(name, value); return this; } /** * Set the name of the tag. * @param tagName */ public void setName(String tagName) { this.tagName = tagName; } /** * @return */ public String getValue() { return text; } public void setValue(String text) { if (text != null && text.length() > 0 && hasChildren()) { throw new RuntimeException("Cannot set content on a node that has children."); } //If an empty string is passed, set text to null. This allows children to be added to node. if (text != null && text.length() == 0) { this.text = null; } else { this.text = text; } } /** * Get contents of attribute, or null if attribute does not exist. * @param name * @return */ public String getAttribute(String name) { return (String) attributes.get(name); } /** * Set or overwrite existing attibute of node. * @param name * @param value */ public void setAttribute(String name, String value) { attributes.put(name, value); } /** * Clear the value of the XML node. */ public void clearValue() { setValue(null); } /** * @param element * @return * @throws SelfReferenceException */ public XmlNode addChildElement(XmlNode element) throws SelfReferenceException { if (element == this) { throw new SelfReferenceException(element); } if (this.text != null) { throw new RuntimeException("Cannot add child elements to a node that has content."); } getChildren().add(element); return element; } /** * Equivalent to addChildElement except that unchecked exception is thrown on self referencing call. * @param element * @return */ public XmlNode addChild(XmlNode element) { if (element == this) { throw new RuntimeException("Cannot add node to itself."); } if (this.text != null) { throw new RuntimeException("Cannot add child elements to a node that has content."); } getChildren().add(element); return element; } /** * @return children of node, or empty list if no children exist. */ public List<XmlNode> getChildren() { if (childElements == null) { childElements = new ArrayList<XmlNode>(); } return childElements; } /** * @return Map of attribute names and values as Strings. */ public Map<String, String> getAttributes() { return attributes; } /* (non-Javadoc) * @see java.lang.Object#toString() */ public String toString() { return printXml(""); } private String printXml(String indent) { String s = indent + "<" + this.tagName; if (attributes != null) { for (Iterator<String> i = attributes.keySet().iterator(); i.hasNext();) { String attrib = i.next(); // Only display attributes with non-null values. if (attributes.get(attrib) != null) { String value = attributes.get(attrib).toString(); s += " " + makeSafeXML(attrib) + "=\"" + makeSafeXML(value) + "\""; } } } if ((this.childElements == null || this.childElements.size() == 0) && text == null) { s += "/>\n"; return s; } if (childElements != null && childElements.size() > 0) { s += ">\n"; for (Iterator<XmlNode> i = getChildren().iterator(); i.hasNext();) { s += i.next().printXml(indent + " "); } s += indent; } else if (text != null) { s += ">" + makeSafeXML(text); } s += "</" + makeSafeXML(tagName) + ">\n"; return s; } /** * @param name * @return true if a node with the given name exists, false otherwise. */ public boolean childExists(String name) { if (!hasChildren()) { return false; } if (getChild(name) != null) { return true; } return false; } /** * @param nodeName * @return node with given name if exists or null otherwise. */ public XmlNode getChild(String nodeName) { if (childElements == null) { return null; } for (Iterator<XmlNode> i = getChildren().iterator(); i.hasNext();) { XmlNode child = i.next() ; if (child.tagName.equals(nodeName)) { return child; } } return null; } /** * Retrieve a node from this element using xpath-like notation. * * Ex. for <root><leaf1><leaf1><leaf2></root> call with "root/leaf1" to return first occurrence leaf1 node. * * @param path * @return */ public XmlNode getFirstElement(String path) { String[] elems = StringUtil.split(path, "/"); XmlNode root = this; for (int i = 0; i < elems.length; ++i) { root = (XmlNode) root.getChild(elems[i]); if (root == null) { break; } } return root; } /** * @return The parent node or <code>null</code> if root node in DOM. */ public XmlNode getParent() { return parentNode; } /** * @param parent * @throws SelfReferenceException */ public void setParent(XmlNode parent) throws SelfReferenceException { if (parentNode != null) { parentNode.getChildren().remove(this); } if (parent != null) { parent.addChildElement(this); } parentNode = parent; } public static String makeSafeXML(String string) { //garbage in, garbage out if (string == null) { return null; } //no occurences of any bad chars, don't spend the time examining every char if (string.indexOf('&') == -1 && string.indexOf('\'') == -1 && string.indexOf('"') == -1 && string.indexOf('<') == -1 && string.indexOf('>') == -1) { return string; } String temp = ""; for (int index = 0; index < string.length(); index++) { if (string.charAt(index) == '&') { if (index + 4 < string.length() && string.substring(index + 1, index + 4).equals("amp;")) { continue; } else if (index + 5 < string.length() && string.substring(index + 1, index + 5).equals("apos;")) { continue; } else if (index + 5 < string.length() && string.substring(index + 1, index + 5).equals("quot;")) { continue; } else if (index + 3 < string.length() && string.substring(index + 1, index + 3).equals("lt;")) { continue; } else if (index + 3 < string.length() && string.substring(index + 1, index + 3).equals("gt;")) { continue; } temp += "&"; } else if (string.charAt(index) == '"') temp += """; else if (string.charAt(index) == '\'') temp += "'"; else if (string.charAt(index) == '<') temp += "<"; else if (string.charAt(index) == '>') temp += ">"; else temp += string.charAt(index); } return temp; } /** * Throw a runtime exception if a name has invalid characters for an XML node name. * * See http://www.xml.com/pub/a/2001/07/25/namingparts.html * * @param name */ private void guardInvalidName(String name) { if (name.endsWith(":") || name.contains("&") || name.contains("%") || name.contains("@")) throw new RuntimeException("Invalid node name: " + name); } }