Converted the HTML-generator to Python3.
[wrw.git] / wrw / sp / util.py
CommitLineData
62551769 1from . import cons
ff79cdbf
FT
2
3def findnsnames(el):
4 names = {}
5 nid = [1]
6 def proc(el):
7 if isinstance(el, cons.element):
8 if el.ns not in names:
62551769 9 names[el.ns] = "n" + str(nid[0])
ff79cdbf
FT
10 nid[:] = [nid[0] + 1]
11 for ch in el.children:
12 proc(ch)
13 proc(el)
14 if None in names:
15 names[None] = None
16 else:
17 names[el.ns] = None
18 return names
19
20class formatter(object):
21 def __init__(self, out, root, nsnames=None, charset="utf-8", doctype=None):
22 self.root = root
23 if nsnames is None:
24 nsnames = findnsnames(root)
25 self.nsnames = nsnames
26 self.out = out
27 self.charset = charset
28 self.doctype = doctype
29
30 def write(self, text):
31 self.out.write(text.encode(self.charset))
32
33 def quotewrite(self, buf):
34 for ch in buf:
62551769
FT
35 if ch == '&':
36 self.write("&")
37 elif ch == '<':
38 self.write("&lt;")
39 elif ch == '>':
40 self.write("&gt;")
ff79cdbf
FT
41 else:
42 self.write(ch)
43
44 def text(self, el):
45 self.quotewrite(el)
46
47 def attrval(self, buf):
62551769 48 qc, qt = ("'", "&apos;") if '"' in buf else ('"', "&quot;")
ff79cdbf
FT
49 self.write(qc)
50 for ch in buf:
62551769
FT
51 if ch == '&':
52 self.write("&amp;")
53 elif ch == '<':
54 self.write("&lt;")
55 elif ch == '>':
56 self.write("&gt;")
ff79cdbf
FT
57 elif ch == qc:
58 self.write(qt)
59 else:
60 self.write(ch)
61 self.write(qc)
62
63 def attr(self, k, v):
64 self.write(k)
62551769 65 self.write('=')
ff79cdbf
FT
66 self.attrval(v)
67
68 def shorttag(self, el, **extra):
62551769
FT
69 self.write('<' + self.elname(el))
70 for k, v in el.attrs.items():
71 self.write(' ')
ff79cdbf 72 self.attr(k, v)
62551769
FT
73 for k, v in extra.items():
74 self.write(' ')
ff79cdbf 75 self.attr(k, v)
62551769 76 self.write(" />")
ff79cdbf
FT
77
78 def elname(self, el):
79 ns = self.nsnames[el.ns]
80 if ns is None:
81 return el.name
82 else:
62551769 83 return ns + ':' + el.name
ff79cdbf
FT
84
85 def starttag(self, el, **extra):
62551769
FT
86 self.write('<' + self.elname(el))
87 for k, v in el.attrs.items():
88 self.write(' ')
ff79cdbf 89 self.attr(k, v)
62551769
FT
90 for k, v in extra.items():
91 self.write(' ')
ff79cdbf 92 self.attr(k, v)
62551769 93 self.write('>')
ff79cdbf
FT
94
95 def endtag(self, el):
62551769 96 self.write('</' + self.elname(el) + '>')
ff79cdbf
FT
97
98 def longtag(self, el):
99 self.starttag(el, **extra)
100 for ch in el.children:
101 self.node(ch)
102 self.endtag(el)
103
104 def element(self, el, **extra):
105 if len(el.children) == 0:
106 self.shorttag(el, **extra)
107 else:
108 self.longtag(el, **extra)
109
110 def node(self, el):
111 if isinstance(el, cons.element):
112 self.element(el)
113 elif isinstance(el, cons.text):
114 self.text(el)
115 else:
116 raise Exception("Unknown object in element tree: " + el)
117
118 def start(self):
62551769 119 self.write('<?xml version="1.0" encoding="' + self.charset + '" ?>\n')
ff79cdbf 120 if self.doctype:
62551769
FT
121 self.write('<!DOCTYPE %s PUBLIC "%s" "%s">\n' % (self.root.name,
122 self.doctype[0],
123 self.doctype[1]))
ff79cdbf 124 extra = {}
62551769 125 for uri, nm in self.nsnames.items():
ff79cdbf
FT
126 if uri is None:
127 continue
128 if nm is None:
62551769 129 extra["xmlns"] = uri
ff79cdbf 130 else:
62551769 131 extra["xmlns:" + nm] = uri
ff79cdbf
FT
132 self.element(self.root, **extra)
133
134 @classmethod
135 def output(cls, out, el, *args, **kw):
136 cls(out=out, root=el, *args, **kw).start()
137
138 def update(self, **ch):
139 ret = type(self).__new__(type(self))
140 ret.__dict__.update(self.__dict__)
141 ret.__dict__.update(ch)
142 return ret
143
144class iwriter(object):
145 def __init__(self, out):
146 self.out = out
147 self.atbol = True
148 self.col = 0
149
150 def write(self, buf):
62551769
FT
151 for i in range(len(buf)):
152 c = buf[i:i + 1]
153 if c == b'\n':
ff79cdbf
FT
154 self.col = 0
155 else:
156 self.col += 1
157 self.out.write(c)
158 self.atbol = False
159
160 def indent(self, indent):
161 if self.atbol:
162 return
163 if self.col != 0:
62551769 164 self.write(b'\n')
ff79cdbf
FT
165 self.write(indent)
166 self.atbol = True
167
168class indenter(formatter):
62551769 169 def __init__(self, indent=" ", *args, **kw):
ff79cdbf
FT
170 super(indenter, self).__init__(*args, **kw)
171 self.out = iwriter(self.out)
172 self.indent = indent
62551769 173 self.curind = ""
ff79cdbf
FT
174
175 def simple(self, el):
176 for ch in el.children:
177 if not isinstance(ch, cons.text):
178 return False
179 return True
180
181 def longtag(self, el, **extra):
182 self.starttag(el, **extra)
183 sub = self
184 reind = False
185 if not self.simple(el):
186 sub = self.update(curind=self.curind + self.indent)
62551769 187 sub.reindent()
ff79cdbf
FT
188 reind = True
189 for ch in el.children:
190 sub.node(ch)
191 if reind:
62551769 192 self.reindent()
ff79cdbf
FT
193 self.endtag(el)
194
195 def element(self, el, **extra):
196 super(indenter, self).element(el, **extra)
197 if self.out.col > 80 and self.simple(el):
62551769
FT
198 self.reindent()
199
200 def reindent(self):
201 self.out.indent(self.curind.encode(self.charset))
ff79cdbf
FT
202
203 def start(self):
204 super(indenter, self).start()
205 self.write('\n')