2121import javax .xml .transform .TransformerFactory ;
2222import javax .xml .transform .dom .DOMSource ;
2323import javax .xml .transform .stream .StreamResult ;
24- import javax .xml .xpath .XPathConstants ;
25- import javax .xml .xpath .XPathExpression ;
26- import javax .xml .xpath .XPathExpressionException ;
27- import javax .xml .xpath .XPathFactory ;
28- import javax .xml .xpath .XPathFactoryConfigurationException ;
24+ import javax .xml .xpath .*;
25+
2926import org .w3c .dom .Document ;
3027import org .w3c .dom .Element ;
3128import org .w3c .dom .NamedNodeMap ;
@@ -44,8 +41,56 @@ public class XMLPrettyPrinter {
4441 public static int IDENT_AMOUNT = 4 ;
4542
4643 /**
47- * This function formats all tags (and their children) which are marked with
48- * the autoformat="true" attribute and removes this attribute.
44+ * This functions autoformats the text content of the child nodes of the
45+ * node which have the {@code autoformat="true"} attribute set. The
46+ * autoformatting results in having the textContent of the node aligned to
47+ * the depth of indentation of its parent node. The
48+ * {@code autoformat="true"} is also removed from the tree.
49+ *
50+ * <br>
51+ * <br>
52+ *
53+ * <b>Note:</b> the function also removes all blank text from nodes which
54+ * may merge {@code <a></a>} into {@code <a/>}.
55+ *
56+ * <br>
57+ * <br>
58+ *
59+ * <b>Caution:</b> the function literally takes the textContent of the child
60+ * nodes of the autoformat node. This removes any other nodes from that
61+ * child node and leaves only the textContent left.
62+ *
63+ * <br>
64+ * <br>
65+ *
66+ * <h3>Example</h3>
67+ *
68+ * Using this XML as input:
69+ *
70+ * <pre>
71+ * {@code
72+ * <root autoformat="true">
73+ * <textnode>
74+ * this text should
75+ * be aligned
76+ * </textnode>
77+ * </root>
78+ * }
79+ * </pre>
80+ *
81+ * Results in this output XML:
82+ *
83+ * <pre>
84+ * {@code
85+ * <root>
86+ * <textnode>
87+ * this text sould
88+ * be aligned
89+ * </textnode>
90+ * </root>
91+ * }
92+ * </pre>
93+ *
4994 *
5095 * @param input
5196 * @return
@@ -55,25 +100,36 @@ public class XMLPrettyPrinter {
55100 * @throws IOException
56101 * @throws TransformerException
57102 * @throws XPathExpressionException
58- * @throws XPathFactoryConfigurationException
59103 */
60104 public static String prettyPrintXML (String input ) throws TransformerConfigurationException ,
61- ParserConfigurationException , SAXException , IOException , TransformerException , XPathExpressionException ,
62- XPathFactoryConfigurationException {
105+ ParserConfigurationException , SAXException , IOException , TransformerException , XPathExpressionException {
63106 Transformer transformer = TransformerFactory .newInstance ().newTransformer ();
64107 transformer .setOutputProperty (OutputKeys .INDENT , "yes" );
65108 transformer .setOutputProperty ("{http://xml.apache.org/xslt}indent-amount" , Integer .toString (IDENT_AMOUNT ));
66109 transformer .setOutputProperty (OutputKeys .OMIT_XML_DECLARATION , "yes" );
67110 StreamResult result = new StreamResult (new StringWriter ());
68111 Document doc = DocumentBuilderFactory .newInstance ().newDocumentBuilder ()
69112 .parse (new InputSource (new StringReader (input )));
113+
114+ // clear out blank text nodes otherwise the formatter will preserver
115+ // them and add additional newlinesq
116+ XPathExpression emptyTextNodeXPath = XPathFactory .newInstance ().newXPath ()
117+ .compile ("//text()[normalize-space()='']" );
118+ NodeList blankNodesList = (NodeList ) emptyTextNodeXPath .evaluate (doc , XPathConstants .NODESET );
119+ for (int j = 0 ; j < blankNodesList .getLength (); ++j ) {
120+ Node blankNode = blankNodesList .item (j );
121+ blankNode .getParentNode ().removeChild (blankNode );
122+ }
123+
70124 XPathExpression xpathDepth = XPathFactory .newInstance ().newXPath ().compile ("count(ancestor-or-self::*)" );
71- XPathExpression toBeFormatted = XPathFactory .newInstance ().newXPath ().compile ("//*[@autoformat = \' true\' ]/*" );
72- NodeList textNodes = (NodeList ) toBeFormatted .evaluate (doc , XPathConstants .NODESET );
73- for (int i = 0 ; i < textNodes .getLength (); i ++) {
74- Node node = textNodes .item (i );
125+ XPathExpression toBeFormatted = XPathFactory .newInstance ().newXPath ().compile ("//*[@autoformat = 'true']/*" );
126+
127+ NodeList toBeFormattedNodeList = (NodeList ) toBeFormatted .evaluate (doc , XPathConstants .NODESET );
128+ for (int i = 0 ; i < toBeFormattedNodeList .getLength (); i ++) {
129+ Node node = toBeFormattedNodeList .item (i );
130+
75131 String content = node .getTextContent ();
76- double doubleDepth = (Double ) xpathDepth .evaluate (textNodes .item (i ), XPathConstants .NUMBER );
132+ double doubleDepth = (Double ) xpathDepth .evaluate (toBeFormattedNodeList .item (i ), XPathConstants .NUMBER );
77133 int depth = (int ) doubleDepth ;
78134 String emptyString = createEmptyString (depth );
79135 String newContent = content .replaceAll ("\n " , ("\n " + emptyString ));
@@ -84,7 +140,6 @@ public static String prettyPrintXML(String input) throws TransformerConfiguratio
84140 node .setTextContent (newContent );
85141 Element element = (Element ) node .getParentNode ();
86142 element .removeAttribute ("autoformat" );
87-
88143 }
89144 DOMSource source = new DOMSource (doc );
90145 transformer .transform (source , result );
0 commit comments