Mercurial > genshi > mirror
comparison genshi/filters/transform.py @ 720:acf7c5ee36e7 experimental-newctxt
newctxt branch: Merged revisions [678:835] via svnmerge from [source:trunk].
author | cmlenz |
---|---|
date | Fri, 11 Apr 2008 08:42:11 +0000 |
parents | 4d486f15c986 |
children |
comparison
equal
deleted
inserted
replaced
567:837786a584d5 | 720:acf7c5ee36e7 |
---|---|
41 </body> | 41 </body> |
42 </html> | 42 </html> |
43 | 43 |
44 The ``Transformer`` support a large number of useful transformations out of the | 44 The ``Transformer`` support a large number of useful transformations out of the |
45 box, but custom transformations can be added easily. | 45 box, but custom transformations can be added easily. |
46 | |
47 :since: version 0.5 | |
46 """ | 48 """ |
47 | 49 |
48 import re | 50 import re |
49 import sys | 51 import sys |
50 | 52 |
51 from genshi.builder import Element | 53 from genshi.builder import Element |
52 from genshi.core import Stream, Attrs, QName, TEXT, START, END, _ensure | 54 from genshi.core import Stream, Attrs, QName, TEXT, START, END, _ensure, Markup |
53 from genshi.path import Path | 55 from genshi.path import Path |
54 | 56 |
55 __all__ = ['Transformer', 'StreamBuffer', 'InjectorTransformation', 'ENTER', | 57 __all__ = ['Transformer', 'StreamBuffer', 'InjectorTransformation', 'ENTER', |
56 'EXIT', 'INSIDE', 'OUTSIDE'] | 58 'EXIT', 'INSIDE', 'OUTSIDE'] |
57 | 59 |
139 class="emphasis">body</em> text.</body></html> | 141 class="emphasis">body</em> text.</body></html> |
140 """ | 142 """ |
141 | 143 |
142 __slots__ = ['transforms'] | 144 __slots__ = ['transforms'] |
143 | 145 |
144 def __init__(self, path=None): | 146 def __init__(self, path='.'): |
145 """Construct a new transformation filter. | 147 """Construct a new transformation filter. |
146 | 148 |
147 :param path: an XPath expression (as string) or a `Path` instance | 149 :param path: an XPath expression (as string) or a `Path` instance |
148 """ | 150 """ |
149 self.transforms = [] | 151 self.transforms = [SelectTransformation(path)] |
150 if path: | |
151 self.transforms.append(SelectTransformation(path)) | |
152 | 152 |
153 def __call__(self, stream): | 153 def __call__(self, stream): |
154 """Apply the transform filter to the marked stream. | 154 """Apply the transform filter to the marked stream. |
155 | 155 |
156 :param stream: the marked event stream to filter | 156 :param stream: the marked event stream to filter |
158 :rtype: `Stream` | 158 :rtype: `Stream` |
159 """ | 159 """ |
160 transforms = self._mark(stream) | 160 transforms = self._mark(stream) |
161 for link in self.transforms: | 161 for link in self.transforms: |
162 transforms = link(transforms) | 162 transforms = link(transforms) |
163 return Stream(self._unmark(transforms)) | 163 return Stream(self._unmark(transforms), |
164 serializer=getattr(stream, 'serializer', None)) | |
164 | 165 |
165 def apply(self, function): | 166 def apply(self, function): |
166 """Apply a transformation to the stream. | 167 """Apply a transformation to the stream. |
167 | 168 |
168 Transformations can be chained, similar to stream filters. Any callable | 169 Transformations can be chained, similar to stream filters. Any callable |
546 | 547 |
547 >>> html = HTML('<html><body>Some text, some more text and ' | 548 >>> html = HTML('<html><body>Some text, some more text and ' |
548 ... '<b>some bold text</b></body></html>') | 549 ... '<b>some bold text</b></body></html>') |
549 >>> print html | Transformer('body').substitute('(?i)some', 'SOME') | 550 >>> print html | Transformer('body').substitute('(?i)some', 'SOME') |
550 <html><body>SOME text, some more text and <b>SOME bold text</b></body></html> | 551 <html><body>SOME text, some more text and <b>SOME bold text</b></body></html> |
552 >>> tags = tag.html(tag.body('Some text, some more text and ', | |
553 ... Markup('<b>some bold text</b>'))) | |
554 >>> print tags.generate() | Transformer('body').substitute('(?i)some', 'SOME') | |
555 <html><body>SOME text, some more text and <b>SOME bold text</b></body></html> | |
551 | 556 |
552 :param pattern: A regular expression object or string. | 557 :param pattern: A regular expression object or string. |
553 :param replace: Replacement pattern. | 558 :param replace: Replacement pattern. |
554 :param count: Number of replacements to make in each text fragment. | 559 :param count: Number of replacements to make in each text fragment. |
555 :rtype: `Transformer` | 560 :rtype: `Transformer` |
556 """ | 561 """ |
557 return self.apply(SubstituteTransformation(pattern, replace, count)) | 562 return self.apply(SubstituteTransformation(pattern, replace, count)) |
563 | |
564 def rename(self, name): | |
565 """Rename matching elements. | |
566 | |
567 >>> html = HTML('<html><body>Some text, some more text and ' | |
568 ... '<b>some bold text</b></body></html>') | |
569 >>> print html | Transformer('body/b').rename('strong') | |
570 <html><body>Some text, some more text and <strong>some bold text</strong></body></html> | |
571 """ | |
572 return self.apply(RenameTransformation(name)) | |
558 | 573 |
559 def trace(self, prefix='', fileobj=None): | 574 def trace(self, prefix='', fileobj=None): |
560 """Print events as they pass through the transform. | 575 """Print events as they pass through the transform. |
561 | 576 |
562 >>> html = HTML('<body>Some <em>test</em> text</body>') | 577 >>> html = HTML('<body>Some <em>test</em> text</body>') |
730 element = list(self.element.generate()) | 745 element = list(self.element.generate()) |
731 for prefix in element[:-1]: | 746 for prefix in element[:-1]: |
732 yield None, prefix | 747 yield None, prefix |
733 yield mark, event | 748 yield mark, event |
734 while True: | 749 while True: |
735 mark, event = stream.next() | 750 try: |
751 mark, event = stream.next() | |
752 except StopIteration: | |
753 yield None, element[-1] | |
736 if not mark: | 754 if not mark: |
737 break | 755 break |
738 yield mark, event | 756 yield mark, event |
739 yield None, element[-1] | 757 yield None, element[-1] |
740 yield mark, event | 758 yield mark, event |
789 queue = [] | 807 queue = [] |
790 for mark, event in stream: | 808 for mark, event in stream: |
791 if mark: | 809 if mark: |
792 queue.append(event) | 810 queue.append(event) |
793 else: | 811 else: |
794 for event in flush(queue): | 812 for queue_event in flush(queue): |
795 yield event | 813 yield queue_event |
796 yield None, event | 814 yield None, event |
797 for event in flush(queue): | 815 for event in flush(queue): |
798 yield event | 816 yield event |
799 | 817 |
800 | 818 |
849 | 867 |
850 :param stream: The marked event stream to filter | 868 :param stream: The marked event stream to filter |
851 """ | 869 """ |
852 for mark, (kind, data, pos) in stream: | 870 for mark, (kind, data, pos) in stream: |
853 if kind is TEXT: | 871 if kind is TEXT: |
854 data = self.pattern.sub(self.replace, data, self.count) | 872 new_data = self.pattern.sub(self.replace, data, self.count) |
873 if isinstance(data, Markup): | |
874 data = Markup(new_data) | |
875 else: | |
876 data = new_data | |
877 yield mark, (kind, data, pos) | |
878 | |
879 | |
880 class RenameTransformation(object): | |
881 """Rename matching elements.""" | |
882 def __init__(self, name): | |
883 """Create the transform. | |
884 | |
885 :param name: New element name. | |
886 """ | |
887 self.name = QName(name) | |
888 | |
889 def __call__(self, stream): | |
890 """Apply the transform filter to the marked stream. | |
891 | |
892 :param stream: The marked event stream to filter | |
893 """ | |
894 for mark, (kind, data, pos) in stream: | |
895 if mark is ENTER: | |
896 data = self.name, data[1] | |
897 elif mark is EXIT: | |
898 data = self.name | |
855 yield mark, (kind, data, pos) | 899 yield mark, (kind, data, pos) |
856 | 900 |
857 | 901 |
858 class InjectorTransformation(object): | 902 class InjectorTransformation(object): |
859 """Abstract base class for transformations that inject content into a | 903 """Abstract base class for transformations that inject content into a |
910 """Apply the transform filter to the marked stream. | 954 """Apply the transform filter to the marked stream. |
911 | 955 |
912 :param stream: The marked event stream to filter | 956 :param stream: The marked event stream to filter |
913 """ | 957 """ |
914 for mark, event in stream: | 958 for mark, event in stream: |
915 if mark in (ENTER, OUTSIDE): | 959 if mark is not None: |
916 for subevent in self._inject(): | 960 for subevent in self._inject(): |
917 yield subevent | 961 yield subevent |
962 yield mark, event | |
963 while True: | |
964 mark, event = stream.next() | |
965 if not mark: | |
966 break | |
967 yield mark, event | |
918 yield mark, event | 968 yield mark, event |
919 | 969 |
920 | 970 |
921 class AfterTransformation(InjectorTransformation): | 971 class AfterTransformation(InjectorTransformation): |
922 """Insert content after selection.""" | 972 """Insert content after selection.""" |
928 """ | 978 """ |
929 for mark, event in stream: | 979 for mark, event in stream: |
930 yield mark, event | 980 yield mark, event |
931 if mark: | 981 if mark: |
932 while True: | 982 while True: |
933 mark, event = stream.next() | 983 try: |
984 mark, event = stream.next() | |
985 except StopIteration: | |
986 break | |
934 if not mark: | 987 if not mark: |
935 break | 988 break |
936 yield mark, event | 989 yield mark, event |
937 for subevent in self._inject(): | 990 for subevent in self._inject(): |
938 yield subevent | 991 yield subevent |