diff genshi/filters/tests/transform.py @ 820:1837f39efd6f experimental-inline

Sync (old) experimental inline branch with trunk@1027.
author cmlenz
date Wed, 11 Mar 2009 17:51:06 +0000
parents
children 09cc3627654c
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/genshi/filters/tests/transform.py
@@ -0,0 +1,1497 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+import doctest
+from pprint import pprint
+import unittest
+
+from genshi import HTML
+from genshi.builder import Element
+from genshi.core import START, END, TEXT, QName, Attrs
+from genshi.filters.transform import Transformer, StreamBuffer, ENTER, EXIT, \
+                                     OUTSIDE, INSIDE, ATTR, BREAK
+import genshi.filters.transform
+
+
+FOO = '<root>ROOT<foo name="foo">FOO</foo></root>'
+FOOBAR = '<root>ROOT<foo name="foo" size="100">FOO</foo><bar name="bar">BAR</bar></root>'
+
+
+def _simplify(stream, with_attrs=False):
+    """Simplify a marked stream."""
+    def _generate():
+        for mark, (kind, data, pos) in stream:
+            if kind is START:
+                if with_attrs:
+                    data = (unicode(data[0]), dict((unicode(k), v)
+                                                   for k, v in data[1]))
+                else:
+                    data = unicode(data[0])
+            elif kind is END:
+                data = unicode(data)
+            elif kind is ATTR:
+                kind = ATTR
+                data = dict((unicode(k), v) for k, v in data[1])
+            yield mark, kind, data
+    return list(_generate())
+
+
+def _transform(html, transformer, with_attrs=False):
+    """Apply transformation returning simplified marked stream."""
+    if isinstance(html, basestring):
+        html = HTML(html)
+    stream = transformer(html, keep_marks=True)
+    return _simplify(stream, with_attrs)
+
+
+class SelectTest(unittest.TestCase):
+    """Test .select()"""
+    def _select(self, select):
+        html = HTML(FOOBAR)
+        if isinstance(select, basestring):
+            select = [select]
+        transformer = Transformer(select[0])
+        for sel in select[1:]:
+            transformer = transformer.select(sel)
+        return _transform(html, transformer)
+
+    def test_select_single_element(self):
+        self.assertEqual(
+            self._select('foo'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')],
+            )
+
+    def test_select_context(self):
+        self.assertEqual(
+            self._select('.'),
+            [(ENTER, START, u'root'),
+             (INSIDE, TEXT, u'ROOT'),
+             (INSIDE, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (INSIDE, END, u'foo'),
+             (INSIDE, START, u'bar'),
+             (INSIDE, TEXT, u'BAR'),
+             (INSIDE, END, u'bar'),
+             (EXIT, END, u'root')]
+            )
+
+    def test_select_inside_select(self):
+        self.assertEqual(
+            self._select(['.', 'foo']),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')],
+            )
+
+    def test_select_text(self):
+        self.assertEqual(
+            self._select('*/text()'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, START, u'foo'),
+             (OUTSIDE, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (None, START, u'bar'),
+             (OUTSIDE, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')],
+            )
+
+    def test_select_attr(self):
+        self.assertEqual(
+            self._select('foo/@name'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ATTR, ATTR, {'name': u'foo'}),
+             (None, START, u'foo'),
+             (None, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_select_text_context(self):
+        self.assertEqual(
+            list(Transformer('.')(HTML('foo'), keep_marks=True)),
+            [('OUTSIDE', ('TEXT', u'foo', (None, 1, 0)))],
+            )
+
+
+class InvertTest(unittest.TestCase):
+    def _invert(self, select):
+        return _transform(FOO, Transformer(select).invert())
+
+    def test_invert_element(self):
+        self.assertEqual(
+            self._invert('foo'),
+            [(OUTSIDE, START, u'root'),
+             (OUTSIDE, TEXT, u'ROOT'),
+             (None, START, u'foo'),
+             (None, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (OUTSIDE, END, u'root')]
+            )
+
+    def test_invert_inverted_element(self):
+        self.assertEqual(
+            _transform(FOO, Transformer('foo').invert().invert()),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (OUTSIDE, START, u'foo'),
+             (OUTSIDE, TEXT, u'FOO'),
+             (OUTSIDE, END, u'foo'),
+             (None, END, u'root')]
+            )
+
+    def test_invert_text(self):
+        self.assertEqual(
+            self._invert('foo/text()'),
+            [(OUTSIDE, START, u'root'),
+             (OUTSIDE, TEXT, u'ROOT'),
+             (OUTSIDE, START, u'foo'),
+             (None, TEXT, u'FOO'),
+             (OUTSIDE, END, u'foo'),
+             (OUTSIDE, END, u'root')]
+            )
+
+    def test_invert_attribute(self):
+        self.assertEqual(
+            self._invert('foo/@name'),
+            [(OUTSIDE, START, u'root'),
+             (OUTSIDE, TEXT, u'ROOT'),
+             (None, ATTR, {'name': u'foo'}),
+             (OUTSIDE, START, u'foo'),
+             (OUTSIDE, TEXT, u'FOO'),
+             (OUTSIDE, END, u'foo'),
+             (OUTSIDE, END, u'root')]
+            )
+
+    def test_invert_context(self):
+        self.assertEqual(
+            self._invert('.'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, START, u'foo'),
+             (None, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (None, END, u'root')]
+            )
+
+    def test_invert_text_context(self):
+        self.assertEqual(
+            _simplify(Transformer('.').invert()(HTML('foo'), keep_marks=True)),
+            [(None, 'TEXT', u'foo')],
+            )
+
+
+
+class EndTest(unittest.TestCase):
+    def test_end(self):
+        stream = _transform(FOO, Transformer('foo').end())
+        self.assertEqual(
+            stream,
+            [(OUTSIDE, START, u'root'),
+             (OUTSIDE, TEXT, u'ROOT'),
+             (OUTSIDE, START, u'foo'),
+             (OUTSIDE, TEXT, u'FOO'),
+             (OUTSIDE, END, u'foo'),
+             (OUTSIDE, END, u'root')]
+            )
+
+
+class EmptyTest(unittest.TestCase):
+    def _empty(self, select):
+        return _transform(FOO, Transformer(select).empty())
+
+    def test_empty_element(self):
+        self.assertEqual(
+            self._empty('foo'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, u'foo'),
+             (EXIT, END, u'foo'),
+             (None, END, u'root')],
+            )
+
+    def test_empty_text(self):
+        self.assertEqual(
+            self._empty('foo/text()'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, START, u'foo'),
+             (OUTSIDE, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (None, END, u'root')]
+            )
+
+    def test_empty_attr(self):
+        self.assertEqual(
+            self._empty('foo/@name'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ATTR, ATTR, {'name': u'foo'}),
+             (None, START, u'foo'),
+             (None, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (None, END, u'root')]
+            )
+
+    def test_empty_context(self):
+        self.assertEqual(
+            self._empty('.'),
+            [(ENTER, START, u'root'),
+             (EXIT, END, u'root')]
+            )
+
+    def test_empty_text_context(self):
+        self.assertEqual(
+            _simplify(Transformer('.')(HTML('foo'), keep_marks=True)),
+            [(OUTSIDE, TEXT, u'foo')],
+            )
+
+
+class RemoveTest(unittest.TestCase):
+    def _remove(self, select):
+        return _transform(FOO, Transformer(select).remove())
+
+    def test_remove_element(self):
+        self.assertEqual(
+            self._remove('foo|bar'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, END, u'root')]
+            )
+
+    def test_remove_text(self):
+        self.assertEqual(
+            self._remove('//text()'),
+            [(None, START, u'root'),
+             (None, START, u'foo'),
+             (None, END, u'foo'),
+             (None, END, u'root')]
+            )
+
+    def test_remove_attr(self):
+        self.assertEqual(
+            self._remove('foo/@name'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, START, u'foo'),
+             (None, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (None, END, u'root')]
+            )
+
+    def test_remove_context(self):
+        self.assertEqual(
+            self._remove('.'),
+            [],
+            )
+
+    def test_remove_text_context(self):
+        self.assertEqual(
+            _transform('foo', Transformer('.').remove()),
+            [],
+            )
+
+
+class UnwrapText(unittest.TestCase):
+    def _unwrap(self, select):
+        return _transform(FOO, Transformer(select).unwrap())
+
+    def test_unwrap_element(self):
+        self.assertEqual(
+            self._unwrap('foo'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (INSIDE, TEXT, u'FOO'),
+             (None, END, u'root')]
+            )
+
+    def test_unwrap_text(self):
+        self.assertEqual(
+            self._unwrap('foo/text()'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, START, u'foo'),
+             (OUTSIDE, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (None, END, u'root')]
+            )
+
+    def test_unwrap_attr(self):
+        self.assertEqual(
+            self._unwrap('foo/@name'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ATTR, ATTR, {'name': u'foo'}),
+             (None, START, u'foo'),
+             (None, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (None, END, u'root')]
+            )
+
+    def test_unwrap_adjacent(self):
+        self.assertEqual(
+            _transform(FOOBAR, Transformer('foo|bar').unwrap()),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (INSIDE, TEXT, u'FOO'),
+             (INSIDE, TEXT, u'BAR'),
+             (None, END, u'root')]
+            )
+
+    def test_unwrap_root(self):
+        self.assertEqual(
+            self._unwrap('.'),
+            [(INSIDE, TEXT, u'ROOT'),
+             (INSIDE, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (INSIDE, END, u'foo')]
+            )
+
+    def test_unwrap_text_root(self):
+        self.assertEqual(
+            _transform('foo', Transformer('.').unwrap()),
+            [(OUTSIDE, TEXT, 'foo')],
+            )
+
+
+class WrapTest(unittest.TestCase):
+    def _wrap(self, select, wrap='wrap'):
+        return _transform(FOO, Transformer(select).wrap(wrap))
+
+    def test_wrap_element(self):
+        self.assertEqual(
+            self._wrap('foo'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, START, u'wrap'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, END, u'wrap'),
+             (None, END, u'root')]
+            )
+
+    def test_wrap_adjacent_elements(self):
+        self.assertEqual(
+            _transform(FOOBAR, Transformer('foo|bar').wrap('wrap')),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, START, u'wrap'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, END, u'wrap'),
+             (None, START, u'wrap'),
+             (ENTER, START, u'bar'),
+             (INSIDE, TEXT, u'BAR'),
+             (EXIT, END, u'bar'),
+             (None, END, u'wrap'),
+             (None, END, u'root')]
+            )
+
+    def test_wrap_text(self):
+        self.assertEqual(
+            self._wrap('foo/text()'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, START, u'foo'),
+             (None, START, u'wrap'),
+             (OUTSIDE, TEXT, u'FOO'),
+             (None, END, u'wrap'),
+             (None, END, u'foo'),
+             (None, END, u'root')]
+            )
+
+    def test_wrap_root(self):
+        self.assertEqual(
+            self._wrap('.'),
+            [(None, START, u'wrap'),
+             (ENTER, START, u'root'),
+             (INSIDE, TEXT, u'ROOT'),
+             (INSIDE, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (INSIDE, END, u'foo'),
+             (EXIT, END, u'root'),
+             (None, END, u'wrap')]
+            )
+
+    def test_wrap_text_root(self):
+        self.assertEqual(
+            _transform('foo', Transformer('.').wrap('wrap')),
+            [(None, START, u'wrap'),
+             (OUTSIDE, TEXT, u'foo'),
+             (None, END, u'wrap')],
+            )
+
+    def test_wrap_with_element(self):
+        element = Element('a', href='http://localhost')
+        self.assertEqual(
+            _transform('foo', Transformer('.').wrap(element), with_attrs=True),
+            [(None, START, (u'a', {u'href': u'http://localhost'})),
+             (OUTSIDE, TEXT, u'foo'),
+             (None, END, u'a')]
+            )
+
+
+class FilterTest(unittest.TestCase):
+    def _filter(self, select, html=FOOBAR):
+        """Returns a list of lists of filtered elements."""
+        output = []
+        def filtered(stream):
+            interval = []
+            output.append(interval)
+            for event in stream:
+                interval.append(event)
+                yield event
+        _transform(html, Transformer(select).filter(filtered))
+        simplified = []
+        for sub in output:
+            simplified.append(_simplify([(None, event) for event in sub]))
+        return simplified
+
+    def test_filter_element(self):
+        self.assertEqual(
+            self._filter('foo'),
+            [[(None, START, u'foo'),
+              (None, TEXT, u'FOO'),
+              (None, END, u'foo')]]
+            )
+
+    def test_filter_adjacent_elements(self):
+        self.assertEqual(
+            self._filter('foo|bar'),
+            [[(None, START, u'foo'),
+              (None, TEXT, u'FOO'),
+              (None, END, u'foo')],
+             [(None, START, u'bar'),
+              (None, TEXT, u'BAR'),
+              (None, END, u'bar')]]
+            )
+
+    def test_filter_text(self):
+        self.assertEqual(
+            self._filter('*/text()'),
+            [[(None, TEXT, u'FOO')],
+             [(None, TEXT, u'BAR')]]
+            )
+    def test_filter_root(self):
+        self.assertEqual(
+            self._filter('.'),
+            [[(None, START, u'root'),
+              (None, TEXT, u'ROOT'),
+              (None, START, u'foo'),
+              (None, TEXT, u'FOO'),
+              (None, END, u'foo'),
+              (None, START, u'bar'),
+              (None, TEXT, u'BAR'),
+              (None, END, u'bar'),
+              (None, END, u'root')]]
+            )
+
+    def test_filter_text_root(self):
+        self.assertEqual(
+            self._filter('.', 'foo'),
+            [[(None, TEXT, u'foo')]])
+
+    def test_filter_after_outside(self):
+        stream = _transform(
+            '<root>x</root>', Transformer('//root/text()').filter(lambda x: x))
+        self.assertEqual(
+            list(stream),
+            [(None, START, u'root'),
+             (OUTSIDE, TEXT, u'x'),
+             (None, END, u'root')])
+
+
+class MapTest(unittest.TestCase):
+    def _map(self, select, kind=None):
+        data = []
+        def record(d):
+            data.append(d)
+            return d
+        _transform(FOOBAR, Transformer(select).map(record, kind))
+        return data
+
+    def test_map_element(self):
+        self.assertEqual(
+            self._map('foo'),
+            [(QName(u'foo'), Attrs([(QName(u'name'), u'foo'),
+                                    (QName(u'size'), u'100')])),
+             u'FOO',
+             QName(u'foo')]
+            )
+
+    def test_map_with_text_kind(self):
+        self.assertEqual(
+            self._map('.', TEXT),
+            [u'ROOT', u'FOO', u'BAR']
+            )
+
+    def test_map_with_root_and_end_kind(self):
+        self.assertEqual(
+            self._map('.', END),
+            [QName(u'foo'), QName(u'bar'), QName(u'root')]
+            )
+
+    def test_map_with_attribute(self):
+        self.assertEqual(
+            self._map('foo/@name'),
+            [(QName(u'foo@*'), Attrs([('name', u'foo')]))]
+            )
+
+
+class SubstituteTest(unittest.TestCase):
+    def _substitute(self, select, pattern, replace):
+        return _transform(FOOBAR, Transformer(select).substitute(pattern, replace))
+
+    def test_substitute_foo(self):
+        self.assertEqual(
+            self._substitute('foo', 'FOO|BAR', 'FOOOOO'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'FOOOOO'),
+             (EXIT, END, u'foo'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_substitute_foobar_with_group(self):
+        self.assertEqual(
+            self._substitute('foo|bar', '(FOO|BAR)', r'(\1)'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'(FOO)'),
+             (EXIT, END, u'foo'),
+             (ENTER, START, u'bar'),
+             (INSIDE, TEXT, u'(BAR)'),
+             (EXIT, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+
+class RenameTest(unittest.TestCase):
+    def _rename(self, select):
+        return _transform(FOOBAR, Transformer(select).rename('foobar'))
+
+    def test_rename_root(self):
+        self.assertEqual(
+            self._rename('.'),
+            [(ENTER, START, u'foobar'),
+             (INSIDE, TEXT, u'ROOT'),
+             (INSIDE, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (INSIDE, END, u'foo'),
+             (INSIDE, START, u'bar'),
+             (INSIDE, TEXT, u'BAR'),
+             (INSIDE, END, u'bar'),
+             (EXIT, END, u'foobar')]
+            )
+
+    def test_rename_element(self):
+        self.assertEqual(
+            self._rename('foo|bar'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, u'foobar'),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foobar'),
+             (ENTER, START, u'foobar'),
+             (INSIDE, TEXT, u'BAR'),
+             (EXIT, END, u'foobar'),
+             (None, END, u'root')]
+            )
+
+    def test_rename_text(self):
+        self.assertEqual(
+            self._rename('foo/text()'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, START, u'foo'),
+             (OUTSIDE, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+
+class ContentTestMixin(object):
+    def _apply(self, select, content=None, html=FOOBAR):
+        class Injector(object):
+            count = 0
+
+            def __iter__(self):
+                self.count += 1
+                return iter(HTML('CONTENT %i' % self.count))
+
+        if isinstance(html, basestring):
+            html = HTML(html)
+        if content is None:
+            content = Injector()
+        elif isinstance(content, basestring):
+            content = HTML(content)
+        return _transform(html, getattr(Transformer(select), self.operation)
+                                (content))
+
+
+class ReplaceTest(unittest.TestCase, ContentTestMixin):
+    operation = 'replace'
+
+    def test_replace_element(self):
+        self.assertEqual(
+            self._apply('foo'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, TEXT, u'CONTENT 1'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_replace_text(self):
+        self.assertEqual(
+            self._apply('text()'),
+            [(None, START, u'root'),
+             (None, TEXT, u'CONTENT 1'),
+             (None, START, u'foo'),
+             (None, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_replace_context(self):
+        self.assertEqual(
+            self._apply('.'),
+            [(None, TEXT, u'CONTENT 1')],
+            )
+
+    def test_replace_text_context(self):
+        self.assertEqual(
+            self._apply('.', html='foo'),
+            [(None, TEXT, u'CONTENT 1')],
+            )
+
+    def test_replace_adjacent_elements(self):
+        self.assertEqual(
+            self._apply('*'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, TEXT, u'CONTENT 1'),
+             (None, TEXT, u'CONTENT 2'),
+             (None, END, u'root')],
+            )
+
+    def test_replace_all(self):
+        self.assertEqual(
+            self._apply('*|text()'),
+            [(None, START, u'root'),
+             (None, TEXT, u'CONTENT 1'),
+             (None, TEXT, u'CONTENT 2'),
+             (None, TEXT, u'CONTENT 3'),
+             (None, END, u'root')],
+            )
+
+    def test_replace_with_callback(self):
+        count = [0]
+        def content():
+            count[0] += 1
+            yield '%2i.' % count[0]
+        self.assertEqual(
+            self._apply('*', content),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, TEXT, u' 1.'),
+             (None, TEXT, u' 2.'),
+             (None, END, u'root')]
+            )
+
+
+class BeforeTest(unittest.TestCase, ContentTestMixin):
+    operation = 'before'
+
+    def test_before_element(self):
+        self.assertEqual(
+            self._apply('foo'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, TEXT, u'CONTENT 1'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_before_text(self):
+        self.assertEqual(
+            self._apply('text()'),
+            [(None, START, u'root'),
+             (None, TEXT, u'CONTENT 1'),
+             (OUTSIDE, TEXT, u'ROOT'),
+             (None, START, u'foo'),
+             (None, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_before_context(self):
+        self.assertEqual(
+            self._apply('.'),
+            [(None, TEXT, u'CONTENT 1'),
+             (ENTER, START, u'root'),
+             (INSIDE, TEXT, u'ROOT'),
+             (INSIDE, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (INSIDE, END, u'foo'),
+             (INSIDE, START, u'bar'),
+             (INSIDE, TEXT, u'BAR'),
+             (INSIDE, END, u'bar'),
+             (EXIT, END, u'root')]
+            )
+
+    def test_before_text_context(self):
+        self.assertEqual(
+            self._apply('.', html='foo'),
+            [(None, TEXT, u'CONTENT 1'),
+             (OUTSIDE, TEXT, u'foo')]
+            )
+
+    def test_before_adjacent_elements(self):
+        self.assertEqual(
+            self._apply('*'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (None, TEXT, u'CONTENT 1'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, TEXT, u'CONTENT 2'),
+             (ENTER, START, u'bar'),
+             (INSIDE, TEXT, u'BAR'),
+             (EXIT, END, u'bar'),
+             (None, END, u'root')]
+
+            )
+
+    def test_before_all(self):
+        self.assertEqual(
+            self._apply('*|text()'),
+            [(None, START, u'root'),
+             (None, TEXT, u'CONTENT 1'),
+             (OUTSIDE, TEXT, u'ROOT'),
+             (None, TEXT, u'CONTENT 2'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, TEXT, u'CONTENT 3'),
+             (ENTER, START, u'bar'),
+             (INSIDE, TEXT, u'BAR'),
+             (EXIT, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_before_with_callback(self):
+        count = [0]
+        def content():
+            count[0] += 1
+            yield '%2i.' % count[0]
+        self.assertEqual(
+            self._apply('foo/text()', content),
+            [(None, 'START', u'root'),
+             (None, 'TEXT', u'ROOT'),
+             (None, 'START', u'foo'),
+             (None, 'TEXT', u' 1.'),
+             ('OUTSIDE', 'TEXT', u'FOO'),
+             (None, 'END', u'foo'),
+             (None, 'START', u'bar'),
+             (None, 'TEXT', u'BAR'),
+             (None, 'END', u'bar'),
+             (None, 'END', u'root')]
+            )
+
+
+class AfterTest(unittest.TestCase, ContentTestMixin):
+    operation = 'after'
+
+    def test_after_element(self):
+        self.assertEqual(
+            self._apply('foo'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, TEXT, u'CONTENT 1'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_after_text(self):
+        self.assertEqual(
+            self._apply('text()'),
+            [(None, START, u'root'),
+             (OUTSIDE, TEXT, u'ROOT'),
+             (None, TEXT, u'CONTENT 1'),
+             (None, START, u'foo'),
+             (None, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_after_context(self):
+        self.assertEqual(
+            self._apply('.'),
+            [(ENTER, START, u'root'),
+             (INSIDE, TEXT, u'ROOT'),
+             (INSIDE, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (INSIDE, END, u'foo'),
+             (INSIDE, START, u'bar'),
+             (INSIDE, TEXT, u'BAR'),
+             (INSIDE, END, u'bar'),
+             (EXIT, END, u'root'),
+             (None, TEXT, u'CONTENT 1')]
+            )
+
+    def test_after_text_context(self):
+        self.assertEqual(
+            self._apply('.', html='foo'),
+            [(OUTSIDE, TEXT, u'foo'),
+             (None, TEXT, u'CONTENT 1')]
+            )
+
+    def test_after_adjacent_elements(self):
+        self.assertEqual(
+            self._apply('*'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, TEXT, u'CONTENT 1'),
+             (ENTER, START, u'bar'),
+             (INSIDE, TEXT, u'BAR'),
+             (EXIT, END, u'bar'),
+             (None, TEXT, u'CONTENT 2'),
+             (None, END, u'root')]
+
+            )
+
+    def test_after_all(self):
+        self.assertEqual(
+            self._apply('*|text()'),
+            [(None, START, u'root'),
+             (OUTSIDE, TEXT, u'ROOT'),
+             (None, TEXT, u'CONTENT 1'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, TEXT, u'CONTENT 2'),
+             (ENTER, START, u'bar'),
+             (INSIDE, TEXT, u'BAR'),
+             (EXIT, END, u'bar'),
+             (None, TEXT, u'CONTENT 3'),
+             (None, END, u'root')]
+            )
+
+    def test_after_with_callback(self):
+        count = [0]
+        def content():
+            count[0] += 1
+            yield '%2i.' % count[0]
+        self.assertEqual(
+            self._apply('foo/text()', content),
+            [(None, 'START', u'root'),
+             (None, 'TEXT', u'ROOT'),
+             (None, 'START', u'foo'),
+             ('OUTSIDE', 'TEXT', u'FOO'),
+             (None, 'TEXT', u' 1.'),
+             (None, 'END', u'foo'),
+             (None, 'START', u'bar'),
+             (None, 'TEXT', u'BAR'),
+             (None, 'END', u'bar'),
+             (None, 'END', u'root')]
+            )
+
+
+class PrependTest(unittest.TestCase, ContentTestMixin):
+    operation = 'prepend'
+
+    def test_prepend_element(self):
+        self.assertEqual(
+            self._apply('foo'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, u'foo'),
+             (None, TEXT, u'CONTENT 1'),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_prepend_text(self):
+        self.assertEqual(
+            self._apply('text()'),
+            [(None, START, u'root'),
+             (OUTSIDE, TEXT, u'ROOT'),
+             (None, START, u'foo'),
+             (None, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_prepend_context(self):
+        self.assertEqual(
+            self._apply('.'),
+            [(ENTER, START, u'root'),
+             (None, TEXT, u'CONTENT 1'),
+             (INSIDE, TEXT, u'ROOT'),
+             (INSIDE, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (INSIDE, END, u'foo'),
+             (INSIDE, START, u'bar'),
+             (INSIDE, TEXT, u'BAR'),
+             (INSIDE, END, u'bar'),
+             (EXIT, END, u'root')],
+            )
+
+    def test_prepend_text_context(self):
+        self.assertEqual(
+            self._apply('.', html='foo'),
+            [(OUTSIDE, TEXT, u'foo')]
+            )
+
+    def test_prepend_adjacent_elements(self):
+        self.assertEqual(
+            self._apply('*'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, u'foo'),
+             (None, TEXT, u'CONTENT 1'),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (ENTER, START, u'bar'),
+             (None, TEXT, u'CONTENT 2'),
+             (INSIDE, TEXT, u'BAR'),
+             (EXIT, END, u'bar'),
+             (None, END, u'root')]
+
+            )
+
+    def test_prepend_all(self):
+        self.assertEqual(
+            self._apply('*|text()'),
+            [(None, START, u'root'),
+             (OUTSIDE, TEXT, u'ROOT'),
+             (ENTER, START, u'foo'),
+             (None, TEXT, u'CONTENT 1'),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (ENTER, START, u'bar'),
+             (None, TEXT, u'CONTENT 2'),
+             (INSIDE, TEXT, u'BAR'),
+             (EXIT, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_prepend_with_callback(self):
+        count = [0]
+        def content():
+            count[0] += 1
+            yield '%2i.' % count[0]
+        self.assertEqual(
+            self._apply('foo', content),
+            [(None, 'START', u'root'),
+             (None, 'TEXT', u'ROOT'),
+             (ENTER, 'START', u'foo'),
+             (None, 'TEXT', u' 1.'),
+             (INSIDE, 'TEXT', u'FOO'),
+             (EXIT, 'END', u'foo'),
+             (None, 'START', u'bar'),
+             (None, 'TEXT', u'BAR'),
+             (None, 'END', u'bar'),
+             (None, 'END', u'root')]
+            )
+
+
+class AppendTest(unittest.TestCase, ContentTestMixin):
+    operation = 'append'
+
+    def test_append_element(self):
+        self.assertEqual(
+            self._apply('foo'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (None, TEXT, u'CONTENT 1'),
+             (EXIT, END, u'foo'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_append_text(self):
+        self.assertEqual(
+            self._apply('text()'),
+            [(None, START, u'root'),
+             (OUTSIDE, TEXT, u'ROOT'),
+             (None, START, u'foo'),
+             (None, TEXT, u'FOO'),
+             (None, END, u'foo'),
+             (None, START, u'bar'),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_append_context(self):
+        self.assertEqual(
+            self._apply('.'),
+            [(ENTER, START, u'root'),
+             (INSIDE, TEXT, u'ROOT'),
+             (INSIDE, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (INSIDE, END, u'foo'),
+             (INSIDE, START, u'bar'),
+             (INSIDE, TEXT, u'BAR'),
+             (INSIDE, END, u'bar'),
+             (None, TEXT, u'CONTENT 1'),
+             (EXIT, END, u'root')],
+            )
+
+    def test_append_text_context(self):
+        self.assertEqual(
+            self._apply('.', html='foo'),
+            [(OUTSIDE, TEXT, u'foo')]
+            )
+
+    def test_append_adjacent_elements(self):
+        self.assertEqual(
+            self._apply('*'),
+            [(None, START, u'root'),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (None, TEXT, u'CONTENT 1'),
+             (EXIT, END, u'foo'),
+             (ENTER, START, u'bar'),
+             (INSIDE, TEXT, u'BAR'),
+             (None, TEXT, u'CONTENT 2'),
+             (EXIT, END, u'bar'),
+             (None, END, u'root')]
+
+            )
+
+    def test_append_all(self):
+        self.assertEqual(
+            self._apply('*|text()'),
+            [(None, START, u'root'),
+             (OUTSIDE, TEXT, u'ROOT'),
+             (ENTER, START, u'foo'),
+             (INSIDE, TEXT, u'FOO'),
+             (None, TEXT, u'CONTENT 1'),
+             (EXIT, END, u'foo'),
+             (ENTER, START, u'bar'),
+             (INSIDE, TEXT, u'BAR'),
+             (None, TEXT, u'CONTENT 2'),
+             (EXIT, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_append_with_callback(self):
+        count = [0]
+        def content():
+            count[0] += 1
+            yield '%2i.' % count[0]
+        self.assertEqual(
+            self._apply('foo', content),
+            [(None, 'START', u'root'),
+             (None, 'TEXT', u'ROOT'),
+             (ENTER, 'START', u'foo'),
+             (INSIDE, 'TEXT', u'FOO'),
+             (None, 'TEXT', u' 1.'),
+             (EXIT, 'END', u'foo'),
+             (None, 'START', u'bar'),
+             (None, 'TEXT', u'BAR'),
+             (None, 'END', u'bar'),
+             (None, 'END', u'root')]
+            )
+
+
+
+class AttrTest(unittest.TestCase):
+    def _attr(self, select, name, value):
+        return _transform(FOOBAR, Transformer(select).attr(name, value),
+                          with_attrs=True)
+
+    def test_set_existing_attr(self):
+        self.assertEqual(
+            self._attr('foo', 'name', 'FOO'),
+            [(None, START, (u'root', {})),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, (u'foo', {u'name': 'FOO', u'size': '100'})),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, START, (u'bar', {u'name': u'bar'})),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_set_new_attr(self):
+        self.assertEqual(
+            self._attr('foo', 'title', 'FOO'),
+            [(None, START, (u'root', {})),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, (u'foo', {u'name': u'foo', u'title': 'FOO', u'size': '100'})),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, START, (u'bar', {u'name': u'bar'})),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_attr_from_function(self):
+        def set(name, event):
+            self.assertEqual(name, 'name')
+            return event[1][1].get('name').upper()
+
+        self.assertEqual(
+            self._attr('foo|bar', 'name', set),
+            [(None, START, (u'root', {})),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, (u'foo', {u'name': 'FOO', u'size': '100'})),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (ENTER, START, (u'bar', {u'name': 'BAR'})),
+             (INSIDE, TEXT, u'BAR'),
+             (EXIT, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_remove_attr(self):
+        self.assertEqual(
+            self._attr('foo', 'name', None),
+            [(None, START, (u'root', {})),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, (u'foo', {u'size': '100'})),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, START, (u'bar', {u'name': u'bar'})),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+    def test_remove_attr_with_function(self):
+        def set(name, event):
+            return None
+
+        self.assertEqual(
+            self._attr('foo', 'name', set),
+            [(None, START, (u'root', {})),
+             (None, TEXT, u'ROOT'),
+             (ENTER, START, (u'foo', {u'size': '100'})),
+             (INSIDE, TEXT, u'FOO'),
+             (EXIT, END, u'foo'),
+             (None, START, (u'bar', {u'name': u'bar'})),
+             (None, TEXT, u'BAR'),
+             (None, END, u'bar'),
+             (None, END, u'root')]
+            )
+
+
+class BufferTestMixin(object):
+    def _apply(self, select, with_attrs=False):
+        buffer = StreamBuffer()
+        events = buffer.events
+
+        class Trace(object):
+            last = None
+            trace = []
+
+            def __call__(self, stream):
+                for event in stream:
+                    if events and hash(tuple(events)) != self.last:
+                        self.last = hash(tuple(events))
+                        self.trace.append(list(events))
+                    yield event
+
+        trace = Trace()
+        output = _transform(FOOBAR, getattr(Transformer(select), self.operation)
+                                    (buffer).apply(trace), with_attrs=with_attrs)
+        simplified = []
+        for interval in trace.trace:
+            simplified.append(_simplify([(None, e) for e in interval],
+                                         with_attrs=with_attrs))
+        return output, simplified
+
+
+class CopyTest(unittest.TestCase, BufferTestMixin):
+    operation = 'copy'
+
+    def test_copy_element(self):
+        self.assertEqual(
+            self._apply('foo')[1],
+            [[(None, START, u'foo'),
+              (None, TEXT, u'FOO'),
+              (None, END, u'foo')]]
+            )
+
+    def test_copy_adjacent_elements(self):
+        self.assertEqual(
+            self._apply('foo|bar')[1],
+            [[(None, START, u'foo'),
+              (None, TEXT, u'FOO'),
+              (None, END, u'foo')],
+             [(None, START, u'bar'),
+              (None, TEXT, u'BAR'),
+              (None, END, u'bar')]]
+            )
+
+    def test_copy_all(self):
+        self.assertEqual(
+            self._apply('*|text()')[1],
+            [[(None, TEXT, u'ROOT')],
+             [(None, START, u'foo'),
+              (None, TEXT, u'FOO'),
+              (None, END, u'foo')],
+             [(None, START, u'bar'),
+              (None, TEXT, u'BAR'),
+              (None, END, u'bar')]]
+            )
+
+    def test_copy_text(self):
+        self.assertEqual(
+            self._apply('*/text()')[1],
+            [[(None, TEXT, u'FOO')],
+             [(None, TEXT, u'BAR')]]
+            )
+
+    def test_copy_context(self):
+        self.assertEqual(
+            self._apply('.')[1],
+            [[(None, START, u'root'),
+              (None, TEXT, u'ROOT'),
+              (None, START, u'foo'),
+              (None, TEXT, u'FOO'),
+              (None, END, u'foo'),
+              (None, START, u'bar'),
+              (None, TEXT, u'BAR'),
+              (None, END, u'bar'),
+              (None, END, u'root')]]
+            )
+
+    def test_copy_attribute(self):
+        self.assertEqual(
+            self._apply('foo/@name', with_attrs=True)[1],
+            [[(None, ATTR, {'name': u'foo'})]]
+            )
+
+    def test_copy_attributes(self):
+        self.assertEqual(
+            self._apply('foo/@*', with_attrs=True)[1],
+            [[(None, ATTR, {u'name': u'foo', u'size': u'100'})]]
+            )
+
+
+class CutTest(unittest.TestCase, BufferTestMixin):
+    operation = 'cut'
+
+    def test_cut_element(self):
+        self.assertEqual(
+            self._apply('foo'),
+            ([(None, START, u'root'),
+              (None, TEXT, u'ROOT'),
+              (None, START, u'bar'),
+              (None, TEXT, u'BAR'),
+              (None, END, u'bar'),
+              (None, END, u'root')],
+             [[(None, START, u'foo'),
+               (None, TEXT, u'FOO'),
+               (None, END, u'foo')]])
+            )
+
+    def test_cut_adjacent_elements(self):
+        self.assertEqual(
+            self._apply('foo|bar'),
+            ([(None, START, u'root'), 
+              (None, TEXT, u'ROOT'),
+              (BREAK, BREAK, None),
+              (None, END, u'root')],
+             [[(None, START, u'foo'),
+               (None, TEXT, u'FOO'),
+               (None, END, u'foo')],
+              [(None, START, u'bar'),
+               (None, TEXT, u'BAR'),
+               (None, END, u'bar')]])
+            )
+
+    def test_cut_all(self):
+        self.assertEqual(
+            self._apply('*|text()'),
+            ([(None, 'START', u'root'),
+              ('BREAK', 'BREAK', None),
+              ('BREAK', 'BREAK', None),
+              (None, 'END', u'root')],
+             [[(None, 'TEXT', u'ROOT')],
+              [(None, 'START', u'foo'),
+               (None, 'TEXT', u'FOO'),
+               (None, 'END', u'foo')],
+              [(None, 'START', u'bar'),
+               (None, 'TEXT', u'BAR'),
+               (None, 'END', u'bar')]])
+            )
+
+    def test_cut_text(self):
+        self.assertEqual(
+            self._apply('*/text()'),
+            ([(None, 'START', u'root'),
+              (None, 'TEXT', u'ROOT'),
+              (None, 'START', u'foo'),
+              (None, 'END', u'foo'),
+              (None, 'START', u'bar'),
+              (None, 'END', u'bar'),
+              (None, 'END', u'root')],
+             [[(None, 'TEXT', u'FOO')],
+              [(None, 'TEXT', u'BAR')]])
+            )
+
+    def test_cut_context(self):
+        self.assertEqual(
+            self._apply('.')[1],
+            [[(None, 'START', u'root'),
+              (None, 'TEXT', u'ROOT'),
+              (None, 'START', u'foo'),
+              (None, 'TEXT', u'FOO'),
+              (None, 'END', u'foo'),
+              (None, 'START', u'bar'),
+              (None, 'TEXT', u'BAR'),
+              (None, 'END', u'bar'),
+              (None, 'END', u'root')]]
+            )
+
+    def test_cut_attribute(self):
+        self.assertEqual(
+            self._apply('foo/@name', with_attrs=True),
+            ([(None, START, (u'root', {})),
+              (None, TEXT, u'ROOT'),
+              (None, START, (u'foo', {u'size': u'100'})),
+              (None, TEXT, u'FOO'),
+              (None, END, u'foo'),
+              (None, START, (u'bar', {u'name': u'bar'})),
+              (None, TEXT, u'BAR'),
+              (None, END, u'bar'),
+              (None, END, u'root')],
+             [[(None, ATTR, {u'name': u'foo'})]])
+            )
+
+    def test_cut_attributes(self):
+        self.assertEqual(
+            self._apply('foo/@*', with_attrs=True),
+            ([(None, START, (u'root', {})),
+              (None, TEXT, u'ROOT'),
+              (None, START, (u'foo', {})),
+              (None, TEXT, u'FOO'),
+              (None, END, u'foo'),
+              (None, START, (u'bar', {u'name': u'bar'})),
+              (None, TEXT, u'BAR'),
+              (None, END, u'bar'),
+              (None, END, u'root')],
+             [[(None, ATTR, {u'name': u'foo', u'size': u'100'})]])
+            )
+
+# XXX Test this when the XPath implementation is fixed (#233).
+#    def test_cut_attribute_or_attribute(self):
+#        self.assertEqual(
+#            self._apply('foo/@name | foo/@size', with_attrs=True),
+#            ([(None, START, (u'root', {})),
+#              (None, TEXT, u'ROOT'),
+#              (None, START, (u'foo', {})),
+#              (None, TEXT, u'FOO'),
+#              (None, END, u'foo'),
+#              (None, START, (u'bar', {u'name': u'bar'})),
+#              (None, TEXT, u'BAR'),
+#              (None, END, u'bar'),
+#              (None, END, u'root')],
+#             [[(None, ATTR, {u'name': u'foo', u'size': u'100'})]])
+#            )
+
+
+
+
+def suite():
+    from genshi.input import HTML
+    from genshi.core import Markup
+    from genshi.builder import tag
+    suite = unittest.TestSuite()
+    for test in (SelectTest, InvertTest, EndTest,
+                 EmptyTest, RemoveTest, UnwrapText, WrapTest, FilterTest,
+                 MapTest, SubstituteTest, RenameTest, ReplaceTest, BeforeTest,
+                 AfterTest, PrependTest, AppendTest, AttrTest, CopyTest, CutTest):
+        suite.addTest(unittest.makeSuite(test, 'test'))
+    suite.addTest(doctest.DocTestSuite(
+        genshi.filters.transform, optionflags=doctest.NORMALIZE_WHITESPACE,
+        extraglobs={'HTML': HTML, 'tag': tag, 'Markup': Markup}))
+    return suite
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
Copyright (C) 2012-2017 Edgewall Software