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.2.2 $
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                     lastChar = token.charAt(token.length() - 1);
1263                 }
1264             } else {
1265                 lastOutputNodeType = Node.TEXT_NODE;
1266                 writer.write(text);
1267                 lastChar = text.charAt(text.length() - 1);
1268             }
1269         }
1270     }
1271 
1272     /***
1273      * This method is used to write out Nodes that contain text and still allow
1274      * for xml:space to be handled properly.
1275      * 
1276      * @param node
1277      *            DOCUMENT ME!
1278      * 
1279      * @throws IOException
1280      *             DOCUMENT ME!
1281      */
1282     protected void writeNodeText(Node node) throws IOException {
1283         String text = node.getText();
1284 
1285         if ((text != null) && (text.length() > 0)) {
1286             if (escapeText) {
1287                 text = escapeElementEntities(text);
1288             }
1289 
1290             lastOutputNodeType = Node.TEXT_NODE;
1291             writer.write(text);
1292             lastChar = text.charAt(text.length() - 1);
1293         }
1294     }
1295 
1296     protected void writeNode(Node node) throws IOException {
1297         int nodeType = node.getNodeType();
1298 
1299         switch (nodeType) {
1300             case Node.ELEMENT_NODE:
1301                 writeElement((Element) node);
1302 
1303                 break;
1304 
1305             case Node.ATTRIBUTE_NODE:
1306                 writeAttribute((Attribute) node);
1307 
1308                 break;
1309 
1310             case Node.TEXT_NODE:
1311                 writeNodeText(node);
1312 
1313                 // write((Text) node);
1314                 break;
1315 
1316             case Node.CDATA_SECTION_NODE:
1317                 writeCDATA(node.getText());
1318 
1319                 break;
1320 
1321             case Node.ENTITY_REFERENCE_NODE:
1322                 writeEntity((Entity) node);
1323 
1324                 break;
1325 
1326             case Node.PROCESSING_INSTRUCTION_NODE:
1327                 writeProcessingInstruction((ProcessingInstruction) node);
1328 
1329                 break;
1330 
1331             case Node.COMMENT_NODE:
1332                 writeComment(node.getText());
1333 
1334                 break;
1335 
1336             case Node.DOCUMENT_NODE:
1337                 write((Document) node);
1338 
1339                 break;
1340 
1341             case Node.DOCUMENT_TYPE_NODE:
1342                 writeDocType((DocumentType) node);
1343 
1344                 break;
1345 
1346             case Node.NAMESPACE_NODE:
1347 
1348                 // Will be output with attributes
1349                 // write((Namespace) node);
1350                 break;
1351 
1352             default:
1353                 throw new IOException("Invalid node type: " + node);
1354         }
1355     }
1356 
1357     protected void installLexicalHandler() {
1358         XMLReader parent = getParent();
1359 
1360         if (parent == null) {
1361             throw new NullPointerException("No parent for filter");
1362         }
1363 
1364         // try to register for lexical events
1365         for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
1366             try {
1367                 parent.setProperty(LEXICAL_HANDLER_NAMES[i], this);
1368 
1369                 break;
1370             } catch (SAXNotRecognizedException ex) {
1371                 // ignore
1372             } catch (SAXNotSupportedException ex) {
1373                 // ignore
1374             }
1375         }
1376     }
1377 
1378     protected void writeDocType(String name, String publicID, String systemID)
1379             throws IOException {
1380         boolean hasPublic = false;
1381 
1382         writer.write("<!DOCTYPE ");
1383         writer.write(name);
1384 
1385         if ((publicID != null) && (!publicID.equals(""))) {
1386             writer.write(" PUBLIC \"");
1387             writer.write(publicID);
1388             writer.write("\"");
1389             hasPublic = true;
1390         }
1391 
1392         if ((systemID != null) && (!systemID.equals(""))) {
1393             if (!hasPublic) {
1394                 writer.write(" SYSTEM");
1395             }
1396 
1397             writer.write(" \"");
1398             writer.write(systemID);
1399             writer.write("\"");
1400         }
1401 
1402         writer.write(">");
1403         writePrintln();
1404     }
1405 
1406     protected void writeEntity(Entity entity) throws IOException {
1407         if (!resolveEntityRefs()) {
1408             writeEntityRef(entity.getName());
1409         } else {
1410             writer.write(entity.getText());
1411         }
1412     }
1413 
1414     protected void writeEntityRef(String name) throws IOException {
1415         writer.write("&");
1416         writer.write(name);
1417         writer.write(";");
1418 
1419         lastOutputNodeType = Node.ENTITY_REFERENCE_NODE;
1420     }
1421 
1422     protected void writeComment(String text) throws IOException {
1423         if (format.isNewlines()) {
1424             println();
1425             indent();
1426         }
1427 
1428         writer.write("<!--");
1429         writer.write(text);
1430         writer.write("-->");
1431 
1432         lastOutputNodeType = Node.COMMENT_NODE;
1433     }
1434 
1435     /***
1436      * Writes the attributes of the given element
1437      * 
1438      * @param element
1439      *            DOCUMENT ME!
1440      * 
1441      * @throws IOException
1442      *             DOCUMENT ME!
1443      */
1444     protected void writeAttributes(Element element) throws IOException {
1445         // I do not yet handle the case where the same prefix maps to
1446         // two different URIs. For attributes on the same element
1447         // this is illegal; but as yet we don't throw an exception
1448         // if someone tries to do this
1449         for (int i = 0, size = element.attributeCount(); i < size; i++) {
1450             Attribute attribute = element.attribute(i);
1451             Namespace ns = attribute.getNamespace();
1452 
1453             if ((ns != null) && (ns != Namespace.NO_NAMESPACE)
1454                     && (ns != Namespace.XML_NAMESPACE)) {
1455                 String prefix = ns.getPrefix();
1456                 String uri = namespaceStack.getURI(prefix);
1457 
1458                 if (!ns.getURI().equals(uri)) {
1459                     writeNamespace(ns);
1460                     namespaceStack.push(ns);
1461                 }
1462             }
1463 
1464             // If the attribute is a namespace declaration, check if we have
1465             // already written that declaration elsewhere (if that's the case,
1466             // it must be in the namespace stack
1467             String attName = attribute.getName();
1468 
1469             if (attName.startsWith("xmlns:")) {
1470                 String prefix = attName.substring(6);
1471 
1472                 if (namespaceStack.getNamespaceForPrefix(prefix) == null) {
1473                     String uri = attribute.getValue();
1474                     namespaceStack.push(prefix, uri);
1475                     writeNamespace(prefix, uri);
1476                 }
1477             } else if (attName.equals("xmlns")) {
1478                 if (namespaceStack.getDefaultNamespace() == null) {
1479                     String uri = attribute.getValue();
1480                     namespaceStack.push(null, uri);
1481                     writeNamespace(null, uri);
1482                 }
1483             } else {
1484                 char quote = format.getAttributeQuoteCharacter();
1485                 writer.write(" ");
1486                 writer.write(attribute.getQualifiedName());
1487                 writer.write("=");
1488                 writer.write(quote);
1489                 writeEscapeAttributeEntities(attribute.getValue());
1490                 writer.write(quote);
1491             }
1492         }
1493     }
1494 
1495     protected void writeAttribute(Attribute attribute) throws IOException {
1496         writer.write(" ");
1497         writer.write(attribute.getQualifiedName());
1498         writer.write("=");
1499 
1500         char quote = format.getAttributeQuoteCharacter();
1501         writer.write(quote);
1502 
1503         writeEscapeAttributeEntities(attribute.getValue());
1504 
1505         writer.write(quote);
1506         lastOutputNodeType = Node.ATTRIBUTE_NODE;
1507     }
1508 
1509     protected void writeAttributes(Attributes attributes) throws IOException {
1510         for (int i = 0, size = attributes.getLength(); i < size; i++) {
1511             writeAttribute(attributes, i);
1512         }
1513     }
1514 
1515     protected void writeAttribute(Attributes attributes, int index)
1516             throws IOException {
1517         char quote = format.getAttributeQuoteCharacter();
1518         writer.write(" ");
1519         writer.write(attributes.getQName(index));
1520         writer.write("=");
1521         writer.write(quote);
1522         writeEscapeAttributeEntities(attributes.getValue(index));
1523         writer.write(quote);
1524     }
1525 
1526     protected void indent() throws IOException {
1527         String indent = format.getIndent();
1528 
1529         if ((indent != null) && (indent.length() > 0)) {
1530             for (int i = 0; i < indentLevel; i++) {
1531                 writer.write(indent);
1532             }
1533         }
1534     }
1535 
1536     /***
1537      * <p>
1538      * This will print a new line only if the newlines flag was set to true
1539      * </p>
1540      * 
1541      * @throws IOException
1542      *             DOCUMENT ME!
1543      */
1544     protected void writePrintln() throws IOException {
1545         if (format.isNewlines()) {
1546             String seperator = format.getLineSeparator();
1547             if (lastChar != seperator.charAt(seperator.length() - 1)) {
1548                 writer.write(format.getLineSeparator());
1549             }
1550         }
1551     }
1552 
1553     /***
1554      * Get an OutputStreamWriter, use preferred encoding.
1555      * 
1556      * @param outStream
1557      *            DOCUMENT ME!
1558      * @param encoding
1559      *            DOCUMENT ME!
1560      * 
1561      * @return DOCUMENT ME!
1562      * 
1563      * @throws UnsupportedEncodingException
1564      *             DOCUMENT ME!
1565      */
1566     protected Writer createWriter(OutputStream outStream, String encoding)
1567             throws UnsupportedEncodingException {
1568         return new BufferedWriter(new OutputStreamWriter(outStream, encoding));
1569     }
1570 
1571     /***
1572      * <p>
1573      * This will write the declaration to the given Writer. Assumes XML version
1574      * 1.0 since we don't directly know.
1575      * </p>
1576      * 
1577      * @throws IOException
1578      *             DOCUMENT ME!
1579      */
1580     protected void writeDeclaration() throws IOException {
1581         String encoding = format.getEncoding();
1582 
1583         // Only print of declaration is not suppressed
1584         if (!format.isSuppressDeclaration()) {
1585             // Assume 1.0 version
1586             if (encoding.equals("UTF8")) {
1587                 writer.write("<?xml version=\"1.0\"");
1588 
1589                 if (!format.isOmitEncoding()) {
1590                     writer.write(" encoding=\"UTF-8\"");
1591                 }
1592 
1593                 writer.write("?>");
1594             } else {
1595                 writer.write("<?xml version=\"1.0\"");
1596 
1597                 if (!format.isOmitEncoding()) {
1598                     writer.write(" encoding=\"" + encoding + "\"");
1599                 }
1600 
1601                 writer.write("?>");
1602             }
1603 
1604             if (format.isNewLineAfterDeclaration()) {
1605                 println();
1606             }
1607         }
1608     }
1609 
1610     protected void writeClose(String qualifiedName) throws IOException {
1611         writer.write("</");
1612         writer.write(qualifiedName);
1613         writer.write(">");
1614     }
1615 
1616     protected void writeEmptyElementClose(String qualifiedName)
1617             throws IOException {
1618         // Simply close up
1619         if (!format.isExpandEmptyElements()) {
1620             writer.write("/>");
1621         } else {
1622             writer.write("></");
1623             writer.write(qualifiedName);
1624             writer.write(">");
1625         }
1626     }
1627 
1628     protected boolean isExpandEmptyElements() {
1629         return format.isExpandEmptyElements();
1630     }
1631 
1632     /***
1633      * This will take the pre-defined entities in XML 1.0 and convert their
1634      * character representation to the appropriate entity reference, suitable
1635      * for XML attributes.
1636      * 
1637      * @param text
1638      *            DOCUMENT ME!
1639      * 
1640      * @return DOCUMENT ME!
1641      */
1642     protected String escapeElementEntities(String text) {
1643         char[] block = null;
1644         int i;
1645         int last = 0;
1646         int size = text.length();
1647 
1648         for (i = 0; i < size; i++) {
1649             String entity = null;
1650             char c = text.charAt(i);
1651 
1652             switch (c) {
1653                 case '<':
1654                     entity = "&lt;";
1655 
1656                     break;
1657 
1658                 case '>':
1659                     entity = "&gt;";
1660 
1661                     break;
1662 
1663                 case '&':
1664                     entity = "&amp;";
1665 
1666                     break;
1667 
1668                 case '\t':
1669                 case '\n':
1670                 case '\r':
1671 
1672                     // don't encode standard whitespace characters
1673                     if (preserve) {
1674                         entity = String.valueOf(c);
1675                     }
1676 
1677                     break;
1678 
1679                 default:
1680 
1681                     if ((c < 32) || shouldEncodeChar(c)) {
1682                         entity = "&#" + (int) c + ";";
1683                     }
1684 
1685                     break;
1686             }
1687 
1688             if (entity != null) {
1689                 if (block == null) {
1690                     block = text.toCharArray();
1691                 }
1692 
1693                 buffer.append(block, last, i - last);
1694                 buffer.append(entity);
1695                 last = i + 1;
1696             }
1697         }
1698 
1699         if (last == 0) {
1700             return text;
1701         }
1702 
1703         if (last < size) {
1704             if (block == null) {
1705                 block = text.toCharArray();
1706             }
1707 
1708             buffer.append(block, last, i - last);
1709         }
1710 
1711         String answer = buffer.toString();
1712         buffer.setLength(0);
1713 
1714         return answer;
1715     }
1716 
1717     protected void writeEscapeAttributeEntities(String txt) throws IOException {
1718         if (txt != null) {
1719             String escapedText = escapeAttributeEntities(txt);
1720             writer.write(escapedText);
1721         }
1722     }
1723 
1724     /***
1725      * This will take the pre-defined entities in XML 1.0 and convert their
1726      * character representation to the appropriate entity reference, suitable
1727      * for XML attributes.
1728      * 
1729      * @param text
1730      *            DOCUMENT ME!
1731      * 
1732      * @return DOCUMENT ME!
1733      */
1734     protected String escapeAttributeEntities(String text) {
1735         char quote = format.getAttributeQuoteCharacter();
1736 
1737         char[] block = null;
1738         int i;
1739         int last = 0;
1740         int size = text.length();
1741 
1742         for (i = 0; i < size; i++) {
1743             String entity = null;
1744             char c = text.charAt(i);
1745 
1746             switch (c) {
1747                 case '<':
1748                     entity = "&lt;";
1749 
1750                     break;
1751 
1752                 case '>':
1753                     entity = "&gt;";
1754 
1755                     break;
1756 
1757                 case '\'':
1758 
1759                     if (quote == '\'') {
1760                         entity = "&apos;";
1761                     }
1762 
1763                     break;
1764 
1765                 case '\"':
1766 
1767                     if (quote == '\"') {
1768                         entity = "&quot;";
1769                     }
1770 
1771                     break;
1772 
1773                 case '&':
1774                     entity = "&amp;";
1775 
1776                     break;
1777 
1778                 case '\t':
1779                 case '\n':
1780                 case '\r':
1781 
1782                     // don't encode standard whitespace characters
1783                     break;
1784 
1785                 default:
1786 
1787                     if ((c < 32) || shouldEncodeChar(c)) {
1788                         entity = "&#" + (int) c + ";";
1789                     }
1790 
1791                     break;
1792             }
1793 
1794             if (entity != null) {
1795                 if (block == null) {
1796                     block = text.toCharArray();
1797                 }
1798 
1799                 buffer.append(block, last, i - last);
1800                 buffer.append(entity);
1801                 last = i + 1;
1802             }
1803         }
1804 
1805         if (last == 0) {
1806             return text;
1807         }
1808 
1809         if (last < size) {
1810             if (block == null) {
1811                 block = text.toCharArray();
1812             }
1813 
1814             buffer.append(block, last, i - last);
1815         }
1816 
1817         String answer = buffer.toString();
1818         buffer.setLength(0);
1819 
1820         return answer;
1821     }
1822 
1823     /***
1824      * Should the given character be escaped. This depends on the encoding of
1825      * the document.
1826      * 
1827      * @param c
1828      *            DOCUMENT ME!
1829      * 
1830      * @return boolean
1831      */
1832     protected boolean shouldEncodeChar(char c) {
1833         int max = getMaximumAllowedCharacter();
1834 
1835         return (max > 0) && (c > max);
1836     }
1837 
1838     /***
1839      * Returns the maximum allowed character code that should be allowed
1840      * unescaped which defaults to 127 in US-ASCII (7 bit) or 255 in ISO- (8
1841      * bit).
1842      * 
1843      * @return DOCUMENT ME!
1844      */
1845     protected int defaultMaximumAllowedCharacter() {
1846         String encoding = format.getEncoding();
1847 
1848         if (encoding != null) {
1849             if (encoding.equals("US-ASCII")) {
1850                 return 127;
1851             }
1852         }
1853 
1854         // no encoding for things like ISO-*, UTF-8 or UTF-16
1855         return -1;
1856     }
1857 
1858     protected boolean isNamespaceDeclaration(Namespace ns) {
1859         if ((ns != null) && (ns != Namespace.XML_NAMESPACE)) {
1860             String uri = ns.getURI();
1861 
1862             if (uri != null) {
1863                 if (!namespaceStack.contains(ns)) {
1864                     return true;
1865                 }
1866             }
1867         }
1868 
1869         return false;
1870     }
1871 
1872     protected void handleException(IOException e) throws SAXException {
1873         throw new SAXException(e);
1874     }
1875 
1876     // Laramie Crocker 4/8/2002 10:38AM
1877 
1878     /***
1879      * Lets subclasses get at the current format object, so they can call
1880      * setTrimText, setNewLines, etc. Put in to support the HTMLWriter, in the
1881      * way that it pushes the current newline/trim state onto a stack and
1882      * overrides the state within preformatted tags.
1883      * 
1884      * @return DOCUMENT ME!
1885      */
1886     protected OutputFormat getOutputFormat() {
1887         return format;
1888     }
1889 
1890     public boolean resolveEntityRefs() {
1891         return resolveEntityRefs;
1892     }
1893 
1894     public void setResolveEntityRefs(boolean resolve) {
1895         this.resolveEntityRefs = resolve;
1896     }
1897 }
1898 
1899 /*
1900  * Redistribution and use of this software and associated documentation
1901  * ("Software"), with or without modification, are permitted provided that the
1902  * following conditions are met:
1903  * 
1904  * 1. Redistributions of source code must retain copyright statements and
1905  * notices. Redistributions must also contain a copy of this document.
1906  * 
1907  * 2. Redistributions in binary form must reproduce the above copyright notice,
1908  * this list of conditions and the following disclaimer in the documentation
1909  * and/or other materials provided with the distribution.
1910  * 
1911  * 3. The name "DOM4J" must not be used to endorse or promote products derived
1912  * from this Software without prior written permission of MetaStuff, Ltd. For
1913  * written permission, please contact dom4j-info@metastuff.com.
1914  * 
1915  * 4. Products derived from this Software may not be called "DOM4J" nor may
1916  * "DOM4J" appear in their names without prior written permission of MetaStuff,
1917  * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
1918  * 
1919  * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
1920  * 
1921  * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
1922  * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1923  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1924  * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
1925  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
1926  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
1927  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
1928  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
1929  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
1930  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
1931  * POSSIBILITY OF SUCH DAMAGE.
1932  * 
1933  * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
1934  */