View Javadoc

1   /*
2    * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
3    *
4    * This software is open source.
5    * See the bottom of this file for the licence.
6    */
7   
8   package org.dom4j.io;
9   
10  import java.io.BufferedWriter;
11  import java.io.IOException;
12  import java.io.OutputStream;
13  import java.io.OutputStreamWriter;
14  import java.io.UnsupportedEncodingException;
15  import java.io.Writer;
16  import java.util.HashMap;
17  import java.util.Iterator;
18  import java.util.List;
19  import java.util.Map;
20  import java.util.StringTokenizer;
21  
22  import org.dom4j.Attribute;
23  import org.dom4j.CDATA;
24  import org.dom4j.Comment;
25  import org.dom4j.Document;
26  import org.dom4j.DocumentType;
27  import org.dom4j.Element;
28  import org.dom4j.Entity;
29  import org.dom4j.Namespace;
30  import org.dom4j.Node;
31  import org.dom4j.ProcessingInstruction;
32  import org.dom4j.Text;
33  import org.dom4j.tree.NamespaceStack;
34  
35  import org.xml.sax.Attributes;
36  import org.xml.sax.InputSource;
37  import org.xml.sax.Locator;
38  import org.xml.sax.SAXException;
39  import org.xml.sax.SAXNotRecognizedException;
40  import org.xml.sax.SAXNotSupportedException;
41  import org.xml.sax.XMLReader;
42  import org.xml.sax.ext.LexicalHandler;
43  import org.xml.sax.helpers.XMLFilterImpl;
44  
45  /***
46   * <p>
47   * <code>XMLWriter</code> takes a DOM4J tree and formats it to a stream as
48   * XML. It can also take SAX events too so can be used by SAX clients as this
49   * object implements the {@link org.xml.sax.ContentHandler}and {@link
50   * LexicalHandler} interfaces. as well. This formatter performs typical document
51   * formatting. The XML declaration and processing instructions are always on
52   * their own lines. An {@link OutputFormat}object can be used to define how
53   * whitespace is handled when printing and allows various configuration options,
54   * such as to allow suppression of the XML declaration, the encoding declaration
55   * or whether empty documents are collapsed.
56   * </p>
57   * 
58   * <p>
59   * There are <code>write(...)</code> methods to print any of the standard
60   * DOM4J classes, including <code>Document</code> and <code>Element</code>,
61   * to either a <code>Writer</code> or an <code>OutputStream</code>.
62   * Warning: using your own <code>Writer</code> may cause the writer's
63   * preferred character encoding to be ignored. If you use encodings other than
64   * UTF8, we recommend using the method that takes an OutputStream instead.
65   * </p>
66   * 
67   * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
68   * @author Joseph Bowbeer
69   * @version $Revision: 1.83 $
70   */
71  public class XMLWriter extends XMLFilterImpl implements LexicalHandler {
72      private static final String PAD_TEXT = " ";
73  
74      protected static final String[] LEXICAL_HANDLER_NAMES = {
75              "http://xml.org/sax/properties/lexical-handler",
76              "http://xml.org/sax/handlers/LexicalHandler"};
77  
78      protected static final OutputFormat DEFAULT_FORMAT = new OutputFormat();
79  
80      /*** Should entityRefs by resolved when writing ? */
81      private boolean resolveEntityRefs = true;
82  
83      /***
84       * Stores the last type of node written so algorithms can refer to the
85       * previous node type
86       */
87      protected int lastOutputNodeType;
88  
89      /***
90       * Stores if the last written element node was a closing tag or an opening
91       * tag.
92       */
93      private boolean lastElementClosed = false;
94  
95      /*** Stores the xml:space attribute value of preserve for whitespace flag */
96      protected boolean preserve = false;
97  
98      /*** The Writer used to output to */
99      protected Writer writer;
100 
101     /*** The Stack of namespaceStack written so far */
102     private NamespaceStack namespaceStack = new NamespaceStack();
103 
104     /*** The format used by this writer */
105     private OutputFormat format;
106 
107     /*** whether we should escape text */
108     private boolean escapeText = true;
109 
110     /***
111      * The initial number of indentations (so you can print a whole document
112      * indented, if you like)
113      */
114     private int indentLevel = 0;
115 
116     /*** buffer used when escaping strings */
117     private StringBuffer buffer = new StringBuffer();
118 
119     /***
120      * whether we have added characters before from the same chunk of characters
121      */
122     private boolean charsAdded = false;
123 
124     private char lastChar;
125 
126     /*** Whether a flush should occur after writing a document */
127     private boolean autoFlush;
128 
129     /*** Lexical handler we should delegate to */
130     private LexicalHandler lexicalHandler;
131 
132     /***
133      * Whether comments should appear inside DTD declarations - defaults to
134      * false
135      */
136     private boolean showCommentsInDTDs;
137 
138     /*** Is the writer curerntly inside a DTD definition? */
139     private boolean inDTD;
140 
141     /*** The namespaces used for the current element when consuming SAX events */
142     private Map namespacesMap;
143 
144     /***
145      * what is the maximum allowed character code such as 127 in US-ASCII (7
146      * bit) or 255 in ISO- (8 bit) or -1 to not escape any characters (other
147      * than the special XML characters like &lt; &gt; &amp;)
148      */
149     private int maximumAllowedCharacter;
150 
151     public XMLWriter(Writer writer) {
152         this(writer, DEFAULT_FORMAT);
153     }
154 
155     public XMLWriter(Writer writer, OutputFormat format) {
156         this.writer = writer;
157         this.format = format;
158         namespaceStack.push(Namespace.NO_NAMESPACE);
159     }
160 
161     public XMLWriter() {
162         this.format = DEFAULT_FORMAT;
163         this.writer = new BufferedWriter(new OutputStreamWriter(System.out));
164         this.autoFlush = true;
165         namespaceStack.push(Namespace.NO_NAMESPACE);
166     }
167 
168     public XMLWriter(OutputStream out) throws UnsupportedEncodingException {
169         this.format = DEFAULT_FORMAT;
170         this.writer = createWriter(out, format.getEncoding());
171         this.autoFlush = true;
172         namespaceStack.push(Namespace.NO_NAMESPACE);
173     }
174 
175     public XMLWriter(OutputStream out, OutputFormat format)
176             throws UnsupportedEncodingException {
177         this.format = format;
178         this.writer = createWriter(out, format.getEncoding());
179         this.autoFlush = true;
180         namespaceStack.push(Namespace.NO_NAMESPACE);
181     }
182 
183     public XMLWriter(OutputFormat format) throws UnsupportedEncodingException {
184         this.format = format;
185         this.writer = createWriter(System.out, format.getEncoding());
186         this.autoFlush = true;
187         namespaceStack.push(Namespace.NO_NAMESPACE);
188     }
189 
190     public void setWriter(Writer writer) {
191         this.writer = writer;
192         this.autoFlush = false;
193     }
194 
195     public void setOutputStream(OutputStream out)
196             throws UnsupportedEncodingException {
197         this.writer = createWriter(out, format.getEncoding());
198         this.autoFlush = true;
199     }
200 
201     /***
202      * DOCUMENT ME!
203      * 
204      * @return true if text thats output should be escaped. This is enabled by
205      *         default. It could be disabled if the output format is textual,
206      *         like in XSLT where we can have xml, html or text output.
207      */
208     public boolean isEscapeText() {
209         return escapeText;
210     }
211 
212     /***
213      * Sets whether text output should be escaped or not. This is enabled by
214      * default. It could be disabled if the output format is textual, like in
215      * XSLT where we can have xml, html or text output.
216      * 
217      * @param escapeText
218      *            DOCUMENT ME!
219      */
220     public void setEscapeText(boolean escapeText) {
221         this.escapeText = escapeText;
222     }
223 
224     /***
225      * Set the initial indentation level. This can be used to output a document
226      * (or, more likely, an element) starting at a given indent level, so it's
227      * not always flush against the left margin. Default: 0
228      * 
229      * @param indentLevel
230      *            the number of indents to start with
231      */
232     public void setIndentLevel(int indentLevel) {
233         this.indentLevel = indentLevel;
234     }
235 
236     /***
237      * Returns the maximum allowed character code that should be allowed
238      * unescaped which defaults to 127 in US-ASCII (7 bit) or 255 in ISO- (8
239      * bit).
240      * 
241      * @return DOCUMENT ME!
242      */
243     public int getMaximumAllowedCharacter() {
244         if (maximumAllowedCharacter == 0) {
245             maximumAllowedCharacter = defaultMaximumAllowedCharacter();
246         }
247 
248         return maximumAllowedCharacter;
249     }
250 
251     /***
252      * Sets the maximum allowed character code that should be allowed unescaped
253      * such as 127 in US-ASCII (7 bit) or 255 in ISO- (8 bit) or -1 to not
254      * escape any characters (other than the special XML characters like &lt;
255      * &gt; &amp;) If this is not explicitly set then it is defaulted from the
256      * encoding.
257      * 
258      * @param maximumAllowedCharacter
259      *            The maximumAllowedCharacter to set
260      */
261     public void setMaximumAllowedCharacter(int maximumAllowedCharacter) {
262         this.maximumAllowedCharacter = maximumAllowedCharacter;
263     }
264 
265     /***
266      * Flushes the underlying Writer
267      * 
268      * @throws IOException
269      *             DOCUMENT ME!
270      */
271     public void flush() throws IOException {
272         writer.flush();
273     }
274 
275     /***
276      * Closes the underlying Writer
277      * 
278      * @throws IOException
279      *             DOCUMENT ME!
280      */
281     public void close() throws IOException {
282         writer.close();
283     }
284 
285     /***
286      * Writes the new line text to the underlying Writer
287      * 
288      * @throws IOException
289      *             DOCUMENT ME!
290      */
291     public void println() throws IOException {
292         writer.write(format.getLineSeparator());
293     }
294 
295     /***
296      * Writes the given {@link Attribute}.
297      * 
298      * @param attribute
299      *            <code>Attribute</code> to output.
300      * 
301      * @throws IOException
302      *             DOCUMENT ME!
303      */
304     public void write(Attribute attribute) throws IOException {
305         writeAttribute(attribute);
306 
307         if (autoFlush) {
308             flush();
309         }
310     }
311 
312     /***
313      * <p>
314      * This will print the <code>Document</code> to the current Writer.
315      * </p>
316      * 
317      * <p>
318      * Warning: using your own Writer may cause the writer's preferred character
319      * encoding to be ignored. If you use encodings other than UTF8, we
320      * recommend using the method that takes an OutputStream instead.
321      * </p>
322      * 
323      * <p>
324      * Note: as with all Writers, you may need to flush() yours after this
325      * method returns.
326      * </p>
327      * 
328      * @param doc
329      *            <code>Document</code> to format.
330      * 
331      * @throws IOException
332      *             if there's any problem writing.
333      */
334     public void write(Document doc) throws IOException {
335         writeDeclaration();
336 
337         if (doc.getDocType() != null) {
338             indent();
339             writeDocType(doc.getDocType());
340         }
341 
342         for (int i = 0, size = doc.nodeCount(); i < size; i++) {
343             Node node = doc.node(i);
344             writeNode(node);
345         }
346 
347         writePrintln();
348 
349         if (autoFlush) {
350             flush();
351         }
352     }
353 
354     /***
355      * <p>
356      * Writes the <code>{@link Element}</code>, including its <code>{@link
357      * Attribute}</code>
358      * s, and its value, and all its content (child nodes) to the current
359      * Writer.
360      * </p>
361      * 
362      * @param element
363      *            <code>Element</code> to output.
364      * 
365      * @throws IOException
366      *             DOCUMENT ME!
367      */
368     public void write(Element element) throws IOException {
369         writeElement(element);
370 
371         if (autoFlush) {
372             flush();
373         }
374     }
375 
376     /***
377      * Writes the given {@link CDATA}.
378      * 
379      * @param cdata
380      *            <code>CDATA</code> to output.
381      * 
382      * @throws IOException
383      *             DOCUMENT ME!
384      */
385     public void write(CDATA cdata) throws IOException {
386         writeCDATA(cdata.getText());
387 
388         if (autoFlush) {
389             flush();
390         }
391     }
392 
393     /***
394      * Writes the given {@link Comment}.
395      * 
396      * @param comment
397      *            <code>Comment</code> to output.
398      * 
399      * @throws IOException
400      *             DOCUMENT ME!
401      */
402     public void write(Comment comment) throws IOException {
403         writeComment(comment.getText());
404 
405         if (autoFlush) {
406             flush();
407         }
408     }
409 
410     /***
411      * Writes the given {@link DocumentType}.
412      * 
413      * @param docType
414      *            <code>DocumentType</code> to output.
415      * 
416      * @throws IOException
417      *             DOCUMENT ME!
418      */
419     public void write(DocumentType docType) throws IOException {
420         writeDocType(docType);
421 
422         if (autoFlush) {
423             flush();
424         }
425     }
426 
427     /***
428      * Writes the given {@link Entity}.
429      * 
430      * @param entity
431      *            <code>Entity</code> to output.
432      * 
433      * @throws IOException
434      *             DOCUMENT ME!
435      */
436     public void write(Entity entity) throws IOException {
437         writeEntity(entity);
438 
439         if (autoFlush) {
440             flush();
441         }
442     }
443 
444     /***
445      * Writes the given {@link Namespace}.
446      * 
447      * @param namespace
448      *            <code>Namespace</code> to output.
449      * 
450      * @throws IOException
451      *             DOCUMENT ME!
452      */
453     public void write(Namespace namespace) throws IOException {
454         writeNamespace(namespace);
455 
456         if (autoFlush) {
457             flush();
458         }
459     }
460 
461     /***
462      * Writes the given {@link ProcessingInstruction}.
463      * 
464      * @param processingInstruction
465      *            <code>ProcessingInstruction</code> to output.
466      * 
467      * @throws IOException
468      *             DOCUMENT ME!
469      */
470     public void write(ProcessingInstruction processingInstruction)
471             throws IOException {
472         writeProcessingInstruction(processingInstruction);
473 
474         if (autoFlush) {
475             flush();
476         }
477     }
478 
479     /***
480      * <p>
481      * Print out a {@link String}, Perfoms the necessary entity escaping and
482      * whitespace stripping.
483      * </p>
484      * 
485      * @param text
486      *            is the text to output
487      * 
488      * @throws IOException
489      *             DOCUMENT ME!
490      */
491     public void write(String text) throws IOException {
492         writeString(text);
493 
494         if (autoFlush) {
495             flush();
496         }
497     }
498 
499     /***
500      * Writes the given {@link Text}.
501      * 
502      * @param text
503      *            <code>Text</code> to output.
504      * 
505      * @throws IOException
506      *             DOCUMENT ME!
507      */
508     public void write(Text text) throws IOException {
509         writeString(text.getText());
510 
511         if (autoFlush) {
512             flush();
513         }
514     }
515 
516     /***
517      * Writes the given {@link Node}.
518      * 
519      * @param node
520      *            <code>Node</code> to output.
521      * 
522      * @throws IOException
523      *             DOCUMENT ME!
524      */
525     public void write(Node node) throws IOException {
526         writeNode(node);
527 
528         if (autoFlush) {
529             flush();
530         }
531     }
532 
533     /***
534      * Writes the given object which should be a String, a Node or a List of
535      * Nodes.
536      * 
537      * @param object
538      *            is the object to output.
539      * 
540      * @throws IOException
541      *             DOCUMENT ME!
542      */
543     public void write(Object object) throws IOException {
544         if (object instanceof Node) {
545             write((Node) object);
546         } else if (object instanceof String) {
547             write((String) object);
548         } else if (object instanceof List) {
549             List list = (List) object;
550 
551             for (int i = 0, size = list.size(); i < size; i++) {
552                 write(list.get(i));
553             }
554         } else if (object != null) {
555             throw new IOException("Invalid object: " + object);
556         }
557     }
558 
559     /***
560      * <p>
561      * Writes the opening tag of an {@link Element}, including its {@link
562      * Attribute}s but without its content.
563      * </p>
564      * 
565      * @param element
566      *            <code>Element</code> to output.
567      * 
568      * @throws IOException
569      *             DOCUMENT ME!
570      */
571     public void writeOpen(Element element) throws IOException {
572         writer.write("<");
573         writer.write(element.getQualifiedName());
574         writeAttributes(element);
575         writer.write(">");
576     }
577 
578     /***
579      * <p>
580      * Writes the closing tag of an {@link Element}
581      * </p>
582      * 
583      * @param element
584      *            <code>Element</code> to output.
585      * 
586      * @throws IOException
587      *             DOCUMENT ME!
588      */
589     public void writeClose(Element element) throws IOException {
590         writeClose(element.getQualifiedName());
591     }
592 
593     // XMLFilterImpl methods
594     // -------------------------------------------------------------------------
595     public void parse(InputSource source) throws IOException, SAXException {
596         installLexicalHandler();
597         super.parse(source);
598     }
599 
600     public void setProperty(String name, Object value)
601             throws SAXNotRecognizedException, SAXNotSupportedException {
602         for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
603             if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
604                 setLexicalHandler((LexicalHandler) value);
605 
606                 return;
607             }
608         }
609 
610         super.setProperty(name, value);
611     }
612 
613     public Object getProperty(String name) throws SAXNotRecognizedException,
614             SAXNotSupportedException {
615         for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
616             if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
617                 return getLexicalHandler();
618             }
619         }
620 
621         return super.getProperty(name);
622     }
623 
624     public void setLexicalHandler(LexicalHandler handler) {
625         if (handler == null) {
626             throw new NullPointerException("Null lexical handler");
627         } else {
628             this.lexicalHandler = handler;
629         }
630     }
631 
632     public LexicalHandler getLexicalHandler() {
633         return lexicalHandler;
634     }
635 
636     // ContentHandler interface
637     // -------------------------------------------------------------------------
638     public void setDocumentLocator(Locator locator) {
639         super.setDocumentLocator(locator);
640     }
641 
642     public void startDocument() throws SAXException {
643         try {
644             writeDeclaration();
645             super.startDocument();
646         } catch (IOException e) {
647             handleException(e);
648         }
649     }
650 
651     public void endDocument() throws SAXException {
652         super.endDocument();
653 
654         if (autoFlush) {
655             try {
656                 flush();
657             } catch (IOException e) {
658             }
659         }
660     }
661 
662     public void startPrefixMapping(String prefix, String uri)
663             throws SAXException {
664         if (namespacesMap == null) {
665             namespacesMap = new HashMap();
666         }
667 
668         namespacesMap.put(prefix, uri);
669         super.startPrefixMapping(prefix, uri);
670     }
671 
672     public void endPrefixMapping(String prefix) throws SAXException {
673         super.endPrefixMapping(prefix);
674     }
675 
676     public void startElement(String namespaceURI, String localName,
677             String qName, Attributes attributes) throws SAXException {
678         try {
679             charsAdded = false;
680 
681             writePrintln();
682             indent();
683             writer.write("<");
684             writer.write(qName);
685             writeNamespaces();
686             writeAttributes(attributes);
687             writer.write(">");
688             ++indentLevel;
689             lastOutputNodeType = Node.ELEMENT_NODE;
690             lastElementClosed = false;
691 
692             super.startElement(namespaceURI, localName, qName, attributes);
693         } catch (IOException e) {
694             handleException(e);
695         }
696     }
697 
698     public void endElement(String namespaceURI, String localName, String qName)
699             throws SAXException {
700         try {
701             charsAdded = false;
702             --indentLevel;
703 
704             if (lastElementClosed) {
705                 writePrintln();
706                 indent();
707             }
708 
709             // XXXX: need to determine this using a stack and checking for
710             // content / children
711             boolean hadContent = true;
712 
713             if (hadContent) {
714                 writeClose(qName);
715             } else {
716                 writeEmptyElementClose(qName);
717             }
718 
719             lastOutputNodeType = Node.ELEMENT_NODE;
720             lastElementClosed = true;
721 
722             super.endElement(namespaceURI, localName, qName);
723         } catch (IOException e) {
724             handleException(e);
725         }
726     }
727 
728     public void characters(char[] ch, int start, int length)
729             throws SAXException {
730         if ((ch == null) || (ch.length == 0) || (length <= 0)) {
731             return;
732         }
733 
734         try {
735             /*
736              * we can't use the writeString method here because it's possible we
737              * don't receive all characters at once and calling writeString
738              * would cause unwanted spaces to be added in between these chunks
739              * of character arrays.
740              */
741             String string = String.valueOf(ch, start, length);
742 
743             if (escapeText) {
744                 string = escapeElementEntities(string);
745             }
746 
747             if (format.isTrimText()) {
748                 if ((lastOutputNodeType == Node.TEXT_NODE) && !charsAdded) {
749                     writer.write(' ');
750                 } else if (charsAdded && Character.isWhitespace(lastChar)) {
751                     writer.write(' ');
752                 } else if (lastOutputNodeType == Node.ELEMENT_NODE
753                         && format.isPadText() && lastElementClosed
754                         && Character.isWhitespace(ch[0])) {
755                     writer.write(PAD_TEXT);
756                 }
757 
758                 String delim = "";
759                 StringTokenizer tokens = new StringTokenizer(string);
760 
761                 while (tokens.hasMoreTokens()) {
762                     writer.write(delim);
763                     writer.write(tokens.nextToken());
764                     delim = " ";
765                 }
766             } else {
767                 writer.write(string);
768             }
769 
770             charsAdded = true;
771             lastChar = ch[(start + length) - 1];
772             lastOutputNodeType = Node.TEXT_NODE;
773 
774             super.characters(ch, start, length);
775         } catch (IOException e) {
776             handleException(e);
777         }
778     }
779 
780     public void ignorableWhitespace(char[] ch, int start, int length)
781             throws SAXException {
782         super.ignorableWhitespace(ch, start, length);
783     }
784 
785     public void processingInstruction(String target, String data)
786             throws SAXException {
787         try {
788             indent();
789             writer.write("<?");
790             writer.write(target);
791             writer.write(" ");
792             writer.write(data);
793             writer.write("?>");
794             writePrintln();
795             lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE;
796 
797             super.processingInstruction(target, data);
798         } catch (IOException e) {
799             handleException(e);
800         }
801     }
802 
803     // DTDHandler interface
804     // -------------------------------------------------------------------------
805     public void notationDecl(String name, String publicID, String systemID)
806             throws SAXException {
807         super.notationDecl(name, publicID, systemID);
808     }
809 
810     public void unparsedEntityDecl(String name, String publicID,
811             String systemID, String notationName) throws SAXException {
812         super.unparsedEntityDecl(name, publicID, systemID, notationName);
813     }
814 
815     // LexicalHandler interface
816     // -------------------------------------------------------------------------
817     public void startDTD(String name, String publicID, String systemID)
818             throws SAXException {
819         inDTD = true;
820 
821         try {
822             writeDocType(name, publicID, systemID);
823         } catch (IOException e) {
824             handleException(e);
825         }
826 
827         if (lexicalHandler != null) {
828             lexicalHandler.startDTD(name, publicID, systemID);
829         }
830     }
831 
832     public void endDTD() throws SAXException {
833         inDTD = false;
834 
835         if (lexicalHandler != null) {
836             lexicalHandler.endDTD();
837         }
838     }
839 
840     public void startCDATA() throws SAXException {
841         try {
842             writer.write("<![CDATA[");
843         } catch (IOException e) {
844             handleException(e);
845         }
846 
847         if (lexicalHandler != null) {
848             lexicalHandler.startCDATA();
849         }
850     }
851 
852     public void endCDATA() throws SAXException {
853         try {
854             writer.write("]]>");
855         } catch (IOException e) {
856             handleException(e);
857         }
858 
859         if (lexicalHandler != null) {
860             lexicalHandler.endCDATA();
861         }
862     }
863 
864     public void startEntity(String name) throws SAXException {
865         try {
866             writeEntityRef(name);
867         } catch (IOException e) {
868             handleException(e);
869         }
870 
871         if (lexicalHandler != null) {
872             lexicalHandler.startEntity(name);
873         }
874     }
875 
876     public void endEntity(String name) throws SAXException {
877         if (lexicalHandler != null) {
878             lexicalHandler.endEntity(name);
879         }
880     }
881 
882     public void comment(char[] ch, int start, int length) throws SAXException {
883         if (showCommentsInDTDs || !inDTD) {
884             try {
885                 charsAdded = false;
886                 writeComment(new String(ch, start, length));
887             } catch (IOException e) {
888                 handleException(e);
889             }
890         }
891 
892         if (lexicalHandler != null) {
893             lexicalHandler.comment(ch, start, length);
894         }
895     }
896 
897     // Implementation methods
898     // -------------------------------------------------------------------------
899     protected void writeElement(Element element) throws IOException {
900         int size = element.nodeCount();
901         String qualifiedName = element.getQualifiedName();
902 
903         writePrintln();
904         indent();
905 
906         writer.write("<");
907         writer.write(qualifiedName);
908 
909         int previouslyDeclaredNamespaces = namespaceStack.size();
910         Namespace ns = element.getNamespace();
911 
912         if (isNamespaceDeclaration(ns)) {
913             namespaceStack.push(ns);
914             writeNamespace(ns);
915         }
916 
917         // Print out additional namespace declarations
918         boolean textOnly = true;
919 
920         for (int i = 0; i < size; i++) {
921             Node node = element.node(i);
922 
923             if (node instanceof Namespace) {
924                 Namespace additional = (Namespace) node;
925 
926                 if (isNamespaceDeclaration(additional)) {
927                     namespaceStack.push(additional);
928                     writeNamespace(additional);
929                 }
930             } else if (node instanceof Element) {
931                 textOnly = false;
932             } else if (node instanceof Comment) {
933                 textOnly = false;
934             }
935         }
936 
937         writeAttributes(element);
938 
939         lastOutputNodeType = Node.ELEMENT_NODE;
940 
941         if (size <= 0) {
942             writeEmptyElementClose(qualifiedName);
943         } else {
944             writer.write(">");
945 
946             if (textOnly) {
947                 // we have at least one text node so lets assume
948                 // that its non-empty
949                 writeElementContent(element);
950             } else {
951                 // we know it's not null or empty from above
952                 ++indentLevel;
953 
954                 writeElementContent(element);
955 
956                 --indentLevel;
957 
958                 writePrintln();
959                 indent();
960             }
961 
962             writer.write("</");
963             writer.write(qualifiedName);
964             writer.write(">");
965         }
966 
967         // remove declared namespaceStack from stack
968         while (namespaceStack.size() > previouslyDeclaredNamespaces) {
969             namespaceStack.pop();
970         }
971 
972         lastOutputNodeType = Node.ELEMENT_NODE;
973     }
974 
975     /***
976      * Determines if element is a special case of XML elements where it contains
977      * an xml:space attribute of "preserve". If it does, then retain whitespace.
978      * 
979      * @param element
980      *            DOCUMENT ME!
981      * 
982      * @return DOCUMENT ME!
983      */
984     protected final boolean isElementSpacePreserved(Element element) {
985         final Attribute attr = (Attribute) element.attribute("space");
986         boolean preserveFound = preserve; // default to global state
987 
988         if (attr != null) {
989             if ("xml".equals(attr.getNamespacePrefix())
990                     && "preserve".equals(attr.getText())) {
991                 preserveFound = true;
992             } else {
993                 preserveFound = false;
994             }
995         }
996 
997         return preserveFound;
998     }
999 
1000     /***
1001      * Outputs the content of the given element. If whitespace trimming is
1002      * enabled then all adjacent text nodes are appended together before the
1003      * whitespace trimming occurs to avoid problems with multiple text nodes
1004      * being created due to text content that spans parser buffers in a SAX
1005      * parser.
1006      * 
1007      * @param element
1008      *            DOCUMENT ME!
1009      * 
1010      * @throws IOException
1011      *             DOCUMENT ME!
1012      */
1013     protected void writeElementContent(Element element) throws IOException {
1014         boolean trim = format.isTrimText();
1015         boolean oldPreserve = preserve;
1016 
1017         if (trim) { // verify we have to before more expensive test
1018             preserve = isElementSpacePreserved(element);
1019             trim = !preserve;
1020         }
1021 
1022         if (trim) {
1023             // concatenate adjacent text nodes together
1024             // so that whitespace trimming works properly
1025             Text lastTextNode = null;
1026             StringBuffer buff = null;
1027             boolean textOnly = true;
1028 
1029             for (int i = 0, size = element.nodeCount(); i < size; i++) {
1030                 Node node = element.node(i);
1031 
1032                 if (node instanceof Text) {
1033                     if (lastTextNode == null) {
1034                         lastTextNode = (Text) node;
1035                     } else {
1036                         if (buff == null) {
1037                             buff = new StringBuffer(lastTextNode.getText());
1038                         }
1039 
1040                         buff.append(((Text) node).getText());
1041                     }
1042                 } else {
1043                     if (!textOnly && format.isPadText()) {
1044                         // only add the PAD_TEXT if the text itself starts with
1045                         // whitespace
1046                         char firstChar = 'a';
1047                         if (buff != null) {
1048                             firstChar = buff.charAt(0);
1049                         } else if (lastTextNode != null) {
1050                             firstChar = lastTextNode.getText().charAt(0);
1051                         }
1052 
1053                         if (Character.isWhitespace(firstChar)) {
1054                             writer.write(PAD_TEXT);
1055                         }
1056                     }
1057 
1058                     if (lastTextNode != null) {
1059                         if (buff != null) {
1060                             writeString(buff.toString());
1061                             buff = null;
1062                         } else {
1063                             writeString(lastTextNode.getText());
1064                         }
1065 
1066                         if (format.isPadText()) {
1067                             // only add the PAD_TEXT if the text itself ends
1068                             // with whitespace
1069                             char lastTextChar = 'a';
1070                             if (buff != null) {
1071                                 lastTextChar = buff.charAt(buff.length() - 1);
1072                             } else if (lastTextNode != null) {
1073                                 String txt = lastTextNode.getText();
1074                                 lastTextChar = txt.charAt(txt.length() - 1);
1075                             }
1076 
1077                             if (Character.isWhitespace(lastTextChar)) {
1078                                 writer.write(PAD_TEXT);
1079                             }
1080                         }
1081 
1082                         lastTextNode = null;
1083                     }
1084 
1085                     textOnly = false;
1086                     writeNode(node);
1087                 }
1088             }
1089 
1090             if (lastTextNode != null) {
1091                 if (!textOnly && format.isPadText()) {
1092                     // only add the PAD_TEXT if the text itself starts with
1093                     // whitespace
1094                     char firstChar = 'a';
1095                     if (buff != null) {
1096                         firstChar = buff.charAt(0);
1097                     } else {
1098                         firstChar = lastTextNode.getText().charAt(0);
1099                     }
1100 
1101                     if (Character.isWhitespace(firstChar)) {
1102                         writer.write(PAD_TEXT);
1103                     }
1104                 }
1105 
1106                 if (buff != null) {
1107                     writeString(buff.toString());
1108                     buff = null;
1109                 } else {
1110                     writeString(lastTextNode.getText());
1111                 }
1112 
1113                 lastTextNode = null;
1114             }
1115         } else {
1116             Node lastTextNode = null;
1117 
1118             for (int i = 0, size = element.nodeCount(); i < size; i++) {
1119                 Node node = element.node(i);
1120 
1121                 if (node instanceof Text) {
1122                     writeNode(node);
1123                     lastTextNode = node;
1124                 } else {
1125                     if ((lastTextNode != null) && format.isPadText()) {
1126                         // only add the PAD_TEXT if the text itself ends with
1127                         // whitespace
1128                         String txt = lastTextNode.getText();
1129                         char lastTextChar = txt.charAt(txt.length() - 1);
1130 
1131                         if (Character.isWhitespace(lastTextChar)) {
1132                             writer.write(PAD_TEXT);
1133                         }
1134                     }
1135 
1136                     writeNode(node);
1137 
1138                     // if ((lastTextNode != null) && format.isPadText()) {
1139                     // writer.write(PAD_TEXT);
1140                     // }
1141 
1142                     lastTextNode = null;
1143                 }
1144             }
1145         }
1146 
1147         preserve = oldPreserve;
1148     }
1149 
1150     protected void writeCDATA(String text) throws IOException {
1151         writer.write("<![CDATA[");
1152 
1153         if (text != null) {
1154             writer.write(text);
1155         }
1156 
1157         writer.write("]]>");
1158 
1159         lastOutputNodeType = Node.CDATA_SECTION_NODE;
1160     }
1161 
1162     protected void writeDocType(DocumentType docType) throws IOException {
1163         if (docType != null) {
1164             docType.write(writer);
1165             writePrintln();
1166         }
1167     }
1168 
1169     protected void writeNamespace(Namespace namespace) throws IOException {
1170         if (namespace != null) {
1171             writeNamespace(namespace.getPrefix(), namespace.getURI());
1172         }
1173     }
1174 
1175     /***
1176      * Writes the SAX namepsaces
1177      * 
1178      * @throws IOException
1179      *             DOCUMENT ME!
1180      */
1181     protected void writeNamespaces() throws IOException {
1182         if (namespacesMap != null) {
1183             for (Iterator iter = namespacesMap.entrySet().iterator(); iter
1184                     .hasNext();) {
1185                 Map.Entry entry = (Map.Entry) iter.next();
1186                 String prefix = (String) entry.getKey();
1187                 String uri = (String) entry.getValue();
1188                 writeNamespace(prefix, uri);
1189             }
1190 
1191             namespacesMap = null;
1192         }
1193     }
1194 
1195     /***
1196      * Writes the SAX namepsaces
1197      * 
1198      * @param prefix
1199      *            the prefix
1200      * @param uri
1201      *            the namespace uri
1202      * 
1203      * @throws IOException
1204      */
1205     protected void writeNamespace(String prefix, String uri) 
1206             throws IOException {
1207         if ((prefix != null) && (prefix.length() > 0)) {
1208             writer.write(" xmlns:");
1209             writer.write(prefix);
1210             writer.write("=\"");
1211         } else {
1212             writer.write(" xmlns=\"");
1213         }
1214 
1215         writer.write(uri);
1216         writer.write("\"");
1217     }
1218 
1219     protected void writeProcessingInstruction(ProcessingInstruction pi)
1220             throws IOException {
1221         // indent();
1222         writer.write("<?");
1223         writer.write(pi.getName());
1224         writer.write(" ");
1225         writer.write(pi.getText());
1226         writer.write("?>");
1227         writePrintln();
1228 
1229         lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE;
1230     }
1231 
1232     protected void writeString(String text) throws IOException {
1233         if ((text != null) && (text.length() > 0)) {
1234             if (escapeText) {
1235                 text = escapeElementEntities(text);
1236             }
1237 
1238             // if (format.isPadText()) {
1239             // if (lastOutputNodeType == Node.ELEMENT_NODE) {
1240             // writer.write(PAD_TEXT);
1241             // }
1242             // }
1243             if (format.isTrimText()) {
1244                 boolean first = true;
1245                 StringTokenizer tokenizer = new StringTokenizer(text);
1246 
1247                 while (tokenizer.hasMoreTokens()) {
1248                     String token = tokenizer.nextToken();
1249 
1250                     if (first) {
1251                         first = false;
1252 
1253                         if (lastOutputNodeType == Node.TEXT_NODE) {
1254                             writer.write(" ");
1255                         }
1256                     } else {
1257                         writer.write(" ");
1258                     }
1259 
1260                     writer.write(token);
1261                     lastOutputNodeType = Node.TEXT_NODE;
1262                 }
1263             } else {
1264                 lastOutputNodeType = Node.TEXT_NODE;
1265                 writer.write(text);
1266             }
1267         }
1268     }
1269 
1270     /***
1271      * This method is used to write out Nodes that contain text and still allow
1272      * for xml:space to be handled properly.
1273      * 
1274      * @param node
1275      *            DOCUMENT ME!
1276      * 
1277      * @throws IOException
1278      *             DOCUMENT ME!
1279      */
1280     protected void writeNodeText(Node node) throws IOException {
1281         String text = node.getText();
1282 
1283         if ((text != null) && (text.length() > 0)) {
1284             if (escapeText) {
1285                 text = escapeElementEntities(text);
1286             }
1287 
1288             lastOutputNodeType = Node.TEXT_NODE;
1289             writer.write(text);
1290         }
1291     }
1292 
1293     protected void writeNode(Node node) throws IOException {
1294         int nodeType = node.getNodeType();
1295 
1296         switch (nodeType) {
1297             case Node.ELEMENT_NODE:
1298                 writeElement((Element) node);
1299 
1300                 break;
1301 
1302             case Node.ATTRIBUTE_NODE:
1303                 writeAttribute((Attribute) node);
1304 
1305                 break;
1306 
1307             case Node.TEXT_NODE:
1308                 writeNodeText(node);
1309 
1310                 // write((Text) node);
1311                 break;
1312 
1313             case Node.CDATA_SECTION_NODE:
1314                 writeCDATA(node.getText());
1315 
1316                 break;
1317 
1318             case Node.ENTITY_REFERENCE_NODE:
1319                 writeEntity((Entity) node);
1320 
1321                 break;
1322 
1323             case Node.PROCESSING_INSTRUCTION_NODE:
1324                 writeProcessingInstruction((ProcessingInstruction) node);
1325 
1326                 break;
1327 
1328             case Node.COMMENT_NODE:
1329                 writeComment(node.getText());
1330 
1331                 break;
1332 
1333             case Node.DOCUMENT_NODE:
1334                 write((Document) node);
1335 
1336                 break;
1337 
1338             case Node.DOCUMENT_TYPE_NODE:
1339                 writeDocType((DocumentType) node);
1340 
1341                 break;
1342 
1343             case Node.NAMESPACE_NODE:
1344 
1345                 // Will be output with attributes
1346                 // write((Namespace) node);
1347                 break;
1348 
1349             default:
1350                 throw new IOException("Invalid node type: " + node);
1351         }
1352     }
1353 
1354     protected void installLexicalHandler() {
1355         XMLReader parent = getParent();
1356 
1357         if (parent == null) {
1358             throw new NullPointerException("No parent for filter");
1359         }
1360 
1361         // try to register for lexical events
1362         for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
1363             try {
1364                 parent.setProperty(LEXICAL_HANDLER_NAMES[i], this);
1365 
1366                 break;
1367             } catch (SAXNotRecognizedException ex) {
1368                 // ignore
1369             } catch (SAXNotSupportedException ex) {
1370                 // ignore
1371             }
1372         }
1373     }
1374 
1375     protected void writeDocType(String name, String publicID, String systemID)
1376             throws IOException {
1377         boolean hasPublic = false;
1378 
1379         writer.write("<!DOCTYPE ");
1380         writer.write(name);
1381 
1382         if ((publicID != null) && (!publicID.equals(""))) {
1383             writer.write(" PUBLIC \"");
1384             writer.write(publicID);
1385             writer.write("\"");
1386             hasPublic = true;
1387         }
1388 
1389         if ((systemID != null) && (!systemID.equals(""))) {
1390             if (!hasPublic) {
1391                 writer.write(" SYSTEM");
1392             }
1393 
1394             writer.write(" \"");
1395             writer.write(systemID);
1396             writer.write("\"");
1397         }
1398 
1399         writer.write(">");
1400         writePrintln();
1401     }
1402 
1403     protected void writeEntity(Entity entity) throws IOException {
1404         if (!resolveEntityRefs()) {
1405             writeEntityRef(entity.getName());
1406         } else {
1407             writer.write(entity.getText());
1408         }
1409     }
1410 
1411     protected void writeEntityRef(String name) throws IOException {
1412         writer.write("&");
1413         writer.write(name);
1414         writer.write(";");
1415 
1416         lastOutputNodeType = Node.ENTITY_REFERENCE_NODE;
1417     }
1418 
1419     protected void writeComment(String text) throws IOException {
1420         if (format.isNewlines()) {
1421             println();
1422             indent();
1423         }
1424 
1425         writer.write("<!--");
1426         writer.write(text);
1427         writer.write("-->");
1428 
1429         lastOutputNodeType = Node.COMMENT_NODE;
1430     }
1431 
1432     /***
1433      * Writes the attributes of the given element
1434      * 
1435      * @param element
1436      *            DOCUMENT ME!
1437      * 
1438      * @throws IOException
1439      *             DOCUMENT ME!
1440      */
1441     protected void writeAttributes(Element element) throws IOException {
1442         // I do not yet handle the case where the same prefix maps to
1443         // two different URIs. For attributes on the same element
1444         // this is illegal; but as yet we don't throw an exception
1445         // if someone tries to do this
1446         for (int i = 0, size = element.attributeCount(); i < size; i++) {
1447             Attribute attribute = element.attribute(i);
1448             Namespace ns = attribute.getNamespace();
1449 
1450             if ((ns != null) && (ns != Namespace.NO_NAMESPACE)
1451                     && (ns != Namespace.XML_NAMESPACE)) {
1452                 String prefix = ns.getPrefix();
1453                 String uri = namespaceStack.getURI(prefix);
1454 
1455                 if (!ns.getURI().equals(uri)) {
1456                     writeNamespace(ns);
1457                     namespaceStack.push(ns);
1458                 }
1459             }
1460 
1461             // If the attribute is a namespace declaration, check if we have
1462             // already written that declaration elsewhere (if that's the case,
1463             // it must be in the namespace stack
1464             String attName = attribute.getName();
1465 
1466             if (attName.startsWith("xmlns:")) {
1467                 String prefix = attName.substring(6);
1468 
1469                 if (namespaceStack.getNamespaceForPrefix(prefix) == null) {
1470                     String uri = attribute.getValue();
1471                     namespaceStack.push(prefix, uri);
1472                     writeNamespace(prefix, uri);
1473                 }
1474             } else if (attName.equals("xmlns")) {
1475                 if (namespaceStack.getDefaultNamespace() == null) {
1476                     String uri = attribute.getValue();
1477                     namespaceStack.push(null, uri);
1478                     writeNamespace(null, uri);
1479                 }
1480             } else {
1481                 char quote = format.getAttributeQuoteCharacter();
1482                 writer.write(" ");
1483                 writer.write(attribute.getQualifiedName());
1484                 writer.write("=");
1485                 writer.write(quote);
1486                 writeEscapeAttributeEntities(attribute.getValue());
1487                 writer.write(quote);
1488             }
1489         }
1490     }
1491 
1492     protected void writeAttribute(Attribute attribute) throws IOException {
1493         writer.write(" ");
1494         writer.write(attribute.getQualifiedName());
1495         writer.write("=");
1496 
1497         char quote = format.getAttributeQuoteCharacter();
1498         writer.write(quote);
1499 
1500         writeEscapeAttributeEntities(attribute.getValue());
1501 
1502         writer.write(quote);
1503         lastOutputNodeType = Node.ATTRIBUTE_NODE;
1504     }
1505 
1506     protected void writeAttributes(Attributes attributes) throws IOException {
1507         for (int i = 0, size = attributes.getLength(); i < size; i++) {
1508             writeAttribute(attributes, i);
1509         }
1510     }
1511 
1512     protected void writeAttribute(Attributes attributes, int index)
1513             throws IOException {
1514         char quote = format.getAttributeQuoteCharacter();
1515         writer.write(" ");
1516         writer.write(attributes.getQName(index));
1517         writer.write("=");
1518         writer.write(quote);
1519         writeEscapeAttributeEntities(attributes.getValue(index));
1520         writer.write(quote);
1521     }
1522 
1523     protected void indent() throws IOException {
1524         String indent = format.getIndent();
1525 
1526         if ((indent != null) && (indent.length() > 0)) {
1527             for (int i = 0; i < indentLevel; i++) {
1528                 writer.write(indent);
1529             }
1530         }
1531     }
1532 
1533     /***
1534      * <p>
1535      * This will print a new line only if the newlines flag was set to true
1536      * </p>
1537      * 
1538      * @throws IOException
1539      *             DOCUMENT ME!
1540      */
1541     protected void writePrintln() throws IOException {
1542         if (format.isNewlines()) {
1543             writer.write(format.getLineSeparator());
1544         }
1545     }
1546 
1547     /***
1548      * Get an OutputStreamWriter, use preferred encoding.
1549      * 
1550      * @param outStream
1551      *            DOCUMENT ME!
1552      * @param encoding
1553      *            DOCUMENT ME!
1554      * 
1555      * @return DOCUMENT ME!
1556      * 
1557      * @throws UnsupportedEncodingException
1558      *             DOCUMENT ME!
1559      */
1560     protected Writer createWriter(OutputStream outStream, String encoding)
1561             throws UnsupportedEncodingException {
1562         return new BufferedWriter(new OutputStreamWriter(outStream, encoding));
1563     }
1564 
1565     /***
1566      * <p>
1567      * This will write the declaration to the given Writer. Assumes XML version
1568      * 1.0 since we don't directly know.
1569      * </p>
1570      * 
1571      * @throws IOException
1572      *             DOCUMENT ME!
1573      */
1574     protected void writeDeclaration() throws IOException {
1575         String encoding = format.getEncoding();
1576 
1577         // Only print of declaration is not suppressed
1578         if (!format.isSuppressDeclaration()) {
1579             // Assume 1.0 version
1580             if (encoding.equals("UTF8")) {
1581                 writer.write("<?xml version=\"1.0\"");
1582 
1583                 if (!format.isOmitEncoding()) {
1584                     writer.write(" encoding=\"UTF-8\"");
1585                 }
1586 
1587                 writer.write("?>");
1588             } else {
1589                 writer.write("<?xml version=\"1.0\"");
1590 
1591                 if (!format.isOmitEncoding()) {
1592                     writer.write(" encoding=\"" + encoding + "\"");
1593                 }
1594 
1595                 writer.write("?>");
1596             }
1597 
1598             if (format.isNewLineAfterDeclaration()) {
1599                 println();
1600             }
1601         }
1602     }
1603 
1604     protected void writeClose(String qualifiedName) throws IOException {
1605         writer.write("</");
1606         writer.write(qualifiedName);
1607         writer.write(">");
1608     }
1609 
1610     protected void writeEmptyElementClose(String qualifiedName)
1611             throws IOException {
1612         // Simply close up
1613         if (!format.isExpandEmptyElements()) {
1614             writer.write("/>");
1615         } else {
1616             writer.write("></");
1617             writer.write(qualifiedName);
1618             writer.write(">");
1619         }
1620     }
1621 
1622     protected boolean isExpandEmptyElements() {
1623         return format.isExpandEmptyElements();
1624     }
1625 
1626     /***
1627      * This will take the pre-defined entities in XML 1.0 and convert their
1628      * character representation to the appropriate entity reference, suitable
1629      * for XML attributes.
1630      * 
1631      * @param text
1632      *            DOCUMENT ME!
1633      * 
1634      * @return DOCUMENT ME!
1635      */
1636     protected String escapeElementEntities(String text) {
1637         char[] block = null;
1638         int i;
1639         int last = 0;
1640         int size = text.length();
1641 
1642         for (i = 0; i < size; i++) {
1643             String entity = null;
1644             char c = text.charAt(i);
1645 
1646             switch (c) {
1647                 case '<':
1648                     entity = "&lt;";
1649 
1650                     break;
1651 
1652                 case '>':
1653                     entity = "&gt;";
1654 
1655                     break;
1656 
1657                 case '&':
1658                     entity = "&amp;";
1659 
1660                     break;
1661 
1662                 case '\t':
1663                 case '\n':
1664                 case '\r':
1665 
1666                     // don't encode standard whitespace characters
1667                     if (preserve) {
1668                         entity = String.valueOf(c);
1669                     }
1670 
1671                     break;
1672 
1673                 default:
1674 
1675                     if ((c < 32) || shouldEncodeChar(c)) {
1676                         entity = "&#" + (int) c + ";";
1677                     }
1678 
1679                     break;
1680             }
1681 
1682             if (entity != null) {
1683                 if (block == null) {
1684                     block = text.toCharArray();
1685                 }
1686 
1687                 buffer.append(block, last, i - last);
1688                 buffer.append(entity);
1689                 last = i + 1;
1690             }
1691         }
1692 
1693         if (last == 0) {
1694             return text;
1695         }
1696 
1697         if (last < size) {
1698             if (block == null) {
1699                 block = text.toCharArray();
1700             }
1701 
1702             buffer.append(block, last, i - last);
1703         }
1704 
1705         String answer = buffer.toString();
1706         buffer.setLength(0);
1707 
1708         return answer;
1709     }
1710 
1711     protected void writeEscapeAttributeEntities(String txt) throws IOException {
1712         if (txt != null) {
1713             String escapedText = escapeAttributeEntities(txt);
1714             writer.write(escapedText);
1715         }
1716     }
1717 
1718     /***
1719      * This will take the pre-defined entities in XML 1.0 and convert their
1720      * character representation to the appropriate entity reference, suitable
1721      * for XML attributes.
1722      * 
1723      * @param text
1724      *            DOCUMENT ME!
1725      * 
1726      * @return DOCUMENT ME!
1727      */
1728     protected String escapeAttributeEntities(String text) {
1729         char quote = format.getAttributeQuoteCharacter();
1730 
1731         char[] block = null;
1732         int i;
1733         int last = 0;
1734         int size = text.length();
1735 
1736         for (i = 0; i < size; i++) {
1737             String entity = null;
1738             char c = text.charAt(i);
1739 
1740             switch (c) {
1741                 case '<':
1742                     entity = "&lt;";
1743 
1744                     break;
1745 
1746                 case '>':
1747                     entity = "&gt;";
1748 
1749                     break;
1750 
1751                 case '\'':
1752 
1753                     if (quote == '\'') {
1754                         entity = "&apos;";
1755                     }
1756 
1757                     break;
1758 
1759                 case '\"':
1760 
1761                     if (quote == '\"') {
1762                         entity = "&quot;";
1763                     }
1764 
1765                     break;
1766 
1767                 case '&':
1768                     entity = "&amp;";
1769 
1770                     break;
1771 
1772                 case '\t':
1773                 case '\n':
1774                 case '\r':
1775 
1776                     // don't encode standard whitespace characters
1777                     break;
1778 
1779                 default:
1780 
1781                     if ((c < 32) || shouldEncodeChar(c)) {
1782                         entity = "&#" + (int) c + ";";
1783                     }
1784 
1785                     break;
1786             }
1787 
1788             if (entity != null) {
1789                 if (block == null) {
1790                     block = text.toCharArray();
1791                 }
1792 
1793                 buffer.append(block, last, i - last);
1794                 buffer.append(entity);
1795                 last = i + 1;
1796             }
1797         }
1798 
1799         if (last == 0) {
1800             return text;
1801         }
1802 
1803         if (last < size) {
1804             if (block == null) {
1805                 block = text.toCharArray();
1806             }
1807 
1808             buffer.append(block, last, i - last);
1809         }
1810 
1811         String answer = buffer.toString();
1812         buffer.setLength(0);
1813 
1814         return answer;
1815     }
1816 
1817     /***
1818      * Should the given character be escaped. This depends on the encoding of
1819      * the document.
1820      * 
1821      * @param c
1822      *            DOCUMENT ME!
1823      * 
1824      * @return boolean
1825      */
1826     protected boolean shouldEncodeChar(char c) {
1827         int max = getMaximumAllowedCharacter();
1828 
1829         return (max > 0) && (c > max);
1830     }
1831 
1832     /***
1833      * Returns the maximum allowed character code that should be allowed
1834      * unescaped which defaults to 127 in US-ASCII (7 bit) or 255 in ISO- (8
1835      * bit).
1836      * 
1837      * @return DOCUMENT ME!
1838      */
1839     protected int defaultMaximumAllowedCharacter() {
1840         String encoding = format.getEncoding();
1841 
1842         if (encoding != null) {
1843             if (encoding.equals("US-ASCII")) {
1844                 return 127;
1845             }
1846         }
1847 
1848         // no encoding for things like ISO-*, UTF-8 or UTF-16
1849         return -1;
1850     }
1851 
1852     protected boolean isNamespaceDeclaration(Namespace ns) {
1853         if ((ns != null) && (ns != Namespace.XML_NAMESPACE)) {
1854             String uri = ns.getURI();
1855 
1856             if (uri != null) {
1857                 if (!namespaceStack.contains(ns)) {
1858                     return true;
1859                 }
1860             }
1861         }
1862 
1863         return false;
1864     }
1865 
1866     protected void handleException(IOException e) throws SAXException {
1867         throw new SAXException(e);
1868     }
1869 
1870     // Laramie Crocker 4/8/2002 10:38AM
1871 
1872     /***
1873      * Lets subclasses get at the current format object, so they can call
1874      * setTrimText, setNewLines, etc. Put in to support the HTMLWriter, in the
1875      * way that it pushes the current newline/trim state onto a stack and
1876      * overrides the state within preformatted tags.
1877      * 
1878      * @return DOCUMENT ME!
1879      */
1880     protected OutputFormat getOutputFormat() {
1881         return format;
1882     }
1883 
1884     public boolean resolveEntityRefs() {
1885         return resolveEntityRefs;
1886     }
1887 
1888     public void setResolveEntityRefs(boolean resolve) {
1889         this.resolveEntityRefs = resolve;
1890     }
1891 }
1892 
1893 /*
1894  * Redistribution and use of this software and associated documentation
1895  * ("Software"), with or without modification, are permitted provided that the
1896  * following conditions are met:
1897  * 
1898  * 1. Redistributions of source code must retain copyright statements and
1899  * notices. Redistributions must also contain a copy of this document.
1900  * 
1901  * 2. Redistributions in binary form must reproduce the above copyright notice,
1902  * this list of conditions and the following disclaimer in the documentation
1903  * and/or other materials provided with the distribution.
1904  * 
1905  * 3. The name "DOM4J" must not be used to endorse or promote products derived
1906  * from this Software without prior written permission of MetaStuff, Ltd. For
1907  * written permission, please contact dom4j-info@metastuff.com.
1908  * 
1909  * 4. Products derived from this Software may not be called "DOM4J" nor may
1910  * "DOM4J" appear in their names without prior written permission of MetaStuff,
1911  * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
1912  * 
1913  * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
1914  * 
1915  * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
1916  * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1917  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1918  * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
1919  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
1920  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
1921  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
1922  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
1923  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
1924  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
1925  * POSSIBILITY OF SUCH DAMAGE.
1926  * 
1927  * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
1928  */