1
// XMLWriter.java - serialize an XML document.
2
// Written by David Megginson, david@megginson.com
3
// NO WARRANTY! This class is in the public domain.
8
import java.io.IOException;
9
import java.io.OutputStreamWriter;
10
import java.io.Writer;
11
import java.util.Enumeration;
12
import java.util.Hashtable;
14
import org.xml.sax.Attributes;
15
import org.xml.sax.SAXException;
16
import org.xml.sax.ext.LexicalHandler;
17
import org.xml.sax.helpers.AttributesImpl;
18
import org.xml.sax.helpers.NamespaceSupport;
19
import org.xml.sax.helpers.XMLFilterImpl;
24
* The original version of this class was written and placed in the
25
* public domain by David Megginson. Elliotte Rusty Harold added
26
* <code>LexicalHandler</code> support. It is included here purely
27
* for help with testing the <code>SAXConverter</code> class. It is
28
* not part of the XOM API; nor is it used internally by XOM anywhere
29
* except in the <code>SAXConverter</code> tests.
33
* This class does not properly preserve additional namespace
34
* declarations in non-root elements. If you encounter that, the
35
* bug is here, not in <code>SAXConverter</code>.
38
* @author David Megginson, Elliotte Rusty Harold
41
class XMLWriter extends XMLFilterImpl implements LexicalHandler {
43
///////////////////////////////////////////////////////////////////
45
///////////////////////////////////////////////////////////////////
50
* Create a new XML writer.
53
* <p>Write to standard output.</p>
62
* Create a new XML writer.
65
* <p>Write to the writer provided.</p>
67
* @param writer the output destination, or null to use standard
70
public XMLWriter (Writer writer) {
76
* Internal initialization method.
79
* <p>All of the public constructors invoke this method.
81
* @param writer the output destination, or null to use
84
private void init (Writer writer) {
86
nsSupport = new NamespaceSupport();
87
prefixTable = new Hashtable();
88
forcedDeclTable = new Hashtable();
89
doneDeclTable = new Hashtable();
93
///////////////////////////////////////////////////////////////////
95
///////////////////////////////////////////////////////////////////
103
* <p>This method is especially useful if the writer throws an
104
* exception before it is finished, and you want to reuse the
105
* writer for a new document. It is usually a good idea to
106
* invoke {@link #flush flush} before resetting the writer,
107
* to make sure that no output is lost.</p>
109
* <p>This method is invoked automatically by the
110
* {@link #startDocument startDocument} method before writing
111
* a new document.</p>
113
* <p><strong>Note:</strong> this method will <em>not</em>
114
* clear the prefix or URI information in the writer or
115
* the selected output writer.</p>
119
public void reset() {
131
* <p>This method flushes the output stream. It is especially useful
132
* when you need to make certain that the entire document has
133
* been written to output but do not want to close the output
136
* <p>This method is invoked automatically by the
137
* {@link #endDocument endDocument} method after writing a
140
* @throws IOException
145
public void flush() throws IOException {
152
* Set a new output destination for the document.
155
* @param writer the output destination, or null to use
158
* @return the current output writer
162
public void setOutput (Writer writer) {
163
if (writer == null) {
164
output = new OutputStreamWriter(System.out);
174
* Specify a preferred prefix for a namespace URI.
177
* <p>Note that this method does not actually force the namespace
178
* to be declared; to do that, use the {@link
179
* #forceNSDecl(java.lang.String) forceNSDecl} method as well.</p>
181
* @param uri the namespace URI
182
* @param prefix the preferred prefix, or "" to select
183
* the default namespace
186
* @see #forceNSDecl(java.lang.String)
187
* @see #forceNSDecl(java.lang.String,java.lang.String)
189
public void setPrefix (String uri, String prefix) {
190
prefixTable.put(uri, prefix);
196
* Get the current or preferred prefix for a namespace URI.
199
* @param uri the namespace URI
201
* @return the preferred prefix, or "" for the default namespace
205
public String getPrefix (String uri) {
206
return (String)prefixTable.get(uri);
209
public void startPrefixMapping(String prefix, String uri) {
210
this.forceNSDecl(uri, prefix);
215
* Force a namespace to be declared on the root element.
218
* <p>By default, the XMLWriter will declare only the namespaces
219
* needed for an element; as a result, a namespace may be
220
* declared many places in a document if it is not used on the
223
* <p>This method forces a namespace to be declared on the root
224
* element even if it is not used there, and reduces the number
225
* of xmlns attributes in the document.</p>
227
* @param uri the namespace URI to declare
229
* @see #forceNSDecl(java.lang.String,java.lang.String)
232
public void forceNSDecl (String uri) {
233
forcedDeclTable.put(uri, Boolean.TRUE);
239
* Force a namespace declaration with a preferred prefix.
242
* <p>This is a convenience method that invokes {@link
243
* #setPrefix setPrefix} then {@link #forceNSDecl(java.lang.String)
246
* @param uri the namespace URI to declare on the root element
247
* @param prefix the preferred prefix for the namespace, or ""
248
* for the default namespace
251
* @see #forceNSDecl(java.lang.String)
253
public void forceNSDecl (String uri, String prefix) {
254
setPrefix(uri, prefix);
259
///////////////////////////////////////////////////////////////////
260
// Methods from org.xml.sax.ContentHandler.
261
///////////////////////////////////////////////////////////////////
266
* Write the XML declaration at the beginning of the document.
270
* Pass the event on down the filter chain for further processing.
273
* @throws org.xml.sax.SAXException if there is an error
274
* writing the XML declaration, or if a handler further
275
* down the filter chain raises an exception
277
* @see org.xml.sax.ContentHandler#startDocument
279
public void startDocument() throws SAXException {
281
write("<?xml version=\"1.0\" standalone=\"yes\"?>\n\n");
282
super.startDocument();
288
* Write a newline at the end of the document.
292
* Pass the event on down the filter chain for further processing.
295
* @throws org.xml.sax.SAXException if there is an error
296
* writing the newline, or if a handler further down
297
* the filter chain raises an exception
299
* @see org.xml.sax.ContentHandler#endDocument
301
public void endDocument() throws SAXException {
306
} catch (IOException ex) {
307
throw new SAXException(ex);
318
* Pass the event on down the filter chain for further processing.
321
* @param uri the namespace URI, or the empty string if none
323
* @param localName the element's local (unprefixed) name (required)
324
* @param qualifiedName the element's qualified (prefixed) name,
325
* or the empty string is none is available. This method
326
* will use the qualified name as a template for generating
327
* a prefix if necessary, but it is not guaranteed to use
328
* the same qualified name.
329
* @param atts the element's attribute list (must not be null)
331
* @throws org.xml.sax.SAXException if there is an error
332
* writing the start-tag, or if a handler further down
333
* the filter chain raises an exception
335
* @see org.xml.sax.ContentHandler#startElement
337
public void startElement (String uri, String localName,
338
String qualifiedName, Attributes atts) throws SAXException {
340
nsSupport.pushContext();
342
writeName(uri, localName, qualifiedName, true);
343
writeAttributes(atts);
344
if (elementLevel == 1) {
349
super.startElement(uri, localName, qualifiedName, atts);
359
* Pass the event on down the filter chain for further processing.
362
* @param uri the namespace URI, or the empty string if none
364
* @param localName the element's local (unprefixed) name (required)
365
* @param qualifiedName the element's qualified (prefixed) name, or the
366
* empty string is none is available. This method will
367
* use the qName as a template for generating a prefix
368
* if necessary, but it is not guaranteed to use the
369
* same qualified name.
371
* @throws org.xml.sax.SAXException if there is an error
372
* writing the end-tag, or if a handler further down
373
* the filter chain raises an exception
375
* @see org.xml.sax.ContentHandler#endElement
377
public void endElement (String uri, String localName, String qualifiedName)
378
throws SAXException {
380
writeName(uri, localName, qualifiedName, true);
382
if (elementLevel == 1) {
385
super.endElement(uri, localName, qualifiedName);
386
nsSupport.popContext();
393
* Write character data.
397
* Pass the event on down the filter chain for further processing.
400
* @param ch the array of characters to write
401
* @param start the starting position in the array
402
* @param length the number of characters to write
404
* @throws org.xml.sax.SAXException if there is an error
405
* writing the characters, or if a handler further down
406
* the filter chain raises an exception
408
* @see org.xml.sax.ContentHandler#characters
410
public void characters (char[] ch, int start, int length)
411
throws SAXException {
412
writeEsc(ch, start, length, false);
413
super.characters(ch, start, length);
419
* Write ignorable whitespace.
423
* Pass the event on down the filter chain for further processing.
426
* @param ch the array of characters to write
427
* @param start the starting position in the array
428
* @param length the number of characters to write
430
* @throws org.xml.sax.SAXException if there is an error
431
* writing the whitespace, or if a handler further down
432
* the filter chain raises an exception
434
* @see org.xml.sax.ContentHandler#ignorableWhitespace
436
public void ignorableWhitespace (char[] ch, int start, int length)
437
throws SAXException {
438
writeEsc(ch, start, length, false);
439
super.ignorableWhitespace(ch, start, length);
446
* Write a processing instruction.
450
* Pass the event on down the filter chain for further processing.
453
* @param target the processing instruction target
454
* @param data the processing instruction data
456
* @throws org.xml.sax.SAXException if there is an error
457
* writing the PI, or if a handler further down
458
* the filter chain raises an exception
460
* @see org.xml.sax.ContentHandler#processingInstruction
462
public void processingInstruction (String target, String data)
463
throws SAXException {
469
if (elementLevel < 1) {
472
super.processingInstruction(target, data);
477
///////////////////////////////////////////////////////////////////
478
// Additional markup.
479
///////////////////////////////////////////////////////////////////
483
* Write an empty element.
487
* This method writes an empty-element tag rather than a start-tag
488
* followed by an end-tag. Both a {@link #startElement
489
* startElement} and an {@link #endElement endElement} event will
490
* be passed on down the filter chain.
493
* @param uri the element's namespace URI, or the empty string
494
* if the element has no namespace or if namespace
495
* processing is not being performed
496
* @param localName the element's local name (without prefix). This
497
* parameter must be provided.
498
* @param qualifiedName the element's qualified name (with prefix), or
499
* the empty string if none is available. This parameter
500
* is strictly advisory: the writer may or may not use
501
* the prefix attached.
502
* @param atts the element's attribute list
504
* @throws org.xml.sax.SAXException if there is an error
505
* writing the empty tag, or if a handler further down
506
* the filter chain raises an exception
511
public void emptyElement (String uri, String localName,
512
String qualifiedName, Attributes atts) throws SAXException {
513
nsSupport.pushContext();
515
writeName(uri, localName, qualifiedName, true);
516
writeAttributes(atts);
517
if (elementLevel == 1) {
522
super.startElement(uri, localName, qualifiedName, atts);
523
super.endElement(uri, localName, qualifiedName);
528
///////////////////////////////////////////////////////////////////
529
// Convenience methods.
530
///////////////////////////////////////////////////////////////////
536
* Start a new element without a qualified name or attributes.
539
* <p>This method will provide a default empty attribute
540
* list and an empty string for the qualified name.
542
* #startElement(String, String, String, Attributes)}
545
* @param uri the element's namespace URI
546
* @param localName the element's local name
548
* @throws org.xml.sax.SAXException if there is an error
549
* writing the start-tag, or if a handler further down
550
* the filter chain raises an exception
552
* @see #startElement(String, String, String, Attributes)
554
public void startElement (String uri, String localName)
555
throws SAXException {
556
startElement(uri, localName, "", EMPTY_ATTS);
562
* Start a new element without a qualified name,
563
* attributes or a namespace URI.</p>
565
* <p>This method will provide an empty string for the
566
* namespace URI, and empty string for the qualified name,
567
* and a default empty attribute list. It invokes
568
* #startElement(String, String, String, Attributes)}
571
* @param localName the element's local name
573
* @throws org.xml.sax.SAXException if there is an error
574
* writing the start-tag, or if a handler further down
575
* the filter chain raises an exception
577
* @see #startElement(String, String, String, Attributes)
579
public void startElement (String localName) throws SAXException {
580
startElement("", localName, "", EMPTY_ATTS);
586
* End an element without a qualfied name.
589
* <p>This method will supply an empty string for the qName.
590
* It invokes {@link #endElement(String, String, String)}
593
* @param uri the element's namespace URI
594
* @param localName the element's local name
596
* @throws org.xml.sax.SAXException if there is an error
597
* writing the end-tag, or if a handler further down
598
* the filter chain raises an exception
600
* @see #endElement(String, String, String)
602
public void endElement(String uri, String localName)
603
throws SAXException {
604
endElement(uri, localName, "");
610
* End an element without a namespace URI or qualfiied name.
613
* <p>This method will supply an empty string for the qName
614
* and an empty string for the namespace URI.
615
* It invokes {@link #endElement(String, String, String)}
618
* @param localName the element's local name`
620
* @throws org.xml.sax.SAXException if there is an error
621
* writing the end-tag, or if a handler further down
622
* the filter chain raises an exception
624
* @see #endElement(String, String, String)
626
public void endElement(String localName) throws SAXException {
627
endElement("", localName, "");
633
* Add an empty element without a qualified name or attributes.
636
* <p>This method will supply an empty string for the qualified name
637
* and an empty attribute list. It invokes
638
* {@link #emptyElement(String, String, String, Attributes)}
641
* @param uri the element's namespace URI
642
* @param localName the element's local name
644
* @throws org.xml.sax.SAXException if there is an error
645
* writing the empty tag, or if a handler further down
646
* the filter chain raises an exception
648
* @see #emptyElement(String, String, String, Attributes)
650
public void emptyElement (String uri, String localName)
651
throws SAXException {
652
emptyElement(uri, localName, "", EMPTY_ATTS);
658
* Add an empty element without a namespace URI, qualified
659
* name or attributes.
662
* <p>This method will supply an empty string for the qualified
663
* name, and empty string for the namespace URI, and an empty
664
* attribute list. It invokes
665
* {@link #emptyElement(String, String, String, Attributes)}
668
* @param localName the element's local name
670
* @throws org.xml.sax.SAXException if there is an error
671
* writing the empty tag, or if a handler further down
672
* the filter chain raises an exception
674
* @see #emptyElement(String, String, String, Attributes)
676
public void emptyElement (String localName) throws SAXException {
677
emptyElement("", localName, "", EMPTY_ATTS);
683
* Write an element with character data content.
686
* <p>This is a convenience method to write a complete element
687
* with character data content, including the start-tag
690
* <p>This method invokes
691
* {@link #startElement(String, String, String, Attributes)},
693
* {@link #characters(String)}, followed by
694
* {@link #endElement(String, String, String)}.</p>
696
* @param uri the element's namespace URI
697
* @param localName the element's local name
698
* @param qualifiedName the element's default qualified name
699
* @param atts the element's attributes
700
* @param content the character data content
702
* @throws org.xml.sax.SAXException if there is an error
703
* writing the empty tag, or if a handler further down
704
* the filter chain raises an exception
706
* @see #startElement(String, String, String, Attributes)
707
* @see #characters(String)
708
* @see #endElement(String, String, String)
710
public void dataElement (String uri, String localName,
711
String qualifiedName, Attributes atts, String content)
712
throws SAXException {
713
startElement(uri, localName, qualifiedName, atts);
715
endElement(uri, localName, qualifiedName);
721
* Write an element with character data content but no attributes.
724
* <p>This is a convenience method to write a complete element
725
* with character data content, including the start-tag
726
* and end-tag. This method provides an empty string
727
* for the qualified name and an empty attribute list.</p>
729
* <p>This method invokes
730
* {@link #startElement(String, String, String, Attributes)},
732
* {@link #characters(String)}, followed by
733
* {@link #endElement(String, String, String)}.</p>
735
* @param uri the element's namespace URI
736
* @param localName the element's local name
737
* @param content the character data content
739
* @throws org.xml.sax.SAXException if there is an error
740
* writing the empty tag, or if a handler further down
741
* the filter chain raises an exception
743
* @see #startElement(String, String, String, Attributes)
744
* @see #characters(String)
745
* @see #endElement(String, String, String)
747
public void dataElement(String uri, String localName, String content)
748
throws SAXException {
749
dataElement(uri, localName, "", EMPTY_ATTS, content);
755
* Write an element with character data content but no attributes
759
* <p>This is a convenience method to write a complete element
760
* with character data content, including the start-tag
761
* and end-tag. The method provides an empty string for the
762
* namespace URI, and empty string for the qualified name,
763
* and an empty attribute list.</p>
765
* <p>This method invokes
766
* {@link #startElement(String, String, String, Attributes)},
768
* {@link #characters(String)}, followed by
769
* {@link #endElement(String, String, String)}.</p>
771
* @param localName the element's local name
772
* @param content the character data content
774
* @throws org.xml.sax.SAXException if there is an error
775
* writing the empty tag, or if a handler further down
776
* the filter chain raises an exception
778
* @see #startElement(String, String, String, Attributes)
779
* @see #characters(String)
780
* @see #endElement(String, String, String)
782
public void dataElement (String localName, String content)
783
throws SAXException {
784
dataElement("", localName, "", EMPTY_ATTS, content);
789
* Write a string of character data, with XML escaping.
792
* <p>This is a convenience method that takes an XML
793
* String, converts it to a character array, then invokes
794
* {@link #characters(char[], int, int)}.</p>
796
* @param data the character data
797
* @throws org.xml.sax.SAXException if there is an error
798
* writing the string, or if a handler further down
799
* the filter chain raises an exception
800
* @see #characters(char[], int, int)
802
public void characters(String data) throws SAXException {
803
char[] ch = data.toCharArray();
804
characters(ch, 0, ch.length);
809
///////////////////////////////////////////////////////////////////
811
///////////////////////////////////////////////////////////////////
816
* Force all namespaces to be declared.
820
* This method is used on the root element to ensure that
821
* the predeclared namespaces all appear.
824
private void forceNSDecls() {
825
Enumeration prefixes = forcedDeclTable.keys();
826
while (prefixes.hasMoreElements()) {
827
String prefix = (String)prefixes.nextElement();
828
doPrefix(prefix, null, true);
835
* Determine the prefix for an element or attribute name.
838
* TODO: this method probably needs some cleanup.
840
* @param uri the namespace URI
841
* @param qName the qualified name (optional); this will be used
842
* to indicate the preferred prefix if none is currently
844
* @param isElement true if this is an element name, false
845
* if it is an attribute name (which cannot use the
846
* default namespace).
848
private String doPrefix (String uri, String qName, boolean isElement) {
849
String defaultNS = nsSupport.getURI("");
850
if ("".equals(uri)) {
851
if (isElement && defaultNS != null)
852
nsSupport.declarePrefix("", "");
856
if (isElement && defaultNS != null && uri.equals(defaultNS)) {
859
prefix = nsSupport.getPrefix(uri);
861
if (prefix != null) {
864
prefix = (String) doneDeclTable.get(uri);
865
if (prefix != null &&
866
((!isElement || defaultNS != null) &&
867
"".equals(prefix) || nsSupport.getURI(prefix) != null)) {
870
if (prefix == null) {
871
prefix = (String) prefixTable.get(uri);
872
if (prefix != null &&
873
((!isElement || defaultNS != null) &&
874
"".equals(prefix) || nsSupport.getURI(prefix) != null)) {
878
if (prefix == null && qName != null && !"".equals(qName)) {
879
int i = qName.indexOf(':');
881
if (isElement && defaultNS == null) {
885
prefix = qName.substring(0, i);
889
prefix == null || nsSupport.getURI(prefix) != null;
890
prefix = "__NS" + ++prefixCounter)
892
nsSupport.declarePrefix(prefix, uri);
893
doneDeclTable.put(uri, prefix);
900
* Write a raw character.
903
* @param c the character to write
905
* @throws org.xml.sax.SAXException if there is an error writing
906
* the character, this method will throw an IOException
907
* wrapped in a SAXException
909
private void write(char c) throws SAXException {
913
catch (IOException ex) {
914
throw new SAXException(ex);
921
* Write a raw string.
926
* @throws org.xml.sax.SAXException if there is an error writing
927
* the string, this method will throw an IOException
928
* wrapped in a SAXException
930
private void write (String s) throws SAXException {
934
catch (IOException e) {
935
throw new SAXException(e);
942
* Write out an attribute list, escaping values.
946
* The names will have prefixes added to them.
949
* @param atts the attribute list to write
951
* @throws org.xml.SAXException if there is an error writing
952
* the attribute list, this method will throw an
953
* IOException wrapped in a SAXException
955
private void writeAttributes (Attributes atts)
956
throws SAXException {
957
int len = atts.getLength();
958
for (int i = 0; i < len; i++) {
959
char[] ch = atts.getValue(i).toCharArray();
961
writeName(atts.getURI(i), atts.getLocalName(i),
962
atts.getQName(i), false);
964
writeEsc(ch, 0, ch.length, true);
972
* Write an array of data characters with escaping.
975
* @param ch the array of characters
976
* @param start the starting position
977
* @param length the number of characters to use
978
* @param isAttVal true if this is an attribute value literal
980
* @throws org.xml.SAXException if there is an error writing
981
* the characters, this method will throw an
982
* IOException wrapped in a SAXException
984
private void writeEsc (char[] ch, int start, int length, boolean isAttVal)
985
throws SAXException {
986
for (int i = start; i < start + length; i++) {
1006
if (ch[i] > '\u007f') {
1008
write(Integer.toString(ch[i]));
1020
* Write an array of data characters without escaping.
1023
* @param ch the array of characters
1024
* @param start the starting position
1025
* @param length the number of characters to use
1027
* @throws org.xml.SAXException if there is an error writing
1028
* the characters, this method will throw an
1029
* IOException wrapped in a SAXException.
1031
private void write(char[] ch, int start, int length)
1032
throws SAXException {
1035
output.write(ch, start, length);
1037
catch (IOException e) {
1038
throw new SAXException(e);
1046
* Write out the list of namespace declarations.
1049
* @throws org.xml.sax.SAXException This method will throw
1050
* an IOException wrapped in a SAXException if
1051
* there is an error writing the namespace
1054
private void writeNSDecls() throws SAXException {
1055
Enumeration prefixes = nsSupport.getDeclaredPrefixes();
1056
while (prefixes.hasMoreElements()) {
1057
String prefix = (String) prefixes.nextElement();
1058
String uri = nsSupport.getURI(prefix);
1062
char[] ch = uri.toCharArray();
1064
if ("".equals(prefix)) {
1072
writeEsc(ch, 0, ch.length, true);
1080
* Write an element or attribute name.
1083
* @param uri the namespace URI
1084
* @param localName the local name
1085
* @param qualifiedName the prefixed name, if available,
1086
* or the empty string.
1087
* @param isElement true if this is an element name, false if it
1088
* is an attribute name
1090
* @throws org.xml.sax.SAXException this method will throw an
1091
* IOException wrapped in a SAXException if there is
1092
* an error writing the name
1094
private void writeName (String uri, String localName,
1095
String qualifiedName, boolean isElement)
1096
throws SAXException {
1097
String prefix = doPrefix(uri, qualifiedName, isElement);
1098
if (prefix != null && !"".equals(prefix)) {
1107
////////////////////////////////////////////////////////////////////
1109
////////////////////////////////////////////////////////////////////
1111
private final Attributes EMPTY_ATTS = new AttributesImpl();
1114
////////////////////////////////////////////////////////////////////
1116
////////////////////////////////////////////////////////////////////
1118
private Hashtable prefixTable;
1119
private Hashtable forcedDeclTable;
1120
private Hashtable doneDeclTable;
1121
private int elementLevel = 0;
1122
private Writer output;
1123
private NamespaceSupport nsSupport;
1124
private int prefixCounter = 0;
1127
///////////////////////////////////////////////////////////////////
1128
// LexicalHandler methods.
1129
///////////////////////////////////////////////////////////////////
1132
public void endCDATA() {}
1134
public void endDTD() throws SAXException {
1138
public void startCDATA() {}
1140
public void comment(char[] ch, int start, int length)
1141
throws SAXException {
1143
write(ch, start, length);
1145
if (elementLevel < 1) {
1150
public void endEntity(String name) {}
1151
public void startEntity(String name) {}
1153
public void startDTD(String name, String publicID, String systemID)
1154
throws SAXException {
1155
write("<!DOCTYPE ");
1157
if (systemID != null) {
1158
if (publicID != null) {
1159
write(" PUBLIC \"");
1166
write(" SYSTEM \"");
1176
// end of XMLWriter.java