21
21
import javax .xml .transform .TransformerFactory ;
22
22
import javax .xml .transform .dom .DOMSource ;
23
23
import 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
+
29
26
import org .w3c .dom .Document ;
30
27
import org .w3c .dom .Element ;
31
28
import org .w3c .dom .NamedNodeMap ;
@@ -44,8 +41,56 @@ public class XMLPrettyPrinter {
44
41
public static int IDENT_AMOUNT = 4 ;
45
42
46
43
/**
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
+ *
49
94
*
50
95
* @param input
51
96
* @return
@@ -55,25 +100,36 @@ public class XMLPrettyPrinter {
55
100
* @throws IOException
56
101
* @throws TransformerException
57
102
* @throws XPathExpressionException
58
- * @throws XPathFactoryConfigurationException
59
103
*/
60
104
public static String prettyPrintXML (String input ) throws TransformerConfigurationException ,
61
- ParserConfigurationException , SAXException , IOException , TransformerException , XPathExpressionException ,
62
- XPathFactoryConfigurationException {
105
+ ParserConfigurationException , SAXException , IOException , TransformerException , XPathExpressionException {
63
106
Transformer transformer = TransformerFactory .newInstance ().newTransformer ();
64
107
transformer .setOutputProperty (OutputKeys .INDENT , "yes" );
65
108
transformer .setOutputProperty ("{http://xml.apache.org/xslt}indent-amount" , Integer .toString (IDENT_AMOUNT ));
66
109
transformer .setOutputProperty (OutputKeys .OMIT_XML_DECLARATION , "yes" );
67
110
StreamResult result = new StreamResult (new StringWriter ());
68
111
Document doc = DocumentBuilderFactory .newInstance ().newDocumentBuilder ()
69
112
.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
+
70
124
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
+
75
131
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 );
77
133
int depth = (int ) doubleDepth ;
78
134
String emptyString = createEmptyString (depth );
79
135
String newContent = content .replaceAll ("\n " , ("\n " + emptyString ));
@@ -84,7 +140,6 @@ public static String prettyPrintXML(String input) throws TransformerConfiguratio
84
140
node .setTextContent (newContent );
85
141
Element element = (Element ) node .getParentNode ();
86
142
element .removeAttribute ("autoformat" );
87
-
88
143
}
89
144
DOMSource source = new DOMSource (doc );
90
145
transformer .transform (source , result );
0 commit comments