changeset 38:ee669cb9cccc trunk

Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
author cmlenz
date Mon, 03 Jul 2006 11:28:13 +0000
parents 37557b8fb925
children 93b4dcbafd7b
files examples/includes/skins/default/layout.html examples/turbogears/markuptest/templates/master.html markup/path.py markup/template.py markup/tests/path.py
diffstat 5 files changed, 59 insertions(+), 41 deletions(-) [+]
line wrap: on
line diff
--- a/examples/includes/skins/default/layout.html
+++ b/examples/includes/skins/default/layout.html
@@ -10,7 +10,7 @@
  <body py:match="body">
    <xi:include href="header.html" />
    <div id="content">
-     ${select('body/*')}
+     ${select('*')}
    </div>
    <xi:include href="footer.html" />
  </body>
--- a/examples/turbogears/markuptest/templates/master.html
+++ b/examples/turbogears/markuptest/templates/master.html
@@ -7,7 +7,7 @@
 <head py:match="head">
     <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
     <title py:replace="''">Your title goes here</title>
-    ${select('*/*')}
+    ${select('*')}
     <style type="text/css">
         #pageLogin
         {
@@ -32,7 +32,7 @@
 
     <div py:if="tg_flash" class="flash" py:content="tg_flash"></div>
 
-    ${select('*/*')}
+    ${select('*')}
 
     <p align="center"><img src="/static/images/tg_under_the_hood.png" alt="TurboGears under the hood"/></p>
 </body>
--- a/markup/path.py
+++ b/markup/path.py
@@ -55,6 +55,8 @@
                     else:
                         raise NotImplementedError('XPath function "%s" not '
                                                   'supported' % cur_tag)
+                elif op == '.':
+                    steps.append([False, self._CurrentElement(), []])
                 else:
                     cur_op += op
                 cur_tag = ''
@@ -67,7 +69,7 @@
                         node_test = self._AttributeByName(tag)
                 else:
                     if tag == '*':
-                        node_test = self._AnyElement()
+                        node_test = self._AnyChildElement()
                     elif in_predicate:
                         if len(tag) > 1 and (tag[0], tag[-1]) in self._QUOTES:
                             node_test = self._LiteralString(tag[1:-1])
@@ -80,14 +82,17 @@
                                                           node_test)
                             steps[-1][2].pop()
                     else:
-                        node_test = self._ElementByName(tag)
+                        node_test = self._ChildElementByName(tag)
                 if in_predicate:
                     steps[-1][2].append(node_test)
                 else:
                     steps.append([closure, node_test, []])
                 cur_op = ''
                 cur_tag = tag
-        self.steps = steps
+
+        self.steps = []
+        for step in steps:
+            self.steps.append(tuple(step))
 
     def __repr__(self):
         return '<%s "%s">' % (self.__class__.__name__, self.source)
@@ -119,17 +124,14 @@
                     depth = 1
                     while depth > 0:
                         ev = stream.next()
-                        if ev[0] is Stream.START:
-                            depth += 1
-                        elif ev[0] is Stream.END:
-                            depth -= 1
+                        depth += {Stream.START: 1, Stream.END: -1}.get(ev[0], 0)
                         yield ev
                         test(*ev)
                 elif result:
                     yield result
         return Stream(_generate())
 
