package com.onaro.util.xml; import java.util.HashMap; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.lang3.StringUtils; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; /** * An iterator for scanning XML DOM documents. Allows easy navigation in the DOM tree, while it filters out comments, * text and empty lines. *

* An iterator is regarded as a pointer to the tree that can travel out of the DOM tree and retreat into the tree. At * all times an iterator points to a node in the tree that isn't a comment, text or an empty line. *

* For convinience, all the navigation methods return this iterator in order to allow easy linking of naigation * commands. *

* The handling of navigation beyond the document's boundaries is limited to undoing a single error only. Therefore, * invoking down() when the current node is a leaf (has no children) is legal and can be undone by * invoking up(), but involing down().down() on a leaf node is ilegal. */ public class XmlIterator { /** * The current node. */ private org.w3c.dom.Node node; /** * The node that was the current node immediatly before the last dsaction that was prev(). Allows * undoing a prev() dsaction performed on a node that is the first child of its parent. */ private org.w3c.dom.Node lastPrev; /** * The node that was the current node immediatly before the last dsaction that was next(). Allows * undoing a next() dsaction performed on a node that is the last child of its parent. */ private org.w3c.dom.Node lastNext; /** * The node that was the current node immediatly before the last dsaction that was up(). Allows * undoing a up() dsaction performed on a node that is the root of the document. */ private org.w3c.dom.Node lastUp; /** * The node that was the current node immediatly before the last dsaction that was down(). Allows * undoing a down() dsaction performed on a node that has no children. */ private org.w3c.dom.Node lastDown; /** * Create an iterator for a document starting at the specified node. * * @param node * the starting point for the iterator */ public XmlIterator(Node node) { this.node = node; lastPrev = lastNext = lastUp = lastDown = null; } /** * Create an exact copy of the specified iterator. Both iterators scan the same document. * * @param o * the source iterator */ public XmlIterator(XmlIterator o) { node = o.node; lastPrev = o.lastPrev; lastNext = o.lastNext; lastUp = o.lastUp; lastDown = o.lastDown; } /** * Gets the current node. * * @return the current node */ public org.w3c.dom.Node getNode() { return node; } /** * sets the current node. * * @param node * the node */ public void setNode(org.w3c.dom.Node node) { this.node = node; } /** * Tests if the current node (the position in the document) is valid. The position is valid as long as the current * node is indeed part of the scanned document. * * @return true if the current node is part of the document (that is, its not null) */ public boolean isValid() { return node != null; } /** * Navigate up in the document tree so the iterator will point to the parent of the current node. *

* If the iterator is currently invalid, then try to deduce the parent by considering the last dsaction and visited * node. If the last dsaction was next() or prev() from a valid node than navigate up * to the parent of that node. * * @return this, pointing to the parent of the current node */ public XmlIterator up() { if (node != null) { lastPrev = lastNext = lastDown = null; lastUp = node; node = node.getParentNode(); } else if (lastDown != null) { node = lastDown; lastPrev = lastNext = lastUp = lastDown = null; } else if (lastNext != null) { node = lastNext.getParentNode(); lastPrev = lastNext = lastUp = lastDown = null; } else if (lastPrev != null) { node = lastPrev.getParentNode(); lastPrev = lastNext = lastUp = lastDown = null; } return this; } /** * Navigate down in the document tree so the iterator will point to the first child of the current node that isn't a * comment, text or an empty line. *

* If the iterator is currently invalid, then try to deduce the first child by considering the last dsaction and * visited node. If the last dsaction was up() from a valid node than navigate down to that node. * * @return this, pointing to the first child of the current node */ public XmlIterator down() { if (node != null) { lastPrev = lastNext = lastUp = null; lastDown = node; node = node.getFirstChild(); // If the new node is text or a comment, go to the next sibling node // to search if its an acceptable node. if (isTypeText()) next(); } else { node = lastUp; lastPrev = lastNext = lastUp = lastDown = null; } return this; } /** * Navigate to the next sibling node in the document tree so the iterator will point to a node that isn't a comment, * text or an empty line having the same parent as the current node. *

* If the iterator is currently invalid, then try to deduce the next node by considering the last dsaction and * visited node. If the last dsaction was prev() from a valid node than navigate to that node. * * @return this, pointing to the next sibling node */ public XmlIterator next() { if (node != null) { lastNext = node; lastPrev = null; // Search the next node that isn't a comment or a text do { node = node.getNextSibling(); } while (isTypeText()); } else { node = lastPrev; lastPrev = lastNext = null; } return this; } /** * Navigate to the previous sibling node in the document tree so the iterator will point to a node that isn't a * comment, text or an empty line having the same parent as the current node. *

* If the iterator is currently invalid, then try to deduce the previous node by considering the last dsaction and * visited node. If the last dsaction was next() from a valid node than navigate to that node. * * @return this, pointing to the previous sibling node */ public XmlIterator prev() { if (node != null) { lastPrev = node; lastNext = null; // Search the next node that isn't a comment or a text do { node = node.getPreviousSibling(); } while (isTypeText()); } else { node = lastNext; lastPrev = lastNext = null; } return this; } /** * Navigate down in the document tree so the iterator will point to the child of the current node with the specified * name. * * @param name * the requeste node's name * @return this, pointing to the child of the current node with the specified name */ public XmlIterator downTo(String name) { for (down(); isValid(); next()) { if (isName(name)) { break; } } return this; } /** * Navigates through brothers untils the current node has the specified name. * * @param name * the requeste node's name * @return this, pointing to the node with the specified name */ public XmlIterator nextTo(String name) { next(); while (!isName(name) && isValid()) next(); return this; } /** * Tests if there is a next sibling node for the current node that isn't a text or comment node. * * @return false if the current node is the last child of its parent */ public boolean hasNext() { if (node != null) { org.w3c.dom.Node nextNode = node.getNextSibling(); while ((nextNode != null) && (nextNode.getNodeType() == org.w3c.dom.Node.TEXT_NODE)) { nextNode = nextNode.getNextSibling(); } return nextNode != null; } return lastPrev != null; } /** * Tests if there is a previous sibling node for the current node that isn't a text or comment node. * * @return false if the current node is the first child of its parent */ public boolean hasPrev() { if (node != null) { org.w3c.dom.Node prevNode = node.getPreviousSibling(); while ((prevNode != null) && (prevNode.getNodeType() == org.w3c.dom.Node.TEXT_NODE)) { prevNode = node.getNextSibling(); } return prevNode != null; } return lastNext != null; } /** * Tests if the current node has children that isn't a text or comment node. * * @return true if the current node has children */ public boolean hasUp() { if (node != null) { return node.getParentNode() != null; } // return lastUp != null; // this line is wrong! look up in lastUp instead (lastUp was set by Up) return lastDown != null; } /** * Tests if the current node has a parent node that isn't a text or comment node. * * @return false if the current node is the root of the document */ public boolean hasDown() { if (node != null) { org.w3c.dom.Node nextNode = node.getFirstChild(); while ((nextNode != null) && (nextNode.getNodeType() == org.w3c.dom.Node.TEXT_NODE)) { nextNode = nextNode.getNextSibling(); } return nextNode != null; } // return lastDown != null; // this line is wrong! look up in lastUp instead (lastUp was set by Up) return lastUp != null; } /** * Sets the text of the current node. * * @param txt * the text for the current node */ public void setText(String txt) { // (1) if i'm a text XML node, replace me if (isTypeText()) { node.setNodeValue(txt); return; } // (2) if i have a child XML text node, find first one and replace it for (Node txtNode = node.getFirstChild(); txtNode != null; txtNode = txtNode.getNextSibling()) { if (txtNode.getNodeType() == org.w3c.dom.Node.TEXT_NODE) { txtNode.setNodeValue(txt); return; } } // (3) if I don't have any, add a new text node Node newText = node.getOwnerDocument().createTextNode(txt); node.appendChild(newText); } /** * Tests if the type of the current node is the specified type. Types are specified by the schema. * * @param tp * any of the types defined in org.w3c.dom.Node * @return true if the type of the current node is the same as tp */ public boolean isType(short tp) { return (node != null) && (tp == node.getNodeType()); } /** * Tests if the current node is of type text (org.w3c.dom.Node.TEXT). * * @return true if the current node is of type org.w3c.dom.Node */ public boolean isTypeText() { return isType(org.w3c.dom.Node.TEXT_NODE); } /** * Gets the name of the current node. A node's name is the name of the element (tag) in the document. * * @return the name of the current node */ public String getName() { return (node == null) ? StringUtils.EMPTY : node.getNodeName(); } /** * Gets the name of the current element. An elements name is the name of the element (tag) in the document. * Disregard the namespace todo is that correct????????? * * @return the name of the current element */ public String getNameWithoutNamespace() { return getName(); } /** * Gets an attribute of the current node. Attributes are values specified within the node's element (tag) in the * form of "key=value". * * @param name * the name of the requested attribute * @return the value of teh requested attribute or an empty string if it doesn't exists */ public String getAttribute(String name) { if (node instanceof Element) { return ((org.w3c.dom.Element) node).getAttribute(name); } return StringUtils.EMPTY; } /** * Same as {@link #getAttribute(String)}, but if the attribute is empty - will return null */ public String getNotEmptyAttribute(String name) { String attribute = getAttribute(name); if(attribute != null) { attribute = attribute.trim(); return attribute.length() > 0 ? attribute : null; }else { return null; } } /** * check if attribute exist * * @param name * the name of the requested attribute * @return the value of teh requested attribute or an empty string if it doesn't exists */ public boolean hasAttribute(String name) { if (node instanceof Element) { return ((org.w3c.dom.Element) node).hasAttribute(name); } return false; } public Map getAllAttributes() { Map nameToValue = new HashMap(); if (node != null) { org.w3c.dom.NamedNodeMap map = node.getAttributes(); if (map != null) { int numOfAttributes = map.getLength(); for (int i = 0; i < numOfAttributes; i++) { org.w3c.dom.Node attr = map.item(i); nameToValue.put(attr.getNodeName(), attr.getNodeValue()); } } } return nameToValue; } /** * Gets a description of the path in the document that lead from the root to the current node. * * @return a description of the path from the root to the current node */ public String getPath() { if (!isValid()) { return StringUtils.EMPTY; } return (new XmlIterator(this)).up().getPath() + "." + getName(); //$NON-NLS-1$ } public boolean removeNode() { org.w3c.dom.Node nodeToDelete = node; boolean ok = isValid(); if (ok) { up(); ok = isValid(); } if (ok) { ok = (node.removeChild(nodeToDelete) != null); } return ok; } /** * Replaces the current node with the specified node. The current node and all its descendants are removed and the * new node (and all its descendants) is placed instaed. * * @param newIter * the new node * @return this */ public XmlIterator replaceNode(XmlIterator newIter) { org.w3c.dom.Node newNode = newIter.node; if (newNode == null) { return this; } if (node == null) { node = newNode; } else { org.w3c.dom.Node oldNode = node; up(); Node importedNode = node.getOwnerDocument().importNode(newNode, true); node.replaceChild(importedNode, oldNode); node = importedNode; // XmlIterator itr = new XmlIterator(node.getOwnerDocument()); // itr.down(); // System.out.print(itr.toString()); } return this; } /** * Gets a copy of the current node (a copy of the document, not the iterator). Because a node's clonning performs a * shallow copy, if the level is greater then 0, also copy the descendants of the current node up to that level. * Lower levels are in common to both the new and source documents. * * @param level * the depth of nodes that are copied * @return the new node (document) * @throws DOMException * if failed to clone a node */ public XmlIterator cloneNode(int level) throws org.w3c.dom.DOMException { return cloneNode(this, level); } /** * Gets a copy of a node (a copy of the document, not the iterator). Because a node's clonning performs a shallow * copy, if the level is greater then 0, also copy the descendants of the current node up to that level. Lower * levels are in common to both the new and source documents. * * @param o * the node to clone * @param level * the depth of nodes that are copied * @return the new node (document) * @throws DOMException * if failed to clone a node */ static public XmlIterator cloneNode(XmlIterator o, int level) throws org.w3c.dom.DOMException { org.w3c.dom.Node newRoot = o.node.cloneNode(false); // cloneText(newRoot, o.node); XmlIterator result = new XmlIterator(newRoot); if (level > 0) { cloneChildren(newRoot, o.node, level); } return result; } static public void cloneChildren(Node dst, Node srcNode, int level) throws org.w3c.dom.DOMException { for (srcNode = srcNode.getFirstChild(); srcNode != null; srcNode = srcNode.getNextSibling()) { if ((level > 0) || (srcNode.getNodeType() == org.w3c.dom.Node.TEXT_NODE)) { Node newSon = srcNode.cloneNode(false); dst.appendChild(newSon); cloneChildren(newSon, srcNode, level - 1); } } } /** * Gets a duplicate of the document starting in the current node. If the iterator points to the root element, the * root is not copied and returned as is. * * @return a copy of the document rooted in the current node * @throws ParserConfigurationException * if failed to duplicate */ private Document copyDocumentIfNotRoot() throws ParserConfigurationException { if (!isValid()) { return null; } if (node instanceof Document) return (Document) node; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // factory.setValidating(true); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); Document documentToSave = builder.newDocument(); documentToSave.appendChild(documentToSave.importNode(node, true)); return documentToSave; } /** * Returns the document pointed by this node. Calls copyDocumentIfNotRoot(). Creates a copy of the document if not * root * * @return the document rooted in the current node * @throws ParserConfigurationException * if failed to duplicate */ public Document getDocument() throws ParserConfigurationException { return copyDocumentIfNotRoot(); } /** * Gets the boolean value of an attribute. * * @param name * the name of the attribute * @return true if the attribute exists and its value is 'true' */ public boolean getBooleanAttribute(String name) { return Boolean.valueOf(getAttribute(name)).booleanValue(); } /** * Gets the boolean value of an attribute. * * @param name * the name of the attribute * @param defaultValue * the default value if the attribute isn't specified * @return true if the attribute exists and its value is 'true' */ public boolean getBooleanAttribute(String name, boolean defaultValue) { String attribute = getAttribute(name); if (attribute == null || attribute.length() == 0) return defaultValue; return Boolean.valueOf(attribute).booleanValue(); } /** * compare the element pointed by iterator to name * * @param name * name to compare to * @return true if equals, false otherwise */ public boolean isName(String name) { return name.equals(getName()); } /** * Recursively search in entire subtree pointed by this iterator for a field with name 'name'. Calling this method * results in the iterator pointing to the designated element, or isValid()==false if not found. Example of use, * * XmlIterator k=new XmlIterator( i ); boolean ok=k.downToRecursive("switchs"); * System.out.println(".downToRecursive(switchs) : ok="+ok+" path: " + k.getPath()); * * @param name * of the element to look for * @return boolean true if element is found, false otherwise */ public boolean downToRecursive(String name) { boolean ok = performDownToRecursive(name); if (ok == false) { node = null; } return ok; } /** * Helper function for downToRecursive(). Recursively search in entire subtree pointed by this iterator for a field * with name 'name'. Calling this method results in the iterator pointing to the designated element, or original * iterator if not found. This method is used by downToRecursive() to do actual work. * * @param name * of the element to look for * @return boolean true if element is found, false otherwise */ private boolean performDownToRecursive(String name) { if (isName(name)) { return true; } for (down(); isValid(); next()) { if (performDownToRecursive(name) == true) { return true; } } up(); return false; } /** * Helper function for downToRecursivePath(). * * @param pathString * of the element to look for (dot notation string, Wildcard permitted) * @return boolean true if element is found, false otherwise */ public boolean performDownToRecursivePath(String pathString, int startIndex) { // (1) get the next token if (!isValid()) { return false; } int endIndex = pathString.indexOf('.', startIndex); if (endIndex == -1) { endIndex = pathString.length(); } String pathElement = pathString.substring(startIndex, endIndex); // (2) check if wildcard if (!pathElement.equals("*")) { //$NON-NLS-1$ // (3) if non wildcard, try to match and then call recursively on next token downTo(pathElement); if (!isValid()) { up(); return false; } if (endIndex == pathString.length()) { return true; } return performDownToRecursivePath(pathString, endIndex + 1); } // (4) if wildcard, try matching excplicitly next token against all child elements for (down(); isValid(); next()) { boolean found = performDownToRecursivePath(pathString, endIndex + 1); if (found) { return true; } } up(); // (5) if didn't match explicit next token, try matching wildcard against next level for (down(); isValid(); next()) { boolean found = performDownToRecursivePath(pathString, startIndex); if (found) { return true; } } up(); return false; } };