Fixed up the DocBuffer a bit and added a basic XML writer.
[jsvc.git] / src / dolda / jsvc / next / XmlWriter.java
1 package dolda.jsvc.next;
2
3 import java.io.*;
4 import java.util.*;
5 import org.w3c.dom.*;
6 import dolda.jsvc.util.Misc;
7
8 public 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))
145             out.indent(indent);
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     }
204     
205     public static void main(String[] args) throws Exception {
206         Html barda = Html.xhtml11("Barda");
207         barda.addcss("/slen.css", "Test");
208         barda.insert("body", barda.el("h1", barda.text("Mast")));
209         barda.finalise();
210         XmlWriter w = new XmlWriter(barda.doc);
211         w.setnsname(Html.ns, null);
212         w.write(System.out);
213         System.out.flush();
214     }
215 }