Mercurial > genshi > mirror
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 |