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