# -*- coding: utf-8 -*-
#
# Copyright (C) 2006 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 unittest
from genshi.core import Attrs, QName
from genshi.input import XML
from genshi.path import Path, PathParser, PathSyntaxError, GenericStrategy, \
SingleStepStrategy, SimplePathStrategy
from genshi.tests.test_utils import doctest_suite
class FakePath(Path):
def __init__(self, strategy):
self.strategy = strategy
def test(self, ignore_context = False):
return self.strategy.test(ignore_context)
class PathTestCase(unittest.TestCase):
strategies = [GenericStrategy, SingleStepStrategy, SimplePathStrategy]
def test_error_no_absolute_path(self):
self.assertRaises(PathSyntaxError, Path, '/root')
def test_error_unsupported_axis(self):
self.assertRaises(PathSyntaxError, Path, '..')
self.assertRaises(PathSyntaxError, Path, 'parent::ma')
def test_1step(self):
xml = XML('')
self._test_eval(
path = 'elem',
equiv = '',
input = xml,
output = ''
)
self._test_eval(
path = 'elem',
equiv = '',
input = xml,
output = ''
)
self._test_eval(
path = 'child::elem',
equiv = '',
input = xml,
output = ''
)
self._test_eval(
path = '//elem',
equiv = '',
input = xml,
output = ''
)
self._test_eval(
path = 'descendant::elem',
equiv = '',
input = xml,
output = ''
)
def test_1step_self(self):
xml = XML('')
self._test_eval(
path = '.',
equiv = '',
input = xml,
output = ''
)
self._test_eval(
path = 'self::node()',
equiv = '',
input = xml,
output = ''
)
def test_1step_wildcard(self):
xml = XML('')
self._test_eval(
path = '*',
equiv = '',
input = xml,
output = ''
)
self._test_eval(
path = 'child::*',
equiv = '',
input = xml,
output = ''
)
self._test_eval(
path = 'child::node()',
equiv = '',
input = xml,
output = ''
)
self._test_eval(
path = '//*',
equiv = '',
input = xml,
output = ''
)
def test_1step_attribute(self):
self._test_eval(
path = '@foo',
equiv = '',
input = XML(''),
output = ''
)
xml = XML('')
self._test_eval(
path = '@foo',
equiv = '',
input = xml,
output = 'bar'
)
self._test_eval(
path = './@foo',
equiv = '',
input = xml,
output = 'bar'
)
def test_1step_text(self):
xml = XML('Hey')
self._test_eval(
path = 'text()',
equiv = '',
input = xml,
output = 'Hey'
)
self._test_eval(
path = './text()',
equiv = '',
input = xml,
output = 'Hey'
)
self._test_eval(
path = '//text()',
equiv = '',
input = xml,
output = 'Hey'
)
self._test_eval(
path = './/text()',
equiv = '',
input = xml,
output = 'Hey'
)
def test_2step(self):
xml = XML('')
self._test_eval('*', input=xml, output='')
self._test_eval('bar', input=xml, output='')
self._test_eval('baz', input=xml, output='')
def test_2step_attribute(self):
xml = XML('Hey Joe')
self._test_eval('@*', input=xml, output='x')
self._test_eval('./@*', input=xml, output='x')
self._test_eval('.//@*', input=xml, output='xjoe')
self._test_eval('*/@*', input=xml, output='joe')
xml = XML('')
self._test_eval('@*', input=xml, output='')
self._test_eval('foo/@*', input=xml, output='12')
def test_2step_complex(self):
xml = XML('')
self._test_eval(
path = 'foo/bar',
equiv = '',
input = xml,
output = ''
)
self._test_eval(
path = './bar',
equiv = '',
input = xml,
output = ''
)
self._test_eval(
path = 'foo/*',
equiv = '',
input = xml,
output = ''
)
xml = XML('')
self._test_eval(
path = './bar',
equiv = '',
input = xml,
output = ''
)
xml = XML('''''')
self._test_eval(
path = 'tr/td[1]',
input = xml,
output = '| 1 | 2 | '
)
xml = XML('''''')
self._test_eval(
path = 'li[2]/ul',
input = xml,
output = ''
)
def test_2step_text(self):
xml = XML('- Foo
')
self._test_eval(
path = 'item/text()',
equiv = '',
input = xml,
output = 'Foo'
)
self._test_eval(
path = '*/text()',
equiv = '',
input = xml,
output = 'Foo'
)
self._test_eval(
path = '//text()',
equiv = '',
input = xml,
output = 'Foo'
)
self._test_eval(
path = './text()',
equiv = '',
input = xml,
output = ''
)
xml = XML('- Foo
- Bar
')
self._test_eval(
path = 'item/text()',
equiv = '',
input = xml,
output = 'FooBar'
)
xml = XML('- FooBar
')
self._test_eval(
path = 'item/name/text()',
equiv = '',
input = xml,
output = 'Foo'
)
def test_3step(self):
xml = XML('')
self._test_eval(
path = 'foo/*',
equiv = '',
input = xml,
output = ''
)
def test_3step_complex(self):
self._test_eval(
path = '*/bar',
equiv = '',
input = XML(''),
output = ''
)
self._test_eval(
path = '//bar',
equiv = '',
input = XML(''),
output = ''
)
def test_3step_complex_text(self):
xml = XML('- Some text in here.
')
self._test_eval(
path = 'item/bar/text()',
equiv = '',
input = xml,
output = 'Some text '
)
self._test_eval(
path = 'item//bar/text()',
equiv = '',
input = xml,
output = 'Some text in here.'
)
def test_node_type_comment(self):
xml = XML('')
self._test_eval(
path = 'comment()',
equiv = '',
input = xml,
output = ''
)
def test_node_type_text(self):
xml = XML('Some text
in here.')
self._test_eval(
path = 'text()',
equiv = '',
input = xml,
output = 'Some text in here.'
)
def test_node_type_node(self):
xml = XML('Some text
in here.')
self._test_eval(
path = 'node()',
equiv = '',
input = xml,
output = 'Some text
in here.'
)
def test_node_type_processing_instruction(self):
xml = XML('')
self._test_eval(
path = '//processing-instruction()',
equiv = '',
input = xml,
output = ''
)
self._test_eval(
path = 'processing-instruction()',
equiv = '',
input = xml,
output = ''
)
self._test_eval(
path = 'processing-instruction("php")',
equiv = '',
input = xml,
output = ''
)
def test_simple_union(self):
xml = XML("""1
2
3
""")
self._test_eval(
path = '*|text()',
equiv = '',
input = xml,
output = '1
2
3
'
)
def test_predicate_name(self):
xml = XML('')
self._test_eval('*[name()="foo"]', input=xml, output='')
def test_predicate_localname(self):
xml = XML('')
self._test_eval('*[local-name()="foo"]', input=xml,
output='')
def test_predicate_namespace(self):
xml = XML('')
self._test_eval('*[namespace-uri()="NS"]', input=xml,
output='')
def test_predicate_not_name(self):
xml = XML('')
self._test_eval('*[not(name()="foo")]', input=xml,
output='')
def test_predicate_attr(self):
xml = XML(' ')
self._test_eval('item[@important]', input=xml,
output=' ')
self._test_eval('item[@important="very"]', input=xml,
output=' ')
def test_predicate_attr_equality(self):
xml = XML(' ')
self._test_eval('item[@important="very"]', input=xml, output='')
self._test_eval('item[@important!="very"]', input=xml,
output=' ')
def test_predicate_attr_greater_than(self):
xml = XML(' ')
self._test_eval('item[@priority>3]', input=xml, output='')
self._test_eval('item[@priority>2]', input=xml,
output=' ')
def test_predicate_attr_less_than(self):
xml = XML(' ')
self._test_eval('item[@priority<3]', input=xml, output='')
self._test_eval('item[@priority<4]', input=xml,
output=' ')
def test_predicate_attr_and(self):
xml = XML(' ')
self._test_eval('item[@important and @important="very"]',
input=xml, output=' ')
self._test_eval('item[@important and @important="notso"]',
input=xml, output='')
def test_predicate_attr_or(self):
xml = XML(' ')
self._test_eval('item[@urgent or @important]', input=xml,
output=' ')
self._test_eval('item[@urgent or @notso]', input=xml, output='')
def test_predicate_boolean_function(self):
xml = XML('bar')
self._test_eval('*[boolean("")]', input=xml, output='')
self._test_eval('*[boolean("yo")]', input=xml,
output='bar')
self._test_eval('*[boolean(0)]', input=xml, output='')
self._test_eval('*[boolean(42)]', input=xml,
output='bar')
self._test_eval('*[boolean(false())]', input=xml, output='')
self._test_eval('*[boolean(true())]', input=xml,
output='bar')
def test_predicate_ceil_function(self):
xml = XML('bar')
self._test_eval('*[ceiling("4.5")=5]', input=xml,
output='bar')
def test_predicate_concat_function(self):
xml = XML('bar')
self._test_eval('*[name()=concat("f", "oo")]', input=xml,
output='bar')
def test_predicate_contains_function(self):
xml = XML('bar')
self._test_eval('*[contains(name(), "oo")]', input=xml,
output='bar')
def test_predicate_matches_function(self):
xml = XML('barfoo')
self._test_eval('*[matches(name(), "foo|bar")]', input=xml,
output='barfoo')
def test_predicate_false_function(self):
xml = XML('bar')
self._test_eval('*[false()]', input=xml, output='')
def test_predicate_floor_function(self):
xml = XML('bar')
self._test_eval('*[floor("4.5")=4]', input=xml,
output='bar')
def test_predicate_normalize_space_function(self):
xml = XML('bar')
self._test_eval('*[normalize-space(" foo bar ")="foo bar"]',
input=xml, output='bar')
def test_predicate_number_function(self):
xml = XML('bar')
self._test_eval('*[number("3.0")=3]', input=xml,
output='bar')
self._test_eval('*[number("3.0")=3.0]', input=xml,
output='bar')
self._test_eval('*[number("0.1")=.1]', input=xml,
output='bar')
def test_predicate_round_function(self):
xml = XML('bar')
self._test_eval('*[round("4.4")=4]', input=xml,
output='bar')
self._test_eval('*[round("4.6")=5]', input=xml,
output='bar')
def test_predicate_starts_with_function(self):
xml = XML('bar')
self._test_eval('*[starts-with(name(), "f")]', input=xml,
output='bar')
self._test_eval('*[starts-with(name(), "b")]', input=xml,
output='')
def test_predicate_string_length_function(self):
xml = XML('bar')
self._test_eval('*[string-length(name())=3]', input=xml,
output='bar')
def test_predicate_substring_function(self):
xml = XML('bar')
self._test_eval('*[substring(name(), 1)="oo"]', input=xml,
output='bar')
self._test_eval('*[substring(name(), 1, 1)="o"]', input=xml,
output='bar')
def test_predicate_substring_after_function(self):
xml = XML('bar')
self._test_eval('*[substring-after(name(), "f")="oo"]', input=xml,
output='bar')
def test_predicate_substring_before_function(self):
xml = XML('bar')
self._test_eval('*[substring-before(name(), "oo")="f"]',
input=xml, output='bar')
def test_predicate_translate_function(self):
xml = XML('bar')
self._test_eval('*[translate(name(), "fo", "ba")="baa"]',
input=xml, output='bar')
def test_predicate_true_function(self):
xml = XML('bar')
self._test_eval('*[true()]', input=xml, output='bar')
def test_predicate_variable(self):
xml = XML('bar')
self._test_eval(
path = '*[name()=$bar]',
input = xml,
output = 'bar',
variables = {'bar': 'foo'}
)
def test_predicate_position(self):
xml = XML('')
self._test_eval('*[2]', input=xml, output='')
def test_predicate_attr_and_position(self):
xml = XML('')
self._test_eval('*[@id][2]', input=xml, output='')
def test_predicate_position_and_attr(self):
xml = XML('')
self._test_eval('*[1][@id]', input=xml, output='')
self._test_eval('*[2][@id]', input=xml, output='')
def test_predicate_advanced_position(self):
xml = XML('')
self._test_eval( 'descendant-or-self::*/'
'descendant-or-self::*/'
'descendant-or-self::*[2]/'
'self::*/descendant::*[3]', input=xml,
output='')
def test_predicate_child_position(self):
xml = XML('\
12345')
self._test_eval('//a/b[2]', input=xml, output='25')
self._test_eval('//a/b[3]', input=xml, output='3')
def test_name_with_namespace(self):
xml = XML('bar')
self._test_eval(
path = 'f:foo',
equiv = '',
input = xml,
output = 'bar',
namespaces = {'f': 'FOO'}
)
def test_wildcard_with_namespace(self):
xml = XML('bar')
self._test_eval(
path = 'f:*',
equiv = '',
input = xml,
output = 'bar',
namespaces = {'f': 'FOO'}
)
def test_predicate_termination(self):
"""
Verify that a patch matching the self axis with a predicate doesn't
cause an infinite loop. See .
"""
xml = XML('')
self._test_eval('.[@flag="1"]/*', input=xml,
output='ab')
xml = XML('')
self._test_eval('.[@flag="0"]/*', input=xml, output='')
def test_attrname_with_namespace(self):
xml = XML('')
self._test_eval('foo[@f:bar]', input=xml,
output='',
namespaces={'f': 'FOO'})
def test_attrwildcard_with_namespace(self):
xml = XML('')
self._test_eval('foo[@f:*]', input=xml,
output='',
namespaces={'f': 'FOO'})
def test_self_and_descendant(self):
xml = XML('')
self._test_eval('self::root', input=xml, output='')
self._test_eval('self::foo', input=xml, output='')
self._test_eval('descendant::root', input=xml, output='')
self._test_eval('descendant::foo', input=xml, output='')
self._test_eval('descendant-or-self::root', input=xml,
output='')
self._test_eval('descendant-or-self::foo', input=xml, output='')
def test_long_simple_paths(self):
xml = XML('!'
'')
self._test_eval('//a/b/a/b/a/c', input=xml, output='!')
self._test_eval('//a/b/a/c', input=xml, output='!')
self._test_eval('//a/c', input=xml, output='!')
self._test_eval('//c', input=xml, output='!')
# Please note that a//b is NOT the same as a/descendant::b
# it is a/descendant-or-self::node()/b, which SimplePathStrategy
# does NOT support
self._test_eval('a/b/descendant::a/c', input=xml, output='!')
self._test_eval('a/b/descendant::a/d/descendant::a/c',
input=xml, output='!')
self._test_eval('a/b/descendant::a/d/a/c', input=xml, output='')
self._test_eval('//d/descendant::b/descendant::b/descendant::b'
'/descendant::c', input=xml, output='!')
self._test_eval('//d/descendant::b/descendant::b/descendant::b'
'/descendant::b/descendant::c', input=xml,
output='')
def test_attr_selection(self):
xml = XML('')
path = Path('foo/@bar')
result = path.select(xml)
self.assertEqual(list(result), [
Attrs([(QName('bar'), u'abc')])
])
def test_attr_selection_with_namespace(self):
xml = XML(
''
''
'')
path = Path('foo/@ns1:bar')
result = path.select(xml, namespaces={'ns1': 'http://example.com'})
self.assertEqual(list(result), [
Attrs([(QName('http://example.com}bar'), u'abc')])
])
def _test_support(self, strategy_class, text):
path = PathParser(text, None, -1).parse()[0]
return strategy_class.supports(path)
def test_simple_strategy_support(self):
self.assertTrue(self._test_support(SimplePathStrategy, 'a/b'))
self.assertTrue(self._test_support(SimplePathStrategy, 'self::a/b'))
self.assertTrue(self._test_support(SimplePathStrategy, 'descendant::a/b'))
self.assertTrue(self._test_support(SimplePathStrategy,
'descendant-or-self::a/b'))
self.assertTrue(self._test_support(SimplePathStrategy, '//a/b'))
self.assertTrue(self._test_support(SimplePathStrategy, 'a/@b'))
self.assertTrue(self._test_support(SimplePathStrategy, 'a/text()'))
# a//b is a/descendant-or-self::node()/b
self.assertTrue(not self._test_support(SimplePathStrategy, 'a//b'))
self.assertTrue(not self._test_support(SimplePathStrategy, 'node()/@a'))
self.assertTrue(not self._test_support(SimplePathStrategy, '@a'))
self.assertTrue(not self._test_support(SimplePathStrategy, 'foo:bar'))
self.assertTrue(not self._test_support(SimplePathStrategy, 'a/@foo:bar'))
def _test_strategies(self, input, path, output,
namespaces=None, variables=None):
for strategy in self.strategies:
if not strategy.supports(path):
continue
s = strategy(path)
rendered = FakePath(s).select(input, namespaces=namespaces,
variables=variables) \
.render(encoding=None)
msg = 'Bad render using %s strategy' % str(strategy)
msg += '\nExpected:\t%r' % output
msg += '\nRendered:\t%r' % rendered
self.assertEqual(output, rendered, msg)
def _test_eval(self, path, equiv=None, input=None, output='',
namespaces=None, variables=None):
path = Path(path)
if equiv is not None:
self.assertEqual(equiv, repr(path))
if input is None:
return
rendered = path.select(input, namespaces=namespaces,
variables=variables).render(encoding=None)
msg = 'Bad output using whole path'
msg += '\nExpected:\t%r' % output
msg += '\nRendered:\t%r' % rendered
self.assertEqual(output, rendered, msg)
if len(path.paths) == 1:
self._test_strategies(input, path.paths[0], output,
namespaces=namespaces, variables=variables)
def suite():
suite = unittest.TestSuite()
suite.addTest(doctest_suite(Path.__module__))
suite.addTest(unittest.makeSuite(PathTestCase, 'test'))
return suite
if __name__ == '__main__':
unittest.main(defaultTest='suite')