Mercurial > genshi > mirror
changeset 216:636fe6766b4d trunk
Many fixes to XPath evaluation. Among other things, this should get rid of the bug that attributes were getting ?pulled up? by `py:match` directives using `py:attrs="select('@*')"` (see #50).
author | cmlenz |
---|---|
date | Mon, 04 Sep 2006 19:29:32 +0000 |
parents | 94120e6b0ce4 |
children | f150cff4da18 |
files | markup/path.py markup/tests/path.py markup/tests/template.py |
diffstat | 3 files changed, 93 insertions(+), 48 deletions(-) [+] |
line wrap: on
line diff
--- a/markup/path.py +++ b/markup/path.py @@ -98,10 +98,10 @@ >>> from markup.input import XML >>> xml = XML('<root><elem><child>Text</child></elem></root>') - >>> print Path('child').select(xml) + >>> print Path('.//child').select(xml) <child>Text</child> - >>> print Path('child/text()').select(xml) + >>> print Path('.//child/text()').select(xml) Text @param stream: the stream to select from @@ -142,49 +142,90 @@ >>> for kind, data, pos in xml: ... if test(kind, data, pos, {}): ... print kind, data - START (u'child', [(u'id', u'1')]) START (u'child', [(u'id', u'2')]) """ - paths = [(steps, len(steps), [0]) for steps in self.paths] + paths = [(steps, len(steps), [0], []) for steps in self.paths] def _test(kind, data, pos, variables): - for steps, size, stack in paths: + for steps, size, stack, cutoff in paths: + # Manage the stack that tells us "where we are" in the stream if kind is END: - if stack: - stack.pop() + if stack: stack.pop() continue - elif kind is START: + elif kind in START: stack.append(stack and stack[-1] or 0) - - if not stack: + elif not stack: continue cursor = stack[-1] + if cutoff and len(stack) + int(kind is not START) > cutoff[0]: + continue + + ctxtnode = not ignore_context and kind is START \ + and len(stack) == 2 + matched = retval = None while 1: + # Fetch the next location step axis, nodetest, predicates = steps[cursor] - matched = nodetest(kind, data, pos, variables) - if matched and predicates: - for predicate in predicates: - if not predicate(kind, data, pos, variables): - matched = None - break - - if matched: - if cursor + 1 == size: # the last location step - if ignore_context or kind is not START \ - or axis is ATTRIBUTE or axis is SELF \ - or len(stack) > 2: - return matched - else: - cursor += 1 - stack[-1] = cursor - - if axis is not SELF: + # If this is the start event for the context node, and the + # axis of the location step doesn't include the current + # element, skip the test + if ctxtnode and (axis is CHILD or axis is DESCENDANT): break - if not matched and kind is START \ - and axis not in (DESCENDANT, DESCENDANT_OR_SELF): + # Is this the last step of the location path? + last_step = cursor + 1 == size + + # Perform the actual node test + matched = nodetest(kind, data, pos, variables) + + # The node test matched + if matched: + + # Check all the predicates for this step + if predicates: + for predicate in predicates: + if not predicate(kind, data, pos, variables): + matched = None + break + + # Both the node test and the predicates matched + if matched: + if last_step: + if ignore_context or kind is not START \ + or axis is ATTRIBUTE or axis is SELF \ + or len(stack) > 2: + retval = matched + elif not ctxtnode or axis is SELF \ + or axis is DESCENDANT_OR_SELF: + cursor += 1 + stack[-1] = cursor + cutoff[:] = [] + elif not ignore_context and kind is START: + cutoff[:] = [len(stack)] + + if last_step and not ignore_context and kind is START: + if (axis is not DESCENDANT and + axis is not DESCENDANT_OR_SELF): + cutoff[:] = [len(stack)] + + if kind is START and not last_step: + next_axis = steps[cursor][0] + if next_axis is ATTRIBUTE: + # If the axis of the next location step is the + # attribute axis, we need to move on to processing + # that step without waiting for the next markup + # event + continue + + # We're done with this step if it's the last step or the + # axis isn't "self" + if last_step or axis is not SELF: + break + + if kind is START and axis is not DESCENDANT \ + and axis is not DESCENDANT_OR_SELF: # If this step is not a closure, it cannot be matched until # the current element is closed... so we need to move the # cursor back to the previous closure and retest that @@ -196,10 +237,12 @@ matched = nodetest(kind, data, pos, variables) if not matched: cursor -= 1 + cutoff[:] = [] break stack[-1] = cursor - return None + if retval: + return retval return _test
--- a/markup/tests/path.py +++ b/markup/tests/path.py @@ -84,7 +84,9 @@ def test_1step_attribute(self): path = Path('@foo') self.assertEqual('<Path "attribute::foo">', repr(path)) - self.assertEqual('', path.select(XML('<root/>')).render()) + + xml = XML('<root/>') + self.assertEqual('', path.select(xml).render()) xml = XML('<root foo="bar"/>') self.assertEqual('bar', path.select(xml).render()) @@ -122,14 +124,14 @@ def test_2step_attribute(self): xml = XML('<elem class="x"><span id="joe">Hey Joe</span></elem>') - #self.assertEqual('x', Path('@*').select(xml).render()) - #self.assertEqual('x', Path('./@*').select(xml).render()) - #self.assertEqual('xjoe', Path('.//@*').select(xml).render()) + self.assertEqual('x', Path('@*').select(xml).render()) + self.assertEqual('x', Path('./@*').select(xml).render()) + self.assertEqual('xjoe', Path('.//@*').select(xml).render()) self.assertEqual('joe', Path('*/@*').select(xml).render()) xml = XML('<elem><foo id="1"/><foo id="2"/></elem>') - #self.assertEqual('', Path('@*').select(xml).render()) - #self.assertEqual('12', Path('foo/@*').select(xml).render()) + self.assertEqual('', Path('@*').select(xml).render()) + self.assertEqual('12', Path('foo/@*').select(xml).render()) def test_2step_complex(self): xml = XML('<root><foo><bar/></foo></root>') @@ -140,7 +142,7 @@ path = Path('./bar') self.assertEqual('<Path "self::node()/child::bar">', repr(path)) - #self.assertEqual('', path.select(xml).render()) + self.assertEqual('', path.select(xml).render()) path = Path('foo/*') self.assertEqual('<Path "child::foo/child::*">', repr(path)) @@ -149,7 +151,7 @@ xml = XML('<root><foo><bar id="1"/></foo><bar id="2"/></root>') path = Path('./bar') self.assertEqual('<Path "self::node()/child::bar">', repr(path)) - #self.assertEqual('<bar id="2"/>', path.select(xml).render()) + self.assertEqual('<bar id="2"/>', path.select(xml).render()) def test_2step_text(self): xml = XML('<root><item>Foo</item></root>') @@ -169,7 +171,7 @@ path = Path('./text()') self.assertEqual('<Path "self::node()/child::text()">', repr(path)) - #self.assertEqual('', path.select(xml).render()) + self.assertEqual('', path.select(xml).render()) xml = XML('<root><item>Foo</item><item>Bar</item></root>') path = Path('item/text()')
--- a/markup/tests/template.py +++ b/markup/tests/template.py @@ -419,7 +419,7 @@ """ tmpl = Template("""<doc xmlns:py="http://markup.edgewall.org/"> <elem py:match="elem" py:strip=""> - <div class="elem">${select('*/text()')}</div> + <div class="elem">${select('text()')}</div> </elem> <elem>Hey Joe</elem> </doc>""") @@ -434,7 +434,7 @@ """ tmpl = Template("""<doc xmlns:py="http://markup.edgewall.org/"> <elem py:match="elem"> - <div class="elem">${select('*/text()')}</div> + <div class="elem">${select('text()')}</div> </elem> <elem>Hey Joe</elem> </doc>""") @@ -450,7 +450,7 @@ """ tmpl = Template("""<doc xmlns:py="http://markup.edgewall.org/"> <py:match path="elem"> - <div class="elem">${select('*/text()')}</div> + <div class="elem">${select('text()')}</div> </py:match> <elem>Hey Joe</elem> </doc>""") @@ -466,7 +466,7 @@ tmpl = Template("""<doc xmlns:py="http://markup.edgewall.org/"> <elem py:match="elem"> <div class="elem"> - ${select('*/*')} + ${select('*')} </div> </elem> <elem> @@ -497,10 +497,10 @@ tmpl = Template("""<html xmlns:py="http://markup.edgewall.org/"> <body py:match="body"> <div id="header"/> - ${select('*/*')} + ${select('*')} </body> <body py:match="body"> - ${select('*/*')} + ${select('*')} <div id="footer"/> </body> <body> @@ -517,7 +517,7 @@ def test_select_all_attrs(self): tmpl = Template("""<doc xmlns:py="http://markup.edgewall.org/"> <div py:match="elem" py:attrs="select('@*')"> - ${select('*/text()')} + ${select('text()')} </div> <elem id="joe">Hey Joe</elem> </doc>""") @@ -530,7 +530,7 @@ def test_select_all_attrs_empty(self): tmpl = Template("""<doc xmlns:py="http://markup.edgewall.org/"> <div py:match="elem" py:attrs="select('@*')"> - ${select('*/text()')} + ${select('text()')} </div> <elem>Hey Joe</elem> </doc>""")