// package xmlutils; import java.io.ByteArrayInputStream; import java.io.File; import java.util.List; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.io.IOException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Element; import org.w3c.dom.Document; import org.w3c.dom.Text; import org.xml.sax.SAXException; /** * DOM represents an XML source at a rather low level, this class represents it at a higher level. * It is a layer on top of DOM. It considers an XML source to consist of a tree of XML elements only. * Each having the following properties only: name, value (corresponding to only one org.w3c.dom.Text node), * attributes list, child elements list. * This class represents such an XML element. * It is suited for simple parsing of an XML source containing structured data, like system properties, * saved objects, etc. * Because it can have only one Text node it is not suited for parsing an XML source containing * mixed content XML elements, like structured text files. *

* NB.1. Since Java 1.4 standard includes a DOM parser, this class is all you need. The whole package/libary * consists of this class only. *

* NB.2. The jar file of this class is even smaller than its NanoXml (http://nanoxml.n3.net) counterpart. * And maybe it is even simpler to use. * * @author Ronald Koster * @version 0.9 * @see Ronald Koster - Home Page */ public class XmlElement { private String name; private String value; private HashMap attributes; private List childElements; public String getName() { return name; } public void setName(String aName) { name = aName; } public String getValue() { return value; } public void setValue(String aValue) { value = aValue; } public HashMap getAttributes() { return attributes; } public void setAttributes(HashMap aAttributes) {attributes = aAttributes; } public List getChildElements() { return childElements; } public void setChildElements(List aList) { childElements = aList; } /** * Gets attribute with name aName. * Encapsulates attributes#get. * @param aName See above. * @return Idem. */ public String getAttribute(String aName) { if (attributes == null) { return null; } return (String) attributes.get(aName); } /** * Sets/Adds an attribute. Set if it already exists, else add. * Encapsulates attributes#put. * @param aName Attribute name. * @param aValue Attribute value. * @return If atttribute already existed, the old value, else null. */ public String setAttribute(String aName, String aValue) { if (attributes == null) { attributes = new HashMap(); } return (String) attributes.put(aName, aValue); } /** * Removes an attribute. * Encapsulates attributes#remove. * @param aName Attribute name. * @return Value of removed attribute. */ public String removeAttribute(String aName) { if (attributes == null) { return null; } return (String) attributes.remove(aName); } /** * Gets child element with index aIndex. * Encapsulates childElements#get(int). * @param aIndex See above. */ public XmlElement getChildElement(int aIndex) { if (childElements == null || aIndex < 0 || aIndex >= childElements.size()) { return null; } return (XmlElement) childElements.get(aIndex); } /** * Get first occurence of child element with name aName. * @param aName See above. */ public XmlElement getChildElement(String aName) { return getChildElement(aName, 0); } /** * Get aIndex-th occurence of child element with name aName. * @param aName See above. * @param aIndex Idem. */ public XmlElement getChildElement(String aName, int aIndex) { if (childElements == null || aName == null|| aIndex < 0 || aIndex >= childElements.size()) { return null; } int counter = -1; for (int i = 0; i < childElements.size(); i++) { XmlElement elem = getChildElement(i); if (aName.equals(elem.getName())) { counter++; } if (counter == aIndex) { return elem; } } return null; } /** * Appends a child element to this element's child elements list. * Encapsulates childElements#add(Object). * @param aChild See above. */ public void addChildElement(XmlElement aChild) { if (childElements == null) { childElements = new ArrayList(); } childElements.add(aChild); } /** * Inserts a child element to this element's child elements list at position aIndex. * Encapsulates childElements#add(int, Object). * @param aIndex See above. * @param aChild Idem. */ public void addChildElement(int aIndex, XmlElement aChild) { if (childElements == null) { childElements = new ArrayList(); } childElements.add(aIndex, aChild); } /** * Removes child element at postion aIndex from this element's child elements list. * Encapsulates childElements#remove(int). * @param aIndex See above. */ public XmlElement removeChildElement(int aIndex) { if (childElements == null) { return null; } return (XmlElement) childElements.remove(aIndex); } /** * Creates an XmlElement from aNode. Includes recursively all relevant child nodes if any. *

* NB. Only limited support for mixed content elements. For each node only the first org.w3c.dom.Text * node is copied to XmlElement#value field. Other Text nodes are ignored. * @param aNode Must be of type org.w3c.dom.Document or org.w3c.dom.Element. * @return XmlElement created. * @throws ClassCastException if aNode is of invalid type. * @throws NullPointeException if aNode == null. */ public static XmlElement createFromNode(Node aNode) { Element node; if (aNode instanceof Document) { node = ((Document) aNode).getDocumentElement(); } else { node = (Element) aNode; } NodeList children = node.getChildNodes(); XmlElement elem = new XmlElement(); // Name. elem.setName(node.getNodeName()); // Value. if (children != null) { for (int i = 0; i < children.getLength(); i++) { if (children.item(i) instanceof Text) { String s = children.item(i).getNodeValue(); elem.setValue(s); } break; } } // Attributes. NamedNodeMap atts = node.getAttributes(); if (atts != null) { for (int i = 0; i < atts.getLength(); i++) { String attName = atts.item(i).getNodeName(); String attValue = atts.item(i).getNodeValue(); if (attValue != null) { elem.setAttribute(attName, attValue); } } } // Child nodes. if (children != null) { for (int i = 0; i < children.getLength(); i++) { if (children.item(i) instanceof Element) { elem.addChildElement(createFromNode(children.item(i))); // NB. Recursive invokation! } } } return elem; } /** * Creates a org.w3c.org.Element from this XmlElement. * @return See above. * @throws SAXException if conversion failes. * @throws ParserConfigurationException idem. * @throws IOException idem. */ public Element toNode() throws SAXException, ParserConfigurationException, IOException { String xmlString = toString(); if (xmlString == null) { return null; } ByteArrayInputStream is = new ByteArrayInputStream(xmlString.getBytes()); DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = fac.newDocumentBuilder(); Document doc = builder.parse(is); return doc.getDocumentElement(); } /** * Converts this class to an XML string. Excluding the <?xml... and <!DOCTYPE... stuff. *

* NB. Does not support the occurence of double and single qoutes simultaniously in the value field of * itself or one of its child nodes. In that cases the XML string returned will not be syntactically * correct. * @return See above. * */ public String toString() { String str = '<' + getName(); // Attributes. if (attributes != null) { Iterator it = attributes.keySet().iterator(); while (it.hasNext()) { Object attName = it.next(); Object attValue = attributes.get(attName); // Default use double qoutes as outer qoutes. However, if double quotes are contained in de // data use single quotes. If both types of quotes occur in the data following appraoch failes // to produce correct XML. However, since such data should be rare this is left as it is. char quote = '"'; // Default double quotes. if (attValue.toString().indexOf(quote) > -1) { quote = '\''; } // Else single qoutes. str += ' ' + attName.toString() + '=' + quote + attValue + quote; } } // Value. String s = getValue(); if (s == null) { s = ""; } str += '>' + s; // Child elements. if (childElements != null) { for (int i = 0; i < childElements.size(); i++) { str += getChildElement(i).toString(); // NB. Recursive invokation! } } str += "'; return str; } /** * For testing puposes only. Converts a given XML file to an XmlElement object which is then * converted to XML again via {@link #toString()}. Output is written to stdout. * Compare the result with the input file to check correct functioning of this class. * @param args See {@link #printUsage}. */ public static void main(String[] args) { if (args == null || args.length != 1) { printUsage(); System.exit(0); } try { DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = fac.newDocumentBuilder(); Document doc = builder.parse(new File(args[0])); XmlElement elem = XmlElement.createFromNode(doc); System.out.println(elem.toString()); } catch (Exception ex) { System.out.println("Unexpected excpetion: " + ex); } } public static void printUsage() { System.out.println("Usage: java "); } }