comparison markup/template.py @ 69:c40a5dcd2b55 trunk

A couple of minor performance improvements.
author cmlenz
date Mon, 10 Jul 2006 17:37:01 +0000
parents 59eb24184e9c
children dd73921530e8
comparison
equal deleted inserted replaced
68:e7f91e75b0e1 69:c40a5dcd2b55
43 import posixpath 43 import posixpath
44 import re 44 import re
45 from StringIO import StringIO 45 from StringIO import StringIO
46 46
47 from markup.core import Attributes, Namespace, Stream, StreamEventKind 47 from markup.core import Attributes, Namespace, Stream, StreamEventKind
48 from markup.core import START, END, START_NS, END_NS, TEXT
48 from markup.eval import Expression 49 from markup.eval import Expression
49 from markup.input import HTML, XMLParser, XML 50 from markup.input import XMLParser
50 from markup.path import Path 51 from markup.path import Path
51 52
52 __all__ = ['Context', 'BadDirectiveError', 'TemplateError', 53 __all__ = ['Context', 'BadDirectiveError', 'TemplateError',
53 'TemplateSyntaxError', 'TemplateNotFound', 'Template', 54 'TemplateSyntaxError', 'TemplateNotFound', 'Template',
54 'TemplateLoader'] 55 'TemplateLoader']
255 def __call__(self, stream, ctxt, directives): 256 def __call__(self, stream, ctxt, directives):
256 def _generate(): 257 def _generate():
257 kind, data, pos = stream.next() 258 kind, data, pos = stream.next()
258 if kind is Stream.START: 259 if kind is Stream.START:
259 yield kind, data, pos # emit start tag 260 yield kind, data, pos # emit start tag
260 yield Template.EXPR, self.expr, pos 261 yield EXPR, self.expr, pos
261 previous = stream.next() 262 previous = stream.next()
262 for event in stream: 263 for event in stream:
263 previous = event 264 previous = event
264 if previous is not None: 265 if previous is not None:
265 yield previous 266 yield previous
340 if args: 341 if args:
341 scope[name] = args.pop(0) 342 scope[name] = args.pop(0)
342 else: 343 else:
343 scope[name] = kwargs.pop(name, self.defaults.get(name)) 344 scope[name] = kwargs.pop(name, self.defaults.get(name))
344 ctxt.push(**scope) 345 ctxt.push(**scope)
345 stream = self._apply_directives(self.stream, ctxt, self.directives) 346 for event in self._apply_directives(self.stream, ctxt, self.directives):
346 for event in stream:
347 yield event 347 yield event
348 ctxt.pop() 348 ctxt.pop()
349 349
350 350
351 class ForDirective(Directive): 351 class ForDirective(Directive):
477 """ 477 """
478 __slots__ = [] 478 __slots__ = []
479 479
480 def __call__(self, stream, ctxt, directives): 480 def __call__(self, stream, ctxt, directives):
481 kind, data, pos = stream.next() 481 kind, data, pos = stream.next()
482 yield Template.EXPR, self.expr, pos 482 yield EXPR, self.expr, pos
483 483
484 484
485 class StripDirective(Directive): 485 class StripDirective(Directive):
486 """Implementation of the `py:strip` template directive. 486 """Implementation of the `py:strip` template directive.
487 487
678 ns_prefix = {} 678 ns_prefix = {}
679 depth = 0 679 depth = 0
680 680
681 for kind, data, pos in XMLParser(self.source, filename=self.filename): 681 for kind, data, pos in XMLParser(self.source, filename=self.filename):
682 682
683 if kind is Stream.START_NS: 683 if kind is START_NS:
684 # Strip out the namespace declaration for template directives 684 # Strip out the namespace declaration for template directives
685 prefix, uri = data 685 prefix, uri = data
686 if uri == self.NAMESPACE: 686 if uri == self.NAMESPACE:
687 ns_prefix[prefix] = uri 687 ns_prefix[prefix] = uri
688 else: 688 else:
689 stream.append((kind, data, pos)) 689 stream.append((kind, data, pos))
690 690
691 elif kind is Stream.END_NS: 691 elif kind is END_NS:
692 if data in ns_prefix: 692 if data in ns_prefix:
693 del ns_prefix[data] 693 del ns_prefix[data]
694 else: 694 else:
695 stream.append((kind, data, pos)) 695 stream.append((kind, data, pos))
696 696
697 elif kind is Stream.START: 697 elif kind is START:
698 # Record any directive attributes in start tags 698 # Record any directive attributes in start tags
699 tag, attrib = data 699 tag, attrib = data
700 directives = [] 700 directives = []
701 strip = False 701 strip = False
702 702
725 dirmap[(depth, tag)] = (directives, len(stream), strip) 725 dirmap[(depth, tag)] = (directives, len(stream), strip)
726 726
727 stream.append((kind, (tag, Attributes(new_attrib)), pos)) 727 stream.append((kind, (tag, Attributes(new_attrib)), pos))
728 depth += 1 728 depth += 1
729 729
730 elif kind is Stream.END: 730 elif kind is END:
731 depth -= 1 731 depth -= 1
732 stream.append((kind, data, pos)) 732 stream.append((kind, data, pos))
733 733
734 # If there have have directive attributes with the corresponding 734 # If there have have directive attributes with the corresponding
735 # start tag, move the events inbetween into a "subprogram" 735 # start tag, move the events inbetween into a "subprogram"
736 if (depth, data) in dirmap: 736 if (depth, data) in dirmap:
737 directives, start_offset, strip = dirmap.pop((depth, data)) 737 directives, start_offset, strip = dirmap.pop((depth, data))
738 substream = stream[start_offset:] 738 substream = stream[start_offset:]
739 if strip: 739 if strip:
740 substream = substream[1:-1] 740 substream = substream[1:-1]
741 stream[start_offset:] = [(Template.SUB, 741 stream[start_offset:] = [(SUB, (directives, substream),
742 (directives, substream), pos)] 742 pos)]
743 743
744 elif kind is Stream.TEXT: 744 elif kind is TEXT:
745 for kind, data, pos in self._interpolate(data, *pos): 745 for kind, data, pos in self._interpolate(data, *pos):
746 stream.append((kind, data, pos)) 746 stream.append((kind, data, pos))
747 747
748 else: 748 else:
749 stream.append((kind, data, pos)) 749 stream.append((kind, data, pos))
766 """ 766 """
767 patterns = [Template._FULL_EXPR_RE, Template._SHORT_EXPR_RE] 767 patterns = [Template._FULL_EXPR_RE, Template._SHORT_EXPR_RE]
768 def _interpolate(text): 768 def _interpolate(text):
769 for idx, group in enumerate(patterns.pop(0).split(text)): 769 for idx, group in enumerate(patterns.pop(0).split(text)):
770 if idx % 2: 770 if idx % 2:
771 yield Template.EXPR, Expression(group), (lineno, offset) 771 yield EXPR, Expression(group), (lineno, offset)
772 elif group: 772 elif group:
773 if patterns: 773 if patterns:
774 for result in _interpolate(group): 774 for result in _interpolate(group):
775 yield result 775 yield result
776 else: 776 else:
777 yield Stream.TEXT, group.replace('$$', '$'), \ 777 yield TEXT, group.replace('$$', '$'), (filename, lineno,
778 (filename, lineno, offset) 778 offset)
779 return _interpolate(text) 779 return _interpolate(text)
780 _interpolate = classmethod(_interpolate) 780 _interpolate = classmethod(_interpolate)
781 781
782 def generate(self, ctxt=None): 782 def generate(self, ctxt=None):
783 """Apply the template to the given context data. 783 """Apply the template to the given context data.
789 if ctxt is None: 789 if ctxt is None:
790 ctxt = Context() 790 ctxt = Context()
791 if not hasattr(ctxt, '_match_templates'): 791 if not hasattr(ctxt, '_match_templates'):
792 ctxt._match_templates = [] 792 ctxt._match_templates = []
793 793
794 stream = self._flatten(self._match(self._eval(self.stream, ctxt), ctxt), 794 stream = self.stream
795 ctxt) 795 for filter_ in [self._eval, self._match, self._flatten] + self.filters:
796 for filter_ in self.filters:
797 stream = filter_(iter(stream), ctxt) 796 stream = filter_(iter(stream), ctxt)
798 return Stream(stream) 797 return Stream(stream)
799 798
800 def _eval(self, stream, ctxt=None): 799 def _eval(self, stream, ctxt=None):
801 """Internal stream filter that evaluates any expressions in `START` and 800 """Internal stream filter that evaluates any expressions in `START` and
802 `TEXT` events. 801 `TEXT` events.
803 """ 802 """
804 for kind, data, pos in stream: 803 for kind, data, pos in stream:
805 804
806 if kind is Stream.START: 805 if kind is START:
807 # Attributes may still contain expressions in start tags at 806 # Attributes may still contain expressions in start tags at
808 # this point, so do some evaluation 807 # this point, so do some evaluation
809 tag, attrib = data 808 tag, attrib = data
810 new_attrib = [] 809 new_attrib = []
811 for name, substream in attrib: 810 for name, substream in attrib:
812 if isinstance(substream, basestring): 811 if isinstance(substream, basestring):
813 value = substream 812 value = substream
814 else: 813 else:
815 values = [] 814 values = []
816 for subkind, subdata, subpos in substream: 815 for subkind, subdata, subpos in substream:
817 if subkind is Template.EXPR: 816 if subkind is EXPR:
818 values.append(subdata.evaluate(ctxt)) 817 values.append(subdata.evaluate(ctxt))
819 else: 818 else:
820 values.append(subdata) 819 values.append(subdata)
821 value = [unicode(x) for x in values if x is not None] 820 value = [unicode(x) for x in values if x is not None]
822 if not value: 821 if not value:
823 continue 822 continue
824 new_attrib.append((name, u''.join(value))) 823 new_attrib.append((name, u''.join(value)))
825 yield kind, (tag, Attributes(new_attrib)), pos 824 yield kind, (tag, Attributes(new_attrib)), pos
826 825
827 elif kind is Template.EXPR: 826 elif kind is EXPR:
828 result = data.evaluate(ctxt) 827 result = data.evaluate(ctxt)
829 if result is None: 828 if result is None:
830 continue 829 continue
831 830
832 # First check for a string, otherwise the iterable test below 831 # First check for a string, otherwise the iterable test below
833 # succeeds, and the string will be chopped up into individual 832 # succeeds, and the string will be chopped up into individual
834 # characters 833 # characters
835 if isinstance(result, basestring): 834 if isinstance(result, basestring):
836 yield Stream.TEXT, result, pos 835 yield TEXT, result, pos
837 else: 836 else:
838 # Test if the expression evaluated to an iterable, in which 837 # Test if the expression evaluated to an iterable, in which
839 # case we yield the individual items 838 # case we yield the individual items
840 try: 839 try:
841 for event in self._match(self._eval(iter(result), ctxt), 840 for event in self._match(self._eval(iter(result), ctxt),
842 ctxt): 841 ctxt):
843 yield event 842 yield event
844 except TypeError: 843 except TypeError:
845 # Neither a string nor an iterable, so just pass it 844 # Neither a string nor an iterable, so just pass it
846 # through 845 # through
847 yield Stream.TEXT, unicode(result), pos 846 yield TEXT, unicode(result), pos
848 847
849 else: 848 else:
850 yield kind, data, pos 849 yield kind, data, pos
851 850
852 def _flatten(self, stream, ctxt=None): 851 def _flatten(self, stream, ctxt=None):
853 """Internal stream filter that expands `SUB` events in the stream.""" 852 """Internal stream filter that expands `SUB` events in the stream."""
854 try: 853 try:
855 for kind, data, pos in stream: 854 for kind, data, pos in stream:
856 if kind is Template.SUB: 855 if kind is SUB:
857 # This event is a list of directives and a list of nested 856 # This event is a list of directives and a list of nested
858 # events to which those directives should be applied 857 # events to which those directives should be applied
859 directives, substream = data 858 directives, substream = data
860 substream = directives[0](iter(substream), ctxt, directives[1:]) 859 if directives:
861 substream = self._match(self._eval(substream, ctxt), ctxt) 860 substream = directives[0](iter(substream), ctxt,
862 for event in self._flatten(substream, ctxt): 861 directives[1:])
862 for filter_ in (self._eval, self._match, self._flatten):
863 substream = filter_(substream, ctxt)
864 for event in substream:
863 yield event 865 yield event
864 continue 866 continue
865 else: 867 else:
866 yield kind, data, pos 868 yield kind, data, pos
867 except SyntaxError, err: 869 except SyntaxError, err:
868 raise TemplateSyntaxError(err, self.filename, pos[1], 870 raise TemplateSyntaxError(err, pos[0], pos[1],
869 pos[2] + (err.offset or 0)) 871 pos[2] + (err.offset or 0))
870 872
871 def _match(self, stream, ctxt=None, match_templates=None): 873 def _match(self, stream, ctxt=None, match_templates=None):
872 """Internal stream filter that applies any defined match templates 874 """Internal stream filter that applies any defined match templates
873 to the stream. 875 to the stream.
877 879
878 for kind, data, pos in stream: 880 for kind, data, pos in stream:
879 881
880 # We (currently) only care about start and end events for matching 882 # We (currently) only care about start and end events for matching
881 # We might care about namespace events in the future, though 883 # We might care about namespace events in the future, though
882 if kind not in (Stream.START, Stream.END): 884 if kind not in (START, END):
883 yield kind, data, pos 885 yield kind, data, pos
884 continue 886 continue
885 887
886 for idx, (test, path, template, directives) in \ 888 for idx, (test, path, template, directives) in \
887 enumerate(match_templates): 889 enumerate(match_templates):
891 # Consume and store all events until an end event 893 # Consume and store all events until an end event
892 # corresponding to this start event is encountered 894 # corresponding to this start event is encountered
893 content = [(kind, data, pos)] 895 content = [(kind, data, pos)]
894 depth = 1 896 depth = 1
895 while depth > 0: 897 while depth > 0:
896 event = stream.next() 898 ev = stream.next()
897 if event[0] is Stream.START: 899 depth += {START: 1, END: -1}.get(ev[0], 0)
898 depth += 1 900 content.append(ev)
899 elif event[0] is Stream.END: 901 test(*ev)
900 depth -= 1
901 content.append(event)
902
903 # enable the path to keep track of the stream state
904 test(*event)
905 902
906 content = list(self._flatten(content, ctxt)) 903 content = list(self._flatten(content, ctxt))
907 ctxt.push(select=lambda path: Stream(content).select(path)) 904 ctxt.push(select=lambda path: Stream(content).select(path))
908 905
909 if directives: 906 if directives:
910 template = directives[0](iter(template), ctxt, 907 template = directives[0](iter(template), ctxt,
911 directives[1:]) 908 directives[1:])
912 template = self._match(self._eval(iter(template), ctxt), 909 for event in self._match(self._eval(template, ctxt),
913 ctxt, match_templates[:idx] + 910 ctxt, match_templates[:idx] +
914 match_templates[idx + 1:]) 911 match_templates[idx + 1:]):
915 for event in template:
916 yield event 912 yield event
917 ctxt.pop() 913 ctxt.pop()
918 914
919 break 915 break
920 else: 916
917 else: # no matches
921 yield kind, data, pos 918 yield kind, data, pos
919
920
921 EXPR = Template.EXPR
922 SUB = Template.SUB
922 923
923 924
924 class TemplateLoader(object): 925 class TemplateLoader(object):
925 """Responsible for loading templates from files on the specified search 926 """Responsible for loading templates from files on the specified search
926 path. 927 path.
988 @param filename: the relative path of the template file to load 989 @param filename: the relative path of the template file to load
989 @param relative_to: the filename of the template from which the new 990 @param relative_to: the filename of the template from which the new
990 template is being loaded, or `None` if the template is being loaded 991 template is being loaded, or `None` if the template is being loaded
991 directly 992 directly
992 """ 993 """
994 from markup.filters import IncludeFilter
995
993 if relative_to: 996 if relative_to:
994 filename = posixpath.join(posixpath.dirname(relative_to), filename) 997 filename = posixpath.join(posixpath.dirname(relative_to), filename)
995 filename = os.path.normpath(filename) 998 filename = os.path.normpath(filename)
996 999
997 # First check the cache to avoid reparsing the same file 1000 # First check the cache to avoid reparsing the same file
1011 for dirname in search_path: 1014 for dirname in search_path:
1012 filepath = os.path.join(dirname, filename) 1015 filepath = os.path.join(dirname, filename)
1013 try: 1016 try:
1014 fileobj = file(filepath, 'rt') 1017 fileobj = file(filepath, 'rt')
1015 try: 1018 try:
1016 from markup.filters import IncludeFilter
1017 tmpl = Template(fileobj, basedir=dirname, filename=filename) 1019 tmpl = Template(fileobj, basedir=dirname, filename=filename)
1018 tmpl.filters.append(IncludeFilter(self)) 1020 tmpl.filters.append(IncludeFilter(self))
1019 finally: 1021 finally:
1020 fileobj.close() 1022 fileobj.close()
1021 self._cache[filename] = tmpl 1023 self._cache[filename] = tmpl
Copyright (C) 2012-2017 Edgewall Software