1
2
3
4 """
5 This file is part of the web2py Web Framework (Copyrighted, 2007-2011).
6 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
7
8 Author: Thadeus Burgess
9
10 Contributors:
11
12 - Thank you to Massimo Di Pierro for creating the original gluon/template.py
13 - Thank you to Jonathan Lundell for extensively testing the regex on Jython.
14 - Thank you to Limodou (creater of uliweb) who inspired the block-element support for web2py.
15 """
16
17 import os
18 import re
19 import cgi
20 import cStringIO
21 import logging
22 try:
23 from restricted import RestrictedError
24 except:
26 logging.error(str(a)+':'+str(b)+':'+str(c))
27 return RuntimeError
28
30 """
31 Basic Container Object
32 """
33 - def __init__(self, value = None, pre_extend = False):
34 self.value = value
35 self.pre_extend = pre_extend
36
38 return str(self.value)
39
41 - def __init__(self, name = '', pre_extend = False):
42 self.name = name
43 self.value = None
44 self.pre_extend = pre_extend
45
47 if self.value:
48 return str(self.value)
49 else:
50 raise SyntaxError("Undefined parent block ``%s``. \n" % self.name + \
51 "You must define a block before referencing it.\nMake sure you have not left out an ``{{end}}`` tag." )
52
54 return "%s->%s" % (self.name, self.value)
55
57 """
58 Block Container.
59
60 This Node can contain other Nodes and will render in a hierarchical order
61 of when nodes were added.
62
63 ie::
64
65 {{ block test }}
66 This is default block test
67 {{ end }}
68 """
69 - def __init__(self, name = '', pre_extend = False, delimiters = ('{{','}}')):
70 """
71 name - Name of this Node.
72 """
73 self.nodes = []
74 self.name = name
75 self.pre_extend = pre_extend
76 self.left, self.right = delimiters
77
79 lines = ['%sblock %s%s' % (self.left,self.name,self.right)]
80 for node in self.nodes:
81 lines.append(str(node))
82 lines.append('%send%s' % (self.left, self.right))
83 return ''.join(lines)
84
86 """
87 Get this BlockNodes content, not including child Nodes
88 """
89 lines = []
90 for node in self.nodes:
91 if not isinstance(node, BlockNode):
92 lines.append(str(node))
93 return ''.join(lines)
94
96 """
97 Add an element to the nodes.
98
99 Keyword Arguments
100
101 - node -- Node object or string to append.
102 """
103 if isinstance(node, str) or isinstance(node, Node):
104 self.nodes.append(node)
105 else:
106 raise TypeError("Invalid type; must be instance of ``str`` or ``BlockNode``. %s" % node)
107
109 """
110 Extend the list of nodes with another BlockNode class.
111
112 Keyword Arguments
113
114 - other -- BlockNode or Content object to extend from.
115 """
116 if isinstance(other, BlockNode):
117 self.nodes.extend(other.nodes)
118 else:
119 raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other)
120
122 """
123 Merges all nodes into a single string.
124
125 blocks -- Dictionary of blocks that are extending
126 from this template.
127 """
128 lines = []
129
130 for node in self.nodes:
131
132 if isinstance(node, BlockNode):
133
134 if node.name in blocks:
135
136 lines.append(blocks[node.name].output(blocks))
137
138 else:
139 lines.append(node.output(blocks))
140
141 else:
142 lines.append(str(node))
143
144 return ''.join(lines)
145
146 -class Content(BlockNode):
147 """
148 Parent Container -- Used as the root level BlockNode.
149
150 Contains functions that operate as such.
151 """
152 - def __init__(self, name = "ContentBlock", pre_extend = False):
153 """
154 Keyword Arguments
155
156 name -- Unique name for this BlockNode
157 """
158 self.name = name
159 self.nodes = []
160 self.blocks = {}
161 self.pre_extend = pre_extend
162
164 lines = []
165
166 for node in self.nodes:
167
168 if isinstance(node, BlockNode):
169
170 if node.name in self.blocks:
171
172 lines.append(self.blocks[node.name].output(self.blocks))
173 else:
174
175 lines.append(node.output(self.blocks))
176 else:
177
178 lines.append(str(node))
179
180 return ''.join(lines)
181
182 - def _insert(self, other, index = 0):
183 """
184 Inserts object at index.
185 """
186 if isinstance(other, str) or isinstance(other, Node):
187 self.nodes.insert(index, other)
188 else:
189 raise TypeError("Invalid type, must be instance of ``str`` or ``Node``.")
190
191 - def insert(self, other, index = 0):
192 """
193 Inserts object at index.
194
195 You may pass a list of objects and have them inserted.
196 """
197 if isinstance(other, (list, tuple)):
198
199 other.reverse()
200 for item in other:
201 self._insert(item, index)
202 else:
203 self._insert(other, index)
204
205 - def append(self, node):
206 """
207 Adds a node to list. If it is a BlockNode then we assign a block for it.
208 """
209 if isinstance(node, str) or isinstance(node, Node):
210 self.nodes.append(node)
211 if isinstance(node, BlockNode):
212 self.blocks[node.name] = node
213 else:
214 raise TypeError("Invalid type, must be instance of ``str`` or ``BlockNode``. %s" % node)
215
216 - def extend(self, other):
217 """
218 Extends the objects list of nodes with another objects nodes
219 """
220 if isinstance(other, BlockNode):
221 self.nodes.extend(other.nodes)
222 self.blocks.update(other.blocks)
223 else:
224 raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other)
225
226 - def clear_content(self):
228
230
231 default_delimiters = ('{{','}}')
232 r_tag = re.compile(r'(\{\{.*?\}\})', re.DOTALL)
233
234 r_multiline = re.compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', re.DOTALL)
235
236
237
238 re_block = re.compile('^(elif |else:|except:|except |finally:).*$',
239 re.DOTALL)
240
241 re_unblock = re.compile('^(return|continue|break|raise)( .*)?$', re.DOTALL)
242
243 re_pass = re.compile('^pass( .*)?$', re.DOTALL)
244
245 - def __init__(self, text,
246 name = "ParserContainer",
247 context = dict(),
248 path = 'views/',
249 writer = 'response.write',
250 lexers = {},
251 delimiters = ('{{','}}'),
252 _super_nodes = [],
253 ):
254 """
255 text -- text to parse
256 context -- context to parse in
257 path -- folder path to templates
258 writer -- string of writer class to use
259 lexers -- dict of custom lexers to use.
260 delimiters -- for example ('{{','}}')
261 _super_nodes -- a list of nodes to check for inclusion
262 this should only be set by "self.extend"
263 It contains a list of SuperNodes from a child
264 template that need to be handled.
265 """
266
267
268 self.name = name
269
270 self.text = text
271
272
273
274 self.writer = writer
275
276
277 if isinstance(lexers, dict):
278 self.lexers = lexers
279 else:
280 self.lexers = {}
281
282
283 self.path = path
284
285 self.context = context
286
287
288 self.delimiters = delimiters
289 if delimiters != self.default_delimiters:
290 escaped_delimiters = (re.escape(delimiters[0]),re.escape(delimiters[1]))
291 self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters, re.DOTALL)
292 elif context.has_key('response'):
293 if context['response'].delimiters != self.default_delimiters:
294 escaped_delimiters = (re.escape(context['response'].delimiters[0]),
295 re.escape(context['response'].delimiters[1]))
296 self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters,re.DOTALL)
297
298
299 self.content = Content(name=name)
300
301
302
303
304
305 self.stack = [self.content]
306
307
308
309 self.super_nodes = []
310
311
312
313 self.child_super_nodes = _super_nodes
314
315
316
317 self.blocks = {}
318
319
320 self.parse(text)
321
323 """
324 Return the parsed template with correct indentation.
325
326 Used to make it easier to port to python3.
327 """
328 return self.reindent(str(self.content))
329
331 "Make sure str works exactly the same as python 3"
332 return self.to_string()
333
335 "Make sure str works exactly the same as python 3"
336 return self.to_string()
337
339 """
340 Reindents a string of unindented python code.
341 """
342
343
344 lines = text.split('\n')
345
346
347 new_lines = []
348
349
350
351
352 credit = 0
353
354
355 k = 0
356
357
358
359
360
361
362
363
364
365 for raw_line in lines:
366 line = raw_line.strip()
367
368
369 if not line:
370 continue
371
372
373
374
375 if TemplateParser.re_block.match(line):
376 k = k + credit - 1
377
378
379 k = max(k,0)
380
381
382 new_lines.append(' '*(4*k)+line)
383
384
385 credit = 0
386
387
388 if TemplateParser.re_pass.match(line):
389 k -= 1
390
391
392
393
394
395
396 if TemplateParser.re_unblock.match(line):
397 credit = 1
398 k -= 1
399
400
401
402 if line.endswith(':') and not line.startswith('#'):
403 k += 1
404
405
406
407 new_text = '\n'.join(new_lines)
408
409 if k > 0:
410 self._raise_error('missing "pass" in view', new_text)
411 elif k < 0:
412 self._raise_error('too many "pass" in view', new_text)
413
414 return new_text
415
417 """
418 Raise an error using itself as the filename and textual content.
419 """
420 raise RestrictedError(self.name, text or self.text, message)
421
422 - def _get_file_text(self, filename):
423 """
424 Attempt to open ``filename`` and retrieve its text.
425
426 This will use self.path to search for the file.
427 """
428
429
430 if not filename.strip():
431 self._raise_error('Invalid template filename')
432
433
434
435 filename = eval(filename, self.context)
436
437
438 filepath = os.path.join(self.path, filename)
439
440
441 try:
442 fileobj = open(filepath, 'rb')
443 text = fileobj.read()
444 fileobj.close()
445 except IOError:
446 self._raise_error('Unable to open included view file: ' + filepath)
447
448 return text
449
450 - def include(self, content, filename):
451 """
452 Include ``filename`` here.
453 """
454 text = self._get_file_text(filename)
455
456 t = TemplateParser(text,
457 name = filename,
458 context = self.context,
459 path = self.path,
460 writer = self.writer,
461 delimiters = self.delimiters)
462
463 content.append(t.content)
464
466 """
467 Extend ``filename``. Anything not declared in a block defined by the
468 parent will be placed in the parent templates ``{{include}}`` block.
469 """
470 text = self._get_file_text(filename)
471
472
473 super_nodes = []
474
475 super_nodes.extend(self.child_super_nodes)
476
477 super_nodes.extend(self.super_nodes)
478
479 t = TemplateParser(text,
480 name = filename,
481 context = self.context,
482 path = self.path,
483 writer = self.writer,
484 delimiters = self.delimiters,
485 _super_nodes = super_nodes)
486
487
488
489 buf = BlockNode(name='__include__' + filename, delimiters=self.delimiters)
490 pre = []
491
492
493 for node in self.content.nodes:
494
495 if isinstance(node, BlockNode):
496
497 if node.name in t.content.blocks:
498
499 continue
500
501 if isinstance(node, Node):
502
503
504 if node.pre_extend:
505 pre.append(node)
506 continue
507
508
509
510 buf.append(node)
511 else:
512 buf.append(node)
513
514
515
516 self.content.nodes = []
517
518
519 t.content.blocks['__include__' + filename] = buf
520
521
522 t.content.insert(pre)
523
524
525 t.content.extend(self.content)
526
527
528 self.content = t.content
529
531
532
533
534
535
536
537 in_tag = False
538 extend = None
539 pre_extend = True
540
541
542
543
544 ij = self.r_tag.split(text)
545
546
547 for j in range(len(ij)):
548 i = ij[j]
549
550 if i:
551 if len(self.stack) == 0:
552 self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag')
553
554
555 top = self.stack[-1]
556
557 if in_tag:
558 line = i
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 line = line[2:-2].strip()
612
613
614 if not line:
615 continue
616
617
618
619 def remove_newline(re_val):
620
621
622 return re_val.group(0).replace('\n', '\\n')
623
624
625
626
627 line = re.sub(TemplateParser.r_multiline,
628 remove_newline,
629 line)
630
631 if line.startswith('='):
632
633 name, value = '=', line[1:].strip()
634 else:
635 v = line.split(' ', 1)
636 if len(v) == 1:
637
638
639
640 name = v[0]
641 value = ''
642 else:
643
644
645
646
647 name = v[0]
648 value = v[1]
649
650
651
652
653
654
655
656 if name in self.lexers:
657
658
659
660
661
662
663 self.lexers[name](parser = self,
664 value = value,
665 top = top,
666 stack = self.stack,)
667
668 elif name == '=':
669
670
671 buf = "\n%s(%s)" % (self.writer, value)
672 top.append(Node(buf, pre_extend = pre_extend))
673
674 elif name == 'block' and not value.startswith('='):
675
676 node = BlockNode(name = value.strip(),
677 pre_extend = pre_extend,
678 delimiters = self.delimiters)
679
680
681 top.append(node)
682
683
684
685
686
687 self.stack.append(node)
688
689 elif name == 'end' and not value.startswith('='):
690
691
692
693 self.blocks[top.name] = top
694
695
696 self.stack.pop()
697
698 elif name == 'super' and not value.startswith('='):
699
700
701
702 if value:
703 target_node = value
704 else:
705 target_node = top.name
706
707
708 node = SuperNode(name = target_node,
709 pre_extend = pre_extend)
710
711
712 self.super_nodes.append(node)
713
714
715 top.append(node)
716
717 elif name == 'include' and not value.startswith('='):
718
719 if value:
720 self.include(top, value)
721
722
723
724 else:
725 include_node = BlockNode(name = '__include__' + self.name,
726 pre_extend = pre_extend,
727 delimiters = self.delimiters)
728 top.append(include_node)
729
730 elif name == 'extend' and not value.startswith('='):
731
732
733 extend = value
734 pre_extend = False
735
736 else:
737
738
739 if line and in_tag:
740
741
742 tokens = line.split('\n')
743
744
745
746
747
748
749 continuation = False
750 len_parsed = 0
751 for k in range(len(tokens)):
752
753 tokens[k] = tokens[k].strip()
754 len_parsed += len(tokens[k])
755
756 if tokens[k].startswith('='):
757 if tokens[k].endswith('\\'):
758 continuation = True
759 tokens[k] = "\n%s(%s" % (self.writer, tokens[k][1:].strip())
760 else:
761 tokens[k] = "\n%s(%s)" % (self.writer, tokens[k][1:].strip())
762 elif continuation:
763 tokens[k] += ')'
764 continuation = False
765
766
767 buf = "\n%s" % '\n'.join(tokens)
768 top.append(Node(buf, pre_extend = pre_extend))
769
770 else:
771
772 buf = "\n%s(%r, escape=False)" % (self.writer, i)
773 top.append(Node(buf, pre_extend = pre_extend))
774
775
776 in_tag = not in_tag
777
778
779 to_rm = []
780
781
782 for node in self.child_super_nodes:
783
784 if node.name in self.blocks:
785
786 node.value = self.blocks[node.name]
787
788
789 to_rm.append(node)
790
791
792 for node in to_rm:
793
794
795 self.child_super_nodes.remove(node)
796
797
798 if extend:
799 self.extend(extend)
800
801
802 -def parse_template(filename,
803 path = 'views/',
804 context = dict(),
805 lexers = {},
806 delimiters = ('{{','}}')
807 ):
808 """
809 filename can be a view filename in the views folder or an input stream
810 path is the path of a views folder
811 context is a dictionary of symbols used to render the template
812 """
813
814
815 if isinstance(filename, str):
816 try:
817 fp = open(os.path.join(path, filename), 'rb')
818 text = fp.read()
819 fp.close()
820 except IOError:
821 raise RestrictedError(filename, '', 'Unable to find the file')
822 else:
823 text = filename.read()
824
825
826 return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters))
827
829 """
830 Returns the indented python code of text. Useful for unit testing.
831
832 """
833 return str(TemplateParser(text))
834
835
836
837 -def render(content = "hello world",
838 stream = None,
839 filename = None,
840 path = None,
841 context = {},
842 lexers = {},
843 delimiters = ('{{','}}')
844 ):
845 """
846 >>> render()
847 'hello world'
848 >>> render(content='abc')
849 'abc'
850 >>> render(content='abc\\'')
851 "abc'"
852 >>> render(content='a"\\'bc')
853 'a"\\'bc'
854 >>> render(content='a\\nbc')
855 'a\\nbc'
856 >>> render(content='a"bcd"e')
857 'a"bcd"e'
858 >>> render(content="'''a\\nc'''")
859 "'''a\\nc'''"
860 >>> render(content="'''a\\'c'''")
861 "'''a\'c'''"
862 >>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5))
863 '0<br />1<br />2<br />3<br />4<br />'
864 >>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}'))
865 '0<br />1<br />2<br />3<br />4<br />'
866 >>> render(content="{{='''hello\\nworld'''}}")
867 'hello\\nworld'
868 >>> render(content='{{for i in range(3):\\n=i\\npass}}')
869 '012'
870 """
871
872 try:
873 from globals import Response
874 except:
875
876 class Response():
877 def __init__(self):
878 self.body = cStringIO.StringIO()
879 def write(self, data, escape=True):
880 if not escape:
881 self.body.write(str(data))
882 elif hasattr(data,'xml') and callable(data.xml):
883 self.body.write(data.xml())
884 else:
885
886 if not isinstance(data, (str, unicode)):
887 data = str(data)
888 elif isinstance(data, unicode):
889 data = data.encode('utf8', 'xmlcharrefreplace')
890 data = cgi.escape(data, True).replace("'","'")
891 self.body.write(data)
892
893
894 class NOESCAPE():
895 def __init__(self, text):
896 self.text = text
897 def xml(self):
898 return self.text
899
900 context['NOESCAPE'] = NOESCAPE
901
902
903 if not content and not stream and not filename:
904 raise SyntaxError, "Must specify a stream or filename or content"
905
906
907 close_stream = False
908 if not stream:
909 if filename:
910 stream = open(filename, 'rb')
911 close_stream = True
912 elif content:
913 stream = cStringIO.StringIO(content)
914
915
916 context['response'] = Response()
917
918
919 code = str(TemplateParser(stream.read(), context=context, path=path, lexers=lexers, delimiters=delimiters))
920 try:
921 exec(code) in context
922 except Exception:
923
924 raise
925
926 if close_stream:
927 stream.close()
928
929
930 return context['response'].body.getvalue()
931
932
933 if __name__ == '__main__':
934 import doctest
935 doctest.testmod()
936