Clover coverage report - dom4j - 1.6.1
Coverage timestamp: ma mei 16 2005 14:23:01 GMT+01:00

file stats: | LOC: | 1.934 |  | Methods: | 93
| NCLOC: | 1.101 |  | Classes: | 1

Source file | Conditionals | Statements | Methods | TOTAL

XMLWriter.java

59,5%

64,3%

57%

62,3%

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 | 18 | public XMLWriter(Writer writer) {
152 | 18 | this(writer, DEFAULT_FORMAT);
153 |  | }
154 |  |
155 | 2143 | public XMLWriter(Writer writer, OutputFormat format) {
156 | 2143 | this.writer = writer;
157 | 2143 | this.format = format;
158 | 2143 | namespaceStack.push(Namespace.NO_NAMESPACE);
159 |  | }
160 |  |
161 | 2 | public XMLWriter() {
162 | 2 | this.format = DEFAULT_FORMAT;
163 | 2 | this.writer = new BufferedWriter(new OutputStreamWriter(System.out));
164 | 2 | this.autoFlush = true;
165 | 2 | namespaceStack.push(Namespace.NO_NAMESPACE);
166 |  | }
167 |  |
168 | 2 | public XMLWriter(OutputStream out) throws UnsupportedEncodingException {
169 | 2 | this.format = DEFAULT_FORMAT;
170 | 2 | this.writer = createWriter(out, format.getEncoding());
171 | 2 | this.autoFlush = true;
172 | 2 | namespaceStack.push(Namespace.NO_NAMESPACE);
173 |  | }
174 |  |
175 | 7 | public XMLWriter(OutputStream out, OutputFormat format)
176 |  | throws UnsupportedEncodingException {
177 | 7 | this.format = format;
178 | 7 | this.writer = createWriter(out, format.getEncoding());
179 | 7 | this.autoFlush = true;
180 | 7 | namespaceStack.push(Namespace.NO_NAMESPACE);
181 |  | }
182 |  |
183 | 8 | public XMLWriter(OutputFormat format) throws UnsupportedEncodingException {
184 | 8 | this.format = format;
185 | 8 | this.writer = createWriter(System.out, format.getEncoding());
186 | 8 | this.autoFlush = true;
187 | 8 | namespaceStack.push(Namespace.NO_NAMESPACE);
188 |  | }
189 |  |
190 | 2 | public void setWriter(Writer writer) {
191 | 2 | this.writer = writer;
192 | 2 | this.autoFlush = false;
193 |  | }
194 |  |
195 | 8 | public void setOutputStream(OutputStream out)
196 |  | throws UnsupportedEncodingException {
197 | 8 | this.writer = createWriter(out, format.getEncoding());
198 | 8 | 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 | 0 | public boolean isEscapeText() {
209 | 0 | 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 | 1 | public void setEscapeText(boolean escapeText) {
221 | 1 | 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 | 0 | public void setIndentLevel(int indentLevel) {
233 | 0 | 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 | 484095 | public int getMaximumAllowedCharacter() {
244 | 484095 | if (maximumAllowedCharacter == 0) {
245 | 2147 | maximumAllowedCharacter = defaultMaximumAllowedCharacter();
246 |  | }
247 |  |
248 | 484095 | 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 | 1 | public void setMaximumAllowedCharacter(int maximumAllowedCharacter) {
262 | 1 | this.maximumAllowedCharacter = maximumAllowedCharacter;
263 |  | }
264 |  |
265 |  | /**
266 |  | * Flushes the underlying Writer
267 |  | *
268 |  | * @throws IOException
269 |  | * DOCUMENT ME!
270 |  | */
271 | 2119 | public void flush() throws IOException {
272 | 2119 | writer.flush();
273 |  | }
274 |  |
275 |  | /**
276 |  | * Closes the underlying Writer
277 |  | *
278 |  | * @throws IOException
279 |  | * DOCUMENT ME!
280 |  | */
281 | 21 | public void close() throws IOException {
282 | 21 | writer.close();
283 |  | }
284 |  |
285 |  | /**
286 |  | * Writes the new line text to the underlying Writer
287 |  | *
288 |  | * @throws IOException
289 |  | * DOCUMENT ME!
290 |  | */
291 | 113 | public void println() throws IOException {
292 | 113 | 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 | 0 | public void write(Attribute attribute) throws IOException {
305 | 0 | writeAttribute(attribute);
306 |  |
307 | 0 | if (autoFlush) {
308 | 0 | 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 | 113 | public void write(Document doc) throws IOException {
335 | 113 | writeDeclaration();
336 |  |
337 | 113 | if (doc.getDocType() != null) {
338 | 1 | indent();
339 | 1 | writeDocType(doc.getDocType());
340 |  | }
341 |  |
342 | 113 | for (int i = 0, size = doc.nodeCount(); i < size; i++) {
343 | 116 | Node node = doc.node(i);
344 | 116 | writeNode(node);
345 |  | }
346 |  |
347 | 113 | writePrintln();
348 |  |
349 | 113 | if (autoFlush) {
350 | 16 | 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 | 2043 | public void write(Element element) throws IOException {
369 | 2043 | writeElement(element);
370 |  |
371 | 2043 | if (autoFlush) {
372 | 2 | 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 | 0 | public void write(CDATA cdata) throws IOException {
386 | 0 | writeCDATA(cdata.getText());
387 |  |
388 | 0 | if (autoFlush) {
389 | 0 | 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 | 0 | public void write(Comment comment) throws IOException {
403 | 0 | writeComment(comment.getText());
404 |  |
405 | 0 | if (autoFlush) {
406 | 0 | 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 | 0 | public void write(DocumentType docType) throws IOException {
420 | 0 | writeDocType(docType);
421 |  |
422 | 0 | if (autoFlush) {
423 | 0 | 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 | 0 | public void write(Entity entity) throws IOException {
437 | 0 | writeEntity(entity);
438 |  |
439 | 0 | if (autoFlush) {
440 | 0 | 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 | 0 | public void write(Namespace namespace) throws IOException {
454 | 0 | writeNamespace(namespace);
455 |  |
456 | 0 | if (autoFlush) {
457 | 0 | 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 | 0 | public void write(ProcessingInstruction processingInstruction)
471 |  | throws IOException {
472 | 0 | writeProcessingInstruction(processingInstruction);
473 |  |
474 | 0 | if (autoFlush) {
475 | 0 | 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 | 0 | public void write(String text) throws IOException {
492 | 0 | writeString(text);
493 |  |
494 | 0 | if (autoFlush) {
495 | 0 | 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 | 0 | public void write(Text text) throws IOException {
509 | 0 | writeString(text.getText());
510 |  |
511 | 0 | if (autoFlush) {
512 | 0 | 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 | 1 | public void write(Node node) throws IOException {
526 | 1 | writeNode(node);
527 |  |
528 | 1 | if (autoFlush) {
529 | 0 | 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 | 1 | public void write(Object object) throws IOException {
544 | 1 | if (object instanceof Node) {
545 | 1 | write((Node) object);
546 | 0 | } else if (object instanceof String) {
547 | 0 | write((String) object);
548 | 0 | } else if (object instanceof List) {
549 | 0 | List list = (List) object;
550 |  |
551 | 0 | for (int i = 0, size = list.size(); i < size; i++) {
552 | 0 | write(list.get(i));
553 |  | }
554 | 0 | } else if (object != null) {
555 | 0 | 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 | 0 | public void writeOpen(Element element) throws IOException {
572 | 0 | writer.write("<");
573 | 0 | writer.write(element.getQualifiedName());
574 | 0 | writeAttributes(element);
575 | 0 | 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 | 0 | public void writeClose(Element element) throws IOException {
590 | 0 | writeClose(element.getQualifiedName());
591 |  | }
592 |  |
593 |  | // XMLFilterImpl methods
594 |  | // -------------------------------------------------------------------------
595 | 0 | public void parse(InputSource source) throws IOException, SAXException {
596 | 0 | installLexicalHandler();
597 | 0 | super.parse(source);
598 |  | }
599 |  |
600 | 0 | public void setProperty(String name, Object value)
601 |  | throws SAXNotRecognizedException, SAXNotSupportedException {
602 | 0 | for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
603 | 0 | if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
604 | 0 | setLexicalHandler((LexicalHandler) value);
605 |  |
606 | 0 | return;
607 |  | }
608 |  | }
609 |  |
610 | 0 | super.setProperty(name, value);
611 |  | }
612 |  |
613 | 0 | public Object getProperty(String name) throws SAXNotRecognizedException,
614 |  | SAXNotSupportedException {
615 | 0 | for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
616 | 0 | if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
617 | 0 | return getLexicalHandler();
618 |  | }
619 |  | }
620 |  |
621 | 0 | return super.getProperty(name);
622 |  | }
623 |  |
624 | 0 | public void setLexicalHandler(LexicalHandler handler) {
625 | 0 | if (handler == null) {
626 | 0 | throw new NullPointerException("Null lexical handler");
627 |  | } else {
628 | 0 | this.lexicalHandler = handler;
629 |  | }
630 |  | }
631 |  |
632 | 0 | public LexicalHandler getLexicalHandler() {
633 | 0 | return lexicalHandler;
634 |  | }
635 |  |
636 |  | // ContentHandler interface
637 |  | // -------------------------------------------------------------------------
638 | 1 | public void setDocumentLocator(Locator locator) {
639 | 1 | super.setDocumentLocator(locator);
640 |  | }
641 |  |
642 | 4 | public void startDocument() throws SAXException {
643 | 4 | try {
644 | 4 | writeDeclaration();
645 | 4 | super.startDocument();
646 |  | } catch (IOException e) {
647 | 0 | handleException(e);
648 |  | }
649 |  | }
650 |  |
651 | 4 | public void endDocument() throws SAXException {
652 | 4 | super.endDocument();
653 |  |
654 | 4 | if (autoFlush) {
655 | 0 | try {
656 | 0 | flush();
657 |  | } catch (IOException e) {
658 |  | }
659 |  | }
660 |  | }
661 |  |
662 | 0 | public void startPrefixMapping(String prefix, String uri)
663 |  | throws SAXException {
664 | 0 | if (namespacesMap == null) {
665 | 0 | namespacesMap = new HashMap();
666 |  | }
667 |  |
668 | 0 | namespacesMap.put(prefix, uri);
669 | 0 | super.startPrefixMapping(prefix, uri);
670 |  | }
671 |  |
672 | 0 | public void endPrefixMapping(String prefix) throws SAXException {
673 | 0 | super.endPrefixMapping(prefix);
674 |  | }
675 |  |
676 | 8 | public void startElement(String namespaceURI, String localName,
677 |  | String qName, Attributes attributes) throws SAXException {
678 | 8 | try {
679 | 8 | charsAdded = false;
680 |  |
681 | 8 | writePrintln();
682 | 8 | indent();
683 | 8 | writer.write("<");
684 | 8 | writer.write(qName);
685 | 8 | writeNamespaces();
686 | 8 | writeAttributes(attributes);
687 | 8 | writer.write(">");
688 | 8 | ++indentLevel;
689 | 8 | lastOutputNodeType = Node.ELEMENT_NODE;
690 | 8 | lastElementClosed = false;
691 |  |
692 | 8 | super.startElement(namespaceURI, localName, qName, attributes);
693 |  | } catch (IOException e) {
694 | 0 | handleException(e);
695 |  | }
696 |  | }
697 |  |
698 | 8 | public void endElement(String namespaceURI, String localName, String qName)
699 |  | throws SAXException {
700 | 8 | try {
701 | 8 | charsAdded = false;
702 | 8 | --indentLevel;
703 |  |
704 | 8 | if (lastElementClosed) {
705 | 4 | writePrintln();
706 | 4 | indent();
707 |  | }
708 |  |
709 |  | // XXXX: need to determine this using a stack and checking for
710 |  | // content / children
711 | 8 | boolean hadContent = true;
712 |  |
713 | 8 | if (hadContent) {
714 | 8 | writeClose(qName);
715 |  | } else {
716 | 0 | writeEmptyElementClose(qName);
717 |  | }
718 |  |
719 | 8 | lastOutputNodeType = Node.ELEMENT_NODE;
720 | 8 | lastElementClosed = true;
721 |  |
722 | 8 | super.endElement(namespaceURI, localName, qName);
723 |  | } catch (IOException e) {
724 | 0 | handleException(e);
725 |  | }
726 |  | }
727 |  |
728 | 16 | public void characters(char[] ch, int start, int length)
729 |  | throws SAXException {
730 | 16 | if ((ch == null) || (ch.length == 0) || (length <= 0)) {
731 | 1 | return;
732 |  | }
733 |  |
734 | 15 | 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 | 15 | String string = String.valueOf(ch, start, length);
742 |  |
743 | 15 | if (escapeText) {
744 | 14 | string = escapeElementEntities(string);
745 |  | }
746 |  |
747 | 15 | if (format.isTrimText()) {
748 | 14 | if ((lastOutputNodeType == Node.TEXT_NODE) && !charsAdded) {
749 | 0 | writer.write(' ');
750 | 14 | } else if (charsAdded && Character.isWhitespace(lastChar)) {
751 | 1 | writer.write(' ');
752 | 13 | } else if (lastOutputNodeType == Node.ELEMENT_NODE
753 |  | && format.isPadText() && lastElementClosed
754 |  | && Character.isWhitespace(ch[0])) {
755 | 1 | writer.write(PAD_TEXT);
756 |  | }
757 |  |
758 | 14 | String delim = "";
759 | 14 | StringTokenizer tokens = new StringTokenizer(string);
760 |  |
761 | 14 | while (tokens.hasMoreTokens()) {
762 | 17 | writer.write(delim);
763 | 17 | writer.write(tokens.nextToken());
764 | 17 | delim = " ";
765 |  | }
766 |  | } else {
767 | 1 | writer.write(string);
768 |  | }
769 |  |
770 | 15 | charsAdded = true;
771 | 15 | lastChar = ch[(start + length) - 1];
772 | 15 | lastOutputNodeType = Node.TEXT_NODE;
773 |  |
774 | 15 | super.characters(ch, start, length);
775 |  | } catch (IOException e) {
776 | 0 | handleException(e);
777 |  | }
778 |  | }
779 |  |
780 | 0 | public void ignorableWhitespace(char[] ch, int start, int length)
781 |  | throws SAXException {
782 | 0 | super.ignorableWhitespace(ch, start, length);
783 |  | }
784 |  |
785 | 0 | public void processingInstruction(String target, String data)
786 |  | throws SAXException {
787 | 0 | try {
788 | 0 | indent();
789 | 0 | writer.write("<?");
790 | 0 | writer.write(target);
791 | 0 | writer.write(" ");
792 | 0 | writer.write(data);
793 | 0 | writer.write("?>");
794 | 0 | writePrintln();
795 | 0 | lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE;
796 |  |
797 | 0 | super.processingInstruction(target, data);
798 |  | } catch (IOException e) {
799 | 0 | handleException(e);
800 |  | }
801 |  | }
802 |  |
803 |  | // DTDHandler interface
804 |  | // -------------------------------------------------------------------------
805 | 0 | public void notationDecl(String name, String publicID, String systemID)
806 |  | throws SAXException {
807 | 0 | super.notationDecl(name, publicID, systemID);
808 |  | }
809 |  |
810 | 0 | public void unparsedEntityDecl(String name, String publicID,
811 |  | String systemID, String notationName) throws SAXException {
812 | 0 | super.unparsedEntityDecl(name, publicID, systemID, notationName);
813 |  | }
814 |  |
815 |  | // LexicalHandler interface
816 |  | // -------------------------------------------------------------------------
817 | 0 | public void startDTD(String name, String publicID, String systemID)
818 |  | throws SAXException {
819 | 0 | inDTD = true;
820 |  |
821 | 0 | try {
822 | 0 | writeDocType(name, publicID, systemID);
823 |  | } catch (IOException e) {
824 | 0 | handleException(e);
825 |  | }
826 |  |
827 | 0 | if (lexicalHandler != null) {
828 | 0 | lexicalHandler.startDTD(name, publicID, systemID);
829 |  | }
830 |  | }
831 |  |
832 | 0 | public void endDTD() throws SAXException {
833 | 0 | inDTD = false;
834 |  |
835 | 0 | if (lexicalHandler != null) {
836 | 0 | lexicalHandler.endDTD();
837 |  | }
838 |  | }
839 |  |
840 | 0 | public void startCDATA() throws SAXException {
841 | 0 | try {
842 | 0 | writer.write("<![CDATA[");
843 |  | } catch (IOException e) {
844 | 0 | handleException(e);
845 |  | }
846 |  |
847 | 0 | if (lexicalHandler != null) {
848 | 0 | lexicalHandler.startCDATA();
849 |  | }
850 |  | }
851 |  |
852 | 0 | public void endCDATA() throws SAXException {
853 | 0 | try {
854 | 0 | writer.write("]]>");
855 |  | } catch (IOException e) {
856 | 0 | handleException(e);
857 |  | }
858 |  |
859 | 0 | if (lexicalHandler != null) {
860 | 0 | lexicalHandler.endCDATA();
861 |  | }
862 |  | }
863 |  |
864 | 0 | public void startEntity(String name) throws SAXException {
865 | 0 | try {
866 | 0 | writeEntityRef(name);
867 |  | } catch (IOException e) {
868 | 0 | handleException(e);
869 |  | }
870 |  |
871 | 0 | if (lexicalHandler != null) {
872 | 0 | lexicalHandler.startEntity(name);
873 |  | }
874 |  | }
875 |  |
876 | 0 | public void endEntity(String name) throws SAXException {
877 | 0 | if (lexicalHandler != null) {
878 | 0 | lexicalHandler.endEntity(name);
879 |  | }
880 |  | }
881 |  |
882 | 0 | public void comment(char[] ch, int start, int length) throws SAXException {
883 | 0 | if (showCommentsInDTDs || !inDTD) {
884 | 0 | try {
885 | 0 | charsAdded = false;
886 | 0 | writeComment(new String(ch, start, length));
887 |  | } catch (IOException e) {
888 | 0 | handleException(e);
889 |  | }
890 |  | }
891 |  |
892 | 0 | if (lexicalHandler != null) {
893 | 0 | lexicalHandler.comment(ch, start, length);
894 |  | }
895 |  | }
896 |  |
897 |  | // Implementation methods
898 |  | // -------------------------------------------------------------------------
899 | 20077 | protected void writeElement(Element element) throws IOException {
900 | 20077 | int size = element.nodeCount();
901 | 20077 | String qualifiedName = element.getQualifiedName();
902 |  |
903 | 20077 | writePrintln();
904 | 20077 | indent();
905 |  |
906 | 20077 | writer.write("<");
907 | 20077 | writer.write(qualifiedName);
908 |  |
909 | 20077 | int previouslyDeclaredNamespaces = namespaceStack.size();
910 | 20077 | Namespace ns = element.getNamespace();
911 |  |
912 | 20077 | if (isNamespaceDeclaration(ns)) {
913 | 602 | namespaceStack.push(ns);
914 | 602 | writeNamespace(ns);
915 |  | }
916 |  |
917 |  | // Print out additional namespace declarations
918 | 20077 | boolean textOnly = true;
919 |  |
920 | 20077 | for (int i = 0; i < size; i++) {
921 | 56305 | Node node = element.node(i);
922 |  |
923 | 56305 | if (node instanceof Namespace) {
924 | 198 | Namespace additional = (Namespace) node;
925 |  |
926 | 198 | if (isNamespaceDeclaration(additional)) {
927 | 16 | namespaceStack.push(additional);
928 | 16 | writeNamespace(additional);
929 |  | }
930 | 56107 | } else if (node instanceof Element) {
931 | 17921 | textOnly = false;
932 | 38186 | } else if (node instanceof Comment) {
933 | 445 | textOnly = false;
934 |  | }
935 |  | }
936 |  |
937 | 20077 | writeAttributes(element);
938 |  |
939 | 20077 | lastOutputNodeType = Node.ELEMENT_NODE;
940 |  |
941 | 20077 | if (size <= 0) {
942 | 300 | writeEmptyElementClose(qualifiedName);
943 |  | } else {
944 | 19777 | writer.write(">");
945 |  |
946 | 19777 | if (textOnly) {
947 |  | // we have at least one text node so lets assume
948 |  | // that its non-empty
949 | 14954 | writeElementContent(element);
950 |  | } else {
951 |  | // we know it's not null or empty from above
952 | 4823 | ++indentLevel;
953 |  |
954 | 4823 | writeElementContent(element);
955 |  |
956 | 4823 | --indentLevel;
957 |  |
958 | 4823 | writePrintln();
959 | 4823 | indent();
960 |  | }
961 |  |
962 | 19777 | writer.write("</");
963 | 19777 | writer.write(qualifiedName);
964 | 19777 | writer.write(">");
965 |  | }
966 |  |
967 |  | // remove declared namespaceStack from stack
968 | 20077 | while (namespaceStack.size() > previouslyDeclaredNamespaces) {
969 | 620 | namespaceStack.pop();
970 |  | }
971 |  |
972 | 20077 | 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 | 93 | protected final boolean isElementSpacePreserved(Element element) {
985 | 93 | final Attribute attr = (Attribute) element.attribute("space");
986 | 93 | boolean preserveFound = preserve; // default to global state
987 |  |
988 | 93 | if (attr != null) {
989 | 5 | if ("xml".equals(attr.getNamespacePrefix())
990 |  | && "preserve".equals(attr.getText())) {
991 | 4 | preserveFound = true;
992 |  | } else {
993 | 1 | preserveFound = false;
994 |  | }
995 |  | }
996 |  |
997 | 93 | 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 | 19777 | protected void writeElementContent(Element element) throws IOException {
1014 | 19777 | boolean trim = format.isTrimText();
1015 | 19777 | boolean oldPreserve = preserve;
1016 |  |
1017 | 19777 | if (trim) { // verify we have to before more expensive test
1018 | 93 | preserve = isElementSpacePreserved(element);
1019 | 93 | trim = !preserve;
1020 |  | }
1021 |  |
1022 | 19777 | if (trim) {
1023 |  | // concatenate adjacent text nodes together
1024 |  | // so that whitespace trimming works properly
1025 | 82 | Text lastTextNode = null;
1026 | 82 | StringBuffer buff = null;
1027 | 82 | boolean textOnly = true;
1028 |  |
1029 | 82 | for (int i = 0, size = element.nodeCount(); i < size; i++) {
1030 | 146 | Node node = element.node(i);
1031 |  |
1032 | 146 | if (node instanceof Text) {
1033 | 76 | if (lastTextNode == null) {
1034 | 53 | lastTextNode = (Text) node;
1035 |  | } else {
1036 | 23 | if (buff == null) {
1037 | 9 | buff = new StringBuffer(lastTextNode.getText());
1038 |  | }
1039 |  |
1040 | 23 | buff.append(((Text) node).getText());
1041 |  | }
1042 |  | } else {
1043 | 70 | if (!textOnly && format.isPadText()) {
1044 |  | // only add the PAD_TEXT if the text itself starts with
1045 |  | // whitespace
1046 | 7 | char firstChar = 'a';
1047 | 7 | if (buff != null) {
1048 | 0 | firstChar = buff.charAt(0);
1049 | 7 | } else if (lastTextNode != null) {
1050 | 0 | firstChar = lastTextNode.getText().charAt(0);
1051 |  | }
1052 |  |
1053 | 7 | if (Character.isWhitespace(firstChar)) {
1054 | 0 | writer.write(PAD_TEXT);
1055 |  | }
1056 |  | }
1057 |  |
1058 | 70 | if (lastTextNode != null) {
1059 | 17 | if (buff != null) {
1060 | 2 | writeString(buff.toString());
1061 | 2 | buff = null;
1062 |  | } else {
1063 | 15 | writeString(lastTextNode.getText());
1064 |  | }
1065 |  |
1066 | 17 | if (format.isPadText()) {
1067 |  | // only add the PAD_TEXT if the text itself ends
1068 |  | // with whitespace
1069 | 10 | char lastTextChar = 'a';
1070 | 10 | if (buff != null) {
1071 | 0 | lastTextChar = buff.charAt(buff.length() - 1);
1072 | 10 | } else if (lastTextNode != null) {
1073 | 10 | String txt = lastTextNode.getText();
1074 | 10 | lastTextChar = txt.charAt(txt.length() - 1);
1075 |  | }
1076 |  |
1077 | 10 | if (Character.isWhitespace(lastTextChar)) {
1078 | 1 | writer.write(PAD_TEXT);
1079 |  | }
1080 |  | }
1081 |  |
1082 | 17 | lastTextNode = null;
1083 |  | }
1084 |  |
1085 | 70 | textOnly = false;
1086 | 70 | writeNode(node);
1087 |  | }
1088 |  | }
1089 |  |
1090 | 82 | if (lastTextNode != null) {
1091 | 36 | if (!textOnly && format.isPadText()) {
1092 |  | // only add the PAD_TEXT if the text itself starts with
1093 |  | // whitespace
1094 | 3 | char firstChar = 'a';
1095 | 3 | if (buff != null) {
1096 | 0 | firstChar = buff.charAt(0);
1097 |  | } else {
1098 | 3 | firstChar = lastTextNode.getText().charAt(0);
1099 |  | }
1100 |  |
1101 | 3 | if (Character.isWhitespace(firstChar)) {
1102 | 2 | writer.write(PAD_TEXT);
1103 |  | }
1104 |  | }
1105 |  |
1106 | 36 | if (buff != null) {
1107 | 7 | writeString(buff.toString());
1108 | 7 | buff = null;
1109 |  | } else {
1110 | 29 | writeString(lastTextNode.getText());
1111 |  | }
1112 |  |
1113 | 36 | lastTextNode = null;
1114 |  | }
1115 |  | } else {
1116 | 19695 | Node lastTextNode = null;
1117 |  |
1118 | 19695 | for (int i = 0, size = element.nodeCount(); i < size; i++) {
1119 | 56159 | Node node = element.node(i);
1120 |  |
1121 | 56159 | if (node instanceof Text) {
1122 | 37643 | writeNode(node);
1123 | 37643 | lastTextNode = node;
1124 |  | } else {
1125 | 18516 | if ((lastTextNode != null) && format.isPadText()) {
1126 |  | // only add the PAD_TEXT if the text itself ends with
1127 |  | // whitespace
1128 | 0 | String txt = lastTextNode.getText();
1129 | 0 | char lastTextChar = txt.charAt(txt.length() - 1);
1130 |  |
1131 | 0 | if (Character.isWhitespace(lastTextChar)) {
1132 | 0 | writer.write(PAD_TEXT);
1133 |  | }
1134 |  | }
1135 |  |
1136 | 18516 | writeNode(node);
1137 |  |
1138 |  | // if ((lastTextNode != null) && format.isPadText()) {
1139 |  | // writer.write(PAD_TEXT);
1140 |  | // }
1141 |  |
1142 | 18516 | lastTextNode = null;
1143 |  | }
1144 |  | }
1145 |  | }
1146 |  |
1147 | 19777 | preserve = oldPreserve;
1148 |  | }
1149 |  |
1150 | 21 | protected void writeCDATA(String text) throws IOException {
1151 | 21 | writer.write("<![CDATA[");
1152 |  |
1153 | 21 | if (text != null) {
1154 | 17 | writer.write(text);
1155 |  | }
1156 |  |
1157 | 21 | writer.write("]]>");
1158 |  |
1159 | 21 | lastOutputNodeType = Node.CDATA_SECTION_NODE;
1160 |  | }
1161 |  |
1162 | 1 | protected void writeDocType(DocumentType docType) throws IOException {
1163 | 1 | if (docType != null) {
1164 | 1 | docType.write(writer);
1165 | 1 | writePrintln();
1166 |  | }
1167 |  | }
1168 |  |
1169 | 618 | protected void writeNamespace(Namespace namespace) throws IOException {
1170 | 618 | if (namespace != null) {
1171 | 618 | writeNamespace(namespace.getPrefix(), namespace.getURI());
1172 |  | }
1173 |  | }
1174 |  |
1175 |  | /**
1176 |  | * Writes the SAX namepsaces
1177 |  | *
1178 |  | * @throws IOException
1179 |  | * DOCUMENT ME!
1180 |  | */
1181 | 8 | protected void writeNamespaces() throws IOException {
1182 | 8 | if (namespacesMap != null) {
1183 | 0 | for (Iterator iter = namespacesMap.entrySet().iterator(); iter
1184 |  | .hasNext();) {
1185 | 0 | Map.Entry entry = (Map.Entry) iter.next();
1186 | 0 | String prefix = (String) entry.getKey();
1187 | 0 | String uri = (String) entry.getValue();
1188 | 0 | writeNamespace(prefix, uri);
1189 |  | }
1190 |  |
1191 | 0 | 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 | 620 | protected void writeNamespace(String prefix, String uri)
1206 |  | throws IOException {
1207 | 620 | if ((prefix != null) && (prefix.length() > 0)) {
1208 | 566 | writer.write(" xmlns:");
1209 | 566 | writer.write(prefix);
1210 | 566 | writer.write("=\"");
1211 |  | } else {
1212 | 54 | writer.write(" xmlns=\"");
1213 |  | }
1214 |  |
1215 | 620 | writer.write(uri);
1216 | 620 | writer.write("\"");
1217 |  | }
1218 |  |
1219 | 1 | protected void writeProcessingInstruction(ProcessingInstruction pi)
1220 |  | throws IOException {
1221 |  | // indent();
1222 | 1 | writer.write("<?");
1223 | 1 | writer.write(pi.getName());
1224 | 1 | writer.write(" ");
1225 | 1 | writer.write(pi.getText());
1226 | 1 | writer.write("?>");
1227 | 1 | writePrintln();
1228 |  |
1229 | 1 | lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE;
1230 |  | }
1231 |  |
1232 | 53 | protected void writeString(String text) throws IOException {
1233 | 53 | if ((text != null) && (text.length() > 0)) {
1234 | 51 | if (escapeText) {
1235 | 51 | text = escapeElementEntities(text);
1236 |  | }
1237 |  |
1238 |  | // if (format.isPadText()) {
1239 |  | // if (lastOutputNodeType == Node.ELEMENT_NODE) {
1240 |  | // writer.write(PAD_TEXT);
1241 |  | // }
1242 |  | // }
1243 | 51 | if (format.isTrimText()) {
1244 | 51 | boolean first = true;
1245 | 51 | StringTokenizer tokenizer = new StringTokenizer(text);
1246 |  |
1247 | 51 | while (tokenizer.hasMoreTokens()) {
1248 | 134 | String token = tokenizer.nextToken();
1249 |  |
1250 | 134 | if (first) {
1251 | 47 | first = false;
1252 |  |
1253 | 47 | if (lastOutputNodeType == Node.TEXT_NODE) {
1254 | 0 | writer.write(" ");
1255 |  | }
1256 |  | } else {
1257 | 87 | writer.write(" ");
1258 |  | }
1259 |  |
1260 | 134 | writer.write(token);
1261 | 134 | lastOutputNodeType = Node.TEXT_NODE;
1262 | 134 | lastChar = token.charAt(token.length() - 1);
1263 |  | }
1264 |  | } else {
1265 | 0 | lastOutputNodeType = Node.TEXT_NODE;
1266 | 0 | writer.write(text);
1267 | 0 | 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 | 37643 | protected void writeNodeText(Node node) throws IOException {
1283 | 37643 | String text = node.getText();
1284 |  |
1285 | 37643 | if ((text != null) && (text.length() > 0)) {
1286 | 37643 | if (escapeText) {
1287 | 37643 | text = escapeElementEntities(text);
1288 |  | }
1289 |  |
1290 | 37643 | lastOutputNodeType = Node.TEXT_NODE;
1291 | 37643 | writer.write(text);
1292 | 37643 | lastChar = text.charAt(text.length() - 1);
1293 |  | }
1294 |  | }
1295 |  |
1296 | 56346 | protected void writeNode(Node node) throws IOException {
1297 | 56346 | int nodeType = node.getNodeType();
1298 |  |
1299 | 56346 | switch (nodeType) {
1300 | 18034 | case Node.ELEMENT_NODE:
1301 | 18034 | writeElement((Element) node);
1302 |  |
1303 | 18034 | break;
1304 |  |
1305 | 0 | case Node.ATTRIBUTE_NODE:
1306 | 0 | writeAttribute((Attribute) node);
1307 |  |
1308 | 0 | break;
1309 |  |
1310 | 37643 | case Node.TEXT_NODE:
1311 | 37643 | writeNodeText(node);
1312 |  |
1313 |  | // write((Text) node);
1314 | 37643 | break;
1315 |  |
1316 | 22 | case Node.CDATA_SECTION_NODE:
1317 | 22 | writeCDATA(node.getText());
1318 |  |
1319 | 22 | break;
1320 |  |
1321 | 0 | case Node.ENTITY_REFERENCE_NODE:
1322 | 0 | writeEntity((Entity) node);
1323 |  |
1324 | 0 | break;
1325 |  |
1326 | 1 | case Node.PROCESSING_INSTRUCTION_NODE:
1327 | 1 | writeProcessingInstruction((ProcessingInstruction) node);
1328 |  |
1329 | 1 | break;
1330 |  |
1331 | 447 | case Node.COMMENT_NODE:
1332 | 447 | writeComment(node.getText());
1333 |  |
1334 | 447 | break;
1335 |  |
1336 | 1 | case Node.DOCUMENT_NODE:
1337 | 1 | write((Document) node);
1338 |  |
1339 | 1 | break;
1340 |  |
1341 | 0 | case Node.DOCUMENT_TYPE_NODE:
1342 | 0 | writeDocType((DocumentType) node);
1343 |  |
1344 | 0 | break;
1345 |  |
1346 | 198 | case Node.NAMESPACE_NODE:
1347 |  |
1348 |  | // Will be output with attributes
1349 |  | // write((Namespace) node);
1350 | 198 | break;
1351 |  |
1352 | 0 | default:
1353 | 0 | throw new IOException("Invalid node type: " + node);
1354 |  | }
1355 |  | }
1356 |  |
1357 | 0 | protected void installLexicalHandler() {
1358 | 0 | XMLReader parent = getParent();
1359 |  |
1360 | 0 | if (parent == null) {
1361 | 0 | throw new NullPointerException("No parent for filter");
1362 |  | }
1363 |  |
1364 |  | // try to register for lexical events
1365 | 0 | for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
1366 | 0 | try {
1367 | 0 | parent.setProperty(LEXICAL_HANDLER_NAMES[i], this);
1368 |  |
1369 | 0 | break;
1370 |  | } catch (SAXNotRecognizedException ex) {
1371 |  | // ignore
1372 |  | } catch (SAXNotSupportedException ex) {
1373 |  | // ignore
1374 |  | }
1375 |  | }
1376 |  | }
1377 |  |
1378 | 0 | protected void writeDocType(String name, String publicID, String systemID)
1379 |  | throws IOException {
1380 | 0 | boolean hasPublic = false;
1381 |  |
1382 | 0 | writer.write("<!DOCTYPE ");
1383 | 0 | writer.write(name);
1384 |  |
1385 | 0 | if ((publicID != null) && (!publicID.equals(""))) {
1386 | 0 | writer.write(" PUBLIC \"");
1387 | 0 | writer.write(publicID);
1388 | 0 | writer.write("\"");
1389 | 0 | hasPublic = true;
1390 |  | }
1391 |  |
1392 | 0 | if ((systemID != null) && (!systemID.equals(""))) {
1393 | 0 | if (!hasPublic) {
1394 | 0 | writer.write(" SYSTEM");
1395 |  | }
1396 |  |
1397 | 0 | writer.write(" \"");
1398 | 0 | writer.write(systemID);
1399 | 0 | writer.write("\"");
1400 |  | }
1401 |  |
1402 | 0 | writer.write(">");
1403 | 0 | writePrintln();
1404 |  | }
1405 |  |
1406 | 0 | protected void writeEntity(Entity entity) throws IOException {
1407 | 0 | if (!resolveEntityRefs()) {
1408 | 0 | writeEntityRef(entity.getName());
1409 |  | } else {
1410 | 0 | writer.write(entity.getText());
1411 |  | }
1412 |  | }
1413 |  |
1414 | 0 | protected void writeEntityRef(String name) throws IOException {
1415 | 0 | writer.write("&");
1416 | 0 | writer.write(name);
1417 | 0 | writer.write(";");
1418 |  |
1419 | 0 | lastOutputNodeType = Node.ENTITY_REFERENCE_NODE;
1420 |  | }
1421 |  |
1422 | 447 | protected void writeComment(String text) throws IOException {
1423 | 447 | if (format.isNewlines()) {
1424 | 3 | println();
1425 | 3 | indent();
1426 |  | }
1427 |  |
1428 | 447 | writer.write("<!--");
1429 | 447 | writer.write(text);
1430 | 447 | writer.write("-->");
1431 |  |
1432 | 447 | 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 | 20077 | 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 | 20077 | for (int i = 0, size = element.attributeCount(); i < size; i++) {
1450 | 4276 | Attribute attribute = element.attribute(i);
1451 | 4276 | Namespace ns = attribute.getNamespace();
1452 |  |
1453 | 4276 | if ((ns != null) && (ns != Namespace.NO_NAMESPACE)
1454 |  | && (ns != Namespace.XML_NAMESPACE)) {
1455 | 14 | String prefix = ns.getPrefix();
1456 | 14 | String uri = namespaceStack.getURI(prefix);
1457 |  |
1458 | 14 | if (!ns.getURI().equals(uri)) {
1459 | 0 | writeNamespace(ns);
1460 | 0 | 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 | 4276 | String attName = attribute.getName();
1468 |  |
1469 | 4276 | if (attName.startsWith("xmlns:")) {
1470 | 3 | String prefix = attName.substring(6);
1471 |  |
1472 | 3 | if (namespaceStack.getNamespaceForPrefix(prefix) == null) {
1473 | 2 | String uri = attribute.getValue();
1474 | 2 | namespaceStack.push(prefix, uri);
1475 | 2 | writeNamespace(prefix, uri);
1476 |  | }
1477 | 4273 | } else if (attName.equals("xmlns")) {
1478 | 1 | if (namespaceStack.getDefaultNamespace() == null) {
1479 | 0 | String uri = attribute.getValue();
1480 | 0 | namespaceStack.push(null, uri);
1481 | 0 | writeNamespace(null, uri);
1482 |  | }
1483 |  | } else {
1484 | 4272 | char quote = format.getAttributeQuoteCharacter();
1485 | 4272 | writer.write(" ");
1486 | 4272 | writer.write(attribute.getQualifiedName());
1487 | 4272 | writer.write("=");
1488 | 4272 | writer.write(quote);
1489 | 4272 | writeEscapeAttributeEntities(attribute.getValue());
1490 | 4272 | writer.write(quote);
1491 |  | }
1492 |  | }
1493 |  | }
1494 |  |
1495 | 0 | protected void writeAttribute(Attribute attribute) throws IOException {
1496 | 0 | writer.write(" ");
1497 | 0 | writer.write(attribute.getQualifiedName());
1498 | 0 | writer.write("=");
1499 |  |
1500 | 0 | char quote = format.getAttributeQuoteCharacter();
1501 | 0 | writer.write(quote);
1502 |  |
1503 | 0 | writeEscapeAttributeEntities(attribute.getValue());
1504 |  |
1505 | 0 | writer.write(quote);
1506 | 0 | lastOutputNodeType = Node.ATTRIBUTE_NODE;
1507 |  | }
1508 |  |
1509 | 8 | protected void writeAttributes(Attributes attributes) throws IOException {
1510 | 8 | for (int i = 0, size = attributes.getLength(); i < size; i++) {
1511 | 5 | writeAttribute(attributes, i);
1512 |  | }
1513 |  | }
1514 |  |
1515 | 5 | protected void writeAttribute(Attributes attributes, int index)
1516 |  | throws IOException {
1517 | 5 | char quote = format.getAttributeQuoteCharacter();
1518 | 5 | writer.write(" ");
1519 | 5 | writer.write(attributes.getQName(index));
1520 | 5 | writer.write("=");
1521 | 5 | writer.write(quote);
1522 | 5 | writeEscapeAttributeEntities(attributes.getValue(index));
1523 | 5 | writer.write(quote);
1524 |  | }
1525 |  |
1526 | 24916 | protected void indent() throws IOException {
1527 | 24916 | String indent = format.getIndent();
1528 |  |
1529 | 24916 | if ((indent != null) && (indent.length() > 0)) {
1530 | 151 | for (int i = 0; i < indentLevel; i++) {
1531 | 172 | 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 | 25027 | protected void writePrintln() throws IOException {
1545 | 25027 | if (format.isNewlines()) {
1546 | 179 | String seperator = format.getLineSeparator();
1547 | 179 | if (lastChar != seperator.charAt(seperator.length() - 1)) {
1548 | 176 | 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 | 25 | protected Writer createWriter(OutputStream outStream, String encoding)
1567 |  | throws UnsupportedEncodingException {
1568 | 25 | 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 | 114 | protected void writeDeclaration() throws IOException {
1581 | 114 | String encoding = format.getEncoding();
1582 |  |
1583 |  | // Only print of declaration is not suppressed
1584 | 114 | if (!format.isSuppressDeclaration()) {
1585 |  | // Assume 1.0 version
1586 | 110 | if (encoding.equals("UTF8")) {
1587 | 0 | writer.write("<?xml version=\"1.0\"");
1588 |  |
1589 | 0 | if (!format.isOmitEncoding()) {
1590 | 0 | writer.write(" encoding=\"UTF-8\"");
1591 |  | }
1592 |  |
1593 | 0 | writer.write("?>");
1594 |  | } else {
1595 | 110 | writer.write("<?xml version=\"1.0\"");
1596 |  |
1597 | 110 | if (!format.isOmitEncoding()) {
1598 | 110 | writer.write(" encoding=\"" + encoding + "\"");
1599 |  | }
1600 |  |
1601 | 110 | writer.write("?>");
1602 |  | }
1603 |  |
1604 | 110 | if (format.isNewLineAfterDeclaration()) {
1605 | 110 | println();
1606 |  | }
1607 |  | }
1608 |  | }
1609 |  |
1610 | 8 | protected void writeClose(String qualifiedName) throws IOException {
1611 | 8 | writer.write("</");
1612 | 8 | writer.write(qualifiedName);
1613 | 8 | writer.write(">");
1614 |  | }
1615 |  |
1616 | 299 | protected void writeEmptyElementClose(String qualifiedName)
1617 |  | throws IOException {
1618 |  | // Simply close up
1619 | 299 | if (!format.isExpandEmptyElements()) {
1620 | 295 | writer.write("/>");
1621 |  | } else {
1622 | 4 | writer.write("></");
1623 | 4 | writer.write(qualifiedName);
1624 | 4 | writer.write(">");
1625 |  | }
1626 |  | }
1627 |  |
1628 | 0 | protected boolean isExpandEmptyElements() {
1629 | 0 | 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 | 37708 | protected String escapeElementEntities(String text) {
1643 | 37708 | char[] block = null;
1644 | 37708 | int i;
1645 | 37708 | int last = 0;
1646 | 37708 | int size = text.length();
1647 |  |
1648 | 37708 | for (i = 0; i < size; i++) {
1649 | 467325 | String entity = null;
1650 | 467325 | char c = text.charAt(i);
1651 |  |
1652 | 467325 | switch (c) {
1653 | 93 | case '<':
1654 | 93 | entity = "&lt;";
1655 |  |
1656 | 93 | break;
1657 |  |
1658 | 84 | case '>':
1659 | 84 | entity = "&gt;";
1660 |  |
1661 | 84 | break;
1662 |  |
1663 | 63 | case '&':
1664 | 63 | entity = "&amp;";
1665 |  |
1666 | 63 | break;
1667 |  |
1668 | 3455 | case '\t':
1669 | 27550 | case '\n':
1670 | 0 | case '\r':
1671 |  |
1672 |  | // don't encode standard whitespace characters
1673 | 31005 | if (preserve) {
1674 | 2 | entity = String.valueOf(c);
1675 |  | }
1676 |  |
1677 | 31005 | break;
1678 |  |
1679 | 436080 | default:
1680 |  |
1681 | 436080 | if ((c < 32) || shouldEncodeChar(c)) {
1682 | 1 | entity = "&#" + (int) c + ";";
1683 |  | }
1684 |  |
1685 | 436080 | break;
1686 |  | }
1687 |  |
1688 | 467325 | if (entity != null) {
1689 | 243 | if (block == null) {
1690 | 145 | block = text.toCharArray();
1691 |  | }
1692 |  |
1693 | 243 | buffer.append(block, last, i - last);
1694 | 243 | buffer.append(entity);
1695 | 243 | last = i + 1;
1696 |  | }
1697 |  | }
1698 |  |
1699 | 37708 | if (last == 0) {
1700 | 37563 | return text;
1701 |  | }
1702 |  |
1703 | 145 | if (last < size) {
1704 | 93 | if (block == null) {
1705 | 0 | block = text.toCharArray();
1706 |  | }
1707 |  |
1708 | 93 | buffer.append(block, last, i - last);
1709 |  | }
1710 |  |
1711 | 145 | String answer = buffer.toString();
1712 | 145 | buffer.setLength(0);
1713 |  |
1714 | 145 | return answer;
1715 |  | }
1716 |  |
1717 | 4277 | protected void writeEscapeAttributeEntities(String txt) throws IOException {
1718 | 4277 | if (txt != null) {
1719 | 4277 | String escapedText = escapeAttributeEntities(txt);
1720 | 4277 | 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 | 4277 | protected String escapeAttributeEntities(String text) {
1735 | 4277 | char quote = format.getAttributeQuoteCharacter();
1736 |  |
1737 | 4277 | char[] block = null;
1738 | 4277 | int i;
1739 | 4277 | int last = 0;
1740 | 4277 | int size = text.length();
1741 |  |
1742 | 4277 | for (i = 0; i < size; i++) {
1743 | 48016 | String entity = null;
1744 | 48016 | char c = text.charAt(i);
1745 |  |
1746 | 48016 | switch (c) {
1747 | 0 | case '<':
1748 | 0 | entity = "&lt;";
1749 |  |
1750 | 0 | break;
1751 |  |
1752 | 0 | case '>':
1753 | 0 | entity = "&gt;";
1754 |  |
1755 | 0 | break;
1756 |  |
1757 | 1 | case '\'':
1758 |  |
1759 | 1 | if (quote == '\'') {
1760 | 0 | entity = "&apos;";
1761 |  | }
1762 |  |
1763 | 1 | break;
1764 |  |
1765 | 0 | case '\"':
1766 |  |
1767 | 0 | if (quote == '\"') {
1768 | 0 | entity = "&quot;";
1769 |  | }
1770 |  |
1771 | 0 | break;
1772 |  |
1773 | 0 | case '&':
1774 | 0 | entity = "&amp;";
1775 |  |
1776 | 0 | break;
1777 |  |
1778 | 0 | case '\t':
1779 | 0 | case '\n':
1780 | 0 | case '\r':
1781 |  |
1782 |  | // don't encode standard whitespace characters
1783 | 0 | break;
1784 |  |
1785 | 48015 | default:
1786 |  |
1787 | 48015 | if ((c < 32) || shouldEncodeChar(c)) {
1788 | 0 | entity = "&#" + (int) c + ";";
1789 |  | }
1790 |  |
1791 | 48015 | break;
1792 |  | }
1793 |  |
1794 | 48016 | if (entity != null) {
1795 | 0 | if (block == null) {
1796 | 0 | block = text.toCharArray();
1797 |  | }
1798 |  |
1799 | 0 | buffer.append(block, last, i - last);
1800 | 0 | buffer.append(entity);
1801 | 0 | last = i + 1;
1802 |  | }
1803 |  | }
1804 |  |
1805 | 4277 | if (last == 0) {
1806 | 4277 | return text;
1807 |  | }
1808 |  |
1809 | 0 | if (last < size) {
1810 | 0 | if (block == null) {
1811 | 0 | block = text.toCharArray();
1812 |  | }
1813 |  |
1814 | 0 | buffer.append(block, last, i - last);
1815 |  | }
1816 |  |
1817 | 0 | String answer = buffer.toString();
1818 | 0 | buffer.setLength(0);
1819 |  |
1820 | 0 | 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 | 484095 | protected boolean shouldEncodeChar(char c) {
1833 | 484095 | int max = getMaximumAllowedCharacter();
1834 |  |
1835 | 484095 | 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 | 2147 | protected int defaultMaximumAllowedCharacter() {
1846 | 2147 | String encoding = format.getEncoding();
1847 |  |
1848 | 2147 | if (encoding != null) {
1849 | 2147 | if (encoding.equals("US-ASCII")) {
1850 | 0 | return 127;
1851 |  | }
1852 |  | }
1853 |  |
1854 |  | // no encoding for things like ISO-*, UTF-8 or UTF-16
1855 | 2147 | return -1;
1856 |  | }
1857 |  |
1858 | 20275 | protected boolean isNamespaceDeclaration(Namespace ns) {
1859 | 20275 | if ((ns != null) && (ns != Namespace.XML_NAMESPACE)) {
1860 | 20275 | String uri = ns.getURI();
1861 |  |
1862 | 20275 | if (uri != null) {
1863 | 20275 | if (!namespaceStack.contains(ns)) {
1864 | 618 | return true;
1865 |  | }
1866 |  | }
1867 |  | }
1868 |  |
1869 | 19657 | return false;
1870 |  | }
1871 |  |
1872 | 0 | protected void handleException(IOException e) throws SAXException {
1873 | 0 | 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 | 7 | protected OutputFormat getOutputFormat() {
1887 | 7 | return format;
1888 |  | }
1889 |  |
1890 | 0 | public boolean resolveEntityRefs() {
1891 | 0 | return resolveEntityRefs;
1892 |  | }
1893 |  |
1894 | 0 | public void setResolveEntityRefs(boolean resolve) {
1895 | 0 | 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 |  | */