-    def test(self):
+    def test(self, ignore_context=False):
         """Returns a function that can be used to track whether the path matches
         a specific stream event.
         
@@ -172,9 +174,11 @@
 
             if matched:
                 if stack[-1] == len(self.steps) - 1:
-                    return matched
-
-                stack[-1] += 1
+                    if ignore_context or len(stack) > 2 \
+                                      or node_test.axis != 'child':
+                        return matched
+                else:
+                    stack[-1] += 1
 
             elif kind is Stream.START and not closure:
                 # If this step is not a closure, it cannot be matched until the
@@ -195,17 +199,31 @@
 
         return _test
 
-    class _AnyElement(object):
-        """Node test that matches any element."""
+    class _NodeTest(object):
+        """Abstract node test."""
+        axis = None
+        def __repr__(self):
+            return '<%s>' % self.__class__.__name__
+
+    class _CurrentElement(_NodeTest):
+        """Node test that matches the context node."""
+        axis = 'self'
         def __call__(self, kind, *_):
             if kind is Stream.START:
                 return True
             return None
-        def __repr__(self):
-            return '<%s>' % self.__class__.__name__
 
-    class _ElementByName(object):
-        """Node test that matches an element with a specific tag name."""
+    class _AnyChildElement(_NodeTest):
+        """Node test that matches any child element."""
+        axis = 'child'
+        def __call__(self, kind, *_):
+            if kind is Stream.START:
+                return True
+            return None
+
+    class _ChildElementByName(_NodeTest):
+        """Node test that matches a child element with a specific tag name."""
+        axis = 'child'
         def __init__(self, name):
             self.name = QName(name)
         def __call__(self, kind, data, _):
@@ -215,8 +233,9 @@
         def __repr__(self):
             return '<%s "%s">' % (self.__class__.__name__, self.name)
 
-    class _AnyAttribute(object):
+    class _AnyAttribute(_NodeTest):
         """Node test that matches any attribute."""
+        axis = 'attribute'
         def __call__(self, kind, data, pos):
             if kind is Stream.START:
                 text = ''.join([val for _, val in data[1]])
@@ -224,11 +243,10 @@
                     return Stream.TEXT, text, pos
                 return None
             return None
-        def __repr__(self):
-            return '<%s>' % (self.__class__.__name__)
 
-    class _AttributeByName(object):
+    class _AttributeByName(_NodeTest):
         """Node test that matches an attribute with a specific name."""
+        axis = 'attribute'
         def __init__(self, name):
             self.name = QName(name)
         def __call__(self, kind, data, pos):
@@ -240,25 +258,24 @@
         def __repr__(self):
             return '<%s "%s">' % (self.__class__.__name__, self.name)
 
-    class _FunctionText(object):
+    class _Function(_NodeTest):
+        """Abstract node test representing a function."""
+
+    class _FunctionText(_Function):
         """Function that returns text content."""
         def __call__(self, kind, data, pos):
             if kind is Stream.TEXT:
                 return kind, data, pos
             return None
-        def __repr__(self):
-            return '<%s>' % (self.__class__.__name__)
 
-    class _LiteralString(object):
+    class _LiteralString(_NodeTest):
         """Always returns a literal string."""
         def __init__(self, value):
             self.value = value
         def __call__(self, *_):
             return Stream.TEXT, self.value, (-1, -1)
-        def __repr__(self):
-            return '<%s>' % (self.__class__.__name__)
 
-    class _OperatorEq(object):
+    class _OperatorEq(_NodeTest):
         """Equality comparison operator."""
         def __init__(self, lval, rval):
             self.lval = lval
@@ -271,7 +288,7 @@
             return '<%s %r = %r>' % (self.__class__.__name__, self.lval,
                                      self.rval)
 
-    class _OperatorNeq(object):
+    class _OperatorNeq(_NodeTest):
         """Inequality comparison operator."""
         def __init__(self, lval, rval):
             self.lval = lval
--- a/markup/template.py
+++ b/markup/template.py
@@ -417,7 +417,8 @@
 
     def __call__(self, stream, ctxt):
         self.stream = list(stream)
-        ctxt._match_templates.append((self.path.test(), self.path, self.stream))
+        ctxt._match_templates.append((self.path.test(ignore_context=True),
+                                      self.path, self.stream))
         return []
 
     def __repr__(self):
--- a/markup/tests/path.py
+++ b/markup/tests/path.py
@@ -21,15 +21,15 @@
 class PathTestCase(unittest.TestCase):
 
     def test_1step(self):
-        xml = XML('<root/>')
-        self.assertEqual('<root/>', Path('root').select(xml).render())
-        self.assertEqual('<root/>', Path('//root').select(xml).render())
-
+        xml = XML('<root><elem/></root>')
+        self.assertEqual('<elem/>', Path('elem').select(xml).render())
+        self.assertEqual('<elem/>', Path('//elem').select(xml).render())
+    
     def test_1step_wildcard(self):
-        xml = XML('<root/>')
-        self.assertEqual('<root/>', Path('*').select(xml).render())
-        self.assertEqual('<root/>', Path('//*').select(xml).render())
-
+        xml = XML('<root><elem/></root>')
+        self.assertEqual('<elem/>', Path('*').select(xml).render())
+        self.assertEqual('<elem/>', Path('//*').select(xml).render())
+    
     def test_1step_attribute(self):
         path = Path('@foo')
         self.assertEqual('', path.select(XML('<root/>')).render())
Copyright (C) 2012-2017 Edgewall Software