Mercurial > genshi > mirror
changeset 504:a2dca8066a5a trunk
Renamed the transformation classes so that their role is clarified; extended the example in the package docstring to show chaining.
author | cmlenz |
---|---|
date | Tue, 05 Jun 2007 09:33:00 +0000 |
parents | 2e1dbb344b89 |
children | 1cc1c38c6d6d |
files | genshi/filters/transform.py |
diffstat | 1 files changed, 119 insertions(+), 99 deletions(-) [+] |
line wrap: on
line diff
--- a/genshi/filters/transform.py +++ b/genshi/filters/transform.py @@ -25,10 +25,21 @@ For example, the following transformation removes the ``<title>`` element from the ``<head>`` of the input document: ->>> html = HTML('<html><head><title>Some Title</title></head>' -... '<body>Some <em>body</em> text.</body></html>') ->>> print html | Transformer('head/title').remove() -<html><head/><body>Some <em>body</em> text.</body></html> +>>> from genshi.builder import tag +>>> html = HTML('''<html> +... <head><title>Some Title</title></head> +... <body> +... Some <em>body</em> text. +... </body> +... </html>''') +>>> print html | Transformer('body/em').apply(unicode.upper, TEXT) \\ +... .unwrap().wrap(tag.u) +<html> + <head><title>Some Title</title></head> + <body> + Some <u>BODY</u> text. + </body> +</html> The ``Transformer`` support a large number of useful transformations out of the box, but custom transformations can be added easily. @@ -36,11 +47,12 @@ import sys -from genshi.path import Path from genshi.builder import Element -from genshi.core import Stream, Attrs, QName, TEXT, START, END +from genshi.core import Stream, Attrs, QName, TEXT, START, END, _ensure +from genshi.path import Path -__all__ = ['Transformer', 'Injector', 'ENTER', 'EXIT', 'INSIDE', 'OUTSIDE'] +__all__ = ['Transformer', 'InjectorTransformation', 'ENTER', 'EXIT', 'INSIDE', + 'OUTSIDE'] class TransformMark(str): @@ -123,7 +135,7 @@ class="emphasis">body</em> text.</body></html> """ - __slots__ = ('transforms',) + __slots__ = ['transforms'] def __init__(self, path=None): """Construct a new transformation filter. @@ -132,7 +144,7 @@ """ self.transforms = [] if path: - self.transforms.append(Select(path)) + self.transforms.append(SelectTransformation(path)) def __call__(self, stream): """Apply the transform filter to the marked stream. @@ -164,13 +176,13 @@ >>> print short_stream | (Transformer('.//em/text()') | upper) <body>Some <em>TEST</em> text</body> """ - transform = Transformer() - transform.transforms = self.transforms[:] + transformer = Transformer() + transformer.transforms = self.transforms[:] if isinstance(function, Transformer): - transform.transforms.extend(function.transforms) + transformer.transforms.extend(function.transforms) else: - transform.transforms.append(function) - return transform + transformer.transforms.append(function) + return transformer #{ Selection operations @@ -192,7 +204,7 @@ :return: the stream augmented by transformation marks :rtype: `Transformer` """ - return self | Select(path) + return self | SelectTransformation(path) def invert(self): """Invert selection so that marked events become unmarked, and vice @@ -214,7 +226,7 @@ :rtype: `Transformer` """ - return self | invert + return self | InvertTransformation() #{ Deletion operations @@ -231,7 +243,7 @@ :rtype: `Transformer` """ - return self | empty + return self | EmptyTransformation() def remove(self): """Remove selection from the stream. @@ -246,12 +258,12 @@ :rtype: `Transformer` """ - return self | remove + return self | RemoveTransformation() #{ Direct element operations def unwrap(self): - """Remove outtermost enclosing elements from selection. + """Remove outermost enclosing elements from selection. Example: @@ -263,8 +275,11 @@ :rtype: `Transformer` """ - return self | unwrap - + def _unwrap(stream): + for mark, event in stream: + if mark not in (ENTER, EXIT): + yield mark, event + return self | UnwrapTransformation() def wrap(self, element): """Wrap selection in an element. @@ -275,10 +290,10 @@ <html><head><title>Some Title</title></head><body>Some <strong><em>body</em></strong> text.</body></html> - :param element: Either a string tag name or a Genshi builder element. + :param element: either a tag name (as string) or an `Element` object :rtype: `Transformer` """ - return self | Wrap(element) + return self | WrapTransformation(element) #{ Content insertion operations @@ -294,7 +309,7 @@ :param content: Either an iterable of events or a string to insert. :rtype: `Transformer` """ - return self | Replace(content) + return self | ReplaceTransformation(content) def before(self, content): """Insert content before selection. @@ -311,7 +326,7 @@ :param content: Either an iterable of events or a string to insert. :rtype: `Transformer` """ - return self | Before(content) + return self | BeforeTransformation(content) def after(self, content): """Insert content after selection. @@ -327,7 +342,7 @@ :param content: Either an iterable of events or a string to insert. :rtype: `Transformer` """ - return self | After(content) + return self | AfterTransformation(content) def prepend(self, content): """Insert content after the ENTER event of the selection. @@ -343,7 +358,7 @@ :param content: Either an iterable of events or a string to insert. :rtype: `Transformer` """ - return self | Prepend(content) + return self | PrependTransformation(content) def append(self, content): """Insert content before the END event of the selection. @@ -357,7 +372,7 @@ :param content: Either an iterable of events or a string to insert. :rtype: `Transformer` """ - return self | Append(content) + return self | AppendTransformation(content) #{ Attribute manipulation @@ -376,7 +391,7 @@ :param value: the value that should be set for the attribute :rtype: `Transformer` """ - return self | SetAttr(name, value) + return self | SetattrTransformation(name, value) def delattr(self, name): """Remove an attribute from selected elements. @@ -393,7 +408,7 @@ :param name: the name of the attribute to remove :rtype: `Transformer` """ - return self | DelAttr(name) + return self | DelattrTransformation(name) #{ Buffer operations @@ -414,7 +429,7 @@ :rtype: `Transformer` :note: this transformation will buffer the entire input stream """ - return self | Copy(buffer) + return self | CopyTransformation(buffer) def cut(self, buffer): """Copy selection into buffer and remove the selection from the stream. @@ -433,7 +448,7 @@ :rtype: `Transformer` :note: this transformation will buffer the entire input stream """ - return self | Cut(buffer) + return self | CutTransformation(buffer) #{ Miscellaneous operations @@ -441,10 +456,9 @@ """Apply a function to the ``data`` element of events of ``kind`` in the selection. - >>> import string >>> html = HTML('<html><head><title>Some Title</title></head>' ... '<body>Some <em>body</em> text.</body></html>') - >>> print html | Transformer('head/title').apply(string.upper, TEXT) + >>> print html | Transformer('head/title').apply(unicode.upper, TEXT) <html><head><title>SOME TITLE</title></head><body>Some <em>body</em> text.</body></html> @@ -452,7 +466,7 @@ :param kind: the kind of event the function should be applied to :rtype: `Transformer` """ - return self | Apply(function, kind) + return self | ApplyTransformation(function, kind) def trace(self, prefix='', fileobj=None): """Print events as they pass through the transform. @@ -473,7 +487,7 @@ the standard output stream :rtype: `Transformer` """ - return self | Trace(prefix, fileobj=fileobj) + return self | TraceTransformation(prefix, fileobj=fileobj) # Internal methods @@ -486,7 +500,7 @@ yield event -class Select(object): +class SelectTransformation(object): """Select and mark events that match an XPath expression.""" def __init__(self, path): @@ -532,49 +546,65 @@ yield None, event -def invert(stream): +class InvertTransformation(object): """Invert selection so that marked events become unmarked, and vice versa. Specificaly, all input marks are converted to null marks, and all input null marks are converted to OUTSIDE marks. - - :param stream: the marked event stream to filter - """ - for mark, event in stream: - if mark: - yield None, event - else: - yield OUTSIDE, event - -def empty(stream): - """Empty selected elements of all content. - - :param stream: the marked event stream to filter """ - for mark, event in stream: - if mark not in (INSIDE, OUTSIDE): - yield mark, event - -def remove(stream): - """Remove selection from the stream. - :param stream: the marked event stream to filter - """ - for mark, event in stream: - if mark is None: - yield mark, event + def __call__(self, stream): + """Apply the transform filter to the marked stream. -def unwrap(stream): - """Remove outtermost enclosing elements from selection. - - :param stream: the marked event stream to filter - """ - for mark, event in stream: - if mark not in (ENTER, EXIT): - yield mark, event + :param stream: the marked event stream to filter + """ + for mark, event in stream: + if mark: + yield None, event + else: + yield OUTSIDE, event -class Wrap(object): +class EmptyTransformation(object): + """Empty selected elements of all content.""" + + def __call__(self, stream): + """Apply the transform filter to the marked stream. + + :param stream: the marked event stream to filter + """ + for mark, event in stream: + if mark not in (INSIDE, OUTSIDE): + yield mark, event + + +class RemoveTransformation(object): + """Remove selection from the stream.""" + + def __call__(self, stream): + """Apply the transform filter to the marked stream. + + :param stream: the marked event stream to filter + """ + for mark, event in stream: + if mark is None: + yield mark, event + + +class UnwrapTransformation(object): + """Remove outtermost enclosing elements from selection.""" + + def __call__(self, stream): + """Apply the transform filter to the marked stream. + + :param stream: the marked event stream to filter + """ + for mark, event in stream: + if mark not in (ENTER, EXIT): + yield mark, event + + +class WrapTransformation(object): """Wrap selection in an element.""" def __init__(self, element): @@ -601,7 +631,7 @@ yield mark, event -class Trace(object): +class TraceTransformation(object): """Print events as they pass through the transform.""" def __init__(self, prefix='', fileobj=None): @@ -623,7 +653,7 @@ yield event -class Apply(object): +class ApplyTransformation(object): """Apply a function to the `data` element of events of ``kind`` in the selection. """ @@ -644,17 +674,17 @@ :param stream: The marked event stream to filter """ for mark, (kind, data, pos) in stream: - if mark and kind == self.kind: + if mark and self.kind in (None, kind): yield mark, (kind, self.function(data), pos) else: yield mark, (kind, data, pos) -class Injector(object): +class InjectorTransformation(object): """Abstract base class for transformations that inject content into a stream. - >>> class Top(Injector): + >>> class Top(InjectorTransformation): ... def __call__(self, stream): ... for event in self._inject(): ... yield event @@ -673,14 +703,11 @@ self.content = content def _inject(self): - if isinstance(self.content, basestring): - yield None, (TEXT, self.content, (None, -1, -1)) - else: - for event in self.content: - yield None, event + for event in _ensure(self.content): + yield None, event -class Replace(Injector): +class ReplaceTransformation(InjectorTransformation): """Replace selection with content.""" def __call__(self, stream): @@ -701,7 +728,7 @@ yield mark, event -class Before(Injector): +class BeforeTransformation(InjectorTransformation): """Insert content before selection.""" def __call__(self, stream): @@ -716,7 +743,7 @@ yield mark, event -class After(Injector): +class AfterTransformation(InjectorTransformation): """Insert content after selection.""" def __call__(self, stream): @@ -737,7 +764,7 @@ yield mark, event -class Prepend(Injector): +class PrependTransformation(InjectorTransformation): """Prepend content to the inside of selected elements.""" def __call__(self, stream): @@ -752,7 +779,7 @@ yield subevent -class Append(Injector): +class AppendTransformation(InjectorTransformation): """Append content after the content of selected elements.""" def __call__(self, stream): @@ -773,7 +800,7 @@ yield mark, event -class SetAttr(object): +class SetattrTransformation(object): """Set an attribute on selected elements.""" def __init__(self, name, value): @@ -796,7 +823,7 @@ yield mark, (kind, data, pos) -class DelAttr(object): +class DelattrTransformation(object): """Delete an attribute of selected elements.""" def __init__(self, name): @@ -817,7 +844,7 @@ yield mark, (kind, data, pos) -class Copy(object): +class CopyTransformation(object): """Copy selected events into a buffer for later insertion.""" def __init__(self, buffer): @@ -840,7 +867,7 @@ return stream -class Cut(Copy): +class CutTransformation(CopyTransformation, RemoveTransformation): """Cut selected events into a buffer for later insertion and remove the selection. """ @@ -850,12 +877,5 @@ :param stream: the marked event stream to filter """ - stream = Copy.__call__(self, stream) - return remove(stream) - - -if __name__ == '__main__': - import doctest - from genshi.input import HTML - doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE, - extraglobs={'HTML': HTML}) + stream = CopyTransformation.__call__(self, stream) + return RemoveTransformation.__call__(self, stream)