')
++ self.assertEqual('
', (html | sanitizer).render())
+
+ def test_sanitize_remove_style_phishing(self):
+ sanitizer = StyleSanitizer()
+ # The position property is not allowed
+- html = HTML(u'
')
+- self.assertEquals('
', (html | sanitizer).render())
++ html = HTML('
')
++ self.assertEqual('
', (html | sanitizer).render())
+ # Normal margins get passed through
+- html = HTML(u'
')
+- self.assertEquals('
',
++ html = HTML('
')
++ self.assertEqual('
',
+ (html | sanitizer).render())
+ # But not negative margins
+- html = HTML(u'
')
+- self.assertEquals('
', (html | sanitizer).render())
+- html = HTML(u'
')
+- self.assertEquals('
', (html | sanitizer).render())
+- html = HTML(u'
')
+- self.assertEquals('
', (html | sanitizer).render())
++ html = HTML('
')
++ self.assertEqual('
', (html | sanitizer).render())
++ html = HTML('
')
++ self.assertEqual('
', (html | sanitizer).render())
++ html = HTML('
')
++ self.assertEqual('
', (html | sanitizer).render())
+
+ def test_sanitize_remove_src_javascript(self):
+- html = HTML(u'
')
+- self.assertEquals('
', (html | HTMLSanitizer()).render())
++ html = HTML('
')
++ self.assertEqual('
', (html | HTMLSanitizer()).render())
+ # Case-insensitive protocol matching
+- html = HTML(u'
')
+- self.assertEquals('
', (html | HTMLSanitizer()).render())
++ html = HTML('
')
++ self.assertEqual('
', (html | HTMLSanitizer()).render())
+ # Grave accents (not parsed)
+- src = u'
'
++ src = '
'
+ self.assert_parse_error_or_equal('
', src)
+ # Protocol encoded using UTF-8 numeric entities
+- html = HTML(u'
')
+- self.assertEquals('
', (html | HTMLSanitizer()).render())
++ self.assertEqual('
', (html | HTMLSanitizer()).render())
+ # Protocol encoded using UTF-8 numeric entities without a semicolon
+ # (which is allowed because the max number of digits is used)
+- html = HTML(u'
')
+- self.assertEquals('
', (html | HTMLSanitizer()).render())
++ self.assertEqual('
', (html | HTMLSanitizer()).render())
+ # Protocol encoded using UTF-8 numeric hex entities without a semicolon
+ # (which is allowed because the max number of digits is used)
+- html = HTML(u'
')
+- self.assertEquals('
', (html | HTMLSanitizer()).render())
++ self.assertEqual('
', (html | HTMLSanitizer()).render())
+ # Embedded tab character in protocol
+- html = HTML(u'
')
+- self.assertEquals('
', (html | HTMLSanitizer()).render())
++ html = HTML('
')
++ self.assertEqual('
', (html | HTMLSanitizer()).render())
+ # Embedded tab character in protocol, but encoded this time
+- html = HTML(u'
')
+- self.assertEquals('
', (html | HTMLSanitizer()).render())
++ html = HTML('
')
++ self.assertEqual('
', (html | HTMLSanitizer()).render())
+
+ def test_sanitize_expression(self):
+- html = HTML(ur'
XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML(r'
XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+ def test_capital_expression(self):
+- html = HTML(ur'
XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML(r'
XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+ def test_sanitize_url_with_javascript(self):
+- html = HTML(u'
'
+- u'XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML('
'
++ 'XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+ def test_sanitize_capital_url_with_javascript(self):
+- html = HTML(u'
'
+- u'XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML('
'
++ 'XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+ def test_sanitize_unicode_escapes(self):
+- html = HTML(ur'
'
+- ur'XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML(r'
'
++ r'XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+ def test_sanitize_backslash_without_hex(self):
+- html = HTML(ur'
XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
+- html = HTML(ur'
XSS
')
++ html = HTML(r'
XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
++ html = HTML(r'
XSS
')
+ self.assertEqual(r'
'
+ 'XSS
',
+- unicode(html | StyleSanitizer()))
++ str(html | StyleSanitizer()))
+
+ def test_sanitize_unsafe_props(self):
+- html = HTML(u'
XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML('
XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+- html = HTML(u'
XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML('
XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+- html = HTML(u'
'
+- u'XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML('
'
++ 'XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+- html = HTML(u"""
XSS
""")
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML("""
XSS
""")
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+- html = HTML(u"""
XSS
""")
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML("""
XSS
""")
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+ def test_sanitize_negative_margin(self):
+- html = HTML(u'
XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
+- html = HTML(u'
XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML('
XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
++ html = HTML('
XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+ def test_sanitize_css_hack(self):
+- html = HTML(u'
XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML('
XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+- html = HTML(u'
XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML('
XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+ def test_sanitize_property_name(self):
+- html = HTML(u'
prop
')
++ html = HTML('
prop
')
+ self.assertEqual('
prop
',
+- unicode(html | StyleSanitizer()))
++ str(html | StyleSanitizer()))
+
+ def test_sanitize_unicode_expression(self):
+ # Fullwidth small letters
+- html = HTML(u'
'
+- u'XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML('
'
++ 'XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+ # Fullwidth capital letters
+- html = HTML(u'
'
+- u'XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML('
'
++ 'XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+ # IPA extensions
+- html = HTML(u'
'
+- u'XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML('
'
++ 'XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+ def test_sanitize_unicode_url(self):
+ # IPA extensions
+- html = HTML(u'
'
+- u'XSS
')
+- self.assertEqual('
XSS
', unicode(html | StyleSanitizer()))
++ html = HTML('
'
++ 'XSS
')
++ self.assertEqual('
XSS
', str(html | StyleSanitizer()))
+
+
+ def suite():
+--- genshi/filters/tests/transform.py.orig 2019-05-27 21:03:08 UTC
++++ genshi/filters/tests/transform.py
+@@ -33,24 +33,24 @@ def _simplify(stream, with_attrs=False):
+ for mark, (kind, data, pos) in stream:
+ if kind is START:
+ if with_attrs:
+- data = (unicode(data[0]), dict((unicode(k), v)
++ data = (str(data[0]), dict((str(k), v)
+ for k, v in data[1]))
+ else:
+- data = unicode(data[0])
++ data = str(data[0])
+ elif kind is END:
+- data = unicode(data)
++ data = str(data)
+ elif kind is ATTR:
+ kind = ATTR
+- data = dict((unicode(k), v) for k, v in data[1])
++ data = dict((str(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) and not isinstance(html, unicode):
++ if isinstance(html, str) and not isinstance(html, str):
+ html = HTML(html, encoding='utf-8')
+- elif isinstance(html, unicode):
++ elif isinstance(html, str):
+ html = HTML(html, encoding='utf-8')
+ stream = transformer(html, keep_marks=True)
+ return _simplify(stream, with_attrs)
+@@ -60,7 +60,7 @@ class SelectTest(unittest.TestCase):
+ """Test .select()"""
+ def _select(self, select):
+ html = HTML(FOOBAR, encoding='utf-8')
+- if isinstance(select, basestring):
++ if isinstance(select, str):
+ select = [select]
+ transformer = Transformer(select[0])
+ for sel in select[1:]:
+@@ -70,78 +70,78 @@ class SelectTest(unittest.TestCase):
+ 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')],
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, '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')]
++ [(ENTER, START, 'root'),
++ (INSIDE, TEXT, 'ROOT'),
++ (INSIDE, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (INSIDE, END, 'foo'),
++ (INSIDE, START, 'bar'),
++ (INSIDE, TEXT, 'BAR'),
++ (INSIDE, END, 'bar'),
++ (EXIT, END, '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')],
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, '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')],
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, START, 'foo'),
++ (OUTSIDE, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, START, 'bar'),
++ (OUTSIDE, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ATTR, ATTR, {'name': 'foo'}),
++ (None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, 'root')]
+ )
+
+ def test_select_text_context(self):
+ self.assertEqual(
+- list(Transformer('.')(HTML(u'foo'), keep_marks=True)),
+- [('OUTSIDE', ('TEXT', u'foo', (None, 1, 0)))],
++ list(Transformer('.')(HTML('foo'), keep_marks=True)),
++ [('OUTSIDE', ('TEXT', 'foo', (None, 1, 0)))],
+ )
+
+
+@@ -152,63 +152,63 @@ class InvertTest(unittest.TestCase):
+ 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')]
++ [(OUTSIDE, START, 'root'),
++ (OUTSIDE, TEXT, 'ROOT'),
++ (None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (OUTSIDE, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (OUTSIDE, START, 'foo'),
++ (OUTSIDE, TEXT, 'FOO'),
++ (OUTSIDE, END, 'foo'),
++ (None, END, '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')]
++ [(OUTSIDE, START, 'root'),
++ (OUTSIDE, TEXT, 'ROOT'),
++ (OUTSIDE, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (OUTSIDE, END, 'foo'),
++ (OUTSIDE, END, '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')]
++ [(OUTSIDE, START, 'root'),
++ (OUTSIDE, TEXT, 'ROOT'),
++ (None, ATTR, {'name': 'foo'}),
++ (OUTSIDE, START, 'foo'),
++ (OUTSIDE, TEXT, 'FOO'),
++ (OUTSIDE, END, 'foo'),
++ (OUTSIDE, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, END, 'root')]
+ )
+
+ def test_invert_text_context(self):
+ self.assertEqual(
+- _simplify(Transformer('.').invert()(HTML(u'foo'), keep_marks=True)),
+- [(None, 'TEXT', u'foo')],
++ _simplify(Transformer('.').invert()(HTML('foo'), keep_marks=True)),
++ [(None, 'TEXT', 'foo')],
+ )
+
+
+@@ -218,12 +218,12 @@ class EndTest(unittest.TestCase):
+ 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')]
++ [(OUTSIDE, START, 'root'),
++ (OUTSIDE, TEXT, 'ROOT'),
++ (OUTSIDE, START, 'foo'),
++ (OUTSIDE, TEXT, 'FOO'),
++ (OUTSIDE, END, 'foo'),
++ (OUTSIDE, END, 'root')]
+ )
+
+
+@@ -234,47 +234,47 @@ class EmptyTest(unittest.TestCase):
+ 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')],
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, 'foo'),
++ (EXIT, END, 'foo'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, START, 'foo'),
++ (OUTSIDE, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ATTR, ATTR, {'name': 'foo'}),
++ (None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, END, 'root')]
+ )
+
+ def test_empty_context(self):
+ self.assertEqual(
+ self._empty('.'),
+- [(ENTER, START, u'root'),
+- (EXIT, END, u'root')]
++ [(ENTER, START, 'root'),
++ (EXIT, END, 'root')]
+ )
+
+ def test_empty_text_context(self):
+ self.assertEqual(
+- _simplify(Transformer('.')(HTML(u'foo'), keep_marks=True)),
+- [(OUTSIDE, TEXT, u'foo')],
++ _simplify(Transformer('.')(HTML('foo'), keep_marks=True)),
++ [(OUTSIDE, TEXT, 'foo')],
+ )
+
+
+@@ -285,29 +285,29 @@ class RemoveTest(unittest.TestCase):
+ def test_remove_element(self):
+ self.assertEqual(
+ self._remove('foo|bar'),
+- [(None, START, u'root'),
+- (None, TEXT, u'ROOT'),
+- (None, END, u'root')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, START, 'foo'),
++ (None, END, 'foo'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, END, 'root')]
+ )
+
+ def test_remove_context(self):
+@@ -330,52 +330,52 @@ class UnwrapText(unittest.TestCase):
+ 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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (INSIDE, TEXT, 'FOO'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, START, 'foo'),
++ (OUTSIDE, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ATTR, ATTR, {'name': 'foo'}),
++ (None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (INSIDE, TEXT, 'FOO'),
++ (INSIDE, TEXT, 'BAR'),
++ (None, END, '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')]
++ [(INSIDE, TEXT, 'ROOT'),
++ (INSIDE, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (INSIDE, END, 'foo')]
+ )
+
+ def test_unwrap_text_root(self):
+@@ -392,75 +392,75 @@ class WrapTest(unittest.TestCase):
+ 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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, START, 'wrap'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, END, 'wrap'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, START, 'wrap'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, END, 'wrap'),
++ (None, START, 'wrap'),
++ (ENTER, START, 'bar'),
++ (INSIDE, TEXT, 'BAR'),
++ (EXIT, END, 'bar'),
++ (None, END, 'wrap'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, START, 'foo'),
++ (None, START, 'wrap'),
++ (OUTSIDE, TEXT, 'FOO'),
++ (None, END, 'wrap'),
++ (None, END, 'foo'),
++ (None, END, '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')]
++ [(None, START, 'wrap'),
++ (ENTER, START, 'root'),
++ (INSIDE, TEXT, 'ROOT'),
++ (INSIDE, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (INSIDE, END, 'foo'),
++ (EXIT, END, 'root'),
++ (None, END, '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')],
++ [(None, START, 'wrap'),
++ (OUTSIDE, TEXT, 'foo'),
++ (None, END, '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')]
++ [(None, START, ('a', {'href': 'http://localhost'})),
++ (OUTSIDE, TEXT, 'foo'),
++ (None, END, 'a')]
+ )
+
+
+@@ -483,55 +483,55 @@ class FilterTest(unittest.TestCase):
+ def test_filter_element(self):
+ self.assertEqual(
+ self._filter('foo'),
+- [[(None, START, u'foo'),
+- (None, TEXT, u'FOO'),
+- (None, END, u'foo')]]
++ [[(None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, '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')]]
++ [[(None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo')],
++ [(None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar')]]
+ )
+
+ def test_filter_text(self):
+ self.assertEqual(
+ self._filter('*/text()'),
+- [[(None, TEXT, u'FOO')],
+- [(None, TEXT, u'BAR')]]
++ [[(None, TEXT, 'FOO')],
++ [(None, TEXT, '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')]]
++ [[(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, 'root')]]
+ )
+
+ def test_filter_text_root(self):
+ self.assertEqual(
+ self._filter('.', 'foo'),
+- [[(None, TEXT, u'foo')]])
++ [[(None, TEXT, 'foo')]])
+
+ def test_filter_after_outside(self):
+ stream = _transform(
+ '
x', Transformer('//root/text()').filter(lambda x: x))
+ self.assertEqual(
+ list(stream),
+- [(None, START, u'root'),
+- (OUTSIDE, TEXT, u'x'),
+- (None, END, u'root')])
++ [(None, START, 'root'),
++ (OUTSIDE, TEXT, 'x'),
++ (None, END, 'root')])
+
+
+ class MapTest(unittest.TestCase):
+@@ -546,16 +546,16 @@ class MapTest(unittest.TestCase):
+ def test_map_element(self):
+ self.assertEqual(
+ self._map('foo'),
+- [(QName('foo'), Attrs([(QName('name'), u'foo'),
+- (QName('size'), u'100')])),
+- u'FOO',
++ [(QName('foo'), Attrs([(QName('name'), 'foo'),
++ (QName('size'), '100')])),
++ 'FOO',
+ QName('foo')]
+ )
+
+ def test_map_with_text_kind(self):
+ self.assertEqual(
+ self._map('.', TEXT),
+- [u'ROOT', u'FOO', u'BAR']
++ ['ROOT', 'FOO', 'BAR']
+ )
+
+ def test_map_with_root_and_end_kind(self):
+@@ -567,7 +567,7 @@ class MapTest(unittest.TestCase):
+ def test_map_with_attribute(self):
+ self.assertEqual(
+ self._map('foo/@name'),
+- [(QName('foo@*'), Attrs([('name', u'foo')]))]
++ [(QName('foo@*'), Attrs([('name', 'foo')]))]
+ )
+
+
+@@ -578,29 +578,29 @@ class SubstituteTest(unittest.TestCase):
+ 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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, 'FOOOOO'),
++ (EXIT, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, '(FOO)'),
++ (EXIT, END, 'foo'),
++ (ENTER, START, 'bar'),
++ (INSIDE, TEXT, '(BAR)'),
++ (EXIT, END, 'bar'),
++ (None, END, 'root')]
+ )
+
+
+@@ -611,43 +611,43 @@ class RenameTest(unittest.TestCase):
+ 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')]
++ [(ENTER, START, 'foobar'),
++ (INSIDE, TEXT, 'ROOT'),
++ (INSIDE, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (INSIDE, END, 'foo'),
++ (INSIDE, START, 'bar'),
++ (INSIDE, TEXT, 'BAR'),
++ (INSIDE, END, 'bar'),
++ (EXIT, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, 'foobar'),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foobar'),
++ (ENTER, START, 'foobar'),
++ (INSIDE, TEXT, 'BAR'),
++ (EXIT, END, 'foobar'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, START, 'foo'),
++ (OUTSIDE, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, 'root')]
+ )
+
+
+@@ -658,15 +658,15 @@ class ContentTestMixin(object):
+
+ def __iter__(self):
+ self.count += 1
+- return iter(HTML(u'CONTENT %i' % self.count))
++ return iter(HTML('CONTENT %i' % self.count))
+
+- if isinstance(html, basestring) and not isinstance(html, unicode):
++ if isinstance(html, str) and not isinstance(html, str):
+ html = HTML(html, encoding='utf-8')
+ else:
+ html = HTML(html)
+ if content is None:
+ content = Injector()
+- elif isinstance(content, basestring):
++ elif isinstance(content, str):
+ content = HTML(content)
+ return _transform(html, getattr(Transformer(select), self.operation)
+ (content))
+@@ -678,59 +678,59 @@ class ReplaceTest(unittest.TestCase, ContentTestMixin)
+ 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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, TEXT, 'CONTENT 1'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'CONTENT 1'),
++ (None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, 'root')]
+ )
+
+ def test_replace_context(self):
+ self.assertEqual(
+ self._apply('.'),
+- [(None, TEXT, u'CONTENT 1')],
++ [(None, TEXT, 'CONTENT 1')],
+ )
+
+ def test_replace_text_context(self):
+ self.assertEqual(
+ self._apply('.', html='foo'),
+- [(None, TEXT, u'CONTENT 1')],
++ [(None, TEXT, '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')],
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, TEXT, 'CONTENT 1'),
++ (None, TEXT, 'CONTENT 2'),
++ (None, END, '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')],
++ [(None, START, 'root'),
++ (None, TEXT, 'CONTENT 1'),
++ (None, TEXT, 'CONTENT 2'),
++ (None, TEXT, 'CONTENT 3'),
++ (None, END, 'root')],
+ )
+
+ def test_replace_with_callback(self):
+@@ -740,11 +740,11 @@ class ReplaceTest(unittest.TestCase, ContentTestMixin)
+ 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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, TEXT, ' 1.'),
++ (None, TEXT, ' 2.'),
++ (None, END, 'root')]
+ )
+
+
+@@ -754,87 +754,87 @@ class BeforeTest(unittest.TestCase, ContentTestMixin):
+ 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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, TEXT, 'CONTENT 1'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'CONTENT 1'),
++ (OUTSIDE, TEXT, 'ROOT'),
++ (None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, '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')]
++ [(None, TEXT, 'CONTENT 1'),
++ (ENTER, START, 'root'),
++ (INSIDE, TEXT, 'ROOT'),
++ (INSIDE, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (INSIDE, END, 'foo'),
++ (INSIDE, START, 'bar'),
++ (INSIDE, TEXT, 'BAR'),
++ (INSIDE, END, 'bar'),
++ (EXIT, END, 'root')]
+ )
+
+ def test_before_text_context(self):
+ self.assertEqual(
+ self._apply('.', html='foo'),
+- [(None, TEXT, u'CONTENT 1'),
+- (OUTSIDE, TEXT, u'foo')]
++ [(None, TEXT, 'CONTENT 1'),
++ (OUTSIDE, TEXT, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, TEXT, 'CONTENT 1'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, TEXT, 'CONTENT 2'),
++ (ENTER, START, 'bar'),
++ (INSIDE, TEXT, 'BAR'),
++ (EXIT, END, 'bar'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'CONTENT 1'),
++ (OUTSIDE, TEXT, 'ROOT'),
++ (None, TEXT, 'CONTENT 2'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, TEXT, 'CONTENT 3'),
++ (ENTER, START, 'bar'),
++ (INSIDE, TEXT, 'BAR'),
++ (EXIT, END, 'bar'),
++ (None, END, 'root')]
+ )
+
+ def test_before_with_callback(self):
+@@ -844,16 +844,16 @@ class BeforeTest(unittest.TestCase, ContentTestMixin):
+ 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')]
++ [(None, 'START', 'root'),
++ (None, 'TEXT', 'ROOT'),
++ (None, 'START', 'foo'),
++ (None, 'TEXT', ' 1.'),
++ ('OUTSIDE', 'TEXT', 'FOO'),
++ (None, 'END', 'foo'),
++ (None, 'START', 'bar'),
++ (None, 'TEXT', 'BAR'),
++ (None, 'END', 'bar'),
++ (None, 'END', 'root')]
+ )
+
+
+@@ -863,87 +863,87 @@ class AfterTest(unittest.TestCase, ContentTestMixin):
+ 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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, TEXT, 'CONTENT 1'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (OUTSIDE, TEXT, 'ROOT'),
++ (None, TEXT, 'CONTENT 1'),
++ (None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, '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')]
++ [(ENTER, START, 'root'),
++ (INSIDE, TEXT, 'ROOT'),
++ (INSIDE, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (INSIDE, END, 'foo'),
++ (INSIDE, START, 'bar'),
++ (INSIDE, TEXT, 'BAR'),
++ (INSIDE, END, 'bar'),
++ (EXIT, END, 'root'),
++ (None, TEXT, 'CONTENT 1')]
+ )
+
+ def test_after_text_context(self):
+ self.assertEqual(
+ self._apply('.', html='foo'),
+- [(OUTSIDE, TEXT, u'foo'),
+- (None, TEXT, u'CONTENT 1')]
++ [(OUTSIDE, TEXT, 'foo'),
++ (None, TEXT, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, TEXT, 'CONTENT 1'),
++ (ENTER, START, 'bar'),
++ (INSIDE, TEXT, 'BAR'),
++ (EXIT, END, 'bar'),
++ (None, TEXT, 'CONTENT 2'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (OUTSIDE, TEXT, 'ROOT'),
++ (None, TEXT, 'CONTENT 1'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, TEXT, 'CONTENT 2'),
++ (ENTER, START, 'bar'),
++ (INSIDE, TEXT, 'BAR'),
++ (EXIT, END, 'bar'),
++ (None, TEXT, 'CONTENT 3'),
++ (None, END, 'root')]
+ )
+
+ def test_after_with_callback(self):
+@@ -953,16 +953,16 @@ class AfterTest(unittest.TestCase, ContentTestMixin):
+ 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')]
++ [(None, 'START', 'root'),
++ (None, 'TEXT', 'ROOT'),
++ (None, 'START', 'foo'),
++ ('OUTSIDE', 'TEXT', 'FOO'),
++ (None, 'TEXT', ' 1.'),
++ (None, 'END', 'foo'),
++ (None, 'START', 'bar'),
++ (None, 'TEXT', 'BAR'),
++ (None, 'END', 'bar'),
++ (None, 'END', 'root')]
+ )
+
+
+@@ -972,84 +972,84 @@ class PrependTest(unittest.TestCase, ContentTestMixin)
+ 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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, 'foo'),
++ (None, TEXT, 'CONTENT 1'),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (OUTSIDE, TEXT, 'ROOT'),
++ (None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, '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')],
++ [(ENTER, START, 'root'),
++ (None, TEXT, 'CONTENT 1'),
++ (INSIDE, TEXT, 'ROOT'),
++ (INSIDE, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (INSIDE, END, 'foo'),
++ (INSIDE, START, 'bar'),
++ (INSIDE, TEXT, 'BAR'),
++ (INSIDE, END, 'bar'),
++ (EXIT, END, 'root')],
+ )
+
+ def test_prepend_text_context(self):
+ self.assertEqual(
+ self._apply('.', html='foo'),
+- [(OUTSIDE, TEXT, u'foo')]
++ [(OUTSIDE, TEXT, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, 'foo'),
++ (None, TEXT, 'CONTENT 1'),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (ENTER, START, 'bar'),
++ (None, TEXT, 'CONTENT 2'),
++ (INSIDE, TEXT, 'BAR'),
++ (EXIT, END, 'bar'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (OUTSIDE, TEXT, 'ROOT'),
++ (ENTER, START, 'foo'),
++ (None, TEXT, 'CONTENT 1'),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (ENTER, START, 'bar'),
++ (None, TEXT, 'CONTENT 2'),
++ (INSIDE, TEXT, 'BAR'),
++ (EXIT, END, 'bar'),
++ (None, END, 'root')]
+ )
+
+ def test_prepend_with_callback(self):
+@@ -1059,16 +1059,16 @@ class PrependTest(unittest.TestCase, ContentTestMixin)
+ 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')]
++ [(None, 'START', 'root'),
++ (None, 'TEXT', 'ROOT'),
++ (ENTER, 'START', 'foo'),
++ (None, 'TEXT', ' 1.'),
++ (INSIDE, 'TEXT', 'FOO'),
++ (EXIT, 'END', 'foo'),
++ (None, 'START', 'bar'),
++ (None, 'TEXT', 'BAR'),
++ (None, 'END', 'bar'),
++ (None, 'END', 'root')]
+ )
+
+
+@@ -1078,84 +1078,84 @@ class AppendTest(unittest.TestCase, ContentTestMixin):
+ 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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (None, TEXT, 'CONTENT 1'),
++ (EXIT, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (OUTSIDE, TEXT, 'ROOT'),
++ (None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, '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')],
++ [(ENTER, START, 'root'),
++ (INSIDE, TEXT, 'ROOT'),
++ (INSIDE, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (INSIDE, END, 'foo'),
++ (INSIDE, START, 'bar'),
++ (INSIDE, TEXT, 'BAR'),
++ (INSIDE, END, 'bar'),
++ (None, TEXT, 'CONTENT 1'),
++ (EXIT, END, 'root')],
+ )
+
+ def test_append_text_context(self):
+ self.assertEqual(
+ self._apply('.', html='foo'),
+- [(OUTSIDE, TEXT, u'foo')]
++ [(OUTSIDE, TEXT, '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')]
++ [(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (None, TEXT, 'CONTENT 1'),
++ (EXIT, END, 'foo'),
++ (ENTER, START, 'bar'),
++ (INSIDE, TEXT, 'BAR'),
++ (None, TEXT, 'CONTENT 2'),
++ (EXIT, END, 'bar'),
++ (None, END, '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')]
++ [(None, START, 'root'),
++ (OUTSIDE, TEXT, 'ROOT'),
++ (ENTER, START, 'foo'),
++ (INSIDE, TEXT, 'FOO'),
++ (None, TEXT, 'CONTENT 1'),
++ (EXIT, END, 'foo'),
++ (ENTER, START, 'bar'),
++ (INSIDE, TEXT, 'BAR'),
++ (None, TEXT, 'CONTENT 2'),
++ (EXIT, END, 'bar'),
++ (None, END, 'root')]
+ )
+
+ def test_append_with_callback(self):
+@@ -1165,16 +1165,16 @@ class AppendTest(unittest.TestCase, ContentTestMixin):
+ 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')]
++ [(None, 'START', 'root'),
++ (None, 'TEXT', 'ROOT'),
++ (ENTER, 'START', 'foo'),
++ (INSIDE, 'TEXT', 'FOO'),
++ (None, 'TEXT', ' 1.'),
++ (EXIT, 'END', 'foo'),
++ (None, 'START', 'bar'),
++ (None, 'TEXT', 'BAR'),
++ (None, 'END', 'bar'),
++ (None, 'END', 'root')]
+ )
+
+
+@@ -1187,29 +1187,29 @@ class AttrTest(unittest.TestCase):
+ 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')]
++ [(None, START, ('root', {})),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, ('foo', {'name': 'FOO', 'size': '100'})),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, START, ('bar', {'name': 'bar'})),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, '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')]
++ [(None, START, ('root', {})),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, ('foo', {'name': 'foo', 'title': 'FOO', 'size': '100'})),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, START, ('bar', {'name': 'bar'})),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, 'root')]
+ )
+
+ def test_attr_from_function(self):
+@@ -1219,29 +1219,29 @@ class AttrTest(unittest.TestCase):
+
+ 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')]
++ [(None, START, ('root', {})),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, ('foo', {'name': 'FOO', 'size': '100'})),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (ENTER, START, ('bar', {'name': 'BAR'})),
++ (INSIDE, TEXT, 'BAR'),
++ (EXIT, END, 'bar'),
++ (None, END, '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')]
++ [(None, START, ('root', {})),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, ('foo', {'size': '100'})),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, START, ('bar', {'name': 'bar'})),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, 'root')]
+ )
+
+ def test_remove_attr_with_function(self):
+@@ -1250,15 +1250,15 @@ class AttrTest(unittest.TestCase):
+
+ 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')]
++ [(None, START, ('root', {})),
++ (None, TEXT, 'ROOT'),
++ (ENTER, START, ('foo', {'size': '100'})),
++ (INSIDE, TEXT, 'FOO'),
++ (EXIT, END, 'foo'),
++ (None, START, ('bar', {'name': 'bar'})),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, 'root')]
+ )
+
+
+@@ -1294,65 +1294,65 @@ class CopyTest(unittest.TestCase, BufferTestMixin):
+ def test_copy_element(self):
+ self.assertEqual(
+ self._apply('foo')[1],
+- [[(None, START, u'foo'),
+- (None, TEXT, u'FOO'),
+- (None, END, u'foo')]]
++ [[(None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, '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')]]
++ [[(None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo')],
++ [(None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, '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')]]
++ [[(None, TEXT, 'ROOT')],
++ [(None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo')],
++ [(None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar')]]
+ )
+
+ def test_copy_text(self):
+ self.assertEqual(
+ self._apply('*/text()')[1],
+- [[(None, TEXT, u'FOO')],
+- [(None, TEXT, u'BAR')]]
++ [[(None, TEXT, 'FOO')],
++ [(None, TEXT, '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')]]
++ [[(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, 'root')]]
+ )
+
+ def test_copy_attribute(self):
+ self.assertEqual(
+ self._apply('foo/@name', with_attrs=True)[1],
+- [[(None, ATTR, {'name': u'foo'})]]
++ [[(None, ATTR, {'name': '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'})]]
++ [[(None, ATTR, {'name': 'foo', 'size': '100'})]]
+ )
+
+
+@@ -1362,104 +1362,104 @@ class CutTest(unittest.TestCase, BufferTestMixin):
+ 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')]])
++ ([(None, START, 'root'),
++ (None, TEXT, 'ROOT'),
++ (None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, 'root')],
++ [[(None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo')]])
+ )
+
+ def test_cut_adjacent_elements(self):
+ self.assertEqual(
+ self._apply('foo|bar'),
+- ([(None, START, u'root'),
+- (None, TEXT, u'ROOT'),
++ ([(None, START, 'root'),
++ (None, TEXT, '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')]])
++ (None, END, 'root')],
++ [[(None, START, 'foo'),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo')],
++ [(None, START, 'bar'),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar')]])
+ )
+
+ def test_cut_all(self):
+ self.assertEqual(
+ self._apply('*|text()'),
+- ([(None, 'START', u'root'),
++ ([(None, 'START', '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')]])
++ (None, 'END', 'root')],
++ [[(None, 'TEXT', 'ROOT')],
++ [(None, 'START', 'foo'),
++ (None, 'TEXT', 'FOO'),
++ (None, 'END', 'foo')],
++ [(None, 'START', 'bar'),
++ (None, 'TEXT', 'BAR'),
++ (None, 'END', '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')]])
++ ([(None, 'START', 'root'),
++ (None, 'TEXT', 'ROOT'),
++ (None, 'START', 'foo'),
++ (None, 'END', 'foo'),
++ (None, 'START', 'bar'),
++ (None, 'END', 'bar'),
++ (None, 'END', 'root')],
++ [[(None, 'TEXT', 'FOO')],
++ [(None, 'TEXT', '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')]]
++ [[(None, 'START', 'root'),
++ (None, 'TEXT', 'ROOT'),
++ (None, 'START', 'foo'),
++ (None, 'TEXT', 'FOO'),
++ (None, 'END', 'foo'),
++ (None, 'START', 'bar'),
++ (None, 'TEXT', 'BAR'),
++ (None, 'END', 'bar'),
++ (None, 'END', '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'})]])
++ ([(None, START, ('root', {})),
++ (None, TEXT, 'ROOT'),
++ (None, START, ('foo', {'size': '100'})),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, START, ('bar', {'name': 'bar'})),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, 'root')],
++ [[(None, ATTR, {'name': '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'})]])
++ ([(None, START, ('root', {})),
++ (None, TEXT, 'ROOT'),
++ (None, START, ('foo', {})),
++ (None, TEXT, 'FOO'),
++ (None, END, 'foo'),
++ (None, START, ('bar', {'name': 'bar'})),
++ (None, TEXT, 'BAR'),
++ (None, END, 'bar'),
++ (None, END, 'root')],
++ [[(None, ATTR, {'name': 'foo', 'size': '100'})]])
+ )
+
+ # XXX Test this when the XPath implementation is fixed (#233).
+--- genshi/filters/transform.py.orig 2019-05-27 21:03:08 UTC
++++ genshi/filters/transform.py
+@@ -115,7 +115,7 @@ class PushBackStream(object):
+ yield peek
+ else:
+ try:
+- event = self.stream.next()
++ event = next(self.stream)
+ yield event
+ except StopIteration:
+ if self.peek is None:
+@@ -730,7 +730,7 @@ class SelectTransformation(object):
+ variables = {}
+ test = self.path.test()
+ stream = iter(stream)
+- next = stream.next
++ next = stream.__next__
+ for mark, event in stream:
+ if mark is None:
+ yield mark, event
+@@ -764,7 +764,7 @@ class SelectTransformation(object):
+ yield OUTSIDE, result
+ elif result:
+ # XXX Assume everything else is "text"?
+- yield None, (TEXT, unicode(result), (None, -1, -1))
++ yield None, (TEXT, str(result), (None, -1, -1))
+ else:
+ yield None, event
+
+@@ -990,7 +990,7 @@ class SubstituteTransformation(object):
+ :param replace: Replacement pattern.
+ :param count: Number of replacements to make in each text fragment.
+ """
+- if isinstance(pattern, basestring):
++ if isinstance(pattern, str):
+ self.pattern = re.compile(pattern)
+ else:
+ self.pattern = pattern
+--- genshi/input.py.orig 2019-05-27 21:03:08 UTC
++++ genshi/input.py
+@@ -17,8 +17,8 @@ sources.
+
+ from itertools import chain
+ import codecs
+-import htmlentitydefs as entities
+-import HTMLParser as html
++import html.entities as entities
++import html.parser as html
+ from xml.parsers import expat
+
+ from genshi.core import Attrs, QName, Stream, stripentities
+@@ -39,7 +39,7 @@ def ET(element):
+ """
+ tag_name = QName(element.tag.lstrip('{'))
+ attrs = Attrs([(QName(attr.lstrip('{')), value)
+- for attr, value in element.items()])
++ for attr, value in list(element.items())])
+
+ yield START, (tag_name, attrs), (None, -1, -1)
+ if element.text:
+@@ -91,8 +91,8 @@ class XMLParser(object):
+ """
+
+ _entitydefs = ['' % (name, value) for name, value in
+- entities.name2codepoint.items()]
+- _external_dtd = u'\n'.join(_entitydefs).encode('utf-8')
++ list(entities.name2codepoint.items())]
++ _external_dtd = '\n'.join(_entitydefs).encode('utf-8')
+
+ def __init__(self, source, filename=None, encoding=None):
+ """Initialize the parser for the given XML input.
+@@ -156,7 +156,7 @@ class XMLParser(object):
+ del self.expat # get rid of circular references
+ done = True
+ else:
+- if isinstance(data, unicode):
++ if isinstance(data, str):
+ data = data.encode('utf-8')
+ self.expat.Parse(data, False)
+ for event in self._queue:
+@@ -164,7 +164,7 @@ class XMLParser(object):
+ self._queue = []
+ if done:
+ break
+- except expat.ExpatError, e:
++ except expat.ExpatError as e:
+ msg = str(e)
+ raise ParseError(msg, self.filename, e.lineno, e.offset)
+ return Stream(_generate()).filter(_coalesce)
+@@ -242,7 +242,7 @@ class XMLParser(object):
+ if text.startswith('&'):
+ # deal with undefined entities
+ try:
+- text = unichr(entities.name2codepoint[text[1:-1]])
++ text = chr(entities.name2codepoint[text[1:-1]])
+ self._enqueue(TEXT, text)
+ except KeyError:
+ filename, lineno, offset = self._getpos()
+@@ -333,7 +333,7 @@ class HTMLParser(html.HTMLParser, object):
+ self.close()
+ done = True
+ else:
+- if not isinstance(data, unicode):
++ if not isinstance(data, str):
+ raise UnicodeError("source returned bytes, but no encoding specified")
+ self.feed(data)
+ for kind, data, pos in self._queue:
+@@ -345,7 +345,7 @@ class HTMLParser(html.HTMLParser, object):
+ for tag in open_tags:
+ yield END, QName(tag), pos
+ break
+- except html.HTMLParseError, e:
++ except html.HTMLParseError as e:
+ msg = '%s: line %d, column %d' % (e.msg, e.lineno, e.offset)
+ raise ParseError(msg, self.filename, e.lineno, e.offset)
+ return Stream(_generate()).filter(_coalesce)
+@@ -388,14 +388,14 @@ class HTMLParser(html.HTMLParser, object):
+
+ def handle_charref(self, name):
+ if name.lower().startswith('x'):
+- text = unichr(int(name[1:], 16))
++ text = chr(int(name[1:], 16))
+ else:
+- text = unichr(int(name))
++ text = chr(int(name))
+ self._enqueue(TEXT, text)
+
+ def handle_entityref(self, name):
+ try:
+- text = unichr(entities.name2codepoint[name])
++ text = chr(entities.name2codepoint[name])
+ except KeyError:
+ text = '&%s;' % name
+ self._enqueue(TEXT, text)
+@@ -434,7 +434,7 @@ def HTML(text, encoding=None):
+ :raises ParseError: if the HTML text is not well-formed, and error recovery
+ fails
+ """
+- if isinstance(text, unicode):
++ if isinstance(text, str):
+ # If it's unicode text the encoding should be set to None.
+ # The option to pass in an incorrect encoding is for ease
+ # of writing doctests that work in both Python 2.x and 3.x.
+--- genshi/output.py.orig 2019-05-27 21:03:08 UTC
++++ genshi/output.py
+@@ -71,7 +71,7 @@ def get_serializer(method='xml', **kwargs):
+ :see: `XMLSerializer`, `XHTMLSerializer`, `HTMLSerializer`, `TextSerializer`
+ :since: version 0.4.1
+ """
+- if isinstance(method, basestring):
++ if isinstance(method, str):
+ method = {'xml': XMLSerializer,
+ 'xhtml': XHTMLSerializer,
+ 'html': HTMLSerializer,
+@@ -581,7 +581,7 @@ class TextSerializer(object):
+ data = event[1]
+ if strip_markup and type(data) is Markup:
+ data = data.striptags().stripentities()
+- yield unicode(data)
++ yield str(data)
+
+
+ class EmptyTagFilter(object):
+@@ -636,7 +636,7 @@ class NamespaceFlattener(object):
+ self.cache = cache
+
+ def __call__(self, stream):
+- prefixes = dict([(v, [k]) for k, v in self.prefixes.items()])
++ prefixes = dict([(v, [k]) for k, v in list(self.prefixes.items())])
+ namespaces = {XML_NAMESPACE.uri: ['xml']}
+ _emit, _get, cache = _prepare_cache(self.cache)
+ def _push_ns(prefix, uri):
+@@ -666,7 +666,7 @@ class NamespaceFlattener(object):
+ while 1:
+ val += 1
+ yield 'ns%d' % val
+- _gen_prefix = _gen_prefix().next
++ _gen_prefix = _gen_prefix().__next__
+
+ for kind, data, pos in stream:
+ if kind is TEXT and isinstance(data, Markup):
+@@ -822,7 +822,7 @@ class DocTypeInserter(object):
+
+ :param doctype: DOCTYPE as a string or DocType object.
+ """
+- if isinstance(doctype, basestring):
++ if isinstance(doctype, str):
+ doctype = DocType.get(doctype)
+ self.doctype_event = (DOCTYPE, doctype, (None, -1, -1))
+
+--- genshi/path.py.orig 2019-05-27 21:03:08 UTC
++++ genshi/path.py
+@@ -576,7 +576,7 @@ class Path(object):
+ variables = {}
+ stream = iter(stream)
+ def _generate(stream=stream, ns=namespaces, vs=variables):
+- next = stream.next
++ next = stream.__next__
+ test = self.test()
+ for event in stream:
+ result = test(event, ns, vs)
+@@ -932,13 +932,13 @@ def as_float(value):
+ return float(as_scalar(value))
+
+ def as_long(value):
+- return long(as_scalar(value))
++ return int(as_scalar(value))
+
+ def as_string(value):
+ value = as_scalar(value)
+ if value is False:
+ return ''
+- return unicode(value)
++ return str(value)
+
+ def as_bool(value):
+ return bool(as_scalar(value))
+@@ -1346,8 +1346,8 @@ class TranslateFunction(Function):
+ string = as_string(self.string(kind, data, pos, namespaces, variables))
+ fromchars = as_string(self.fromchars(kind, data, pos, namespaces, variables))
+ tochars = as_string(self.tochars(kind, data, pos, namespaces, variables))
+- table = dict(zip([ord(c) for c in fromchars],
+- [ord(c) for c in tochars]))
++ table = dict(list(zip([ord(c) for c in fromchars],
++ [ord(c) for c in tochars])))
+ return string.translate(table)
+ def __repr__(self):
+ return 'translate(%r, %r, %r)' % (self.string, self.fromchars,
+--- genshi/template/ast24.py.orig 2019-05-27 21:03:08 UTC
++++ genshi/template/ast24.py
+@@ -378,7 +378,7 @@ class ASTUpgrader(object):
+ def visit_Const(self, node):
+ if node.value is None: # appears in slices
+ return None
+- elif isinstance(node.value, basestring):
++ elif isinstance(node.value, str):
+ return self._new(_ast.Str, node.value)
+ else:
+ return self._new(_ast.Num, node.value)
+--- genshi/template/base.py.orig 2019-05-27 21:03:08 UTC
++++ genshi/template/base.py
+@@ -179,7 +179,7 @@ class Context(object):
+
+ :return: the number of variables in the context
+ """
+- return len(self.items())
++ return len(list(self.items()))
+
+ def __setitem__(self, key, value):
+ """Set a variable in the current scope.
+@@ -232,7 +232,7 @@ class Context(object):
+
+ :return: a list of variables
+ """
+- return [(key, self.get(key)) for key in self.keys()]
++ return [(key, self.get(key)) for key in list(self.keys())]
+
+ def update(self, mapping):
+ """Update the context from the mapping provided."""
+@@ -321,12 +321,11 @@ class DirectiveFactoryMeta(type):
+ return type.__new__(cls, name, bases, d)
+
+
+-class DirectiveFactory(object):
++class DirectiveFactory(object, metaclass=DirectiveFactoryMeta):
+ """Base for classes that provide a set of template directives.
+
+ :since: version 0.6
+ """
+- __metaclass__ = DirectiveFactoryMeta
+
+ directives = []
+ """A list of ``(name, cls)`` tuples that define the set of directives
+@@ -379,7 +378,7 @@ class Template(DirectiveFactory):
+ """
+
+ serializer = None
+- _number_conv = unicode # function used to convert numbers to event data
++ _number_conv = str # function used to convert numbers to event data
+
+ def __init__(self, source, filepath=None, filename=None, loader=None,
+ encoding=None, lookup='strict', allow_exec=True):
+@@ -411,13 +410,13 @@ class Template(DirectiveFactory):
+ self._prepared = False
+
+ if not isinstance(source, Stream) and not hasattr(source, 'read'):
+- if isinstance(source, unicode):
++ if isinstance(source, str):
+ source = StringIO(source)
+ else:
+ source = BytesIO(source)
+ try:
+ self._stream = self._parse(source, encoding)
+- except ParseError, e:
++ except ParseError as e:
+ raise TemplateSyntaxError(e.msg, self.filepath, e.lineno, e.offset)
+
+ def __getstate__(self):
+@@ -502,7 +501,7 @@ class Template(DirectiveFactory):
+ if kind is INCLUDE:
+ href, cls, fallback = data
+ tmpl_inlined = False
+- if (isinstance(href, basestring) and
++ if (isinstance(href, str) and
+ not getattr(self.loader, 'auto_reload', True)):
+ # If the path to the included template is static, and
+ # auto-reloading is disabled on the template loader,
+@@ -601,16 +600,16 @@ class Template(DirectiveFactory):
+ # First check for a string, otherwise the iterable test
+ # below succeeds, and the string will be chopped up into
+ # individual characters
+- if isinstance(result, basestring):
++ if isinstance(result, str):
+ yield TEXT, result, pos
+- elif isinstance(result, (int, float, long)):
++ elif isinstance(result, (int, float)):
+ yield TEXT, number_conv(result), pos
+ elif hasattr(result, '__iter__'):
+ push(stream)
+ stream = _ensure(result)
+ break
+ else:
+- yield TEXT, unicode(result), pos
++ yield TEXT, str(result), pos
+
+ elif kind is SUB:
+ # This event is a list of directives and a list of nested
+@@ -639,7 +638,7 @@ class Template(DirectiveFactory):
+ for event in stream:
+ if event[0] is INCLUDE:
+ href, cls, fallback = event[1]
+- if not isinstance(href, basestring):
++ if not isinstance(href, str):
+ parts = []
+ for subkind, subdata, subpos in self._flatten(href, ctxt,
+ **vars):
+--- genshi/template/directives.py.orig 2019-05-27 21:03:08 UTC
++++ genshi/template/directives.py
+@@ -35,7 +35,7 @@ class DirectiveMeta(type):
+ return type.__new__(cls, name, bases, d)
+
+
+-class Directive(object):
++class Directive(object, metaclass=DirectiveMeta):
+ """Abstract base class for template directives.
+
+ A directive is basically a callable that takes three positional arguments:
+@@ -53,7 +53,6 @@ class Directive(object):
+ described above, and can only be applied programmatically (for example by
+ template filters).
+ """
+- __metaclass__ = DirectiveMeta
+ __slots__ = ['expr']
+
+ def __init__(self, value, template=None, namespaces=None, lineno=-1,
+@@ -108,7 +107,7 @@ class Directive(object):
+ try:
+ return expr and Expression(expr, template.filepath, lineno,
+ lookup=template.lookup) or None
+- except SyntaxError, err:
++ except SyntaxError as err:
+ err.msg += ' in expression "%s" of "%s" directive' % (expr,
+ cls.tagname)
+ raise TemplateSyntaxError(err, template.filepath, lineno,
+@@ -165,18 +164,18 @@ class AttrsDirective(Directive):
+
+ def __call__(self, stream, directives, ctxt, **vars):
+ def _generate():
+- kind, (tag, attrib), pos = stream.next()
++ kind, (tag, attrib), pos = next(stream)
+ attrs = _eval_expr(self.expr, ctxt, vars)
+ if attrs:
+ if isinstance(attrs, Stream):
+ try:
+- attrs = iter(attrs).next()
++ attrs = next(iter(attrs))
+ except StopIteration:
+ attrs = []
+ elif not isinstance(attrs, list): # assume it's a dict
+- attrs = attrs.items()
++ attrs = list(attrs.items())
+ attrib |= [
+- (QName(n), v is not None and unicode(v).strip() or None)
++ (QName(n), v is not None and str(v).strip() or None)
+ for n, v in attrs
+ ]
+ yield kind, (tag, attrib), pos
+@@ -537,8 +536,8 @@ class StripDirective(Directive):
+ def __call__(self, stream, directives, ctxt, **vars):
+ def _generate():
+ if not self.expr or _eval_expr(self.expr, ctxt, vars):
+- stream.next() # skip start tag
+- previous = stream.next()
++ next(stream) # skip start tag
++ previous = next(stream)
+ for event in stream:
+ yield previous
+ previous = event
+@@ -630,13 +629,13 @@ class WhenDirective(Directive):
+ if not info:
+ raise TemplateRuntimeError('"when" directives can only be used '
+ 'inside a "choose" directive',
+- self.filename, *(stream.next())[2][1:])
++ self.filename, *(next(stream))[2][1:])
+ if info[0]:
+ return []
+ if not self.expr and not info[1]:
+ raise TemplateRuntimeError('either "choose" or "when" directive '
+ 'must have a test expression',
+- self.filename, *(stream.next())[2][1:])
++ self.filename, *(next(stream))[2][1:])
+ if info[1]:
+ value = info[2]
+ if self.expr:
+@@ -669,7 +668,7 @@ class OtherwiseDirective(Directive):
+ if not info:
+ raise TemplateRuntimeError('an "otherwise" directive can only be '
+ 'used inside a "choose" directive',
+- self.filename, *(stream.next())[2][1:])
++ self.filename, *(next(stream))[2][1:])
+ if info[0]:
+ return []
+ info[0] = True
+@@ -706,7 +705,7 @@ class WithDirective(Directive):
+ self.vars.append(([_assignment(n) for n in node.targets],
+ Expression(node.value, template.filepath,
+ lineno, lookup=template.lookup)))
+- except SyntaxError, err:
++ except SyntaxError as err:
+ err.msg += ' in expression "%s" of "%s" directive' % (value,
+ self.tagname)
+ raise TemplateSyntaxError(err, template.filepath, lineno,
+--- genshi/template/eval.py.orig 2019-05-27 21:03:08 UTC
++++ genshi/template/eval.py
+@@ -13,7 +13,7 @@
+
+ """Support for "safe" evaluation of Python expressions."""
+
+-import __builtin__
++import builtins
+
+ from textwrap import dedent
+ from types import CodeType
+@@ -37,7 +37,7 @@ has_star_import_bug = False
+ try:
+ class _FakeMapping(object):
+ __getitem__ = __setitem__ = lambda *a: None
+- exec 'from sys import *' in {}, _FakeMapping()
++ exec('from sys import *', {}, _FakeMapping())
+ except SystemError:
+ has_star_import_bug = True
+ del _FakeMapping
+@@ -75,7 +75,7 @@ class Code(object):
+ if `None`, the appropriate transformation is chosen
+ depending on the mode
+ """
+- if isinstance(source, basestring):
++ if isinstance(source, str):
+ self.source = source
+ node = _parse(source, mode=self.mode)
+ else:
+@@ -94,13 +94,13 @@ class Code(object):
+ filename=filename, lineno=lineno, xform=xform)
+ if lookup is None:
+ lookup = LenientLookup
+- elif isinstance(lookup, basestring):
++ elif isinstance(lookup, str):
+ lookup = {'lenient': LenientLookup, 'strict': StrictLookup}[lookup]
+ self._globals = lookup.globals
+
+ def __getstate__(self):
+ state = {'source': self.source, 'ast': self.ast,
+- 'lookup': self._globals.im_self}
++ 'lookup': self._globals.__self__}
+ state['code'] = get_code_params(self.code)
+ return state
+
+@@ -196,7 +196,7 @@ class Suite(Code):
+ """
+ __traceback_hide__ = 'before_and_this'
+ _globals = self._globals(data)
+- exec self.code in _globals, data
++ exec(self.code, _globals, data)
+
+
+ UNDEFINED = object()
+@@ -264,7 +264,7 @@ class Undefined(object):
+ def __iter__(self):
+ return iter([])
+
+- def __nonzero__(self):
++ def __bool__(self):
+ return False
+
+ def __repr__(self):
+@@ -333,8 +333,8 @@ class LookupBase(object):
+ key = key[0]
+ try:
+ return obj[key]
+- except (AttributeError, KeyError, IndexError, TypeError), e:
+- if isinstance(key, basestring):
++ except (AttributeError, KeyError, IndexError, TypeError) as e:
++ if isinstance(key, str):
+ val = getattr(obj, key, UNDEFINED)
+ if val is UNDEFINED:
+ val = cls.undefined(key, owner=obj)
+@@ -424,8 +424,8 @@ def _parse(source, mode='eval'):
+ if first.rstrip().endswith(':') and not rest[0].isspace():
+ rest = '\n'.join([' %s' % line for line in rest.splitlines()])
+ source = '\n'.join([first, rest])
+- if isinstance(source, unicode):
+- source = (u'\ufeff' + source).encode('utf-8')
++ if isinstance(source, str):
++ source = ('\ufeff' + source).encode('utf-8')
+ return parse(source, mode)
+
+
+@@ -435,11 +435,11 @@ def _compile(node, source=None, mode='eval', filename=
+ filename = '
'
+ if IS_PYTHON2:
+ # Python 2 requires non-unicode filenames
+- if isinstance(filename, unicode):
++ if isinstance(filename, str):
+ filename = filename.encode('utf-8', 'replace')
+ else:
+ # Python 3 requires unicode filenames
+- if not isinstance(filename, unicode):
++ if not isinstance(filename, str):
+ filename = filename.decode('utf-8', 'replace')
+ if lineno <= 0:
+ lineno = 1
+@@ -483,7 +483,7 @@ def _new(class_, *args, **kwargs):
+ return ret
+
+
+-BUILTINS = __builtin__.__dict__.copy()
++BUILTINS = builtins.__dict__.copy()
+ BUILTINS.update({'Markup': Markup, 'Undefined': Undefined})
+ CONSTANTS = frozenset(['False', 'True', 'None', 'NotImplemented', 'Ellipsis'])
+
+@@ -527,7 +527,7 @@ class TemplateASTTransformer(ASTTransformer):
+ return names
+
+ def visit_Str(self, node):
+- if not isinstance(node.s, unicode):
++ if not isinstance(node.s, str):
+ try: # If the string is ASCII, return a `str` object
+ node.s.decode('ascii')
+ except ValueError: # Otherwise return a `unicode` object
+--- genshi/template/interpolation.py.orig 2019-05-27 21:03:08 UTC
++++ genshi/template/interpolation.py
+@@ -77,7 +77,7 @@ def interpolate(text, filepath=None, lineno=-1, offset
+ expr = Expression(chunk.strip(), pos[0], pos[1],
+ lookup=lookup)
+ yield EXPR, expr, tuple(pos)
+- except SyntaxError, err:
++ except SyntaxError as err:
+ raise TemplateSyntaxError(err, filepath, pos[1],
+ pos[2] + (err.offset or 0))
+ else:
+--- genshi/template/loader.py.orig 2019-05-27 21:03:08 UTC
++++ genshi/template/loader.py
+@@ -217,7 +217,7 @@ class TemplateLoader(object):
+ raise TemplateError('Search path for templates not configured')
+
+ for loadfunc in search_path:
+- if isinstance(loadfunc, basestring):
++ if isinstance(loadfunc, str):
+ loadfunc = directory(loadfunc)
+ try:
+ filepath, filename, fileobj, uptodate = loadfunc(filename)
+@@ -327,9 +327,9 @@ class TemplateLoader(object):
+ :rtype: ``function``
+ """
+ def _dispatch_by_prefix(filename):
+- for prefix, delegate in delegates.items():
++ for prefix, delegate in list(delegates.items()):
+ if filename.startswith(prefix):
+- if isinstance(delegate, basestring):
++ if isinstance(delegate, str):
+ delegate = directory(delegate)
+ filepath, _, fileobj, uptodate = delegate(
+ filename[len(prefix):].lstrip('/\\')
+--- genshi/template/markup.py.orig 2019-05-27 21:03:08 UTC
++++ genshi/template/markup.py
+@@ -93,7 +93,7 @@ class MarkupTemplate(Template):
+ try:
+ suite = Suite(data[1], self.filepath, pos[1],
+ lookup=self.lookup)
+- except SyntaxError, err:
++ except SyntaxError as err:
+ raise TemplateSyntaxError(err, self.filepath,
+ pos[1] + (err.lineno or 1) - 1,
+ pos[2] + (err.offset or 0))
+@@ -311,7 +311,7 @@ class MarkupTemplate(Template):
+
+ def _strip(stream, append):
+ depth = 1
+- next = stream.next
++ next = stream.__next__
+ while 1:
+ event = next()
+ if event[0] is START:
+--- genshi/template/plugin.py.orig 2019-05-27 21:03:08 UTC
++++ genshi/template/plugin.py
+@@ -46,7 +46,7 @@ class AbstractTemplateEnginePlugin(object):
+
+ self.default_encoding = options.get('genshi.default_encoding', None)
+ auto_reload = options.get('genshi.auto_reload', '1')
+- if isinstance(auto_reload, basestring):
++ if isinstance(auto_reload, str):
+ auto_reload = auto_reload.lower() in ('1', 'on', 'yes', 'true')
+ search_path = [p for p in
+ options.get('genshi.search_path', '').split(':') if p]
+@@ -168,7 +168,7 @@ class TextTemplateEnginePlugin(AbstractTemplateEngineP
+ options = {}
+
+ new_syntax = options.get('genshi.new_text_syntax')
+- if isinstance(new_syntax, basestring):
++ if isinstance(new_syntax, str):
+ new_syntax = new_syntax.lower() in ('1', 'on', 'yes', 'true')
+ if new_syntax:
+ self.template_class = NewTextTemplate
+--- genshi/template/tests/directives.py.orig 2019-05-27 21:03:08 UTC
++++ genshi/template/tests/directives.py
+@@ -426,7 +426,7 @@ class ForDirectiveTestCase(unittest.TestCase):
+ 3
+ 4
+ 5
+- """, tmpl.generate(items=range(1, 6)).render(encoding=None))
++ """, tmpl.generate(items=list(range(1, 6))).render(encoding=None))
+
+ def test_as_element(self):
+ """
+@@ -443,7 +443,7 @@ class ForDirectiveTestCase(unittest.TestCase):
+ 3
+ 4
+ 5
+- """, tmpl.generate(items=range(1, 6)).render(encoding=None))
++ """, tmpl.generate(items=list(range(1, 6))).render(encoding=None))
+
+ def test_multi_assignment(self):
+ """
+@@ -487,7 +487,7 @@ class ForDirectiveTestCase(unittest.TestCase):
+ try:
+ list(tmpl.generate(foo=12))
+ self.fail('Expected TemplateRuntimeError')
+- except TypeError, e:
++ except TypeError as e:
+ assert (str(e) == "iteration over non-sequence" or
+ str(e) == "'int' object is not iterable")
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+@@ -513,7 +513,7 @@ class ForDirectiveTestCase(unittest.TestCase):
+
+ """, filename='test.html').generate()
+ self.fail('ExpectedTemplateSyntaxError')
+- except TemplateSyntaxError, e:
++ except TemplateSyntaxError as e:
+ self.assertEqual('test.html', e.filename)
+ if sys.version_info[:2] > (2,4):
+ self.assertEqual(2, e.lineno)
+@@ -1050,7 +1050,7 @@ class ContentDirectiveTestCase(unittest.TestCase):
+ Foo
+ """, filename='test.html').generate()
+ self.fail('Expected TemplateSyntaxError')
+- except TemplateSyntaxError, e:
++ except TemplateSyntaxError as e:
+ self.assertEqual('test.html', e.filename)
+ self.assertEqual(2, e.lineno)
+
+@@ -1068,7 +1068,7 @@ class ReplaceDirectiveTestCase(unittest.TestCase):
+ Foo
+ """, filename='test.html').generate()
+ self.fail('Expected TemplateSyntaxError')
+- except TemplateSyntaxError, e:
++ except TemplateSyntaxError as e:
+ self.assertEqual('test.html', e.filename)
+ self.assertEqual(2, e.lineno)
+
+@@ -1190,12 +1190,12 @@ class WithDirectiveTestCase(unittest.TestCase):
+ """, tmpl.generate(foo={'bar': 42}).render(encoding=None))
+
+ def test_unicode_expr(self):
+- tmpl = MarkupTemplate(u"""