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'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;
}
};