# HG changeset patch
# User mkurczych
# Date 1212866551 0
# Node ID 7fa3af78556f583c64cd945b8bd2a22ec46b59c5
# Parent b3f6c8a11d6a152dc191e1a846a98a32136e6dcc
Path.test() function reimplementation and few
Changed path.py tests to play well with new understanding of few things
In test suite one test fails, don't know why yet
diff --git a/genshi/path.py b/genshi/path.py
--- a/genshi/path.py
+++ b/genshi/path.py
@@ -181,122 +181,154 @@
stream against the path
:rtype: ``function``
"""
- paths = [(p, len(p), [0], [], [0] * len(p)) for p in [
- (ignore_context and [_DOTSLASHSLASH] or []) + p for p in self.paths
- ]]
+ #For every path we store (path, stack, isDescendant)
+ paths = []
+ for p in self.paths:
+ if ignore_context:
+ if p[0][0] is ATTRIBUTE:
+ steps = [_DOTSLASHSLASH] + p
+ else:
+ steps = [(DESCENDANT_OR_SELF, p[0][1], p[0][2],)] + p[1:]
+ elif p[0][0] is CHILD or p[0][0] is ATTRIBUTE:
+ steps = [_DOTSLASH] + p
+ else:
+ steps = p
+
+ #for node it contains all positions of xpath expression
+ #where its child should start checking for matches
+ stack = [[0]]
+
+ #for every position in expression stores counters' list
+ #it is used for position based predicates
+ counters = [[] for _ in xrange(len(steps))]
+
+ #indexes where explession has descendant(-or-self) axis
+ descendantAxes = [i for (i, (a, _, _,),) in
+ enumerate(steps) if a is DESCENDANT
+ or a is DESCENDANT_OR_SELF]
+
+ paths.append((steps, stack, descendantAxes, counters,))
def _test(event, namespaces, variables, updateonly=False):
+
kind, data, pos = event[:3]
retval = None
- for steps, size, cursors, cutoff, counter in paths:
+ for steps, stack, descendantAxes, counters in paths:
# Manage the stack that tells us "where we are" in the stream
if kind is END:
- if cursors:
- cursors.pop()
+ stack.pop()
continue
elif kind is START:
- cursors.append(cursors and cursors[-1] or 0)
+ pass
elif kind is START_NS or kind is END_NS \
or kind is START_CDATA or kind is END_CDATA:
+ #should we make namespaces work?
continue
- if updateonly or retval or not cursors:
- continue
- cursor = cursors[-1]
- depth = len(cursors)
-
- if cutoff and depth + int(kind is not START) > cutoff[0]:
- continue
-
- ctxtnode = not ignore_context and kind is START \
- and depth == 2
- matched = None
- while 1:
- # Fetch the next location step
- axis, nodetest, predicates = steps[cursor]
+ #FIXME: Need to find out when we can do this
+ #if updateonly:
+ # continue
- # 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
-
- # Is this the last step of the location path?
- last_step = cursor + 1 == size
+ myPositions = stack[-1]
+ nextPositions = []
- # Perform the actual node test
- matched = nodetest(kind, data, pos, namespaces, variables)
+ #length of real part of path - we omit attribute axis
+ realLen = len(steps) - ((steps[-1][0] == ATTRIBUTE) or 1 and 0)
+ lastChecked = realLen
+
+ #places where we have to check for match, are these
+ #provided by parent
+ for position in myPositions:
- # The node test matched
- if matched:
+ #we have already checked this position
+ #(it had to be because of self-like axes)
+ if position >= lastChecked:
+ continue
- # Check all the predicates for this step
- if predicates:
+ for x in xrange(position, realLen):
+ #number of counters - we have to create one
+ #for every context position based predicate
+ cnum = 0
+
+ axis, nodetest, predicates = steps[x]
+
+ if x != position and axis is not SELF:
+ nextPositions.append(x)
+
+ #to go further we have to use self-like axes
+ if axis is not DESCENDANT_OR_SELF and \
+ axis is not SELF and x != position:
+ break
+
+
+ #tells if we have match with position x
+ matched = False
+
+ #nodetest first
+ if nodetest(kind, data, pos, namespaces, variables):
+ matched = True
+
+ #TODO: or maybe add nodetest here
+ #(chain((nodetest,), predicates))?
+ if matched and predicates:
for predicate in predicates:
- pretval = predicate(kind, data, pos, namespaces,
+ pretval = predicate(kind, data, pos,
+ namespaces,
variables)
if type(pretval) is float: # FIXME <- need to
# check this for
# other types that
# can be coerced to
# float
- counter[cursor] += 1
- if counter[cursor] != int(pretval):
+ if len(counters[x]) < cnum+1:
+ counters[x].append(0)
+ counters[x][cnum] += 1
+ if counters[x][cnum] != int(pretval):
pretval = False
+ cnum += 1
if not pretval:
- matched = None
- break
-
- # Both the node test and the predicates matched
+ matched = False
+ break
+ if not matched:
+ break
+ else:
+ #we reached end of expression, because x
+ #is equal to the length of expression
+ matched = True
+ axis, nodetest, predicates = steps[-1]
+ if axis is ATTRIBUTE:
+ matched = nodetest(kind, data, pos, \
+ namespaces, variables)
if matched:
- if last_step:
- if not ctxtnode or kind is not START \
- or axis is ATTRIBUTE or axis is SELF:
- retval = matched
- elif not ctxtnode or axis is SELF \
- or axis is DESCENDANT_OR_SELF:
- cursor += 1
- cursors[-1] = cursor
- cutoff[:] = []
-
- if kind is START:
- if last_step and not (axis is DESCENDANT or
- axis is DESCENDANT_OR_SELF):
- cutoff[:] = [depth]
+ retval = matched
- elif steps[cursor][0] 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 not matched or last_step or not (
- axis is SELF or axis is DESCENDANT_OR_SELF):
- break
- if ctxtnode and axis is DESCENDANT_OR_SELF:
- ctxtnode = False
- if (retval or not matched) and kind is START and \
- not (axis is DESCENDANT or axis is 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
- # against the current element
- backsteps = [(i, k, d, p) for i, (k, d, p)
- in enumerate(steps[:cursor])
- if k is DESCENDANT or k is DESCENDANT_OR_SELF]
- backsteps.reverse()
- for cursor, axis, nodetest, predicates in backsteps:
- if nodetest(kind, data, pos, namespaces, variables):
- cutoff[:] = []
- break
- cursors[-1] = cursor
+ if kind is START:
+ #in nextPositions there are positions that are implied
+ #by current matches, but we have to add previous
+ #descendant-like axis positions
+ #(which can "jump" over tree)
+ i = 0
+ stack.append([])
+ #every descendant axis before furthest position is ok
+ for pos in nextPositions:
+ while i != len(descendantAxes) \
+ and descendantAxes[i] < pos:
+ stack[-1].append(descendantAxes[i])
+ i += 1
+ if i != len(descendantAxes) \
+ and descendantAxes[i] == pos:
+ i += 1
+ stack[-1].append(pos)
+
+ #every descendant that was parent's one is ok
+ if myPositions:
+ while i != len(descendantAxes) \
+ and descendantAxes[i] <= myPositions[-1]:
+ stack[-1].append(descendantAxes[i])
+ i += 1
return retval
-
return _test
@@ -370,19 +402,28 @@
steps = []
while True:
if self.cur_token.startswith('/'):
- if self.cur_token == '//':
+ if not steps:
+ if self.cur_token == '//':
+ #hack to make //* match every node - also root
+ self.next_token()
+ axis, nodetest, predicates = self._location_step()
+ steps.append((DESCENDANT_OR_SELF, nodetest,
+ predicates))
+ if self.at_end or not self.cur_token.startswith('/'):
+ break
+ continue
+ else:
+ raise PathSyntaxError('Absolute location paths not '
+ 'supported', self.filename,
+ self.lineno)
+ elif self.cur_token == '//':
steps.append((DESCENDANT_OR_SELF, NodeTest(), []))
- elif not steps:
- raise PathSyntaxError('Absolute location paths not '
- 'supported', self.filename,
- self.lineno)
self.next_token()
axis, nodetest, predicates = self._location_step()
if not axis:
axis = CHILD
steps.append((axis, nodetest, predicates))
-
if self.at_end or not self.cur_token.startswith('/'):
break
@@ -1168,3 +1209,4 @@
_DOTSLASHSLASH = (DESCENDANT_OR_SELF, PrincipalTypeTest(None), ())
+_DOTSLASH = (SELF, PrincipalTypeTest(None), ())
diff --git a/genshi/tests/path.py b/genshi/tests/path.py
--- a/genshi/tests/path.py
+++ b/genshi/tests/path.py
@@ -39,7 +39,7 @@
self.assertEqual('', path.select(xml).render())
path = Path('//elem')
- self.assertEqual('',
+ self.assertEqual('',
repr(path))
self.assertEqual('', path.select(xml).render())
@@ -74,7 +74,7 @@
self.assertEqual('', Path('child::node()').select(xml).render())
path = Path('//*')
- self.assertEqual('',
+ self.assertEqual('',
repr(path))
self.assertEqual('', path.select(xml).render())
@@ -104,7 +104,7 @@
self.assertEqual('Hey', path.select(xml).render())
path = Path('//text()')
- self.assertEqual('',
+ self.assertEqual('',
repr(path))
self.assertEqual('Hey', path.select(xml).render())
@@ -162,7 +162,7 @@
self.assertEqual('Foo', path.select(xml).render())
path = Path('//text()')
- self.assertEqual('',
+ self.assertEqual('',
repr(path))
self.assertEqual('Foo', path.select(xml).render())
@@ -192,7 +192,7 @@
xml = XML('')
path = Path('//bar')
- self.assertEqual('',
+ self.assertEqual('',
repr(path))
self.assertEqual('',
path.select(xml).render())
@@ -218,11 +218,16 @@
def test_node_type_processing_instruction(self):
xml = XML('')
+ path = Path('//processing-instruction()')
+ self.assertEqual('',
+ repr(path))
+ self.assertEqual('',
+ path.select(xml).render())
+
path = Path('processing-instruction()')
self.assertEqual('',
repr(path))
- self.assertEqual('',
- path.select(xml).render())
+ self.assertEqual('', path.select(xml).render())
path = Path('processing-instruction("php")')
self.assertEqual('',