-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathHTMLTemplate.py
731 lines (592 loc) · 28.9 KB
/
HTMLTemplate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
"""HTMLTemplate - A fast, powerful, easy-to-use HTML templating system.
Copyright (C) 2004-2008 HAS
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
__all__ = ['ParseError', 'Node', 'Template']
from html.parser import HTMLParser
from keyword import kwlist
import re, sys
#################################################
# SUPPORT
#################################################
def renderAtts(atts):
# Renders an HTML tag's attributes from a list of name-value tuples.
result = ''
for name, value in atts:
if value is None:
result += ' ' + name
elif '"' in value:
result += " %s='%s'" % (name, value)
else:
result += ' %s="%s"' % (name, value)
return result
def defaultEncoder(txt):
# Used to HTML-encode value in 'node.content = value'.
return txt.replace('&', '&').replace(
'<', '<').replace('>', '>').replace('"', '"')
def defaultDecoder(txt):
# Used to HTML-decode content in 'value = node.content'.
return txt.replace('"', '"').replace(
'<', '<').replace('>', '>').replace('&', '&')
#################################################
# TEMPLATE PARSER
#################################################
class ParseError(Exception):
"""A template parsing error."""
pass
class ElementCollector:
# Collects a node's type, name and content as it's parsed.
# When end of node is reached, the collected data is used
# to construct a Template/Repeater/Container instance.
def __init__(self, *args):
self.nodeType, self.nodeName, self.tagName, self.atts, \
self.isEmpty, self.omitTags, self.shouldDelete = args
self.content = ['']
self.elementNames = {}
self.__depth = 1
# Methods used to track nested tags of same tag name; used to locate
# close tag that marks the end of this node.
def incDepth(self):
self.__depth += 1
def decDepth(self):
self.__depth -= 1
def isComplete(self):
return self.__depth < 1
# Methods used to collect plain HTML and any sub-nodes.
def addText(self, txt):
self.content[-1] += txt
def addElement(self, node, nodeType, nodeName):
self.content.extend([node, ''])
self.elementNames[nodeName] = nodeType
class Parser(HTMLParser):
def unescape(self, s):
# kludge: avoid HTMLParser's stupid mangling of attribute values
return s
# Handles parsing events sent by parseTemplate() as it processes
# a template string. Collects data in ElementCollector instances,
# then converts these into Template/Repeater/Container objects
# which it assembles into a finished object model. Stack-based.
# Regular expressions used to match special tag attributes,
# a.k.a. template compiler directives, that indicate an HTML
# element should be converted into a template node.
__specialAttValuePattern = re.compile('(-)?(con|rep|sep|del):(.*)')
__validNodeNamePattern = re.compile('[a-zA-Z][_a-zA-Z0-9]*')
# List of words already used as property and method names,
# so cannot be used as template node names as well:
__invalidNodeNames = kwlist + [
'content', 'raw', 'atts', 'omittags', 'omit', 'repeat', 'render']
def __init__(self, attribute, encoder, decoder, warn):
HTMLParser.__init__(self)
self.__specialAttributeName = attribute
self.__encoder = encoder
self.__decoder = decoder
self.__warn = warn
# Each node's content is collected in an ElementCollector instance
# that's stored in the __outputStack stack while it's being parsed.
# Once the end of the node is found, the ElementCollector's content
# is used to create an instance of the appropriate node class, which
# is then added to previous ElementCollector in the stack (i.e. the
# collector for its parent node).
self.__outputStack = [
ElementCollector('tem', '', None, None, False, False, False)]
def __isSpecialTag(self, atts, specialAttName):
# Determines if any of an HTML tag's attributes are
# compiler directives.
for name, value in atts:
if name == specialAttName:
matchedValue = self.__specialAttValuePattern.match(value)
if matchedValue:
atts = dict(atts)
del atts[specialAttName]
omitTags, nodeType, nodeName = matchedValue.groups()
return True, nodeType, nodeName, omitTags, atts
elif self.__warn:
from warnings import warn
warn("Non-directive tag attribute found: %s=%r"
% (name, value))
return False, '', '', False, renderAtts(atts)
def __startTag(self, tagName, atts, isEmpty):
# Process an HTML tag that marks the start of an HTML element,
# i.e. <foo> or <foo/>. If the tag contains a compiler directive,
# start collecting the element's content to be turned into a
# template node, otherwise reassemble it as regular HTML markup.
node = self.__outputStack[-1]
if node.shouldDelete:
isSpecial = 0
else:
isSpecial, nodeType, nodeName, omitTags, atts = \
self.__isSpecialTag(atts, self. __specialAttributeName)
if isSpecial:
# Verify node name is legal, then process according to
# directive's type (con, rep, sep, del).
if nodeType != 'del' and (
not self.__validNodeNamePattern.match(nodeName)
or nodeName in self.__invalidNodeNames):
raise ParseError("Invalid node name: %r" % nodeName)
shouldDelete = nodeType == 'del'
if nodeName in node.elementNames:
if node.elementNames[nodeName] == nodeType:
shouldDelete = True
elif nodeType != 'sep':
raise ParseError(("Invalid node name: %s:%s "
"(node %s:%s already found).") % (nodeType,
nodeName, node.elementNames[nodeName], nodeName))
self.__outputStack.append(ElementCollector(nodeType, nodeName,
tagName, atts, isEmpty, omitTags, shouldDelete))
else:
if node.tagName == tagName:
# Keep track of nested open tags of same name.
node.incDepth()
if not node.shouldDelete:
if isEmpty:
endOfOpenTag = ' />'
else:
endOfOpenTag = '>'
node.addText('<' + tagName + atts + endOfOpenTag)
def __hasCompletedElement(self, element, parent):
# Called by __endTag when it finds the close tag that ends an HTML
# element denoting a template node.
if element.isEmpty:
content = []
else:
content = element.content
if element.nodeType in ['con', 'rep']:
node = makeNode(
element.nodeType, element.nodeName, element.tagName,
element.atts, content, self.__encoder, self.__decoder)
if element.omitTags:
node.omittags()
parent.addElement(node, element.nodeType, element.nodeName)
else: # element.nodeType == 'sep'
# Add this separator to its repeater
for node in parent.content[1::2]:
if node._nodeName == element.nodeName:
if node._nodeType != 'rep':
raise ParseError(("Can't process separator node "
"'sep:%s': repeater node 'rep:%s' wasn't "
"found. Found node '%s:%s' instead.") % (
element.nodeName, element.nodeName,
element.nodeType, element.nodeName))
if element.omitTags:
if content:
node._sep = content[0]
else:
node._sep = ''
else:
if content:
node._sep = '<%s%s>%s</%s>' % (element.tagName,
renderAtts(iter(element.atts.items())), # FIXED
content[0], element.tagName)
else:
node._sep = '<%s%s />' % (element.tagName,
renderAtts(iter(element.atts.items()))) # FIXED
return
raise ParseError(("Can't process separator node 'sep:%s' in node "
"'%s:%s': repeater node 'rep:%s' wasn't found.") % (
element.nodeName, parent.nodeType, parent.nodeName,
element.nodeName))
def __endTag(self, tagName, isEmpty):
# Process an end tag that closes an HTML element, i.e. </foo> or
# <foo/>. If the tag closes an HTML element representing a template
# node, call __hasCompletedElement() to finish that node's creation.
node = self.__outputStack[-1]
if node.tagName == tagName:
# Keep track of nested close tags of same name.
node.decDepth()
if node.isComplete():
self.__outputStack.pop()
if not node.shouldDelete:
parent = self.__outputStack[-1]
self.__hasCompletedElement(node, parent)
elif not isEmpty:
node.addText('</%s>' % tagName)
def __addText(self, txt):
self.__outputStack[-1].addText(txt)
# event handlers; called by HTMLParser base class.
def handle_startendtag(self, tagName, atts):
self.__startTag(tagName, atts, True)
self.__endTag(tagName, True)
def handle_starttag(self, tagName, atts):
self.__startTag(tagName, atts, False)
def handle_endtag(self, tagName):
self.__endTag(tagName, False)
def handle_charref(self, txt):
self.__addText('&#%s;' % txt)
def handle_entityref(self, txt):
self.__addText('&%s;' % txt)
def handle_data(self, txt):
self.__addText(txt)
def handle_comment(self, txt):
self.__addText('<!--%s-->' % txt)
def handle_decl(self, txt):
self.__addText('<!%s>' % txt)
def handle_pi(self, txt):
self.__addText('<?%s>' % txt)
def result(self):
# Get content of template's ElementCollector once parsing is done.
element = self.__outputStack.pop()
if element.nodeType != 'tem':
raise ParseError(("Can't complete template: node '%s:%s' wasn't "
"correctly closed.") % (element.nodeType, element.nodeName))
#if len(element.content) == 1:
# raise ParseError, "No special %r attributes were found." % (
# self. __specialAttributeName)
return element.content
#################################################
# OBJECT MODEL CLASSES
#################################################
# Note: HTMLTemplate implements its own performance-optimised object copying
# system which is much faster than standard lib's general-purpose deepcopy().
# All cloning and rendering code is optimised for speed over grokability.
# To summarise, cloning and rendering involves bouncing between a template
# node's various base classes to perform the various steps of each operation
# with child nodes being processed recursively. When cloning, all child nodes
# are cloned as a single operation. When rendering, rather than wait until all
# processing is complete, Repeaters perform a mini-render of their content
# as soon as they finish rendering each clone of themselves. This reduces
# the number of template object instances that build up during rendering,
# reducing memory overheads, and is a bit faster than performing two separate
# traversals, one to call the template's Controller functions to insert
# content, and another to traverse the (by now very large) template object
# model to extract the finished HTML.
# Note that the one disadvantage of using incremental rendering is that it
# makes it awkward for an asynchronous system to acquire new template objects
# in advance and put them into storage for later processing. By the time the
# system digs these nodes back out of storage and fills them in, their content
# has long since been rendered by some Repeater node, so any further changes
# will not appear in the final page. Should anyone ever need to use
# HTMLTemplate in this fashion they can, of course, redesign the rendering
# system so that all nodes created remain viable until a final, separate
# 'render everything' message is sent through the entire object model, but
# it'll obviously cost extra in both performance and memory overheads -
# which is why it's not used as the standard operating model.
class CloneNode(object):
"""Makes cloned nodes."""
def __init__(self, node):
self.__dict__ = node.__dict__.copy()
self.__class__ = node.__class__
# Node classes provide the basic functionality for a template node, with
# additional functionality being added by the Content classes. The Python
# implementation uses multiple inheritance to compose these behaviours;
# languages with native support for mixins or delegates may find those
# more appropriate (and easier for developers to follow).
class Node:
"""Abstract base class for template nodes; used for type checking when
user replaces an existing template node with a new one.
"""
pass
class Container(Node):
"""A Container node has a one-to-one relationship with the node
that contains it.
"""
_nodeType = 'con'
def __init__(self, nodeName, tagName, atts):
self._nodeName = nodeName
self._atts = dict(atts) # On cloning, shallow copy this dict.
if isinstance(self, NullContent):
self.__startTag = '<%s%%s />' % tagName
self.__endTag = ''
else:
self.__startTag = '<%s%%s>' % tagName
self.__endTag = '</%s>' % tagName
self.__omitTags = False
self._omit = False
def _clone(self):
clone = CloneNode(self)
clone._atts = self._atts.copy()
return clone
def _renderNode(self, collector):
# Adds node's content to supplied collector list. Tags are added
# here; PlainContent/RichContent's _renderContent() adds content.
if self.__omitTags:
self._renderContent(collector)
else:
collector.append(self.__startTag % renderAtts(iter(self._atts.items())))
self._renderContent(collector)
collector.append(self.__endTag)
def _render(self, collector):
# Called by parent node to render this node and all its contents.
if not self._omit:
self._renderNode(collector)
def __attsGet(self):
return Attributes(self._atts, self._encode, self._decode)
def __attsSet(self, val):
self._atts = {}
atts = Attributes(self._atts, self._encode, self._decode)
for name, value in val.items():
atts[name] = value
atts = property(__attsGet, __attsSet,
doc="Get this element's tag attributes.")
def omittags(self):
"""Don't render this element's tag(s)."""
self.__omitTags = True
def omit(self):
"""Don't render this element."""
self._omit = True
class Repeater(Container):
"""A Repeater node has a one-to-many relationship with the node
that contains it.
"""
_nodeType = 'rep'
def __init__(self, nodeName, tagName, atts):
self._sep = '\n'
self.__renderedContent = [] # On cloning, shallow-copy this list.
Container.__init__(self, nodeName, tagName, atts)
_fastClone = Container._clone
def _clone(self):
clone = Container._clone(self)
clone.__renderedContent = self.__renderedContent[:]
return clone
def _render(self, collector):
# Called by parent node to render this node and all its contents.
if not self._omit:
collector.extend(self.__renderedContent[1:])
def repeat(self, fn, list, *args):
"""Render an instance of this node for each item in list."""
self.__renderedContent = collector = [] # replaces any previous content
# For each item in list, copy this node and pass it and the list item
# to the supplied callback function. Once the function has finished
# inserting data into the cloned node, perform a mini-render of its
# content and add the result to self.__renderedContent - where it will
# remain until the original node is finally rendered by its parent.
for item in list:
clone = self._fastClone()
fn(clone, item, *args)
if not clone._omit:
collector.append(clone._sep)
clone._renderNode(collector)
##
class Attributes:
"""Public facade for modifying a node's tag attributes. Behaves like
a much simplified dict object. Vended by Node's atts property.
"""
__attNamePattern = re.compile('^[a-zA-Z_][-.:a-zA-Z_0-9]*$')
def __init__(self, atts, encoder, decoder):
self.__atts = atts # The Node's tag attributes dict.
self.__encode = encoder
self.__decode = decoder
def __getitem__(self, name):
return self.__decode(self.__atts[name])
def __setitem__(self, name, val):
try:
if not self.__attNamePattern.match(name): # Note: this
# will throw a TypeError if 'name' is not string/unicode.
raise KeyError("bad name.")
if val != None:
if not isinstance(val, str):
raise TypeError("bad value: %r" % val)
val = self.__encode(val)
if '"' in val and "'" in val:
raise ValueError("value %r contains " \
"both single and double quotes." % val)
self.__atts[name] = val
except Exception as e:
msg = str(e)
if not isinstance(name, str):
msg = "bad name."
raise e.__class__("Can't set tag attribute %r: %s" % (name, msg))
def __delitem__(self, name):
del self.__atts[name]
def __repr__(self):
return '<Attributes [%s]>' % renderAtts(iter(self.__atts.items()))[1:]
#######
# Content classes provide nodes representing non-empty HTML elements with
# support for containing plain HTML content/sub-nodes.
class Content(object):
def __init__(self, encoder, decoder):
self._encode = encoder
self._decode = decoder
def _printStructure(self, indent):
print(indent + self._nodeType + ':' + self._nodeName, file=sys.stderr)
##
class NullContent(Content):
"""Represents an empty HTML element's non-existent content."""
def _renderContent(self, collector):
pass
class PlainContent(Content):
"""Represents a non-empty HTML element's content where it contains plain
text/markup only.
"""
def __init__(self, content, encoder, decoder):
Content.__init__(self, encoder, decoder)
self.raw = content # Get/Set this element's content as raw markup;
# use with care.
def _renderContent(self, collector):
# Called by Node classes to add HTML element's content.
collector.append(self.raw)
def __contentGet(self):
return self._decode(self.raw)
def __contentSet(self, txt):
self.raw = self._encode(txt)
content = property(__contentGet, __contentSet,
doc="Get/Set this element's content as escaped text.")
class RichContent(Content):
"""Represents a non-empty HTML element's content where it contains other
Container/Repeater nodes.
"""
__validIdentifierPattern = re.compile('^[a-zA-Z_][a-zA-Z_0-9]*$')
# KLUDGE: The following line keeps Python 2.3 sweet while it instantiates
# instances of this class; without it, the process crashes hard as
# __init__ conflicts with __setattr__.
__nodesDict = {}
def __init__(self, content, encoder, decoder):
Content.__init__(self, encoder, decoder)
self.__nodesList = content # On cloning, deep copy this list.
self.__nodesDict = dict(
[(node._nodeName, node) for node in content[1::2]]) # (On clon-
# ing: replace with a new dict built from cloned self.__nodesList.)
def __rawGet(self):
if self.__nodesDict:
raise RuntimeError("Can't get raw/content of a node that "
"contains other nodes.")
else:
return self.__nodesList[0]
# Note: property setting is done by __setattr__(), which takes precedence
# over property-based setters for some reason.
raw = property(__rawGet,
doc="Get/Set this element's raw content.")
content = property(lambda self:self._decode(self.__rawGet()),
doc="Get/Set this element's content as escaped text.")
def _initRichClone(self, clone):
# Once node is cloned, this is called to clone its sub-nodes.
clone.__nodesDict = {}
L = clone.__nodesList = self.__nodesList[:]
for i in range(1, len(L), 2):
clone.__nodesDict[L[i]._nodeName] = L[i] = L[i]._clone()
return clone
def _renderContent(self, collector):
# Called by Node classes to add HTML element's content.
L = self.__nodesList
collector.append(L[0])
for i in range(1, len(L), 2):
L[i]._render(collector)
collector.append(L[i + 1])
def _printStructure(self, indent):
Content._printStructure(self, indent)
for node in self.__nodesList[1::2]:
node._printStructure(indent + '\t')
def __getattr__(self, name):
# Get a sub-node.
if name in self.__nodesDict:
return self.__nodesDict[name]
else:
raise AttributeError("%s instance has no attribute %r." % (
self.__class__.__name__, name))
def __setattr__(self, name, value):
# Replace a sub-node, or replace node's content.
if name in self.__nodesDict:
if not isinstance(value, Node):
# Note: This type check is to catch careless user mistakes like
# 'node.foo = "text"' instead of 'node.foo.content = "text"'
raise TypeError(("Can't replace node '%s:%s': value isn't a "
"Node object.") % (self.__nodesDict[name]._nodeType,
self.__nodesDict[name]._nodeName))
value = value._clone()
value._nodeName = name
idx = self.__nodesList.index(self.__nodesDict[name])
self.__nodesDict[name] = self.__nodesList[idx] = value
elif name == 'content':
self.__nodesList = [self._encode(value)]
self.__nodesDict = {}
elif name == 'raw':
self.__nodesList = [value]
self.__nodesDict = {}
else:
self.__dict__[name] = value
#######
# Note: Container and Repeater objects are instantiated via the makeNode()
# constructor function. This returns the appropriate class for the content
# supplied ('abstract factory'). Container and Repeater nodes are actually
# represented by three different classes apiece, depending on whether they
# represent empty or non-empty HTML elements and, in the case of the latter,
# whether or not they contain any sub-nodes. The documentation glosses over
# these details for simplicity, since the user doesn't need to know the
# exact class of a node in order to use it.
class EmptyContainer(NullContent, Container):
def __init__(self, nodeName, tagName, atts, content, encoder, decoder):
NullContent.__init__(self, encoder, decoder)
Container.__init__(self, nodeName, tagName, atts)
class PlainContainer(PlainContent, Container):
def __init__(self, nodeName, tagName, atts, content, encoder, decoder):
PlainContent.__init__(self, content[0], encoder, decoder)
Container.__init__(self, nodeName, tagName, atts)
class RichContainer(RichContent, Container):
def __init__(self, nodeName, tagName, atts, content, encoder, decoder):
RichContent.__init__(self, content, encoder, decoder)
Container.__init__(self, nodeName, tagName, atts)
def _clone(self):
return self._initRichClone(Container._clone(self))
##
class EmptyRepeater(NullContent, Repeater):
def __init__(self, nodeName, tagName, atts, content, encoder, decoder):
NullContent.__init__(self, encoder, decoder)
Repeater.__init__(self, nodeName, tagName, atts)
class PlainRepeater(PlainContent, Repeater):
def __init__(self, nodeName, tagName, atts, content, encoder, decoder):
PlainContent.__init__(self, content[0], encoder, decoder)
Repeater.__init__(self, nodeName, tagName, atts)
class RichRepeater(RichContent, Repeater):
def __init__(self, nodeName, tagName, atts, content, encoder, decoder):
RichContent.__init__(self, content, encoder, decoder)
Repeater.__init__(self, nodeName, tagName, atts)
def _clone(self):
return self._initRichClone(Repeater._clone(self))
def _fastClone(self):
return self._initRichClone(Repeater._fastClone(self))
##
__nodeClasses = {
'con': {'empty': EmptyContainer,
'plain': PlainContainer,
'rich': RichContainer},
'rep': {'empty': EmptyRepeater,
'plain': PlainRepeater,
'rich': RichRepeater}}
def makeNode(nodeType, nodeName, tagName, atts, content, encoder, decoder):
# Called by template parser.
return __nodeClasses[nodeType][{0: 'empty', 1: 'plain'}.get(len(content),
'rich')](nodeName, tagName, atts, content, encoder, decoder)
#################################################
# MAIN
#################################################
class Template(RichContent):
"""The top-level (i.e. root) node of the template object model."""
_nodeType = 'tem'
_nodeName = ''
def __init__(self, callback, html, attribute='node',
codecs=(defaultEncoder, defaultDecoder), warnings=False):
"""
callback : function -- the function that controls how this
template is rendered
html : string or unicode -- the template HTML
[attribute : string or unicode] -- name of the tag attribute used
to hold compiler directives
[codecs : tuple] -- a tuple containing two functions used by the
content property to encode/decode HTML entities
[warnings : boolean] -- warn when non-directive attribute
is encountered
"""
self.__callback = callback
parser = Parser(attribute, codecs[0], codecs[1], warnings)
parser.feed(html)
parser.close()
RichContent.__init__(self, parser.result(), *codecs)
def render(self, *args, **kwargs):
"""Render this template; *args will be passed directly to the template.
"""
clone = self._initRichClone(CloneNode(self))
self.__callback(clone, *args, **kwargs)
collector = []
clone._renderContent(collector)
try: # quick-n-dirty error reporting; not a real substitute for type-
# checking for bad value assignments at point of origin, but cheap
return ''.join(collector)
except TypeError:
raise TypeError("Can't render template: some node's content was "
"set to a non-text value.")
def structure(self):
"""Print the object model's structure for diagnostic use."""
print('-' * 80, file=sys.stderr)
self._printStructure('')
print('-' * 80, file=sys.stderr)