package com.retrieve.utils;
import java.io.PrintStream;
import java.io.PrintWriter;
/**
* @Description An exception for the lucene search.
* <p>
* It can handle nested exceptions. Nested exceptions will be
* printed with the stacktrace.
* <p>
* @Author zhangzuoqiang
* @Date 2012-3-17 | 下午8:11:04
*/
public class RetrieveException extends Exception {
/**
*
*/
private static final long serialVersionUID = 1L;
/** The nested exception. May be null. */
private Throwable mCause;
/**
* Creates a new instance of SearchException.
*
* @param message
* The error message
*/
public RetrieveException(String message) {
this(message, null);
}
/**
* Creates a new instance of SearchException.
*
* @param message
* The error message
* @param cause
* The nested exception.
*/
public RetrieveException(String message, Throwable cause) {
super(message);
mCause = cause;
}
/**
* Gets the cause of this exception. (May be null)
*
* @return The cause of this exception.
*/
public Throwable getCause() {
return mCause;
}
/**
* Prints the stack trace of this exception an of the nested exception, if
* present.
*
* @param stream
* The stream to print to.
*/
public void printStackTrace(PrintStream stream) {
super.printStackTrace(stream);
if ((mCause != null) && (!superClassPrintsCause())) {
stream.println("Caused by: " + mCause.getMessage() + ":");
mCause.printStackTrace(stream);
}
}
/**
* Prints the stack trace of this exception an of the nested exception, if
* present.
*
* @param writer
* The writer to print to.
*/
public void printStackTrace(PrintWriter writer) {
super.printStackTrace(writer);
if ((mCause != null) && (!superClassPrintsCause())) {
writer.println("Caused by: " + mCause.getMessage() + ":");
mCause.printStackTrace(writer);
}
}
/**
* Gets whether the superclass is able to print the cause of the exception.
*
* @return Whether the superclass is able to print the cause of the
* exception.
*/
private boolean superClassPrintsCause() {
// Check whether there is a getCause method in the super class
try {
getClass().getSuperclass().getMethod("getCause");
// The superclass has a getCause method
return true;
} catch (Exception exc) {
// The superclass has no getCause method
return false;
}
}
}
package com.retrieve.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.StringTokenizer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* @Description
* @Author zhangzuoqiang
* @Date 2012-3-17 | 下午8:29:10
*/
public class XmlToolkit {
/**
* Loads an XML file and returns its content as Document.
*
* @param xmlFile
* The XML file to load.
* @return The XML document of the file.
* @throws RetrieveException
* If loading the XML file failed.
*/
public static Document loadXmlDocument(File xmlFile)
throws RetrieveException {
DocumentBuilder builder;
try {
builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch (Exception exc) {
throw new RetrieveException(
"Creating XML document builder failed!", exc);
}
Document doc = null;
FileInputStream stream = null;
try {
stream = new FileInputStream(xmlFile);
doc = builder.parse(stream);
} catch (Exception exc) {
throw new RetrieveException("Parsing XML failed: "
+ xmlFile.getAbsolutePath(), exc);
} finally {
if (stream != null) {
try {
stream.close();
} catch (Exception exc) {
}
}
}
return doc;
}
/**
* Saves an XML Document to a file.
*
* @param xmlFile
* The XML file to save to.
* @param doc
* The XML document to save.
* @throws RetrieveException
* If saving the XML file failed.
*/
public static void saveXmlDocument(File xmlFile, Document doc)
throws RetrieveException {
FileOutputStream stream = null;
try {
stream = new FileOutputStream(xmlFile);
String encoding = "UTF-8";
PrintStream out = new PrintStream(stream, true, encoding);
out.println("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>");
out.println();
out.println("<!DOCTYPE entities [");
out.println(" <!ENTITY minus \"-\">");
out.println(" <!ENTITY lt \"<\">");
out.println(" <!ENTITY gt \">\">");
out.println("]>");
out.println();
Element root = doc.getDocumentElement();
printNode(out, "", root);
out.close();
} catch (Exception exc) {
throw new RetrieveException("Saving XML file failed: "
+ xmlFile.getAbsolutePath(), exc);
} finally {
if (stream != null) {
try {
stream.close();
} catch (Exception exc) {
}
}
}
}
/**
* Prints a XML node.
*
* @param out
* The PrintStream where to print the node.
* @param prefix
* The prefix to put before every line.
* @param node
* The node to print.
* @throws IOException
* If printing failed.
*/
private static void printNode(PrintStream out, String prefix, Node node)
throws IOException {
prefix = "";
String name = node.getNodeName();
boolean isText = name.equals("#text");
boolean isComment = name.equals("#comment");
boolean isCDATA = name.equals("#cdata-section");
if (isText) {
// This is a text tag
String text = node.getNodeValue();
text = replace(text, "<", "<");
text = replace(text, ">", ">");
text = replace(text, "--", "−−");
out.print(text);
} else if (isComment) {
// This is a comment tag
String comment = node.getNodeValue();
out.print("<!--" + comment + "-->");
} else if (isCDATA) {
String text = node.getNodeValue();
out.print("<![CDATA[" + text + "]]>");
} else {
// This is a normal tag
out.print(prefix + "<" + name);
if (node.hasAttributes()) {
NamedNodeMap attributes = node.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node attrib = attributes.item(i);
out.print(" " + attrib.getNodeName() + "=\""
+ attrib.getNodeValue() + "\"");
}
}
if (!node.hasChildNodes()) {
out.print("/>");
} else {
out.print(">");
NodeList childList = node.getChildNodes();
String childPrefix = prefix + " ";
for (int i = 0; i < childList.getLength(); i++) {
printNode(out, childPrefix, childList.item(i));
}
out.print(prefix + "</" + name + ">");
}
}
}
public static String replace(String source, String pattern,
String replacement) {
// Check whether the pattern occurs in the source at all
int pos = source.indexOf(pattern);
if (pos == -1) {
// The pattern does not occur in the source -> return the source
return source;
}
// Build a new String where pattern is replaced by the replacement
StringBuilder target = new StringBuilder(source.length());
int start = 0; // The start of a part without the pattern
do {
target.append(source.substring(start, pos));
target.append(replacement);
start = pos + pattern.length();
} while ((pos = source.indexOf(pattern, start)) != -1);
target.append(source.substring(start, source.length()));
// return the String
return target.toString();
}
/**
*
* @param node
* @return
* @throws RetrieveException
*/
public static boolean getTextAsBoolean(Node node) throws RetrieveException {
String asString = getText(node, true, true);
if (asString.equalsIgnoreCase("true")) {
return true;
} else if (asString.equalsIgnoreCase("false")) {
return false;
} else {
throw new RetrieveException("Value of node '" + node.getNodeName()
+ "' must be either 'true' or 'false'!");
}
}
/**
*
* @param node
* @return
* @throws RetrieveException
*/
public static int getTextAsInt(Node node) throws RetrieveException {
String asString = getText(node, true, true);
try {
return Integer.parseInt(asString);
} catch (NumberFormatException exc) {
throw new RetrieveException("Value of node '" + node.getNodeName()
+ "' must be an integer: '" + asString + "'", exc);
}
}
/**
*
* @param node
* @return
* @throws RetrieveException
*/
public static double getTextAsDouble(Node node) throws RetrieveException {
String asString = getText(node, true, true);
try {
return Double.parseDouble(asString);
} catch (NumberFormatException exc) {
throw new RetrieveException("Value of node '" + node.getNodeName()
+ "' must be a floating-point number (double): '"
+ asString + "'", exc);
}
}
/**
*
* @param node
* @param mandatory
* @return
* @throws RetrieveException
*/
public static String[] getTextAsWordList(Node node, boolean mandatory)
throws RetrieveException {
String asString = getText(node, mandatory);
if (asString == null) {
return null;
} else {
StringTokenizer tokenizer = new StringTokenizer(asString);
String[] wordList = new String[tokenizer.countTokens()];
for (int i = 0; i < wordList.length; i++) {
wordList[i] = tokenizer.nextToken();
}
return wordList;
}
}
/**
*
* @param node
* @return
* @throws RetrieveException
*/
public static String getTextOrCDataAsUrl(Node node)
throws RetrieveException {
String asString = getTextOrCData(node, true);
// Check whether the text contains a back slash
if (asString.indexOf('\\') != -1) {
throw new RetrieveException(
"Text of node '"
+ node.getNodeName()
+ "' is not a valid URL. Use normal slashes instead of backslashes: '"
+ asString + "'");
}
return asString;
}
/**
*
* @param node
* @return
*/
public static String getText(Node node) {
Node textNode = getChild(node, "#text");
if (textNode == null) {
return null;
}
return textNode.getNodeValue();
}
/**
* Returns the CDATA value from an node.
*
* @param node
* from which the cdata will be read
* @param mandatory
* should an exception be thrown in case of an empty node
* @return value of cdata node
* @throws RetrieveException
*/
public static String getCData(Node node, boolean mandatory)
throws RetrieveException {
return getCData(node, mandatory, false);
}
/**
* Returns the CDATA value from an node (optional trimmed).
*
* @param node
* from which the cdata will be read
* @param mandatory
* should an exception be thrown in case of an empty node
* @param trimmed
* should the value from the node be trimmed
* @return value of cdata node
* @throws RetrieveException
*/
public static String getCData(Node node, boolean mandatory, boolean trimmed)
throws RetrieveException {
String text = getCData(node);
if (trimmed && (text != null)) {
text = text.trim();
}
if (mandatory && ((text == null) || (text.length() == 0))) {
throw new RetrieveException("Node '" + node.getNodeName()
+ "' has no CDATA element.");
} else {
return text;
}
}
/**
* Gets the CDATA value of a node.
*
* @param node
* The node to get the CDATA value from.
* @return The text of the node or <code>null</code> if the node has no
* CDATA value.
*/
public static String getCData(Node node) {
Node cDataNode = getChild(node, "#cdata-section");
if (cDataNode == null) {
return null;
}
return cDataNode.getNodeValue();
}
/**
* Returns text from a text or CDATA node.
*
* @param node
* The node from which to read.
* @param mandatory
* Sets whether the result ( a text with minimal 1 char) has to
* be present or not.
* @return the text value from the node.
* @throws RetrieveException
*/
public static String getTextOrCData(Node node, boolean mandatory)
throws RetrieveException {
String asString = getText(node, false);
// Check whether we've found a text node with at least 1 char
if (asString == null || asString.length() == 0) {
// Try to read CDATA content from the node
asString = getCData(node, false);
}
// Nothing found but this is a mandatory value
if ((asString == null || asString.length() == 0) && mandatory) {
throw new RetrieveException("Node '" + node.getNodeName()
+ "' contains no text/CDATA. This is a mandatory value.");
}
return asString;
}
/**
*
* @param node
* @param mandatory
* @return
* @throws RetrieveException
*/
public static String getText(Node node, boolean mandatory)
throws RetrieveException {
return getText(node, mandatory, false);
}
/**
*
* @param node
* @param mandatory
* @param trimmed
* @return
* @throws RetrieveException
*/
public static String getText(Node node, boolean mandatory, boolean trimmed)
throws RetrieveException {
String text = getText(node);
if (trimmed && (text != null)) {
text = text.trim();
}
if (mandatory && ((text == null) || (text.length() == 0))) {
throw new RetrieveException("Node '" + node.getNodeName()
+ "' has no text");
} else {
return text;
}
}
/**
* Gets a child node with a certain name.
* <p>
* If the node has more than one such children, then the first child is
* returned.
*
* @param node
* The node whichs child should be returned.
* @param childNodeName
* The name of the child node.
* @return The child node or <code>null</code> if there is no such child.
*/
public static Node getChild(Node node, String childNodeName) {
NodeList list = node.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
Node child = list.item(i);
if (child.getNodeName().equals(childNodeName)) {
return child;
}
}
// No such child found
return null;
}
/**
*
* @param node
* @param childNodeName
* @param mandatory
* @return
* @throws RetrieveException
*/
public static Node getChild(Node node, String childNodeName,
boolean mandatory) throws RetrieveException {
Node childNode = getChild(node, childNodeName);
if (mandatory && (childNode == null)) {
throw new RetrieveException("Node '" + node.getNodeName()
+ "' must have a child named '" + childNodeName + "'!");
} else {
return childNode;
}
}
/**
*
* @param node
* @param childNodeName
* @return
*/
public static Node[] getChildArr(Node node, String childNodeName) {
ArrayList<Node> list = new ArrayList<Node>();
NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node child = nodeList.item(i);
if (child.getNodeName().equals(childNodeName)) {
list.add(child);
}
}
Node[] nodeArr = new Node[list.size()];
list.toArray(nodeArr);
return nodeArr;
}
/**
* Gets a child node from a parent node. If the parent node has no such
* child the child of the <code>defaultNode</code> is used.
*
* @param node
* The node to get the child from.
* @param defaultNode
* The node to get the child from if <code>node</code> has no
* such child.
* @param childNodeName
* The name of the child.
* @return The child with the given name or <code>null</code> if both the
* <code>node</code> and the <code>defaultNode</code> have no child
* with the given name.
*/
public static Node getCascadedChild(Node node, Node defaultNode,
String childNodeName) {
Node child = XmlToolkit.getChild(node, childNodeName);
if (child == null) {
// Try to get the cascaded child
child = XmlToolkit.getChild(defaultNode, childNodeName);
}
return child;
}
/**
* Gets a child node from a parent node. If the parent node has no such
* child the child of the <code>defaultNode</code> is used.
*
* @param node
* The node to get the child from.
* @param defaultNode
* The node to get the child from if <code>node</code> has no
* such child.
* @param childNodeName
* The name of the child.
* @param mandatory
* Specifies whether to throw an exception if none of the nodes
* have such a child.
* @return The child with the given name.
* @throws RetrieveException
* If both the <code>node</code> and the
* <code>defaultNode</code> have no child with the given name
* and <code>mandatory</code> is <code>true</code>.
*/
public static Node getCascadedChild(Node node, Node defaultNode,
String childNodeName, boolean mandatory) throws RetrieveException {
Node child = getCascadedChild(node, defaultNode, childNodeName);
if (mandatory && (child == null)) {
throw new RetrieveException("Node '" + node.getNodeName()
+ "' or node '" + defaultNode.getNodeName()
+ "' must have a child named '" + childNodeName + "'!");
}
return child;
}
/**
* Gets the text of a child node.
*
* @param node
* The (parent) node that has the child to get the text from.
* @param childNodeName
* The name of the child node.
* @param mandatory
* Specifies whether an exception should be thrown if the child
* has no text.
* @return The text of the child node or <code>null</code> if the child has
* no text and <code>mandatory</code> is <code>false</code>.
* @throws RetrieveException
* If the given node has no child with the given name of if the
* child node has no text and <code>mandatory</code> is
* <code>true</code>.
*/
public static String getChildText(Node node, String childNodeName,
boolean mandatory) throws RetrieveException {
Node child = getChild(node, childNodeName, mandatory);
if (child == null) {
return null;
} else {
return getText(child, mandatory);
}
}
/**
* Gets an attribute value from a node and converts it to a boolean.
*
* @param node
* The node to get the attribute value from.
* @param attributeName
* The name of the attribute to get.
* @return The value of the attribute or <code>defaultValue</code> if there
* is no such attribute.
* @throws RetrieveException
* If there is no such attribute or if the attribute value is no
* boolean.
*/
public static boolean getAttributeAsBoolean(Node node, String attributeName)
throws RetrieveException {
String asString = getAttribute(node, attributeName, true);
if (asString.equalsIgnoreCase("true")) {
return true;
} else if (asString.equalsIgnoreCase("false")) {
return false;
} else {
throw new RetrieveException("Attribute '" + attributeName
+ "' of node '" + node.getNodeName()
+ "' must be either 'true' or 'false': '" + asString + "'");
}
}
/**
* Gets an attribute value from a node and converts it to a boolean.
*
* @param node
* The node to get the attribute value from.
* @param attributeName
* The name of the attribute to get.
* @param defaultValue
* The default value to return if there is no such attribute.
* @return The value of the attribute or <code>defaultValue</code> if there
* is no such attribute.
* @throws RetrieveException
* If the attribute value is no boolean.
*/
public static boolean getAttributeAsBoolean(Node node,
String attributeName, boolean defaultValue)
throws RetrieveException {
String asString = getAttribute(node, attributeName);
if (asString == null) {
return defaultValue;
} else if (asString.equalsIgnoreCase("true")) {
return true;
} else if (asString.equalsIgnoreCase("false")) {
return false;
} else {
throw new RetrieveException("Attribute '" + attributeName
+ "' of node '" + node.getNodeName()
+ "' must be either 'true' or 'false': '" + asString + "'");
}
}
/**
* Gets an attribute value from a node and converts it to an int.
*
* @param node
* The node to get the attribute value from.
* @param attributeName
* The name of the attribute to get.
* @return The value of the attribute or <code>defaultValue</code> if there
* is no such attribute.
* @throws RetrieveException
* If there is no such attribute or if the attribute value is no
* int.
*/
public static int getAttributeAsInt(Node node, String attributeName)
throws RetrieveException {
String asString = getAttribute(node, attributeName, true);
try {
return Integer.parseInt(asString);
} catch (NumberFormatException exc) {
throw new RetrieveException("Attribute '" + attributeName
+ "' of node '" + node.getNodeName()
+ "' must be a number: '" + asString + "'");
}
}
/**
* Gets an attribute value from a node and converts it to an int.
*
* @param node
* The node to get the attribute value from.
* @param attributeName
* The name of the attribute to get.
* @param defaultValue
* The default value to return if there is no such attribute.
* @return The value of the attribute or <code>defaultValue</code> if there
* is no such attribute.
* @throws RetrieveException
* If the attribute value is no int.
*/
public static int getAttributeAsInt(Node node, String attributeName,
int defaultValue) throws RetrieveException {
String asString = getAttribute(node, attributeName);
if (asString == null) {
return defaultValue;
} else {
try {
return Integer.parseInt(asString);
} catch (NumberFormatException exc) {
throw new RetrieveException("Attribute '" + attributeName
+ "' of node '" + node.getNodeName()
+ "' must be a number: '" + asString + "'");
}
}
}
/**
* Gets an attribute value from a node.
*
* @param node
* The node to get the attribute value from.
* @param attributeName
* The name of the wanted attribute.
* @return The attribute value or <code>null</code> if there is no such
* attribute.
*/
public static String getAttribute(Node node, String attributeName) {
Node attributeNode = node.getAttributes().getNamedItem(attributeName);
if (attributeNode == null) {
return null;
} else {
return attributeNode.getNodeValue();
}
}
/**
*
* @param node
* @param attributeName
* @param mandatory
* @return
* @throws RetrieveException
*/
public static String getAttribute(Node node, String attributeName,
boolean mandatory) throws RetrieveException {
String value = getAttribute(node, attributeName);
if (value == null) {
if (mandatory) {
throw new RetrieveException("Node '" + node.getNodeName()
+ "' has no attribute '" + attributeName + "'");
} else {
return null;
}
} else {
return value;
}
}
/**
* Sets the text of a node.
*
* @param doc
* The document the node comes from.
* @param node
* The node whichs text should be changed.
* @param text
* The text to set.
*/
public static void setText(Document doc, Node node, String text) {
Node textNode = getChild(node, "#text");
if (textNode == null) {
textNode = doc.createTextNode(text);
node.appendChild(textNode);
} else {
textNode.setNodeValue(text);
}
}
/**
* Set the text as a CDATA section of a node.
*
* @param doc
* The document the node comes from.
* @param node
* The node whichs CDATA section should be changed.
* @param text
* The text to set.
*/
public static void setCData(Document doc, Node node, String text) {
Node cDataNode = getChild(node, "#cdata-section");
if (cDataNode == null) {
CDATASection cDataSection = doc.createCDATASection(text);
node.appendChild(cDataSection);
} else {
cDataNode.setNodeValue(text);
}
}
/**
* Removes all child nodes from a node.
*
* @param node
* The node to remove the children from.
*/
public static void removeAllChildren(Node node) {
NodeList nodeList = node.getChildNodes();
for (int i = nodeList.getLength() - 1; i >= 0; i--) {
node.removeChild(nodeList.item(i));
}
}
/**
* Removes all child nodes with a certain name.
*
* @param node
* The node to remove the children from.
* @param childNodeName
* The name of the children to remove.
*/
public static void removeAllChildren(Node node, String childNodeName) {
Node[] childArr = getChildArr(node, childNodeName);
for (int i = 0; i < childArr.length; i++) {
node.removeChild(childArr[i]);
}
}
/**
* Adds a child node to a node.
*
* @param doc
* The document the node comes from.
* @param node
* The node were to add the child.
* @param childNodeName
* The name of the child node to add.
* @return The added child node.
*/
public static Node addChild(Document doc, Node node, String childNodeName) {
Node childNode = doc.createElement(childNodeName);
node.appendChild(childNode);
return childNode;
}
/**
* Gets a child node or creates it if no such node exists.
*
* @param doc
* The document the node comes from.
* @param node
* The node were to get the child from or where to add the child.
* @param childNodeName
* The name of the child node to get or add.
* @return The child node.
*/
public static Node getOrAddChild(Document doc, Node node,
String childNodeName) {
Node child = getChild(node, childNodeName);
if (child == null) {
child = addChild(doc, node, childNodeName);
}
return child;
}
/**
* Adds a child node to a node and gives it a text.
*
* @param doc
* The document the node comes from.
* @param node
* The node where to add the child.
* @param childNodeName
* The name of the child node to add.
* @param text
* The text to set to the child.
* @return The added child node.
*/
public static Node addChildWithText(Document doc, Node node,
String childNodeName, String text) {
Node childNode = addChild(doc, node, childNodeName);
setText(doc, childNode, text);
return childNode;
}
/**
* Adds a child node to a node and gives it a CDATA section.
*
* @param doc
* The document the node comes from.
* @param node
* The node where to add the child.
* @param childNodeName
* The name of the child node to add.
* @param text
* The text (as CDATA section) to set to the child.
* @return The added child node.
*/
public static Node addChildWithCData(Document doc, Node node,
String childNodeName, String text) {
Node childNode = addChild(doc, node, childNodeName);
setCData(doc, childNode, text);
return childNode;
}
/**
* Sets an attribute of a node.
*
* @param doc
* The document the node comes from.
* @param node
* The node where to set the attribute.
* @param attribName
* The name of the attribute to set.
* @param attribValue
* The value of the attribute to set.
*/
public static void setAttribute(Document doc, Node node, String attribName,
String attribValue) {
Attr attr = doc.createAttribute(attribName);
attr.setNodeValue(attribValue);
node.getAttributes().setNamedItem(attr);
}
/**
* Pretty prints a node.
*
* @param doc
* The document the node comes from.
* @param node
* The node that should be pretty printed.
*/
public static void prettyPrint(Document doc, Node node) {
// Get the text before the node and extract the indenting
Node parent = node.getParentNode();
String indenting = "";
NodeList siblingList = parent.getChildNodes();
for (int i = 1; i < siblingList.getLength(); i++) {
Node sibling = siblingList.item(i);
if (sibling == node) {
Node nodeBefore = siblingList.item(i - 1);
// Check whether this is a text node
if (nodeBefore.getNodeName().equals("#text")) {
// There is text before the node -> Extract the indenting
String text = nodeBefore.getNodeValue();
int newlinePos = text.lastIndexOf('\n');
if (newlinePos != -1) {
indenting = text.substring(newlinePos);
if (indenting.trim().length() != 0) {
// The indenting is no whitespace -> Forget it
indenting = "";
}
}
}
break;
}
}
// Now pretty print the node
prettyPrint(doc, node, indenting);
}
/**
* Pretty prints a node.
*
* @param doc
* The document the node comes from.
* @param node
* The node that should be pretty printed.
* @param prefix
* The prefix the node should get.
*/
private static void prettyPrint(Document doc, Node node, String prefix) {
String childPrefix = prefix + " ";
// Add the indenting to the children
NodeList childList = node.getChildNodes();
boolean hasChildren = false;
for (int i = childList.getLength() - 1; i >= 0; i--) {
Node child = childList.item(i);
boolean isNormalNode = (!child.getNodeName().startsWith("#"));
if (isNormalNode) {
// Add the indenting to this node
Node textNode = doc.createTextNode(childPrefix);
node.insertBefore(textNode, child);
// pretty print the child's children
prettyPrint(doc, child, childPrefix);
hasChildren = true;
}
}
// Add the indenting to the end tag
if (hasChildren) {
Node textNode = doc.createTextNode(prefix);
node.appendChild(textNode);
}
}
}