Added basic HTML generation and response handling.
[jrw.git] / src / jrw / sp / Formatter.java
CommitLineData
6e0043cc
FT
1package jrw.sp;
2
3import jrw.util.*;
4import java.util.*;
5
6public class Formatter extends LazyPChannel {
7 private final Element root;
8 private final String header;
9 private final List<Frame> stack = new ArrayList<>();
10 private final Map<Namespace, String> ns = new IdentityHashMap<>();
11 private boolean headed = false;
12
13 class Frame {
14 Element el;
15 Iterator<Map.Entry<Name, String>> ai;
16 Iterator<Node> ci;
17 boolean sh;
18 boolean h, e, t;
19
20 Frame(Element el) {
21 this.el = el;
22 this.ai = el.attribs.entrySet().iterator();
23 this.ci = el.children.iterator();
24 this.sh = shorten(el);
25 }
26 }
27
28 private void countns(Map<Namespace, Integer> freq, Set<Namespace> attrs, Element el) {
29 for(Name anm : el.attribs.keySet()) {
30 if(anm.ns != null) {
31 attrs.add(anm.ns);
32 Integer f = freq.get(anm.ns);
33 freq.put(anm.ns, ((f == null) ? 0 : f) + 1);
34 }
35 }
36 Integer f = freq.get(el.name.ns);
37 freq.put(el.name.ns, ((f == null) ? 0 : f) + 1);
38 for(Node ch : el.children) {
39 if(ch instanceof Element)
40 countns(freq, attrs, (Element)ch);
41 }
42 }
43
44 private void calcnsnames() {
45 Map<Namespace, Integer> freq = new IdentityHashMap<>();
46 Set<Namespace> attrs = new HashSet<>();
47 countns(freq, attrs, root);
48 if(freq.get(null) != null) {
49 ns.put(null, null);
50 freq.remove(null);
51 } else if(!attrs.contains(root.name.ns)) {
52 ns.put(root.name.ns, null);
53 freq.remove(root.name.ns);
54 }
55 List<Namespace> order = new ArrayList<>(freq.keySet());
56 Collection<String> ass = new HashSet<>();
57 ass.add(null);
58 Collections.sort(order, (x, y) -> (freq.get(y) - freq.get(x)));
59 for(Namespace ns : order) {
60 String p = ns.prefabb;
61 if((p != null) && !ass.contains(p)) {
62 this.ns.put(ns, p);
63 ass.add(p);
64 } else {
65 int i;
66 if(p == null) {
67 p = "ns";
68 i = 1;
69 } else {
70 i = 2;
71 }
72 while(ass.contains(p + i))
73 i++;
74 this.ns.put(ns, p + i);
75 ass.add(p + i);
76 }
77 }
78 }
79
80 public Formatter(DocType doctype, Element root) {
81 this.header = "<?xml version=\"1.0\" encoding=\"US-ASCII\"?>\n" + doctype.format() + "\n";
82 this.root = root;
83 calcnsnames();
84 Frame rf = new Frame(root);
85 Map<Name, String> ra = new HashMap<>(root.attribs);
86 for(Map.Entry<Namespace, String> ent : this.ns.entrySet()) {
87 Namespace ns = ent.getKey();
88 String abb = ent.getValue();
89 if(ns == null)
90 continue;
91 ra.put(new Name((abb == null) ? "xmlns" : ("xmlns:" + abb)), ns.uri);
92 }
93 rf.ai = ra.entrySet().iterator();
94 stack.add(rf);
95 }
96
97 private String fmtname(Name nm) {
98 String abb = ns.get(nm.ns);
99 return((abb == null) ? nm.local : (abb + ":" + nm.local));
100 }
101
102 private String head(Element el) {
103 return(String.format("<%s", fmtname(el.name)));
104 }
105
106 private String tail(Element el) {
107 return(String.format("</%s>", fmtname(el.name)));
108 }
109
110 private String attrquote(String val) {
111 char qc;
112 if(val.indexOf('"') >= 0) {
113 qc = '\'';
114 val = val.replace("'", "&apos;");
115 } else {
116 qc = '"';
117 val = val.replace("\"", "&quot;");
118 }
119 val = val.replace("&", "&amp;");
120 val = val.replace("<", "&lt;");
121 val = val.replace(">", "&gt;");
122 return(qc + val + qc);
123 }
124
125 private String attr(Name nm, String value) {
126 String anm = (nm.ns == null) ? nm.local : fmtname(nm);
127 return(String.format(" %s=%s", anm, attrquote(value)));
128 }
129
130 private String quote(String text) {
131 text = text.replace("&", "&amp;");
132 text = text.replace("<", "&lt;");
133 text = text.replace(">", "&gt;");
134 return(text);
135 }
136
137 protected boolean shorten(Element el) {
138 return(el.children.isEmpty());
139 }
140
141 protected boolean produce() {
142 if(!headed) {
143 headed = true;
144 if(write(header))
145 return(false);
146 }
147 if(stack.isEmpty())
148 return(true);
149 Frame f = stack.get(stack.size() - 1);
150 if(!f.h && (f.h = true) && write(head(f.el)))
151 return(false);
152 while(f.ai.hasNext()) {
153 Map.Entry<Name, String> ent = f.ai.next();
154 if(write(attr(ent.getKey(), ent.getValue())))
155 return(false);
156 }
157 if(!f.sh) {
158 if(!f.e && (f.e = true) && write(">"))
159 return(false);
160 if(f.ci.hasNext()) {
161 Node ch = f.ci.next();
162 if(ch instanceof Text) {
163 write(quote(((Text)ch).text));
164 } else if(ch instanceof Raw) {
165 write(((Raw)ch).text);
166 } else {
167 stack.add(new Frame((Element)ch));
168 }
169 return(false);
170 }
171 if(!f.t && (f.t = true) && write(tail(f.el)))
172 return(false);
173 } else {
174 if(!f.e && (f.e = true) && write(" />"))
175 return(false);
176 }
177 stack.remove(stack.size() - 1);
178 return(false);
179 }
180}