Added a basic indenting XML writer.
[jsvc.git] / src / dolda / jsvc / next / XmlWriter.java
CommitLineData
6e40b32e
FT
1package dolda.jsvc.next;
2
3import java.io.*;
4import java.util.*;
5import org.w3c.dom.*;
6import dolda.jsvc.util.Misc;
7
8public class XmlWriter {
9 private Map<String, String> nsnames = new HashMap<String, String>();
10 private Document doc;
11 private int nsser = 1;
12
13 public XmlWriter(Document doc) {
14 this.doc = doc;
15 }
16
17 public void setnsname(String uri, String name) {
18 nsnames.put(uri, name);
19 }
20
21 private String nsname(String uri) {
22 String ret;
23 if(nsnames.containsKey(uri))
24 return(nsnames.get(uri));
25 do {
26 ret = "n" + (nsser++);
27 } while(nsnames.containsValue(ret));
28 nsnames.put(uri, ret);
29 return(ret);
30 }
31
32 protected void findallnsnames() {
33 Node n = doc;
34 while(true) {
35 String ns = n.getNamespaceURI();
36 if(ns != null)
37 nsname(ns);
38 if(n.getFirstChild() != null) {
39 n = n.getFirstChild();
40 } else if(n.getNextSibling() != null) {
41 n = n.getNextSibling();
42 } else {
43 for(n = n.getParentNode(); n != null; n = n.getParentNode()) {
44 if(n.getNextSibling() != null) {
45 n = n.getNextSibling();
46 break;
47 }
48 }
49 if(n == null)
50 break;
51 }
52 }
53 }
54
55 protected boolean prebreak(ColumnWriter out, Element el) {
56 return(false);
57 }
58
59 protected int indent(ColumnWriter out, Element el) {
60 return(-1);
61 }
62
63 protected boolean postbreak(ColumnWriter out, Element el) {
64 return(false);
65 }
66
67 protected boolean asempty(ColumnWriter out, Element el) {
68 return(true);
69 }
70
71 protected void attribute(ColumnWriter out, String nm, String val, int indent) throws IOException {
72 char qt = '\"';
73 if((val.indexOf("\"") >= 0) && (val.indexOf('\'') < 0))
74 qt = '\'';
75 out.write(" " + nm + "=" + qt);
76 for(int i = 0; i < val.length(); i++) {
77 char c = val.charAt(i);
78 if(c == '<')
79 out.write("&lt;");
80 else if(c == '>')
81 out.write("&gt;");
82 else if(c == '&')
83 out.write("&amp;");
84 else if(c == qt)
85 out.write((c == '\'')?"&apos;":"&quot;");
86 else
87 out.write(c);
88 }
89 out.write(qt);
90 }
91
92 protected void attribute(ColumnWriter out, Attr attr, int indent) throws IOException {
93 String ns = attr.getNamespaceURI();
94 if(ns == null)
95 attribute(out, attr.getName(), attr.getValue(), indent);
96 else
97 attribute(out, nsname(ns) + ":" + attr.getName(), attr.getValue(), indent);
98 }
99
100 protected void element(ColumnWriter out, Element el, int indent) throws IOException {
101 if(prebreak(out, el))
102 out.indent(indent);
103
104 String tagname = el.getTagName();
105 String ns = nsname(el.getNamespaceURI());
106 if(ns != null)
107 tagname = ns + ":" + tagname;
108 out.write("<" + tagname);
109 NamedNodeMap attrs = el.getAttributes();
110 int acol = out.col + 1;
111 if(attrs != null) {
112 for(int i = 0; i < attrs.getLength(); i++) {
113 Attr attr = (Attr)attrs.item(i);
114 attribute(out, attr, acol);
115 }
116 }
117 if(el == doc.getDocumentElement()) {
118 for(Map.Entry<String, String> nd : nsnames.entrySet()) {
119 String nm = nd.getValue();
120 if(nm == null)
121 attribute(out, "xmlns", nd.getKey(), acol);
122 else
123 attribute(out, "xmlns:" + nm, nd.getKey(), acol);
124 }
125 }
126
127 if((el.getFirstChild() == null) && asempty(out, el)) {
128 out.write(" />");
129 } else {
130 out.write(">");
131 int inner = indent(out, el);
132 if(inner >= 0) {
133 out.indent(indent + inner);
134 }
135
136 for(Node ch = el.getFirstChild(); ch != null; ch = ch.getNextSibling())
137 node(out, ch, (inner >= 0)?(indent + inner):indent);
138
139 if(inner >= 0)
140 out.indent(indent);
141 out.write("</" + tagname + ">");
142 }
143
144 if(postbreak(out, el))
938bdee1 145 out.write('\n');
6e40b32e
FT
146 }
147
148 protected void text(ColumnWriter out, String s, int indent) throws IOException {
149 out.write(s);
150 }
151
152 protected void text(ColumnWriter out, Text txt, int indent) throws IOException {
153 String s = txt.getData();
154 text(out, s, indent);
155 }
156
157 protected void comment(ColumnWriter out, Comment c, int indent) throws IOException {
158 out.write("<!--");
159 String s = c.getData();
160 text(out, s, indent);
161 out.write("-->");
162 }
163
164 protected void node(ColumnWriter out, Node n, int indent) throws IOException {
165 if(n instanceof Element) {
166 Element el = (Element)n;
167 element(out, el, indent);
168 } else if(n instanceof Text) {
169 Text txt = (Text)n;
170 text(out, txt, indent);
171 } else if(n instanceof Comment) {
172 Comment c = (Comment)n;
173 comment(out, c, indent);
174 } else {
175 throw(new RuntimeException(String.format("Unknown DOM node encountered (%s)", n.getClass())));
176 }
177 }
178
179 public void write(Writer out) throws IOException {
180 findallnsnames();
181 ColumnWriter col = new ColumnWriter(out);
182 DocumentType t = doc.getDoctype();
183 if(t != null)
184 out.write(String.format("<!DOCTYPE %s PUBLIC \"%s\" \"%s\">\n", t.getName(), t.getPublicId(), t.getSystemId()));
185 node(col, doc.getDocumentElement(), 0);
186 }
187
188 public void write(OutputStream out) throws IOException {
189 /* The OutputStreamWriter may need to be flushed to clear any
190 * internal buffers it may have, but it would be a pity to
191 * force-flush the underlying stream just because of that. */
192 class FlushGuard extends FilterOutputStream {
193 FlushGuard(OutputStream out) {
194 super(out);
195 }
196
197 public void flush() {}
198 }
199 Writer w = new OutputStreamWriter(new FlushGuard(out), Misc.utf8);
200 w.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
201 write(w);
202 w.flush();
203 }
6e40b32e 204}