KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > ant > util > DOMElementWriter


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */

18
19 package org.apache.tools.ant.util;
20
21 import java.io.IOException JavaDoc;
22 import java.io.OutputStream JavaDoc;
23 import java.io.OutputStreamWriter JavaDoc;
24 import java.io.Writer JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import org.w3c.dom.Attr JavaDoc;
29 import org.w3c.dom.Element JavaDoc;
30 import org.w3c.dom.NamedNodeMap JavaDoc;
31 import org.w3c.dom.Node JavaDoc;
32 import org.w3c.dom.NodeList JavaDoc;
33 import org.w3c.dom.Text JavaDoc;
34
35 /**
36  * Writes a DOM tree to a given Writer.
37  * warning: this utility currently does not declare XML Namespaces.
38  * <p>Utility class used by {@link org.apache.tools.ant.XmlLogger
39  * XmlLogger} and
40  * org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter
41  * XMLJUnitResultFormatter}.</p>
42  *
43  */

44 public class DOMElementWriter {
45
46     /** prefix for genefrated prefixes */
47     private static final String JavaDoc NS = "ns";
48
49     /** xml declaration is on by default */
50     private boolean xmlDeclaration = true;
51
52     /**
53      * XML Namespaces are ignored by default.
54      */

55     private XmlNamespacePolicy namespacePolicy = XmlNamespacePolicy.IGNORE;
56
57     /**
58      * Map (URI to prefix) of known namespaces.
59      */

60     private HashMap JavaDoc nsPrefixMap = new HashMap JavaDoc();
61
62     /**
63      * Number of generated prefix to use next.
64      */

65     private int nextPrefix = 0;
66
67     /**
68      * Map (Element to URI) of namespaces defined on a given element.
69      */

70     private HashMap JavaDoc nsURIByElement = new HashMap JavaDoc();
71
72     /**
73      * Whether namespaces should be ignored for elements and attributes.
74      *
75      * @since Ant 1.7
76      */

77     public static class XmlNamespacePolicy {
78         private boolean qualifyElements;
79         private boolean qualifyAttributes;
80
81         /**
82          * Ignores namespaces for elements and attributes, the default.
83          */

84         public static final XmlNamespacePolicy IGNORE =
85             new XmlNamespacePolicy(false, false);
86
87         /**
88          * Ignores namespaces for attributes.
89          */

90         public static final XmlNamespacePolicy ONLY_QUALIFY_ELEMENTS =
91             new XmlNamespacePolicy(true, false);
92
93         /**
94          * Qualifies namespaces for elements and attributes.
95          */

96         public static final XmlNamespacePolicy QUALIFY_ALL =
97             new XmlNamespacePolicy(true, true);
98
99         /**
100          * @param qualifyElements whether to qualify elements
101          * @param qualifyAttributes whether to qualify elements
102          */

103         public XmlNamespacePolicy(boolean qualifyElements,
104                                   boolean qualifyAttributes) {
105             this.qualifyElements = qualifyElements;
106             this.qualifyAttributes = qualifyAttributes;
107         }
108     }
109
110     /**
111      * Create an element writer.
112      * The ?xml? declaration will be included, namespaces ignored.
113      */

114     public DOMElementWriter() {
115     }
116
117     /**
118      * Create an element writer
119      * XML namespaces will be ignored.
120      * @param xmlDeclaration flag to indicate whether the ?xml? declaration
121      * should be included.
122      * @since Ant1.7
123      */

124     public DOMElementWriter(boolean xmlDeclaration) {
125         this(xmlDeclaration, XmlNamespacePolicy.IGNORE);
126     }
127
128     /**
129      * Create an element writer
130      * XML namespaces will be ignored.
131      * @param xmlDeclaration flag to indicate whether the ?xml? declaration
132      * should be included.
133      * @param namespacePolicy the policy to use.
134      * @since Ant1.7
135      */

136     public DOMElementWriter(boolean xmlDeclaration,
137                             XmlNamespacePolicy namespacePolicy) {
138         this.xmlDeclaration = xmlDeclaration;
139         this.namespacePolicy = namespacePolicy;
140     }
141
142     private static String JavaDoc lSep = System.getProperty("line.separator");
143
144     // CheckStyle:VisibilityModifier OFF - bc
145
/**
146      * Don't try to be too smart but at least recognize the predefined
147      * entities.
148      */

149     protected String JavaDoc[] knownEntities = {"gt", "amp", "lt", "apos", "quot"};
150     // CheckStyle:VisibilityModifier ON
151

152
153     /**
154      * Writes a DOM tree to a stream in UTF8 encoding. Note that
155      * it prepends the &lt;?xml version='1.0' encoding='UTF-8'?&gt; if
156      * the xmlDeclaration field is true.
157      * The indent number is set to 0 and a 2-space indent.
158      * @param root the root element of the DOM tree.
159      * @param out the outputstream to write to.
160      * @throws IOException if an error happens while writing to the stream.
161      */

162     public void write(Element JavaDoc root, OutputStream JavaDoc out) throws IOException JavaDoc {
163         Writer JavaDoc wri = new OutputStreamWriter JavaDoc(out, "UTF8");
164         writeXMLDeclaration(wri);
165         write(root, wri, 0, " ");
166         wri.flush();
167     }
168
169     /**
170      * Writes the XML declaration if xmlDeclaration is true.
171      * @param wri the writer to write to.
172      * @throws IOException if there is an error.
173      * @since Ant 1.7.0
174      */

175     public void writeXMLDeclaration(Writer JavaDoc wri) throws IOException JavaDoc {
176         if (xmlDeclaration) {
177             wri.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
178         }
179     }
180
181     /**
182      * Writes a DOM tree to a stream.
183      *
184      * @param element the Root DOM element of the tree
185      * @param out where to send the output
186      * @param indent number of
187      * @param indentWith string that should be used to indent the
188      * corresponding tag.
189      * @throws IOException if an error happens while writing to the stream.
190      */

191     public void write(Element JavaDoc element, Writer JavaDoc out, int indent,
192                       String JavaDoc indentWith)
193         throws IOException JavaDoc {
194
195         // Write child elements and text
196
NodeList JavaDoc children = element.getChildNodes();
197         boolean hasChildren = (children.getLength() > 0);
198         boolean hasChildElements = false;
199         openElement(element, out, indent, indentWith, hasChildren);
200
201         if (hasChildren) {
202             for (int i = 0; i < children.getLength(); i++) {
203                 Node JavaDoc child = children.item(i);
204
205                 switch (child.getNodeType()) {
206
207                 case Node.ELEMENT_NODE:
208                     hasChildElements = true;
209                     if (i == 0) {
210                         out.write(lSep);
211                     }
212                     write((Element JavaDoc) child, out, indent + 1, indentWith);
213                     break;
214
215                 case Node.TEXT_NODE:
216                     out.write(encode(child.getNodeValue()));
217                     break;
218
219                 case Node.COMMENT_NODE:
220                     out.write("<!--");
221                     out.write(encode(child.getNodeValue()));
222                     out.write("-->");
223                     break;
224
225                 case Node.CDATA_SECTION_NODE:
226                     out.write("<![CDATA[");
227                     out.write(encodedata(((Text JavaDoc) child).getData()));
228                     out.write("]]>");
229                     break;
230
231                 case Node.ENTITY_REFERENCE_NODE:
232                     out.write('&');
233                     out.write(child.getNodeName());
234                     out.write(';');
235                     break;
236
237                 case Node.PROCESSING_INSTRUCTION_NODE:
238                     out.write("<?");
239                     out.write(child.getNodeName());
240                     String JavaDoc data = child.getNodeValue();
241                     if (data != null && data.length() > 0) {
242                         out.write(' ');
243                         out.write(data);
244                     }
245                     out.write("?>");
246                     break;
247                 default:
248                     // Do nothing
249
}
250             }
251             closeElement(element, out, indent, indentWith, hasChildElements);
252         }
253     }
254
255     /**
256      * Writes the opening tag - including all attributes -
257      * corresponding to a DOM element.
258      *
259      * @param element the DOM element to write
260      * @param out where to send the output
261      * @param indent number of
262      * @param indentWith string that should be used to indent the
263      * corresponding tag.
264      * @throws IOException if an error happens while writing to the stream.
265      */

266     public void openElement(Element JavaDoc element, Writer JavaDoc out, int indent,
267                             String JavaDoc indentWith)
268         throws IOException JavaDoc {
269         openElement(element, out, indent, indentWith, true);
270     }
271
272     /**
273      * Writes the opening tag - including all attributes -
274      * corresponding to a DOM element.
275      *
276      * @param element the DOM element to write
277      * @param out where to send the output
278      * @param indent number of
279      * @param indentWith string that should be used to indent the
280      * corresponding tag.
281      * @param hasChildren whether this element has children.
282      * @throws IOException if an error happens while writing to the stream.
283      * @since Ant 1.7
284      */

285     public void openElement(Element JavaDoc element, Writer JavaDoc out, int indent,
286                             String JavaDoc indentWith, boolean hasChildren)
287         throws IOException JavaDoc {
288         // Write indent characters
289
for (int i = 0; i < indent; i++) {
290             out.write(indentWith);
291         }
292
293         // Write element
294
out.write("<");
295         if (namespacePolicy.qualifyElements) {
296             String JavaDoc uri = getNamespaceURI(element);
297             String JavaDoc prefix = (String JavaDoc) nsPrefixMap.get(uri);
298             if (prefix == null) {
299                 if (nsPrefixMap.isEmpty()) {
300                     // steal default namespace
301
prefix = "";
302                 } else {
303                     prefix = NS + (nextPrefix++);
304                 }
305                 nsPrefixMap.put(uri, prefix);
306                 addNSDefinition(element, uri);
307             }
308             if (!"".equals(prefix)) {
309                 out.write(prefix);
310                 out.write(":");
311             }
312         }
313         out.write(element.getTagName());
314
315         // Write attributes
316
NamedNodeMap JavaDoc attrs = element.getAttributes();
317         for (int i = 0; i < attrs.getLength(); i++) {
318             Attr JavaDoc attr = (Attr JavaDoc) attrs.item(i);
319             out.write(" ");
320             if (namespacePolicy.qualifyAttributes) {
321                 String JavaDoc uri = getNamespaceURI(attr);
322                 String JavaDoc prefix = (String JavaDoc) nsPrefixMap.get(uri);
323                 if (prefix == null) {
324                     prefix = NS + (nextPrefix++);
325                     nsPrefixMap.put(uri, prefix);
326                     addNSDefinition(element, uri);
327                 }
328                 out.write(prefix);
329                 out.write(":");
330             }
331             out.write(attr.getName());
332             out.write("=\"");
333             out.write(encode(attr.getValue()));
334             out.write("\"");
335         }
336
337         // write namespace declarations
338
ArrayList JavaDoc al = (ArrayList JavaDoc) nsURIByElement.get(element);
339         if (al != null) {
340             Iterator JavaDoc iter = al.iterator();
341             while (iter.hasNext()) {
342                 String JavaDoc uri = (String JavaDoc) iter.next();
343                 String JavaDoc prefix = (String JavaDoc) nsPrefixMap.get(uri);
344                 out.write(" xmlns");
345                 if (!"".equals(prefix)) {
346                     out.write(":");
347                     out.write(prefix);
348                 }
349                 out.write("=\"");
350                 out.write(uri);
351                 out.write("\"");
352             }
353         }
354
355         if (hasChildren) {
356             out.write(">");
357         } else {
358             removeNSDefinitions(element);
359             out.write(" />");
360             out.write(lSep);
361             out.flush();
362         }
363     }
364
365     /**
366      * Writes a DOM tree to a stream.
367      *
368      * @param element the Root DOM element of the tree
369      * @param out where to send the output
370      * @param indent number of
371      * @param indentWith string that should be used to indent the
372      * corresponding tag.
373      * @param hasChildren if true indent.
374      * @throws IOException if an error happens while writing to the stream.
375      */

376     public void closeElement(Element JavaDoc element, Writer JavaDoc out, int indent,
377                              String JavaDoc indentWith, boolean hasChildren)
378         throws IOException JavaDoc {
379         // If we had child elements, we need to indent before we close
380
// the element, otherwise we're on the same line and don't need
381
// to indent
382
if (hasChildren) {
383             for (int i = 0; i < indent; i++) {
384                 out.write(indentWith);
385             }
386         }
387
388         // Write element close
389
out.write("</");
390         if (namespacePolicy.qualifyElements) {
391             String JavaDoc uri = getNamespaceURI(element);
392             String JavaDoc prefix = (String JavaDoc) nsPrefixMap.get(uri);
393             if (prefix != null && !"".equals(prefix)) {
394                 out.write(prefix);
395                 out.write(":");
396             }
397             removeNSDefinitions(element);
398         }
399         out.write(element.getTagName());
400         out.write(">");
401         out.write(lSep);
402         out.flush();
403     }
404
405     /**
406      * Escape &lt;, &gt; &amp; &apos;, &quot; as their entities and
407      * drop characters that are illegal in XML documents.
408      * @param value the string to encode.
409      * @return the encoded string.
410      */

411     public String JavaDoc encode(String JavaDoc value) {
412         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
413         int len = value.length();
414         for (int i = 0; i < len; i++) {
415             char c = value.charAt(i);
416             switch (c) {
417             case '<':
418                 sb.append("&lt;");
419                 break;
420             case '>':
421                 sb.append("&gt;");
422                 break;
423             case '\'':
424                 sb.append("&apos;");
425                 break;
426             case '\"':
427                 sb.append("&quot;");
428                 break;
429             case '&':
430                 int nextSemi = value.indexOf(";", i);
431                 if (nextSemi < 0
432                     || !isReference(value.substring(i, nextSemi + 1))) {
433                     sb.append("&amp;");
434                 } else {
435                     sb.append('&');
436                 }
437                 break;
438             default:
439                 if (isLegalCharacter(c)) {
440                     sb.append(c);
441                 }
442                 break;
443             }
444         }
445         return sb.substring(0);
446     }
447
448     /**
449      * Drop characters that are illegal in XML documents.
450      *
451      * <p>Also ensure that we are not including an <code>]]&gt;</code>
452      * marker by replacing that sequence with
453      * <code>&amp;#x5d;&amp;#x5d;&amp;gt;</code>.</p>
454      *
455      * <p>See XML 1.0 2.2 <a
456      * HREF="http://www.w3.org/TR/1998/REC-xml-19980210#charsets">
457      * http://www.w3.org/TR/1998/REC-xml-19980210#charsets</a> and
458      * 2.7 <a
459      * HREF="http://www.w3.org/TR/1998/REC-xml-19980210#sec-cdata-sect">http://www.w3.org/TR/1998/REC-xml-19980210#sec-cdata-sect</a>.</p>
460      * @param value the value to be encoded.
461      * @return the encoded value.
462
463      */

464     public String JavaDoc encodedata(final String JavaDoc value) {
465         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
466         int len = value.length();
467         for (int i = 0; i < len; ++i) {
468             char c = value.charAt(i);
469             if (isLegalCharacter(c)) {
470                 sb.append(c);
471             }
472         }
473
474         String JavaDoc result = sb.substring(0);
475         int cdEnd = result.indexOf("]]>");
476         while (cdEnd != -1) {
477             sb.setLength(cdEnd);
478             sb.append("&#x5d;&#x5d;&gt;")
479                 .append(result.substring(cdEnd + 3));
480             result = sb.substring(0);
481             cdEnd = result.indexOf("]]>");
482         }
483
484         return result;
485     }
486
487     /**
488      * Is the given argument a character or entity reference?
489      * @param ent the value to be checked.
490      * @return true if it is an entity.
491      */

492     public boolean isReference(String JavaDoc ent) {
493         if (!(ent.charAt(0) == '&') || !ent.endsWith(";")) {
494             return false;
495         }
496
497         if (ent.charAt(1) == '#') {
498             if (ent.charAt(2) == 'x') {
499                 try {
500                     Integer.parseInt(ent.substring(3, ent.length() - 1), 16);
501                     return true;
502                 } catch (NumberFormatException JavaDoc nfe) {
503                     return false;
504                 }
505             } else {
506                 try {
507                     Integer.parseInt(ent.substring(2, ent.length() - 1));
508                     return true;
509                 } catch (NumberFormatException JavaDoc nfe) {
510                     return false;
511                 }
512             }
513         }
514
515         String JavaDoc name = ent.substring(1, ent.length() - 1);
516         for (int i = 0; i < knownEntities.length; i++) {
517             if (name.equals(knownEntities[i])) {
518                 return true;
519             }
520         }
521         return false;
522     }
523
524     /**
525      * Is the given character allowed inside an XML document?
526      *
527      * <p>See XML 1.0 2.2 <a
528      * HREF="http://www.w3.org/TR/1998/REC-xml-19980210#charsets">
529      * http://www.w3.org/TR/1998/REC-xml-19980210#charsets</a>.</p>
530      * @param c the character to test.
531      * @return true if the character is allowed.
532      * @since 1.10, Ant 1.5
533      */

534     public boolean isLegalCharacter(char c) {
535         if (c == 0x9 || c == 0xA || c == 0xD) {
536             return true;
537         } else if (c < 0x20) {
538             return false;
539         } else if (c <= 0xD7FF) {
540             return true;
541         } else if (c < 0xE000) {
542             return false;
543         } else if (c <= 0xFFFD) {
544             return true;
545         }
546         return false;
547     }
548
549     private void removeNSDefinitions(Element JavaDoc element) {
550         ArrayList JavaDoc al = (ArrayList JavaDoc) nsURIByElement.get(element);
551         if (al != null) {
552             Iterator JavaDoc iter = al.iterator();
553             while (iter.hasNext()) {
554                 nsPrefixMap.remove(iter.next());
555             }
556             nsURIByElement.remove(element);
557         }
558     }
559
560     private void addNSDefinition(Element JavaDoc element, String JavaDoc uri) {
561         ArrayList JavaDoc al = (ArrayList JavaDoc) nsURIByElement.get(element);
562         if (al == null) {
563             al = new ArrayList JavaDoc();
564             nsURIByElement.put(element, al);
565         }
566         al.add(uri);
567     }
568
569     private static String JavaDoc getNamespaceURI(Node JavaDoc n) {
570         String JavaDoc uri = n.getNamespaceURI();
571         if (uri == null) {
572             // FIXME: Is "No Namespace is Empty Namespace" really OK?
573
uri = "";
574         }
575         return uri;
576     }
577 }
578
Popular Tags