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\")">',
Copyright (C) 2012-2017 Edgewall Software