KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openharmonise > commons > xml > XMLPrettyPrint


1 /*
2  * The contents of this file are subject to the
3  * Mozilla Public License Version 1.1 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at http://www.mozilla.org/MPL/
6  *
7  * Software distributed under the License is distributed on an "AS IS"
8  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
9  * See the License for the specific language governing rights and
10  * limitations under the License.
11  *
12  * The Initial Developer of the Original Code is Simulacra Media Ltd.
13  * Portions created by Simulacra Media Ltd are Copyright (C) Simulacra Media Ltd, 2004.
14  *
15  * All Rights Reserved.
16  *
17  * Contributor(s):
18  */

19
20
21 package org.openharmonise.commons.xml;
22
23 import java.io.*;
24 import java.util.*;
25 import java.util.logging.*;
26
27 import org.openharmonise.commons.xml.namespace.*;
28 import org.w3c.dom.*;
29
30 /**
31  * This class prints XML, formatted so that it is nicely indented and empty elements
32  * are self-closing. It can print to a String or directly to a file, by passing it
33  * either a <code>java.io.File</code> object or a path to the required output file.
34  *
35  * @author Matthew Large
36  */

37
38 public class XMLPrettyPrint {
39
40     /**
41      * Namespace Resolver to use during output.
42      */

43     private NamespaceResolver m_resolver = null;
44
45     /**
46      * true if this printer should act in a namespace aware mode.
47      */

48     private boolean m_bNamespaceAware = false;
49     
50     /**
51      * Logger for this class.
52      */

53     private static final Logger m_logger = Logger.getLogger(XMLPrettyPrint.class.getName());
54
55     public XMLPrettyPrint() {
56     }
57
58     /**
59      * Sets whether XMLPrettyPrint will deal with XML Namespaces. If you are setting
60      * XMLPrettyPrint to be Namespace Aware and you have not set a NamespaceResolver
61      * then a XMLPrettyPrint will create a new local one, this can be overridden by
62      * setting your own using setNamespaceResolver.
63      *
64      * @param bNamespaceAware
65      */

66     public void setNamespaceAware(boolean bNamespaceAware) {
67         this.m_bNamespaceAware = bNamespaceAware;
68         if( bNamespaceAware && m_resolver==null ) {
69             m_resolver = new LocalNamespaceResolver();
70         }
71     }
72
73     /**
74      * Method to check if XMLPrettyPrint is set to be Namespace Aware.
75      *
76      * @return True if XMLPrettyPrint will deal with XML Namespaces
77      */

78     public boolean isNamespaceAware() {
79         return this.m_bNamespaceAware;
80     }
81
82     /**
83      * Sets the NamespaceResolver that XMLPrettyPrint will use when printing namespaced
84      * XML. If the NamespaceResolver that is passed in is not null, XMLPrettyPrint will
85      * automatically be set to be namespace aware. If the NamespaceResolver that is passed in is
86      * null, then XMLPrettyPrint will be set to be namespace unaware and the currently
87      * held resolver (if any) will be removed.
88      *
89      * @param resolver The NamespaceResolver that XMLPrettyPrint is to use
90      */

91     public void setNamespaceResolver(NamespaceResolver resolver) {
92         if (resolver != null) {
93             this.m_resolver = resolver;
94             this.m_bNamespaceAware = true;
95         } else {
96             this.m_resolver = null;
97             this.m_bNamespaceAware = false;
98         }
99     }
100
101     /**
102      * Accessor to the current NamespaceResolver.
103      *
104      * @return The NamespaceResolver that XMLPrettyPrint will use when printing namespaced XML.
105      */

106     public NamespaceResolver getNamespaceResolver() {
107         return this.m_resolver;
108     }
109
110     /**
111      * Prints a given DOM Node to a File as specified by a path.
112      *
113      * @param node DOM Node to print
114      * @param sFilepath Path of File to print DOM Node to
115      */

116     public void printNodeToFile(Node node, String JavaDoc sFilepath) {
117         printNodeToFile(node, new File(sFilepath));
118     }
119
120     /**
121      * Prints a given DOM Node to a given File.
122      *
123      * @param node DOM Node to print
124      * @param file File to print DOM Node to
125      */

126     public void printNodeToFile(Node node, File file) {
127         try {
128             FileOutputStream fos = new FileOutputStream(file);
129             OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
130
131             osw.write(printNode(node));
132
133             osw.close();
134             fos.close();
135         } catch (Exception JavaDoc e) {
136             m_logger.log(Level.WARNING, e.getMessage(), e);
137         }
138     }
139
140     /**
141      * Prints a given DOM Document to a String.
142      *
143      * @param doc DOM Document to print
144      * @return String with output of printed DOM Node
145      */

146     public String JavaDoc printNode(Document doc) throws NamespaceClashException {
147         StringBuffer JavaDoc sBuff = new StringBuffer JavaDoc("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
148         
149         List aInScopeNamespaces = new ArrayList();
150
151         int nChildren = doc.getChildNodes().getLength();
152         
153         if (nChildren == 1) {
154             Node firstNode = doc.getFirstChild();
155             
156             if (firstNode.getNodeType() == Node.TEXT_NODE
157                 && ((Text) firstNode).getNodeValue().equals("")) {
158                 
159             } else if (firstNode.getNodeType() == Node.TEXT_NODE) {
160                 sBuff.append(((Text) firstNode).getData());
161                     
162             } else {
163                 printNode(firstNode, 1, sBuff, (List)((ArrayList)aInScopeNamespaces).clone());
164             }
165         } else {
166             NodeList nl = doc.getChildNodes();
167             
168             for (int i = 0; i < nl.getLength(); i++) {
169                 printNode(nl.item(i), 1, sBuff, (List)((ArrayList)aInScopeNamespaces).clone());
170             }
171         }
172
173         return sBuff.toString();
174     }
175     
176     /**
177      * Prints a given DOM Node to a String.
178      *
179      * @param node DOM Node to print
180      * @return String with output of printed DOM Node
181      * @throws NamespaceClashException
182      */

183     public String JavaDoc printNode(Node node) throws NamespaceClashException {
184         return printNode(node,true);
185     }
186     
187     /**
188      * Prints a given DOM Node to a String.
189      *
190      * @param node DOM Node to print
191      * @param includeXMLDeclaration true if the XML declaration should be included at the top of the output
192      * @return String with output of printed DOM Node
193      * @throws NamespaceClashException
194      */

195     public String JavaDoc printNode(Node node, boolean includeXMLDeclaration) throws NamespaceClashException {
196         StringBuffer JavaDoc sBuff = new StringBuffer JavaDoc();
197         if(includeXMLDeclaration){
198             sBuff.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
199         }
200         
201         List aInScopeNamespaces = new ArrayList();
202         
203         if( this.m_bNamespaceAware ) {
204             if( node.getNodeType()==Node.ELEMENT_NODE && ((Element)node).hasAttribute("xmlns")) {
205                 this.m_resolver.addNamespace( ((Element)node).getAttribute("xmlns"), "" );
206             }
207         }
208
209         if (node instanceof Text) {
210             printTextNode((Text) node, sBuff);
211         } else if(node instanceof Comment) {
212             printComment((Comment) node, 0, sBuff);
213         } else {
214             sBuff.append("<").append( this.handleElementName(node, aInScopeNamespaces) );
215
216             if (node instanceof Element) {
217                 printAttributes((Element) node, sBuff, aInScopeNamespaces);
218             }
219             int nChildren = node.getChildNodes().getLength();
220             if (nChildren == 0) {
221                 sBuff.append("/>");
222                 return sBuff.toString();
223             } else if (nChildren == 1) {
224                 Node firstNode = node.getFirstChild();
225                 if (firstNode.getNodeType() == Node.TEXT_NODE
226                     && ((Text) firstNode).getNodeValue().equals("")) {
227                     sBuff.append("/>");
228                 } else if (firstNode.getNodeType() == Node.TEXT_NODE) {
229                     sBuff
230                         .append(">")
231                         .append(((Text) firstNode).getData())
232                         .append("</")
233                         .append(node.getNodeName())
234                         .append(">");
235                 } else {
236                     sBuff.append(">\n");
237                     printNode(firstNode, 1, sBuff, (List)((ArrayList)aInScopeNamespaces).clone());
238                     sBuff.append("\n</").append( this.handleElementName(node, aInScopeNamespaces) ).append(">");
239                 }
240             } else {
241                 NodeList nl = node.getChildNodes();
242                 sBuff.append(">\n");
243                 for (int i = 0; i < nl.getLength(); i++) {
244                     printNode(nl.item(i), 1, sBuff, (List)((ArrayList)aInScopeNamespaces).clone());
245                 }
246                 sBuff.append("</").append( this.handleElementName(node, aInScopeNamespaces) ).append(">");
247             }
248         }
249         return sBuff.toString();
250     }
251
252     /**
253      * Internal method to print a DOM Node.
254      *
255      * @param node DOM Node to print
256      * @param nLevel Depth of node in XML tree
257      * @param sBuff StringBuffer to print to
258      * @param aInScopeNamespaces List of namespaces that are currently in scope
259      * @throws NamespaceClashException Thrown from underlying NamespaceResolver
260      */

261     private void printNode(Node node, int nLevel, StringBuffer JavaDoc sBuff, List aInScopeNamespaces) throws NamespaceClashException {
262         if (node.getNodeType() == Node.ELEMENT_NODE) {
263             printElementNode((Element) node, nLevel + 1, sBuff, aInScopeNamespaces);
264         } else if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
265             printCDATA((CDATASection) node, nLevel + 1, sBuff);
266         } else if (node.getNodeType() == Node.TEXT_NODE) {
267             printTextNode((Text) node, sBuff);
268         } else if(node.getNodeType() == Node.COMMENT_NODE) {
269             printComment((Comment) node, nLevel, sBuff);
270         }
271     }
272
273     /**
274      * Internal method to print a DOM Element.
275      *
276      * @param el DOM Element to print
277      * @param nLevel Depth of element in XML tree
278      * @param sBuff StringBuffer to print to
279      * @param aInScopeNamespaces List of namespaces that are currently in scope
280      * @throws NamespaceClashException Thrown from underlying NamespaceResolver
281      */

282     private void printElementNode(Element el, int nLevel, StringBuffer JavaDoc sBuff, List aInScopeNamespaces) throws NamespaceClashException {
283         NodeList nl = el.getChildNodes();
284         int nChildren = nl.getLength();
285         if (nChildren == 0) {
286             printElementSelfClosing(el, nLevel, true, sBuff, aInScopeNamespaces);
287             sBuff.append("\n");
288         } else if (nChildren == 1) {
289             Node firstnode = el.getFirstChild();
290             if (firstnode.getNodeType() == Node.TEXT_NODE
291                 && (((Text) firstnode).getNodeValue() == null
292                     || ((Text) firstnode).getNodeValue().equals(""))) {
293                 printElementSelfClosing(el, nLevel, true, sBuff, (List)((ArrayList)aInScopeNamespaces).clone());
294                 sBuff.append("\n");
295             } else if (firstnode.getNodeType() == Node.TEXT_NODE) {
296                 List newInScopeNamespaces = (List)((ArrayList)aInScopeNamespaces).clone();
297                 printElementStart(el, nLevel, true, sBuff, newInScopeNamespaces);
298                 printNode(firstnode, nLevel, sBuff, newInScopeNamespaces);
299                 printElementEnd(el, nLevel, false, sBuff, newInScopeNamespaces);
300                 sBuff.append("\n");
301             } else {
302                 List newInScopeNamespaces = (List)((ArrayList)aInScopeNamespaces).clone();
303                 printElementStart(el, nLevel, true, sBuff, newInScopeNamespaces);
304                 sBuff.append("\n");
305                 printNode(firstnode, nLevel + 1, sBuff, newInScopeNamespaces);
306                 printElementEnd(el, nLevel, true, sBuff, newInScopeNamespaces);
307                 sBuff.append("\n");
308             }
309         } else {
310             printElementStart(el, nLevel, true, sBuff, aInScopeNamespaces);
311             sBuff.append("\n");
312             for (int i = 0; i < nChildren; i++) {
313                 Node node = nl.item(i);
314                 printNode(node, nLevel + 1, sBuff, (List)((ArrayList)aInScopeNamespaces).clone());
315             }
316             printElementEnd(el, nLevel, true, sBuff, aInScopeNamespaces);
317             sBuff.append("\n");
318         }
319     }
320
321     /**
322      * Internal method for printing the opening tag of a DOM Element.
323      *
324      * @param el DOM Element for which to print the opening tag
325      * @param nLevel Depth of element in XML tree
326      * @param bTabs Controls if tabs are printed, they wouldn't be required for inline elements
327      * @param sBuff StringBuffer to print to
328      * @param aInScopeNamespaces List of namespaces that are currently in scope
329      * @throws NamespaceClashException Thrown from underlying NamespaceResolver
330      */

331     private void printElementStart(
332         Element el,
333         int nLevel,
334         boolean bTabs,
335         StringBuffer JavaDoc sBuff, List aInScopeNamespaces) throws NamespaceClashException {
336         if (bTabs) {
337             printTabs(nLevel, sBuff);
338         }
339         sBuff.append("<").append( this.handleElementName(el, aInScopeNamespaces) );
340         printAttributes(el, sBuff, aInScopeNamespaces);
341         sBuff.append(">");
342     }
343
344     /**
345      * Internal method for printing the closing tag of a DOM Element.
346      *
347      * @param el DOM Element for which to print the opening tag
348      * @param nLevel Depth of element in XML tree
349      * @param bTabs Controls if tabs are printed, they wouldn't be required for inline elements
350      * @param sBuff StringBuffer to print to
351      * @param aInScopeNamespaces List of namespaces that are currently in scope
352      * @throws NamespaceClashException Thrown from underlying NamespaceResolver
353      */

354     private void printElementEnd(
355         Element el,
356         int nLevel,
357         boolean bTabs,
358         StringBuffer JavaDoc sBuff, List aInScopeNamespaces) throws NamespaceClashException {
359         if (bTabs) {
360             printTabs(nLevel, sBuff);
361         }
362         sBuff.append("</").append( this.handleElementName(el, aInScopeNamespaces) ).append(">");
363     }
364
365     /**
366      * Internal method for printing the tag of a self-closing DOM Element, i.e. one that does
367      * not have any contents.
368      *
369      * @param el DOM Element for which to print the opening tag
370      * @param nLevel Depth of element in XML tree
371      * @param bTabs Controls if tabs are printed, they wouldn't be required for inline elements
372      * @param sBuff StringBuffer to print to
373      * @param aInScopeNamespaces List of namespaces that are currently in scope
374      * @throws NamespaceClashException Thrown from underlying NamespaceResolver
375      */

376     private void printElementSelfClosing(
377         Element el,
378         int nLevel,
379         boolean bTabs,
380         StringBuffer JavaDoc sBuff, List aInScopeNamespaces) throws NamespaceClashException {
381         if (bTabs) {
382             printTabs(nLevel, sBuff);
383         }
384         sBuff.append("<").append( this.handleElementName(el, aInScopeNamespaces) );
385         printAttributes(el, sBuff, aInScopeNamespaces);
386         sBuff.append("/>");
387     }
388
389     /**
390      * Internal method for printing the DOM Attributes of a DOM Element.
391      *
392      * @param el DOM Element for which to print attributes
393      * @param sBuff StringBuffer to print to
394      * @param aInScopeNamespaces List of namespaces that are currently in scope
395      * @throws NamespaceClashException Thrown from underlying NamespaceResolver
396      */

397     private void printAttributes(Element el, StringBuffer JavaDoc sBuff, List aInScopeNamespaces) throws NamespaceClashException {
398         NamedNodeMap attribs = el.getAttributes();
399         for (int i = 0; i < attribs.getLength(); i++) {
400         Attr nextAtt = (Attr) attribs.item(i);
401             if( !this.m_bNamespaceAware || nextAtt.getNodeName().indexOf("xmlns")==-1 ) {
402                 sBuff
403                     .append(" ")
404                     .append( this.handleAttributeName(nextAtt, aInScopeNamespaces) )
405                     .append("=\"")
406                     .append(this.encodeXMLText(nextAtt.getValue()))
407                     .append("\"");
408             }
409         }
410     }
411
412     /**
413      * Internal method for printing a DOM Text node, wraps up any encoding duties.
414      *
415      * @param txt DOM Text node to print
416      * @param sBuff StringBuffer to print to
417      */

418     private void printTextNode(Text txt, StringBuffer JavaDoc sBuff) {
419         sBuff.append(encodeXMLText(txt.getNodeValue().trim()).trim());
420     }
421     
422     /**
423      * Internal method to print a DOM Element's name with all namespace issues dealt
424      * with.
425      *
426      * @param node DOM Element to deal with
427      * @param aInScopeNamespaces List of namespaces currently in scope
428      * @return Printed element name with any namespace declarations required
429      * @throws NamespaceClashException Thrown from underlying NamespaceResolver
430      */

431     private String JavaDoc handleElementName(Node node, List aInScopeNamespaces) throws NamespaceClashException {
432         String JavaDoc sReturn="";
433         
434         if( this.m_bNamespaceAware ) {
435             if( node.getLocalName()==null ) {
436                 sReturn=node.getNodeName();
437             } else {
438                 if( node.getNamespaceURI()!=null ) {
439                     String JavaDoc sPrefix = this.m_resolver.getPrefixByNode(node);
440                     sReturn = sPrefix;
441                     if( !sPrefix.equals("")){
442                         sReturn = sReturn + ":";
443                     }
444                     sReturn = sReturn + node.getLocalName();
445                     if( !aInScopeNamespaces.contains(node.getNamespaceURI()) ) {
446                         aInScopeNamespaces.add(node.getNamespaceURI());
447                         sReturn = sReturn + " xmlns";
448                         if( !sPrefix.equals("")){
449                             sReturn = sReturn + ":";
450                         }
451                         sReturn = sReturn + sPrefix + "=\"" + node.getNamespaceURI() + "\"";
452                     }
453                 } else {
454                     sReturn = node.getLocalName();
455                 }
456             }
457         } else {
458             if( node.getLocalName()==null ) {
459                 sReturn=node.getNodeName();
460             } else {
461                 sReturn=node.getLocalName();
462             }
463         }
464         
465         return sReturn;
466     }
467     
468     /**
469      * Internal method to print a DOM Attributes's name with all namespace issues dealt
470      * with.
471      *
472      * @param node DOM Attribute to deal with
473      * @param aInScopeNamespaces List of namespaces currently in scope
474      * @return Printed attribute name with any namespace declarations required
475      * @throws NamespaceClashException Thrown from underlying NamespaceResolver
476      */

477     private String JavaDoc handleAttributeName(Node node, List aInScopeNamespaces) throws NamespaceClashException {
478         String JavaDoc sReturn="";
479         
480         if( this.m_bNamespaceAware ) {
481             if( node.getLocalName()==null ) {
482                 sReturn=node.getNodeName();
483             } else {
484                 if( node.getNamespaceURI()!=null ) {
485                     String JavaDoc sPrefix = this.m_resolver.getPrefixByNode(node);
486                     if( !aInScopeNamespaces.contains(node.getNamespaceURI()) ) {
487                         aInScopeNamespaces.add(node.getNamespaceURI());
488                         sReturn = "xmlns:" + sPrefix + "=\"" + node.getNamespaceURI() + "\" ";
489                     }
490                     sReturn = sReturn + sPrefix + ":" + node.getLocalName();
491                 } else {
492                     sReturn = node.getLocalName();
493                 }
494             }
495         } else {
496             if( node.getLocalName()==null ) {
497                 sReturn=node.getNodeName();
498             } else {
499                 sReturn=node.getLocalName();
500             }
501         }
502         
503         return sReturn;
504     }
505
506     /**
507      * Handles XML encoding of text, e.g. & to &amp;.
508      *
509      * @param sText Text to XML encode
510      * @return XML Encoded text
511      */

512     public String JavaDoc encodeXMLText(String JavaDoc sText) {
513         StringBuffer JavaDoc sBuff2 = new StringBuffer JavaDoc(sText);
514         StringBuffer JavaDoc sNewBuff = new StringBuffer JavaDoc();
515
516         for (int i = 0; i < sBuff2.length(); i++) {
517             char currChar = sBuff2.charAt(i);
518             Character JavaDoc currCharObj = new Character JavaDoc(sBuff2.charAt(i));
519             if (currChar == '&') {
520                 if ((sBuff2.length() - 1 - i) >= 4
521                     && sBuff2.charAt(i + 1) == 'a'
522                     && sBuff2.charAt(i + 2) == 'm'
523                     && sBuff2.charAt(i + 3) == 'p'
524                     && sBuff2.charAt(i + 4) == ';') {
525                     i = i + 4;
526                     sNewBuff.append("&amp;");
527                 } else {
528                     sNewBuff.append("&amp;");
529                 }
530             } else if (currChar == '>') {
531                 sNewBuff.append("&gt;");
532             } else if (currChar == '<') {
533                 sNewBuff.append("&lt;");
534             } else {
535                 sNewBuff.append(currChar);
536             }
537         }
538
539         return sNewBuff.toString();
540     }
541
542     /**
543      * Handles XML decoding of text, e.g. &amp;amp; to &;.
544      *
545      * @param sText Text to XML decode
546      * @return XML decoded text
547      */

548     public String JavaDoc decodeXMLText(String JavaDoc sText) {
549         StringBuffer JavaDoc sBuff2 = new StringBuffer JavaDoc(sText);
550         StringBuffer JavaDoc sNewBuff = new StringBuffer JavaDoc();
551
552         for (int i = 0; i < sBuff2.length(); i++) {
553             char currChar = sBuff2.charAt(i);
554             Character JavaDoc currCharObj = new Character JavaDoc(sBuff2.charAt(i));
555             if (currChar == '&') {
556                 if (sBuff2.charAt(i + 1) == 'a'
557                     && sBuff2.charAt(i + 2) == 'm'
558                     && sBuff2.charAt(i + 3) == 'p'
559                     && sBuff2.charAt(i + 4) == ';'
560                     && sBuff2.charAt(i + 5) == 'a'
561                     && sBuff2.charAt(i + 6) == 'm'
562                     && sBuff2.charAt(i + 7) == 'p'
563                     && sBuff2.charAt(i + 8) == ';') {
564                     i = i + 8;
565                     sNewBuff.append("&");
566                 } else if (
567                     sBuff2.charAt(i + 1) == 'a'
568                         && sBuff2.charAt(i + 2) == 'm'
569                         && sBuff2.charAt(i + 3) == 'p'
570                         && sBuff2.charAt(i + 4) == ';') {
571                     i = i + 4;
572                     sNewBuff.append("&");
573                 } else if (currChar == '£') {
574                     System.out.println("Decoding pound");
575                     sNewBuff.append("&#163;");
576                 } else {
577                     sNewBuff.append("&");
578                 }
579             } else {
580                 sNewBuff.append(currChar);
581             }
582         }
583
584         return sNewBuff.toString();
585     }
586
587     /**
588      * Internal method to print a DOM CDATA Section.
589      *
590      * @param cdata DOM CDATASection to be printed
591      * @param nLevel Depth of CDATA Section in XML tree
592      * @param sBuff StringBuffer to print to
593      */

594     private void printCDATA(
595         CDATASection cdata,
596         int nLevel,
597         StringBuffer JavaDoc sBuff) {
598         printTabs(nLevel, sBuff);
599         sBuff.append("<![CDATA[").append(cdata.getData()).append("]]>\n");
600     }
601     
602     /**
603      * Internal method to print a DOM Comment.
604      *
605      * @param comment DOM Comment to be printed
606      * @param nLevel Depth of CDATA Section in XML tree
607      * @param sBuff StringBuffer to print to
608      */

609     private void printComment(
610         Comment comment,
611         int nLevel,
612         StringBuffer JavaDoc sBuff) {
613         printTabs(nLevel, sBuff);
614         sBuff.append("<!--").append(comment.getData()).append("-->\n");
615     }
616
617     /**
618      * Internal method to print the required number of tabs for pretty printing.
619      *
620      * @param nLevel Number of tab sets to print, i.e. depth in XML tree
621      * @param sBuff StringBuffer to print to
622      */

623     private void printTabs(int nLevel, StringBuffer JavaDoc sBuff) {
624         for (int i = 0; i < nLevel; i++) {
625             sBuff.append(" ");
626         }
627     }
628
629 }
Popular Tags