# HG changeset patch
# User cmlenz
# Date 1157712674 0
# Node ID f79b20a5091920b26a49a74fb7f574d492faa577
# Parent 98b8e1a92df82ce5195864b7f83200e73b69a21b
Add support for position predicates in XPath expressions.
diff --git a/markup/path.py b/markup/path.py
--- a/markup/path.py
+++ b/markup/path.py
@@ -154,25 +154,28 @@
... print kind, data
START (u'child', [(u'id', u'2')])
"""
- paths = [(steps, len(steps), [0], []) for steps in self.paths]
+ paths = [(p, len(p), [0], [], [0] * len(p)) for p in self.paths]
def _test(kind, data, pos, namespaces, variables):
- for steps, size, stack, cutoff in paths:
+ for steps, size, cursors, cutoff, counter in paths:
+
# Manage the stack that tells us "where we are" in the stream
if kind is END:
- if stack: stack.pop()
+ if cursors:
+ cursors.pop()
continue
elif kind is START:
- stack.append(stack and stack[-1] or 0)
- elif not stack:
+ cursors.append(cursors and cursors[-1] or 0)
+ elif not cursors:
continue
- cursor = stack[-1]
+ cursor = cursors[-1]
+ depth = len(cursors)
- if cutoff and len(stack) + int(kind is not START) > cutoff[0]:
+ if cutoff and depth + int(kind is not START) > cutoff[0]:
continue
ctxtnode = not ignore_context and kind is START \
- and len(stack) == 2
+ and depth == 2
matched = retval = None
while 1:
# Fetch the next location step
@@ -196,8 +199,13 @@
# Check all the predicates for this step
if predicates:
for predicate in predicates:
- if not predicate(kind, data, pos, namespaces,
- variables):
+ pretval = predicate(kind, data, pos, namespaces,
+ variables)
+ if type(pretval) is float:
+ counter[cursor] += 1
+ if counter[cursor] != int(pretval):
+ pretval = False
+ if not pretval:
matched = None
break
@@ -210,15 +218,16 @@
elif not ctxtnode or axis is SELF \
or axis is DESCENDANT_OR_SELF:
cursor += 1
- stack[-1] = cursor
+ cursors[-1] = cursor
cutoff[:] = []
+
elif not ignore_context and kind is START:
- cutoff[:] = [len(stack)]
+ cutoff[:] = [depth]
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)]
+ cutoff[:] = [depth]
if kind is START and not last_step:
next_axis = steps[cursor][0]
@@ -251,7 +260,7 @@
cursor -= 1
cutoff[:] = []
break
- stack[-1] = cursor
+ cursors[-1] = cursor
if retval:
return retval
@@ -423,8 +432,6 @@
assert self.cur_token == '['
self.next_token()
expr = self._or_expr()
- if isinstance(expr, NumberLiteral):
- raise PathSyntaxError('Position predicates not yet supported')
if self.cur_token != ']':
raise PathSyntaxError('Expected "]" to close predicate, '
'but found "%s"' % self.cur_token,
@@ -973,7 +980,7 @@
def __init__(self, text):
self.text = text
def __call__(self, kind, data, pos, namespaces, variables):
- return TEXT, self.text, (None, -1, -1)
+ return self.text
def __repr__(self):
return '"%s"' % self.text
@@ -983,7 +990,7 @@
def __init__(self, number):
self.number = number
def __call__(self, kind, data, pos, namespaces, variables):
- return TEXT, self.number, (None, -1, -1)
+ return self.number
def __repr__(self):
return str(self.number)
@@ -993,7 +1000,7 @@
def __init__(self, name):
self.name = name
def __call__(self, kind, data, pos, namespaces, variables):
- return TEXT, variables.get(self.name), (None, -1, -1)
+ return variables.get(self.name)
def __repr__(self):
return str(self.name)
diff --git a/markup/tests/path.py b/markup/tests/path.py
--- a/markup/tests/path.py
+++ b/markup/tests/path.py
@@ -27,9 +27,6 @@
self.assertRaises(PathSyntaxError, Path, '..')
self.assertRaises(PathSyntaxError, Path, 'parent::ma')
- def test_error_position_predicate(self):
- self.assertRaises(PathSyntaxError, Path, 'item[0]')
-
def test_1step(self):
xml = XML('')
@@ -407,6 +404,23 @@
self.assertEqual('bar',
path.select(xml, variables=variables).render())
+ def test_predicate_position(self):
+ xml = XML('')
+ path = Path('*[2]')
+ self.assertEqual('', path.select(xml).render())
+
+ def test_predicate_attr_and_position(self):
+ xml = XML('')
+ path = Path('*[@id][2]')
+ self.assertEqual('', path.select(xml).render())
+
+ def test_predicate_position_and_attr(self):
+ xml = XML('')
+ path = Path('*[1][@id]')
+ self.assertEqual('', path.select(xml).render())
+ path = Path('*[2][@id]')
+ self.assertEqual('', path.select(xml).render())
+
def test_name_with_namespace(self):
xml = XML('bar')
path = Path('f:foo')