6 public 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;
15 Iterator<Map.Entry<Name, String>> ai;
22 this.ai = el.attribs.entrySet().iterator();
23 this.ci = el.children.iterator();
24 this.sh = shorten(el);
28 private void countns(Map<Namespace, Integer> freq, Set<Namespace> attrs, Element el) {
29 for(Name anm : el.attribs.keySet()) {
32 Integer f = freq.get(anm.ns);
33 freq.put(anm.ns, ((f == null) ? 0 : f) + 1);
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);
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) {
51 } else if(!attrs.contains(root.name.ns)) {
52 ns.put(root.name.ns, null);
53 freq.remove(root.name.ns);
55 List<Namespace> order = new ArrayList<>(freq.keySet());
56 Collection<String> ass = new HashSet<>();
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)) {
72 while(ass.contains(p + i))
74 this.ns.put(ns, p + i);
80 public Formatter(DocType doctype, Element root) {
81 this.header = "<?xml version=\"1.0\" encoding=\"US-ASCII\"?>\n" + doctype.format() + "\n";
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();
91 ra.put(new Name((abb == null) ? "xmlns" : ("xmlns:" + abb)), ns.uri);
93 rf.ai = ra.entrySet().iterator();
97 private String fmtname(Name nm) {
98 String abb = ns.get(nm.ns);
99 return((abb == null) ? nm.local : (abb + ":" + nm.local));
102 private String head(Element el) {
103 return(String.format("<%s", fmtname(el.name)));
106 private String tail(Element el) {
107 return(String.format("</%s>", fmtname(el.name)));
110 private String attrquote(String val) {
112 if(val.indexOf('"') >= 0) {
114 val = val.replace("'", "'");
117 val = val.replace("\"", """);
119 val = val.replace("&", "&");
120 val = val.replace("<", "<");
121 val = val.replace(">", ">");
122 return(qc + val + qc);
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)));
130 private String quote(String text) {
131 text = text.replace("&", "&");
132 text = text.replace("<", "<");
133 text = text.replace(">", ">");
137 protected boolean shorten(Element el) {
138 return(el.children.isEmpty());
141 protected boolean produce() {
149 Frame f = stack.get(stack.size() - 1);
150 if(!f.h && (f.h = true) && write(head(f.el)))
152 while(f.ai.hasNext()) {
153 Map.Entry<Name, String> ent = f.ai.next();
154 if(write(attr(ent.getKey(), ent.getValue())))
158 if(!f.e && (f.e = true) && write(">"))
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);
167 stack.add(new Frame((Element)ch));
171 if(!f.t && (f.t = true) && write(tail(f.el)))
174 if(!f.e && (f.e = true) && write(" />"))
177 stack.remove(stack.size() - 1);