changeset 121:062e51ad7b19 trunk

Added support for the XPath functions `name()`, `namespace-uri()`, `local-name()`, and `not()`.
author cmlenz
date Wed, 02 Aug 2006 12:55:05 +0000
parents c9f0a26e28a2
children 6c5c6f67d3e8
files markup/path.py markup/tests/path.py
diffstat 2 files changed, 91 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/markup/path.py
+++ b/markup/path.py
@@ -232,6 +232,27 @@
         return kind is COMMENT and (kind, data, pos)
     return _function_comment
 
+def _function_local_name():
+    def _function_local_name(kind, data, pos):
+        if kind is START:
+            return TEXT, data[0].localname, pos
+        return kind, data, pos
+    return _function_local_name
+
+def _function_name():
+    def _function_name(kind, data, pos):
+        if kind is START:
+            return TEXT, data[0], pos
+        return kind, data, pos
+    return _function_name
+
+def _function_namespace_uri():
+    def _function_namespace_uri(kind, data, pos):
+        if kind is START:
+            return TEXT, data[0].namespace, pos
+        return kind, data, pos
+    return _function_namespace_uri
+
 def _function_node():
     def _function_node(kind, data, pos):
         if kind is START:
@@ -239,6 +260,11 @@
         return kind, data, pos
     return _function_node
 
+def _function_not(expr):
+    def _function_not(kind, data, pos):
+        return not expr(kind, data, pos)
+    return _function_not
+
 def _function_processing_instruction(name=None):
     def _function_processing_instruction(kind, data, pos):
         if kind is PI and (not name or data[0] == name):
@@ -258,37 +284,49 @@
 def _operator_eq(lval, rval):
     def _operator_eq(kind, data, pos):
         lv = lval(kind, data, pos)
+        if type(lv) is tuple:
+            lv = lv[1]
         rv = rval(kind, data, pos)
-        return (lv and lv[1]) == (rv and rv[1])
+        if type(rv) is tuple:
+            rv = rv[1]
+        return lv == rv
     return _operator_eq
 
 def _operator_neq(lval, rval):
     def _operator_neq(kind, data, pos):
         lv = lval(kind, data, pos)
+        if type(lv) is tuple:
+            lv = lv[1]
         rv = rval(kind, data, pos)
-        return (lv and lv[1]) != (rv and rv[1])
+        if type(rv) is tuple:
+            rv = rv[1]
+        return lv != rv
     return _operator_neq
 
 def _operator_and(lval, rval):
     def _operator_and(kind, data, pos):
         lv = lval(kind, data, pos)
-        if not lv or (lv is not True and not lv[1]):
+        if type(lv) is tuple:
+            lv = lv[1]
+        if not lv:
             return False
         rv = rval(kind, data, pos)
-        if not rv or (rv is not True and not rv[1]):
-            return False
-        return True
+        if type(rv) is tuple:
+            rv = rv[1]
+        return bool(rv)
     return _operator_and
 
 def _operator_or(lval, rval):
     def _operator_or(kind, data, pos):
         lv = lval(kind, data, pos)
-        if lv and (lv is True or lv[1]):
+        if type(lv) is tuple:
+            lv = lv[1]
+        if lv:
             return True
         rv = rval(kind, data, pos)
-        if rv and (rv is True or rv[1]):
-            return True
-        return False
+        if type(rv) is tuple:
+            rv = rv[1]
+        return bool(rv)
     return _operator_or
 
 
@@ -309,7 +347,7 @@
 
     _QUOTES = (("'", "'"), ('"', '"'))
     _TOKENS = ('::', ':', '..', '.', '//', '/', '[', ']', '()', '(', ')', '@',
-               '=', '!=', '!', '|')
+               '=', '!=', '!', '|', ',')
     _tokenize = re.compile('(%s)|([^%s\s]+)|\s+' % (
                            '|'.join([re.escape(t) for t in _TOKENS]),
                            ''.join([re.escape(t[0]) for t in _TOKENS]))).findall
@@ -447,7 +485,9 @@
         assert self.cur_token == '['
         self.next_token()
         expr = self._or_expr()
-        assert self.cur_token == ']'
+        if self.cur_token != ']':
+            raise PathSyntaxError('Expected "]" to close predicate, '
+                                  'but found "%s"' % self.cur_token)
         if not self.at_end:
             self.next_token()
         return expr
@@ -482,6 +522,25 @@
         elif token[0].isdigit():
             self.next_token()
             return _literal_number(float(token))
+        elif not self.at_end and self.peek_token().startswith('('):
+            if self.next_token() == '()':
+                args = []
+            else:
+                self.next_token()
+                args = [self._or_expr()]
+                while self.cur_token not in (',', ')'):
+                    args.append(self._or_expr())
+            self.next_token()
+            if token == 'local-name':
+                return _function_local_name(*args)
+            elif token == 'name':
+                return _function_name(*args)
+            elif token == 'namespace-uri':
+                return _function_namespace_uri(*args)
+            elif token == 'not':
+                return _function_not(*args)
+            else:
+                raise PathSyntaxError('Unsupported function "%s"' % token)
         else:
             axis = None
             if token == '@':
--- a/markup/tests/path.py
+++ b/markup/tests/path.py
@@ -118,6 +118,26 @@
         self.assertEqual('Oh <foo>my</foo>',
                          Path('*|text()').select(xml).render())
 
+    def test_predicate_name(self):
+        xml = XML('<root><foo/><bar/></root>')
+        self.assertEqual('<foo/>',
+                         Path('*[name()="foo"]').select(xml).render())
+
+    def test_predicate_localname(self):
+        xml = XML('<root><foo xmlns="NS"/><bar/></root>')
+        self.assertEqual('<foo xmlns="NS"/>',
+                         Path('*[local-name()="foo"]').select(xml).render())
+
+    def test_predicate_namespace(self):
+        xml = XML('<root><foo xmlns="NS"/><bar/></root>')
+        self.assertEqual('<foo xmlns="NS"/>',
+                         Path('*[namespace-uri()="NS"]').select(xml).render())
+
+    def test_predicate_not_name(self):
+        xml = XML('<root><foo/><bar/></root>')
+        self.assertEqual('<bar/>',
+                         Path('*[not(name()="foo")]').select(xml).render())
+
     def test_predicate_attr(self):
         xml = XML('<root><item/><item important="very"/></root>')
         self.assertEqual('<item important="very"/>',
Copyright (C) 2012-2017 Edgewall Software