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