1
2
3
4
5
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 < > &)
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 <
255 * > &) 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
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
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
710
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
737
738
739
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
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
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
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
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
948
949 writeElementContent(element);
950 } else {
951
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
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;
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) {
1018 preserve = isElementSpacePreserved(element);
1019 trim = !preserve;
1020 }
1021
1022 if (trim) {
1023
1024
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
1045
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
1068
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
1093
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
1127
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
1139
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
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
1239
1240
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
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
1349
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
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
1372 } catch (SAXNotSupportedException ex) {
1373
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
1446
1447
1448
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
1465
1466
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
1584 if (!format.isSuppressDeclaration()) {
1585
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
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 = "<";
1655
1656 break;
1657
1658 case '>':
1659 entity = ">";
1660
1661 break;
1662