Mercurial > genshi > mirror
changeset 743:7fa3af78556f experimental-soc2008
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
author | mkurczych |
---|---|
date | Sat, 07 Jun 2008 19:22:31 +0000 |
parents | b3f6c8a11d6a |
children | 7e93b9a22fcb |
files | genshi/path.py genshi/tests/path.py |
diffstat | 2 files changed, 146 insertions(+), 99 deletions(-) [+] |
line wrap: on
line diff
--- 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), ())
--- a/genshi/tests/path.py +++ b/genshi/tests/path.py @@ -39,7 +39,7 @@ self.assertEqual('<elem/>', path.select(xml).render()) path = Path('//elem') - self.assertEqual('<Path "descendant-or-self::node()/child::elem">', + self.assertEqual('<Path "descendant-or-self::elem">', repr(path)) self.assertEqual('<elem/>', path.select(xml).render()) @@ -74,7 +74,7 @@ self.assertEqual('<elem/>', Path('child::node()').select(xml).render()) path = Path('//*') - self.assertEqual('<Path "descendant-or-self::node()/child::*">', + self.assertEqual('<Path "descendant-or-self::*">', repr(path)) self.assertEqual('<root><elem/></root>', path.select(xml).render()) @@ -104,7 +104,7 @@ self.assertEqual('Hey', path.select(xml).render()) path = Path('//text()') - self.assertEqual('<Path "descendant-or-self::node()/child::text()">', + self.assertEqual('<Path "descendant-or-self::text()">', 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('<Path "descendant-or-self::node()/child::text()">', + self.assertEqual('<Path "descendant-or-self::text()">', repr(path)) self.assertEqual('Foo', path.select(xml).render()) @@ -192,7 +192,7 @@ xml = XML('<root><foo><bar id="1"/></foo><bar id="2"/></root>') path = Path('//bar') - self.assertEqual('<Path "descendant-or-self::node()/child::bar">', + self.assertEqual('<Path "descendant-or-self::bar">', repr(path)) self.assertEqual('<bar id="1"/><bar id="2"/>', path.select(xml).render()) @@ -218,11 +218,16 @@ def test_node_type_processing_instruction(self): xml = XML('<?python x = 2 * 3 ?><root><?php echo("x") ?></root>') + path = Path('//processing-instruction()') + self.assertEqual('<Path "descendant-or-self::processing-instruction()">', + repr(path)) + self.assertEqual('<?python x = 2 * 3 ?><?php echo("x") ?>', + path.select(xml).render()) + path = Path('processing-instruction()') self.assertEqual('<Path "child::processing-instruction()">', repr(path)) - self.assertEqual('<?python x = 2 * 3 ?><?php echo("x") ?>', - path.select(xml).render()) + self.assertEqual('<?php echo("x") ?>', path.select(xml).render()) path = Path('processing-instruction("php")') self.assertEqual('<Path "child::processing-instruction(\"php\")">',