root/trunk/convert.py

Revision 141 (checked in by johnny, 1 year ago)

Do not remove an attr if its value is 0.

Line 
1 from templess import (
2     nodebase, elnode, textnode, commentnode, cdatanode,
3     templessnode, template)
4 import util
5
6 # used for getting (X)HTML output from the XML serializers
7 HTMLNONSINGLETONS = (
8     'a', 'abbr', 'acronym', 'address', 'applet',
9     'b', 'bdo', 'big', 'blink', 'blockquote',
10     'button', 'caption', 'center', 'cite',
11     'comment', 'del', 'dfn', 'dir', 'div',
12     'dl', 'dt', 'em', 'embed', 'fieldset',
13     'font', 'form', 'frameset', 'h1', 'h2',
14     'h3', 'h4', 'h5', 'h6', 'i', 'iframe',
15     'ins', 'kbd', 'label', 'legend', 'li',
16     'listing', 'map', 'marquee', 'menu',
17     'multicol', 'nobr', 'noembed', 'noframes',
18     'noscript', 'object', 'ol', 'optgroup',
19     'option', 'p', 'pre', 'q', 's', 'script',
20     'select', 'small', 'span', 'strike',
21     'strong', 'style', 'sub', 'sup', 'table',
22     'tbody', 'td', 'textarea', 'tfoot',
23     'th', 'thead', 'title', 'tr', 'tt', 'u',
24     'ul', 'xmp')
25
26 # convert/serialize functionality
27 class convertor(object):
28     def __init__(self, node):
29         if isinstance(node, template):
30             node = node.tree
31         self.node = node
32
33     def convert(self, context):
34         """ process the templess directives
35         
36             returns a copy of element, or a list of copies of element (in case
37             of t:content), or None (in case we have a matching cond or not
38             attribute)
39             
40             can return unexpected results in case of t:replace, use only on
41             root nodes of documents in production situation (iow: if you want
42             to convert a tree, always make sure there's no t:replace on the
43             node you call convert() on)
44
45             context is a context dict (see docs), parent is an internal helper
46             argument
47         """
48         self.node = self._convert_node(self.node, context)
49         return self.node
50
51     def _convert_node(self, node, context, parent=None):
52         # first see if we need to be rendered at all
53         cont = self._handle_cond(node, context)
54         if not cont:
55             return
56
57         doreplace, attrs, cvalue, ctype = self._process_attrs(node, context)
58
59         # render our children
60         if cvalue is None:
61             return self._handle_none(
62                 node, context, parent, cvalue, doreplace, attrs)
63         elif (ctype in ('iter', 'ctx') or
64                 (util.is_iterable_not_string(cvalue) and ctype is None)):
65             return self._handle_list(
66                 node, context, parent, cvalue, doreplace, ctype, attrs)
67         else:
68             return self._handle_direct_insert(
69                 node, context, parent, cvalue, doreplace, attrs)
70
71     def _handle_none(self, node, context, parent, cvalue, doreplace, attrs):
72         retnode = parent
73         if not doreplace:
74             retnode = elnode(node.name, attrs, parent, node.charset)
75         for child in node:
76             if isinstance(child, templessnode):
77                 self._convert_node(child, context, retnode)
78             elif isinstance(child, elnode):
79                 elnode(
80                     child.name, child.attrs, retnode, child.charset)
81             elif (isinstance(child, textnode) or
82                     isinstance(child, commentnode) or
83                     isinstance(child, cdatanode)):
84                 child.__class__(child.text, retnode, child.charset)
85         return retnode
86
87     def _handle_list(
88             self, node, context, parent, cvalue, doreplace, ctype, attrs):
89         ret = []
90         if (ctype == 'ctx' or isinstance(cvalue, dict) or
91                 (isinstance(cvalue, util.objectcontext) and ctype != 'iter')):
92             cvalue = [cvalue]
93         for ccontext in cvalue:
94             if (ctype == 'ctx' or
95                     (isinstance(ccontext, dict) and ctype is None) or
96                     isinstance(ccontext, util.objectcontext)):
97                 newparent = parent
98                 if not doreplace:
99                     # create a similar node to retnode
100                     newparent = elnode(node.name, attrs, parent,
101                                        node.charset)
102                 for child in node:
103                     if isinstance(child, templessnode):
104                         self._convert_node(child, ccontext, newparent)
105                     elif isinstance(child, elnode):
106                         elnode(
107                             child.name, child.attrs, newparent, child.charset)
108                     elif (isinstance(child, textnode) or
109                             isinstance(child, commentnode) or
110                             isinstance(child, cdatanode)):
111                         child.__class__(child.text, newparent, child.charset)
112                     # return the last of the new nodes
113                 ret.append(newparent)
114             else:
115                 newparent = parent
116                 if not doreplace:
117                     newparent = elnode(node.name, attrs, parent,
118                                        node.charset)
119                 if isinstance(ccontext, nodebase):
120                     if ccontext.parent:
121                         ccontext.parent.remove(ccontext)
122                     ccontext.parent = newparent
123                     newparent.append(ccontext)
124                 else:
125                     textnode(
126                         util.strconvert(ccontext, node.charset),
127                         newparent, node.charset)
128                 ret.append(newparent)
129         return ret
130
131     def _handle_direct_insert(
132             self, node, context, parent, cvalue, doreplace, attrs):
133         retnode = parent
134         if not doreplace:
135             retnode = elnode(node.name, attrs, parent, node.charset)
136         if isinstance(cvalue, nodebase):
137             if cvalue.parent:
138                 cvalue.parent.remove(cvalue)
139             cvalue.parent = retnode
140             retnode.append(cvalue)
141         else:
142             textnode(
143                 util.strconvert(cvalue, node.charset),
144                 retnode, node.charset)
145         return retnode
146
147     def _handle_cond(self, node, context):
148         """ handle the 't:cond' and 't:not' directives
149         
150             returns False if there's a t:cond or t:not that doesn't allow
151             rendering, True otherwise
152         """
153         cond = node.directives.get('cond')
154         if cond:
155             try:
156                 return not not context[cond]
157             except KeyError:
158                 return False
159         cnot = node.directives.get('not', False)
160         if cnot:
161             try:
162                 return not context[cnot]
163             except KeyError:
164                 return True
165         return True
166
167     def _process_attrs(self, node, context):
168         replacekey = self._get_replace(node)
169         contentkey = self._get_content(node)
170         attrs = self._process_attr(node, context)
171
172         # find out what context we need to use for rendering our children, or
173         # what data we need to interpolate
174         cvalue = None
175         ctype = None
176         if replacekey:
177             if ':' in replacekey:
178                 replacekey, ctype = replacekey.rsplit(':', 1)
179             cvalue = context[replacekey]
180         elif contentkey:
181             if ':' in contentkey:
182                 contentkey, ctype = contentkey.rsplit(':', 1)
183             cvalue = context[contentkey]
184
185         return not not replacekey, attrs, cvalue, ctype
186
187     def _process_attr(self, node, context):
188         strvalue = node.directives.get('attr')
189         attrs = node.attrs.copy()
190         if strvalue:
191             pairs = [v.strip() for v in strvalue.split(';')]
192             for pair in pairs:
193                 k, v = pair.split(' ')
194                 value = context[v]
195                 if value is False: # allow '' and 0 and such
196                     attrs.pop(k, None)
197                 else:
198                     attrs[k] = value
199         return attrs
200
201     def _get_replace(self, node):
202         replace = node.directives.get('replace', False)
203         return replace
204
205     def _get_content(self, node):
206         content = node.directives.get('content', False)
207         return content
208
209
210 class xmlserializer(convertor):
211     def unicode(self, html=False):
212         """ convert self.node to an XML unicode object
213
214             this does _not_ implicitly call convert - you'll need to call that
215             first if self.node is a template or templessnode (or you'll get
216             the original document re-serialized)
217         """
218         return self._unicode(self.node, html)
219
220     def _unicode(self, node, html=False):
221         if isinstance(node, elnode):
222             return self._unicode_element(node, html)
223         elif isinstance(node, cdatanode):
224             return u'<![CDATA[%s]]>' % (node.text,)
225         elif isinstance(node, commentnode):
226             return u'<!--%s-->' % (node.text,)
227         elif isinstance(node, textnode):
228             return node.text
229
230     def _start_node_start(self, node, attrs):
231         return u'<' + node.name + self._serialize_attrs(node, attrs)
232
233     def _end_node(self, node):
234         return u'</'  + node.name + '>'
235
236     def _serialize_attrs(self, node, attrs):
237         """ return the attributes as a string
238         """
239         if not len(attrs):
240             return ''
241         items = sorted(attrs.items())
242         return ' ' + ' '.join(
243             [k + '="' + util.strconvert(v, node.charset) + '"'
244              for (k, v) in items])
245
246     def _unicode_element(self, node, html=False):
247         """ return a string (XML) representation of ourselves (unrendered!)
248         """
249         ret = [self._start_node_start(node, node.attrs)]
250         if not len(node) and (
251                 not html or node.name.lower() not in HTMLNONSINGLETONS):
252             ret.append(u' />')
253         else:
254             ret.append(u'>')
255             for child in node:
256                 ret.append(self._unicode(child, html=html))
257             ret.append(self._end_node(node))
258         return ''.join(ret)
259
260
261 class xmlgenerator(xmlserializer):
262     def convert_generate(self, context, html=False):
263         """ process templess directives and yield string result bits
264
265             this has the advantage over calling convert() and then generate()
266             seperately that it's really lazy - dict access happens only
267             when the part that requires it is generated
268         """
269         return self._generate_node(self.node, context, html=html)
270
271     def generate(self, html=False):
272         """ serialize to XML, yield chunks of unicode
273
274             this does not convert - to get converted XML, use
275             'xmlgenerator.convert_generate()', or call 'xmlgenerator.convert()'
276             before calling this (although the latter is not entirely lazy)
277         """
278         return self._generate_el(self.node, html)
279
280     def _generate_node(self, node, context, html=False, parent=None):
281         if isinstance(node, templessnode):
282             for chunk in self._generate_tnode(node, context, html, parent):
283                 yield chunk
284         elif isinstance(node, elnode):
285             for chunk in self._generate_el(node, html):
286                 yield chunk
287         elif isinstance(node, commentnode):
288             yield u'<!--' + node.text + '-->'
289         elif isinstance(node, cdatanode):
290             yield u'<![CDATA[' + node.text + ']]>'
291         elif isinstance(node, textnode):
292             yield node.text
293
294     def _generate_tnode(self, node, context, html=False, parent=None):
295         cont = self._handle_cond(node, context)
296         if cont:
297             doreplace, attrs, cvalue, ctype = self._process_attrs(
298                 node, context)
299
300             if cvalue is None: # nothing to do, just return copy of self
301                 issingle = not len(node) and (
302                     not html or node.name.lower() not in
303                     HTMLNONSINGLETONS)
304                 if issingle and not doreplace:
305                     for s in self._start_node(node, attrs, True):
306                         yield s
307                 else:
308                     retnode = parent
309                     yieldstartend = False
310                     yieldend = True
311                     if not doreplace:
312                         retnode = elnode(
313                             node.name, attrs, parent, node.charset)
314                         # first yield the start node bit up to the closing
315                         # > (or /> for singletons)
316                         yield self._start_node_start(retnode, attrs)
317                         # if this has no children, and it's not an html node
318                         # that must not be closed, the closing bit should be
319                         # yielded next
320                         if html and node.name in HTMLNONSINGLETONS:
321                             yield u'>'
322                         else:
323                             yieldstartend = True
324                     for child in node:
325                         for s in self._generate_node(
326                                 child, context, html, retnode):
327                             if yieldstartend:
328                                 yield u'>'
329                                 yieldstartend = False
330                             yield s
331                     else:
332                         # not yet yielded the closing > of the start tag, so
333                         # we don't seem to have children, nor are an HTML
334                         # tag that must be closed... render as singleton
335                         if yieldstartend:
336                             yield u' />'
337                             yieldend = False
338                     if yieldend and not doreplace:
339                         yield self._end_node(node)
340             elif (ctype == 'iter' or ctype == 'ctx' or
341                     (util.is_iterable_not_string(cvalue) and ctype is None)):
342                 if (ctype == 'ctx' or
343                         ((isinstance(cvalue, dict) or
344                           isinstance(cvalue, util.objectcontext)) and
345                          ctype is None)):
346                     cvalue = [cvalue]
347                 if cvalue:
348                     for ccontext in cvalue:
349                         if (ctype == 'ctx' or
350                                 ((isinstance(ccontext, dict) or
351                                   isinstance(ccontext, util.objectcontext)) and
352                                  ctype is None)):
353                             if not doreplace:
354                                 yieldstartend = False
355                                 yieldend = True
356                                 yield self._start_node_start(node, attrs)
357                                 if html and node.name in HTMLNONSINGLETONS:
358                                     yield u'>'
359                                 else:
360                                     yieldstartend = True
361                             newparent = parent
362                             if not doreplace:
363                                 # create a similar node to retnode
364                                 newparent = elnode(node.name, attrs, parent,
365                                                     node.charset)
366                             for child in node:
367                                 for s in self._generate_node(
368                                         child, ccontext, html, newparent):
369                                     if not doreplace and yieldstartend:
370                                         yield u'>'
371                                         yieldstartend = False
372                                     yield s
373                             if not doreplace and yieldstartend:
374                                 yield u' />'
375                             elif not doreplace:
376                                 yield self._end_node(node)
377                         else:
378                             newparent = elnode(
379                                 node.name, attrs, parent,
380                                 node.charset)
381                             if isinstance(ccontext, nodebase):
382                                 if ccontext.parent:
383                                     ccontext.parent.remove(ccontext)
384                                 ccontext.parent = newparent
385                                 newparent.append(ccontext)
386                             else:
387                                 ccontext = textnode(
388                                     util.strconvert(ccontext, node.charset),
389                                     newparent, node.charset)
390                             if doreplace:
391                                 for child in newparent:
392                                     for s in self._generate_node(
393                                             child, None, html):
394                                         yield s
395                             else:
396                                 for s in self._generate_node(
397                                         newparent, ccontext, html):
398                                     yield s
399             else:
400                 retnode = parent
401                 if not doreplace:
402                     retnode = elnode(
403                         node.name, attrs, parent, node.charset)
404                 if isinstance(cvalue, nodebase):
405                     if cvalue.parent:
406                         cvalue.parent.remove(cvalue)
407                     cvalue.parent = retnode
408                     retnode.append(cvalue)
409                 else:
410                     textnode(
411                         util.strconvert(cvalue, node.charset),
412                         retnode, node.charset)
413                 if doreplace:
414                     children = list(retnode.children)
415                     for child in children:
416                         # avoid converting text nodes more than once
417                         if (isinstance(child, textnode) and
418                                 child != children[-1]):
419                             continue
420                         for s in self._generate_node(child, context, html):
421                             yield s
422                 else:
423                     for s in self._generate_node(retnode, context, html):
424                         yield s
425
426     def _generate_el(self, node, html=False):
427         """ returns self as a generator (yielding unicode strings)
428         """
429         issingle = html and node.name.lower() not in HTMLNONSINGLETONS
430         yield self._start_node_start(node, node.attrs)
431         if not issingle:
432             yield u'>'
433         for child in node:
434             if issingle:
435                 # not so single after all :)
436                 yield u'>'
437                 issingle = False
438             for chunk in self._generate_node(child, None, html=html):
439                 yield chunk
440         if not issingle:
441             yield self._end_node(node)
442         else:
443             yield u' />'
444
445     def _start_node(self, node, attrs, single):
446         yield self._start_node_start(node, attrs)
447         if single:
448             yield u' />'
449         else:
450             yield u'>'
Note: See TracBrowser for help on using the browser.