/*
 */
import java.util.*;
import org.w3c.dom.*;

/**
 * The NodeReader class provides some basic functionality
 * for stepping through the elements of a Node, without having
 * to write your own loops around a NodeList. It's similar to
 * the TreeWalker class that ships with many org.w3c.dom compatible packages,
 * but the XML4j.jar package that ships with Lotus Notes/Domino doesn't
 * have a TreeWalker class, so I wrote this. This has been tested with the 
 * JDK 1.1.8 and 1.3.1.
 * 

* When a new NodeReader is instantiated, it will read an entire Node into * memory and create a Vector of child elements for that Node. Because of * this, you should be aware that if memory is limited, you may want to * consider parsing the Node manually instead of creating a NodeReader. *

* You can also optionally use a custom NodeReaderFilter to filter out certain * nodes. The NodeReaderFilter interface simply looks like this: *

 * public interface NodeReaderFilter {
 *   boolean accept(Node n);
 * }
 * 
*

* If you want to use a NodeReaderFilter (you don't have to), just write a class * that Implements NodeReaderFilter and include it either * when you create an instance of a NodeReader or when you call the readNodeAgain() * method. *

* Here's an example of reading an XML file and putting it through * the reader: *

 * import java.io.*;
 * import org.w3c.dom.*;
 * import org.xml.sax.*;	// InputSource
 * //import org.apache.xerces.parsers.*;	// DOMParser, if you're using Xerces
 * import com.ibm.xml.parsers.*;	// DOMParser, if you're using XML4j (Notes agent)
 * 
 * // read an XML file in as a DOM Document
 * String fileName = "SomeFile.xml";
 * BufferedReader in = new BufferedReader(new FileReader(fileName));
 * InputSource input = new InputSource(in);
 * DOMParser parser = new DOMParser();
 * parser.parse(input);
 * org.w3c.dom.Document doc = parser.getDocument();
 * 
 * // read the whole thing into a NodeReader and print it to the console
 * Node topNode = doc.getDocumentElement();
 * NodeReader nr = new NodeReader(topNode);
 * System.out.println("nr (" + nr.getChildCount() + ")=\n" + nr + "\n");
 * nr.readNodeAgain(0);
 * System.out.println("nr (" + nr.getChildCount() + ")=\n" + nr + "\n");
 * 
 * // now step through the nodes and print again
 * Node n;
 * System.out.println("\nAll Kids:");
 * while ((n = nr.getNextChild()) != null) {
 *     System.out.println(nr.getChildNodeName() + " [" + 
 *                        nr.getAllChildAttributes() + "] " + 
 *                        nr.getChildText());
 * }
 * 
 * // and step through all the first-level nodes
 * System.out.println("\nAll First-Level Kids:");
 * n = nr.getFirstChild();
 * while (n != null) {
 *     System.out.println(nr.getChildNodeName());
 *     n = nr.getNextChildBrother();
 * }
 *
 * // see what it looks like if you apply a filter
 * System.out.println("\nWith Filter Applied:");
 * NodeReaderFilter filter = new NodeReaderFilter() {
 *     public boolean accept(Node n) {
 *         // allow only nodes with lowercase node names
 *         return n.getNodeName().equals(n.getNodeName().toLowerCase());
 *     }
 * };
 * nr.readNodeAgain(0, filter);
 * //just for fun, print it with no indents or line breaks
 * System.out.println("nr (" + nr.getChildCount() + ")=\n" + nr.toString("", "") + "\n");
 *
 * 
*

* There are also a few methods you can use to easily get information about * other Nodes: hasNodeAttribute, getNodeAttribute, getAllNodeAttributes, * getNodeText, and getChildVector. These can all be called on Nodes that * aren't currently being read by a NodeReader, and have been exposed for * convenience so you don't have to instantiate an entire NodeReader just * to use those functions. *

* Changes:
* version 1.1 -- changed the global ceCurrent variable to non-static, * because as a static variable it was holding bad information if you * had instantiated multiple versions of the class (thanks to * Guiseppe Maio for telling me about that). *

* * @author Julian Robichaux ( http://www.nsftools.com ) * @version 1.1 */ public class NodeReader { private Node node = null; private Vector childElements = null; private int depth = 1; private int ceCount = 0; private int ceCurrent = -1; /** * Creates a NodeReader for the given Node, of depth 1. * * @param n the Node you want to read */ public NodeReader (Node n) { this(n, 1); } /** * Creates a NodeReader for the given Node at the given depth. * The depth indicates how many levels of child elements we * should read (for example, a depth of 1 reads only the * immediate children of the Node, a depth of 2 reads the * immediate children and their children, etc). If the depth * is zero (0), all children at all levels are read. * * @param n the Node you want to read * @param depthLevel the depth (number of levels of child * elements) at which to read this Node. * A depth of 0 means we read all levels. */ public NodeReader (Node n, int depthLevel) { this(n, depthLevel, null); } /** * Creates a NodeReader for the given Node at the given depth, * using a given NodeReaderFilter. * The depth indicates how many levels of child elements we * should read (for example, a depth of 1 reads only the * immediate children of the Node, a depth of 2 reads the * immediate children and their children, etc). If the depth * is zero (0), all children at all levels are read. * * @param n the Node you want to read * @param depthLevel the depth (number of levels of child * elements) at which to read this Node. * A depth of 0 means we read all levels. * @param filter the NodeReaderFilter to use when selecting the elements */ public NodeReader (Node n, int depthLevel, NodeReaderFilter filter) { node = n; readNodeAgain(depthLevel, filter); } /** * Re-reads the Node that this NodeReader is accessing, and * resets the internal counter for the current child to the * first child of this Node. Useful if you've added elements * to the Node after the NodeReader was created. */ public void readNodeAgain () { readNodeAgain(depth); } /** * Re-reads the Node that this NodeReader is accessing at the * given depth, and resets the internal counter for the current * child to the first child of this Node. Useful if you've added * elements to the Node after the NodeReader was created, or if * you wish to change the depth at which you're reading the Node. * * @param depthLevel the depth (number of levels of child * elements) at which to read this Node. * A depth of 0 means we read all levels. */ public void readNodeAgain (int depthLevel) { readNodeAgain(depthLevel, null); } /** * Re-reads the Node that this NodeReader is accessing at the * given depth and using the given NodeReaderFilter, * and resets the internal counter for the current * child to the first child of this Node. Useful if you've added * elements to the Node after the NodeReader was created, or if * you wish to change the depth at which you're reading the Node, * or if you're adding or changing a NodeReaderFilter. * * @param depthLevel the depth (number of levels of child * elements) at which to read this Node. * A depth of 0 means we read all levels. * @param filter the NodeReaderFilter to use when selecting the elements */ public void readNodeAgain (int depthLevel, NodeReaderFilter filter) { depth = (depthLevel >= 0) ? depthLevel : 0; childElements = getChildVector(node, depth, filter); ceCount = childElements.size(); ceCurrent = -1; } /** * Returns the current depth at which we're reading this Node. * The depth is the number of levels of child Nodes that we * read. A depth of 0 means we're reading all levels. * * @return the current depth */ public int getDepth () { return depth; } /** * Returns the current Vector of child elements that we're using * to read this Node. * * @return a Vector of child elements for this Node */ public Vector getCurrentChildVector () { return childElements; } /** * Returns the immediate child elements for this Node as a Vector. * * @param n the Node you'd like to get child elements for * @return a Vector of child elements for this Node */ public static Vector getChildVector (Node n) { return getChildVector(n, 1); } /** * Returns the child elements for this Node as a Vector, at a depth * of depthLevel. * * @param n the Node you'd like to get child elements for * @param depthLevel the depth at which to read the Node * @return a Vector of child elements for this Node */ public static Vector getChildVector (Node n, int depthLevel) { return getChildVector(n, depthLevel, null); } /** * Returns the child elements for this Node as a Vector, at a depth * of depthLevel, using a specified NodeReaderFilter. * * @param n the Node you'd like to get child elements for * @param depthLevel the depth at which to read the Node * @param filter the NodeReaderFilter to use when selecting the elements * @return a Vector of child elements for this Node */ public static Vector getChildVector (Node n, int depthLevel, NodeReaderFilter filter) { Vector v = new Vector(); getCE(v, n, filter, depthLevel, 0); return v; } /** * The private method that actually reads the child elements of * a Node. It's a separate method because it's recursive. */ private static void getCE (Vector v, Node n, NodeReaderFilter filter, int depthLevel, int currentDepth) { if (n == null) return; currentDepth++; if (n.hasChildNodes()) { NodeList kids = n.getChildNodes(); for (int i = 0; i < kids.getLength(); i++) { Node thisKid = kids.item(i); if (thisKid.getNodeType() == Node.ELEMENT_NODE) { try { if (filter.accept(thisKid)) v.addElement(thisKid); } catch (Exception e) { // if there is no filter or the filter has // an error, just add the node v.addElement(thisKid); } if ((depthLevel == 0) || (currentDepth < depthLevel)) getCE(v, thisKid, filter, depthLevel, currentDepth); } } } } /** * Returns the total number of child elements under the Node * that we're reading (at the current depth and filter). * * @return the number of child elements for this Node */ public int getChildCount () { return ceCount; } /** * Positions the internal counter at the given Node, if it exists in the current * working Vector of children. * * @param n the Node that you want to go to * @return the Node that was passed, if it was found; null otherwise */ public Node getChild (Node getNode) { Node n = getFirstChild(); while (n != null) { if (n == getNode) break; n = getNextChild(); } return n; } /** * Returns the first child element of this Node, at the working depth and filter. * * @return a child element as a Node, or null if there are * no children */ public Node getFirstChild () { ceCurrent = 0; if (ceCount == 0) return null; else return (Node)childElements.firstElement(); } /** * Returns the first child element of this Node with the given node name, * at the working depth and filter. * * @param nodeName the case-sensitive name of the Node you want to get * @return a child element as a Node, or null if there are * no children */ public Node getFirstChild (String nodeName) { Node n = getFirstChild(); while (n != null) { if (n.getNodeName().equals(nodeName)) break; n = getNextChild(); } return n; } /** * Returns the first child element of this Node with the given attribute value, * at the working depth and filter. * * @param attrName the name of the Attribute you're looking at * @param attrValue the value the Attribute should have * @return a child element as a Node, or null if there are * no children */ public Node getFirstChild (String attrName, String attrValue) { if (attrName == null) return null; if (attrValue == null) attrValue = ""; Node n = getFirstChild(); while (n != null) { if (getNodeAttribute(n, attrName).equals(attrValue)) break; n = getNextChild(); } return n; } /** * Returns the last child element of this Node, at the working depth and filter. * * @return a child element as a Node, or null if there are * no more children */ public Node getLastChild () { ceCurrent = ceCount - 1; if (ceCount == 0) return null; else return (Node)childElements.lastElement(); } /** * Returns the last child element of this Node with the given node name, * at the working depth and filter. * * @param nodeName the case-sensitive name of the Node you want to get * @return a child element as a Node, or null if there are * no children */ public Node getLastChild (String nodeName) { Node n = getLastChild(); while (n != null) { if (n.getNodeName().equals(nodeName)) break; n = getPreviousChild(); } return n; } /** * Returns the last child element of this Node with the given attribute value, * at the working depth and filter. * * @param attrName the name of the Attribute you're looking at * @param attrValue the value the Attribute should have * @return a child element as a Node, or null if there are * no children */ public Node getLastChild (String attrName, String attrValue) { if (attrName == null) return null; if (attrValue == null) attrValue = ""; Node n = getLastChild(); while (n != null) { if (getNodeAttribute(n, attrName).equals(attrValue)) break; n = getPreviousChild(); } return n; } /** * Returns the next child element of this Node, at the working depth and filter. * If no child elements have been examined yet, the first call to * this method will return the first child element. * * @return a child element as a Node, or null if there are * no more children */ public Node getNextChild () { ceCurrent++; return getCurrentChild(); } /** * Returns the next child element of this Node with the given node name, * at the working depth and filter. * * @param nodeName the case-sensitive name of the Node you want to get * @return a child element as a Node, or null if there are * no children */ public Node getNextChild (String nodeName) { Node n = getNextChild(); while (n != null) { if (n.getNodeName().equals(nodeName)) break; n = getNextChild(); } return n; } /** * Returns the next child element of this Node with the given attribute value, * at the working depth and filter. * * @param attrName the name of the Attribute you're looking at * @param attrValue the value the Attribute should have * @return a child element as a Node, or null if there are * no children */ public Node getNextChild (String attrName, String attrValue) { if (attrName == null) return null; if (attrValue == null) attrValue = ""; Node n = getNextChild(); while (n != null) { if (getNodeAttribute(n, attrName).equals(attrValue)) break; n = getNextChild(); } return n; } /** * Returns the previous child element of this Node, at the working depth and filter. * * @return a child element as a Node, or null if there are * no previous children */ public Node getPreviousChild () { ceCurrent--; return getCurrentChild(); } /** * Returns the previous child element of this Node with the given node name, * at the working depth and filter. * * @param nodeName the case-sensitive name of the Node you want to get * @return a child element as a Node, or null if there are * no children */ public Node getPreviousChild (String nodeName) { Node n = getPreviousChild(); while (n != null) { if (n.getNodeName().equals(nodeName)) break; n = getPreviousChild(); } return n; } /** * Returns the previous child element of this Node with the given attribute value, * at the working depth and filter. * * @param attrName the name of the Attribute you're looking at * @param attrValue the value the Attribute should have * @return a child element as a Node, or null if there are * no children */ public Node getPreviousChild (String attrName, String attrValue) { if (attrName == null) return null; if (attrValue == null) attrValue = ""; Node n = getPreviousChild(); while (n != null) { if (getNodeAttribute(n, attrName).equals(attrValue)) break; n = getPreviousChild(); } return n; } /** * Returns the parent of the current child element of this Node, * if any. * * @return an element as a Node, or null if the current * child has no parent or if the parent is the * top of the Node we're reading */ public Node getChildParent () { Node child = getCurrentChild(); if (child == null) return null; Node parent = child.getParentNode(); if ((parent == null) || (parent == node)) { getFirstChild(); return null; } while ((child = getPreviousChild()) != null) { if (child == parent) break; } return child; } /** * Returns the next child that has the same parent as the current * child element if any. I used the term "brother" because the Node * class uses the term "sibling" to mean the very next Node, not * necessarily the next Node with the same parent (which seems * a little confusing to me). * * @return an element as a Node, or null if the current * child has no next brother */ public Node getNextChildBrother () { Node child = getCurrentChild(); if (child == null) return null; Node parent = child.getParentNode(); if (parent == null) { getLastChild(); ceCurrent++; return null; } while ((child = getNextChild()) != null) { if (child.getParentNode() == parent) break; } return child; } /** * Returns the previous child that has the same parent as the current * child element if any. I used the term "brother" because the Node * class uses the term "sibling" to mean the very next Node, not * necessarily the next Node with the same parent (which seems * a little confusing to me). * * @return an element as a Node, or null if the current * child has no previous brother */ public Node getPreviousChildBrother () { Node child = getCurrentChild(); if (child == null) return null; Node parent = child.getParentNode(); if (parent == null) { getFirstChild(); return null; } while ((child = getPreviousChild()) != null) { if (child.getParentNode() == parent) break; } return child; } /** * Returns the current child element of this Node. If no child * elements have been examined yet, this method will return null. * * @return a child element as a Node, or null if there are * no children */ public Node getCurrentChild () { if (ceCurrent >= ceCount) { ceCurrent = ceCount; return null; } else if (ceCurrent < 0) { ceCurrent = -1; return null; } else { return (Node)childElements.elementAt(ceCurrent); } } /** * Returns the node name of the root of this Node. * * @return the node name of this Node */ public String getRootNodeName () { if (node == null) return null; else return node.getNodeName(); } /** * Returns all the immediate TEXT_NODE elements of the root of this Node, * separated by spaces and concatenated into a String. * * @return all the immediate TEXT_NODE elements of this Node */ public String getRootText () { return getNodeText(node); } /** * Returns the value of a specified Attribute of the root of this Node, or an * empty String if the Attribute does not exist. * * @return the value of a specified Attribute of this Node */ public String getRootAttribute (String attrName) { return getNodeAttribute(node, attrName); } /** * Returns a boolean value indicating whether or not the root of this Node has * a specified Attribute. * * @return true if this Node has the Attribute, * false if it doesn't */ public boolean hasRootAttribute (String attrName) { return hasNodeAttribute(node, attrName); } /** * Returns all the Attributes of the root of this Node as a String, or an * empty String if there are no Attributes. * * @return the Attributes of this Node */ public String getAllRootAttributes () { return getAllNodeAttributes(node); } /** * Returns the node name of the current child of this Node. * * @return the node name of this child */ public String getChildNodeName () { if (getCurrentChild() == null) return null; else return getCurrentChild().getNodeName(); } /** * Returns all the immediate TEXT_NODE elements of the current child, * separated by spaces and concatenated into a String. * * @return all the immediate TEXT_NODE elements of this child */ public String getChildText () { return getNodeText(getCurrentChild()); } /** * Returns the value of a specified Attribute of the current child, or an * empty String if the Attribute does not exist. * * @return the value of a specified Attribute of this child */ public String getChildAttribute (String attrName) { return getNodeAttribute(getCurrentChild(), attrName); } /** * Returns a boolean value indicating whether or not the current child has * a specified Attribute. * * @return true if the child has the Attribute, * false if it doesn't */ public boolean hasChildAttribute (String attrName) { return hasNodeAttribute(getCurrentChild(), attrName); } /** * Returns all the Attributes of the current child as a String, or an * empty String if there are no Attributes. * * @return the Attributes of the current child */ public String getAllChildAttributes () { return getAllNodeAttributes(getCurrentChild()); } /** * Returns all the immediate TEXT_NODE elements of the given Node, * separated by spaces and concatenated into a String. * * @param n the Node to examine * @return all the immediate TEXT_NODE elements of this child */ public static String getNodeText (Node n) { StringBuffer sb = new StringBuffer(""); if (n == null) return null; if (n.hasChildNodes()) { NodeList kids = n.getChildNodes(); for (int i = 0; i < kids.getLength(); i++) { Node thisKid = kids.item(i); if ((thisKid.getNodeType() == Node.TEXT_NODE) && (thisKid.getNodeValue() != null)) sb.append(thisKid.getNodeValue() + " "); } } return sb.toString().trim(); } /** * Returns the value of a specified Attribute of the given Node, or an * empty String if the Attribute does not exist. * * @param n the Node to examine * @param attrName the name of the Attribute you're looking for * @return the value of a specified Attribute of the Node */ public static String getNodeAttribute (Node n, String attrName) { String value = ""; if ((n == null) || (attrName == null)) return null; NamedNodeMap nodeMap = n.getAttributes(); if (nodeMap != null) { for (int i = 0; i < nodeMap.getLength(); i++) { Node att = nodeMap.item(i); if (att.getNodeName().equalsIgnoreCase(attrName)) { value = att.getNodeValue(); break; } } } return value; } /** * Returns a boolean value indicating whether or not the given Node has * a specified Attribute. * * @param n the Node to examine * @param attrName the name of the Attribute you're looking for * @return true if the child has the Attribute, * false if it doesn't */ public static boolean hasNodeAttribute (Node n, String attrName) { boolean isThere = false; if ((n == null) || (attrName == null)) return isThere; NamedNodeMap nodeMap = n.getAttributes(); if (nodeMap != null) { for (int i = 0; i < nodeMap.getLength(); i++) { Node att = nodeMap.item(i); if (att.getNodeName().equalsIgnoreCase(attrName)) { isThere = true; break; } } } return isThere; } /** * Returns all the Attributes of the given Node as a String, or an * empty String if there are no Attributes. * * @param n the Node to examine * @return the Attributes of the Node */ public static String getAllNodeAttributes (Node n) { StringBuffer tags = new StringBuffer(); NamedNodeMap nodeMap = n.getAttributes(); if (nodeMap != null) { for (int i = 0; i < nodeMap.getLength(); i++) { Node nm = nodeMap.item(i); tags.append(" " + nm.getNodeName() + "=\"" + nm.getNodeValue() + "\""); } } return tags.toString(); } /** * Returns this Node as a String of XML, at the current depth level and * with the current NodeReaderFilter (if any). * * @return a String representation of this Node */ public String toString () { return toString(" ", "\n"); } /** * Returns this Node as a String of XML, at the current depth level and * with the current NodeReaderFilter (if any). * * @param indent a String (usually spaces or tabs) used to indent * each level of the XML * @param newLine a String used as a newLine separator * @return a String representation of this Node */ public String toString (String indent, String newLine) { StringBuffer sb = new StringBuffer(""); StringBuffer ind = new StringBuffer(""); Vector parents = new Vector(); if ((node == null) || (ceCount == 0)) return ""; if (indent == null) indent = ""; if (newLine == null) newLine = ""; try { sb.append("<" + node.getNodeName()); sb.append(getAllNodeAttributes(node) + ">"); sb.append(getNodeText(node) + newLine); for (Enumeration e = childElements.elements() ; e.hasMoreElements() ;) { Node n = (Node)e.nextElement(); for (int i = parents.size(); i > 0; i--) { Node p = (Node)parents.elementAt(i-1); if (p == n.getParentNode()) { break; } else { sb.append(ind + "" + newLine); parents.removeElementAt(i-1); ind.setLength(ind.length() - indent.length()); } } ind.append(indent); sb.append(ind + "<" + n.getNodeName()); sb.append(getAllNodeAttributes(n) + ">"); sb.append(getNodeText(n) + newLine); parents.addElement(n); } for (int i = parents.size(); i > 0; i--) { Node p = (Node)parents.elementAt(i-1); sb.append(ind + "" + newLine); ind.setLength(ind.length() - indent.length()); } sb.append(""); } catch (Exception e) { sb.append(newLine + "toString Error: " + e); } return sb.toString(); } } /** * Classes that implement this interface are used to filter * the Nodes that are read by a NodeReader. * Here's a very simple example: *

 * public class LowercaseFilter implements NodeReaderFilter {
 *     boolean accept(Node n) {
 *         // allow only nodes with lowercase node names
 *         return n.getNodeName().equals(n.getNodeName().toLowerCase());
 *     }
 * }
 * 
*

* * @author Julian Robichaux ( http://www.nsftools.com ) * @version 1.0 */ /* NOTE: I included the NodeReaderFilter interface definition with the * main NodeReader.java file for convenience, so you could download and * compile just a single file. You really should cut and paste this class * into a separate NodeReaderFilter.java file. When you do that, make sure * to declare it as a public interface, and import org.w3c.dom.*; */ //import org.w3c.dom.*; //public interface NodeReaderFilter interface NodeReaderFilter { /** * Returns a boolean value indicating whether or not the given Node * should be included in the list of Nodes that are read by a NodeReader. * * @param n the Node to examine * @return true if Node should be included, * false if it shouldn't */ boolean accept(Node n); }