changeset 162:456039594db9 trunk

Implement the XPath relational operators and the `round()` function.
author cmlenz
date Wed, 16 Aug 2006 22:48:48 +0000
parents 7b1f07496bf7
children 9c023c395e44
files ChangeLog markup/path.py markup/tests/path.py
diffstat 3 files changed, 150 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -21,9 +21,9 @@
  * String literals in XPath expressions that contain spaces are now parsed
    as expected.
  * Added support for the XPath functions boolean(), ceiling(), concat(),
-   contains(), false(), floor(), normalize-space(), number(), starts-with(),
-   string-length(), substring(), substring-after(), substring-before(),
-   translate(), and true().
+   contains(), false(), floor(), normalize-space(), number(), round(),
+   starts-with(), string-length(), substring(), substring-after(),
+   substring-before(), translate(), and true().
 
 
 Version 0.1
--- a/markup/path.py
+++ b/markup/path.py
@@ -223,7 +223,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
@@ -381,9 +381,17 @@
         return expr
 
     def _equality_expr(self):
+        expr = self._relational_expr()
+        while self.cur_token in ('=', '!='):
+            op = _operator_map[self.cur_token]
+            self.next_token()
+            expr = op(expr, self._relational_expr())
+        return expr
+
+    def _relational_expr(self):
         expr = self._primary_expr()
-        while self.cur_token in ('=', '!='):
-            op = _operator_map.get(self.cur_token)
+        while self.cur_token in ('>', '>=', '<', '>='):
+            op = _operator_map[self.cur_token]
             self.next_token()
             expr = op(expr, self._primary_expr())
         return expr
@@ -675,6 +683,21 @@
     def __repr__(self):
         return 'number(%r)' % self.expr
 
+class RoundFunction(Function):
+    """The `round` function, which returns the nearest integer number for the
+    given number.
+    """
+    __slots__ = ['number']
+    def __init__(self, number):
+        self.number = number
+    def __call__(self, kind, data, pos):
+        number = self.number(kind, data, pos)
+        if type(number) is tuple:
+            number = number[1]
+        return round(float(number))
+    def __repr__(self):
+        return 'round(%r)' % self.number
+
 class StartsWithFunction(Function):
     """The `starts-with` function that returns whether one string starts with
     a given substring.
@@ -822,7 +845,8 @@
                  'local-name': LocalNameFunction, 'name': NameFunction,
                  'namespace-uri': NamespaceUriFunction,
                  'normalize-space': NormalizeSpaceFunction, 'not': NotFunction,
-                 'number': NumberFunction, 'starts-with': StartsWithFunction,
+                 'number': NumberFunction, 'round': RoundFunction,
+                 'starts-with': StartsWithFunction,
                  'string-length': StringLengthFunction,
                  'substring': SubstringFunction,
                  'substring-after': SubstringAfterFunction,
@@ -928,4 +952,91 @@
     def __repr__(self):
         return '%s or %s' % (self.lval, self.rval)
 
-_operator_map = {'=': EqualsOperator, '!=': NotEqualsOperator}
+class GreaterThanOperator(object):
+    """The relational operator `>` (greater than)."""
+    __slots__ = ['lval', 'rval']
+    def __init__(self, lval, rval):
+        self.lval = lval
+        self.rval = rval
+    def __call__(self, kind, data, pos):
+        lval = self.lval(kind, data, pos)
+        if type(lval) is tuple:
+            lval = lval[1]
+        rval = self.rval(kind, data, pos)
+        if type(rval) is tuple:
+            rval = rval[1]
+        return float(lval) > float(rval)
+    def __repr__(self):
+        return '%s>%s' % (self.lval, self.rval)
+
+class GreaterThanOperator(object):
+    """The relational operator `>` (greater than)."""
+    __slots__ = ['lval', 'rval']
+    def __init__(self, lval, rval):
+        self.lval = lval
+        self.rval = rval
+    def __call__(self, kind, data, pos):
+        lval = self.lval(kind, data, pos)
+        if type(lval) is tuple:
+            lval = lval[1]
+        rval = self.rval(kind, data, pos)
+        if type(rval) is tuple:
+            rval = rval[1]
+        return float(lval) > float(rval)
+    def __repr__(self):
+        return '%s>%s' % (self.lval, self.rval)
+
+class GreaterThanOrEqualOperator(object):
+    """The relational operator `>=` (greater than or equal)."""
+    __slots__ = ['lval', 'rval']
+    def __init__(self, lval, rval):
+        self.lval = lval
+        self.rval = rval
+    def __call__(self, kind, data, pos):
+        lval = self.lval(kind, data, pos)
+        if type(lval) is tuple:
+            lval = lval[1]
+        rval = self.rval(kind, data, pos)
+        if type(rval) is tuple:
+            rval = rval[1]
+        return float(lval) >= float(rval)
+    def __repr__(self):
+        return '%s>=%s' % (self.lval, self.rval)
+
+class LessThanOperator(object):
+    """The relational operator `<` (less than)."""
+    __slots__ = ['lval', 'rval']
+    def __init__(self, lval, rval):
+        self.lval = lval
+        self.rval = rval
+    def __call__(self, kind, data, pos):
+        lval = self.lval(kind, data, pos)
+        if type(lval) is tuple:
+            lval = lval[1]
+        rval = self.rval(kind, data, pos)
+        if type(rval) is tuple:
+            rval = rval[1]
+        return float(lval) < float(rval)
+    def __repr__(self):
+        return '%s<%s' % (self.lval, self.rval)
+
+class LessThanOrEqualOperator(object):
+    """The relational operator `<=` (less than or equal)."""
+    __slots__ = ['lval', 'rval']
+    def __init__(self, lval, rval):
+        self.lval = lval
+        self.rval = rval
+    def __call__(self, kind, data, pos):
+        lval = self.lval(kind, data, pos)
+        if type(lval) is tuple:
+            lval = lval[1]
+        rval = self.rval(kind, data, pos)
+        if type(rval) is tuple:
+            rval = rval[1]
+        return float(lval) <= float(rval)
+    def __repr__(self):
+        return '%s<=%s' % (self.lval, self.rval)
+
+_operator_map = {'=': EqualsOperator, '!=': NotEqualsOperator,
+                 '>': GreaterThanOperator, '>=': GreaterThanOrEqualOperator,
+                 '<': LessThanOperator, '>=': LessThanOrEqualOperator}
--- a/markup/tests/path.py
+++ b/markup/tests/path.py
@@ -177,8 +177,8 @@
 
     def test_3step(self):
         xml = XML('<root><foo><bar/></foo></root>')
-        path = Path('root/foo/*')
-        self.assertEqual('<Path "child::root/child::foo/child::*">',
+        path = Path('foo/*')
+        self.assertEqual('<Path "child::foo/child::*">',
                          repr(path))
         self.assertEqual('<bar/>', path.select(xml).render())
 
@@ -257,29 +257,43 @@
     def test_predicate_attr(self):
         xml = XML('<root><item/><item important="very"/></root>')
         self.assertEqual('<item important="very"/>',
-                         Path('root/item[@important]').select(xml).render())
+                         Path('item[@important]').select(xml).render())
         self.assertEqual('<item important="very"/>',
-                         Path('root/item[@important="very"]').select(xml).render())
+                         Path('item[@important="very"]').select(xml).render())
 
     def test_predicate_attr_equality(self):
         xml = XML('<root><item/><item important="notso"/></root>')
         self.assertEqual('',
-                         Path('root/item[@important="very"]').select(xml).render())
+                         Path('item[@important="very"]').select(xml).render())
         self.assertEqual('<item/><item important="notso"/>',
-                         Path('root/item[@important!="very"]').select(xml).render())
+                         Path('item[@important!="very"]').select(xml).render())
+
+    def test_predicate_attr_greater_than(self):
+        xml = XML('<root><item priority="3"/></root>')
+        self.assertEqual('',
+                         Path('item[@priority>3]').select(xml).render())
+        self.assertEqual('<item priority="3"/>',
+                         Path('item[@priority>2]').select(xml).render())
+
+    def test_predicate_attr_less_than(self):
+        xml = XML('<root><item priority="3"/></root>')
+        self.assertEqual('',
+                         Path('item[@priority<3]').select(xml).render())
+        self.assertEqual('<item priority="3"/>',
+                         Path('item[@priority<4]').select(xml).render())
 
     def test_predicate_attr_and(self):
         xml = XML('<root><item/><item important="very"/></root>')
-        path = Path('root/item[@important and @important="very"]')
+        path = Path('item[@important and @important="very"]')
         self.assertEqual('<item important="very"/>', path.select(xml).render())
-        path = Path('root/item[@important and @important="notso"]')
+        path = Path('item[@important and @important="notso"]')
         self.assertEqual('', path.select(xml).render())
 
     def test_predicate_attr_or(self):
         xml = XML('<root><item/><item important="very"/></root>')
-        path = Path('root/item[@urgent or @important]')
+        path = Path('item[@urgent or @important]')
         self.assertEqual('<item important="very"/>', path.select(xml).render())
-        path = Path('root/item[@urgent or @notso]')
+        path = Path('item[@urgent or @notso]')
         self.assertEqual('', path.select(xml).render())
 
     def test_predicate_boolean_function(self):
@@ -332,6 +346,13 @@
         path = Path('*[number("3.0")=3]')
         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
 
+    def test_predicate_round_function(self):
+        xml = XML('<root><foo>bar</foo></root>')
+        path = Path('*[round("4.4")=4]')
+        self.assertEqual('<foo>bar</foo>', path.select(xml).render())
+        path = Path('*[round("4.6")=5]')
+        self.assertEqual('<foo>bar</foo>', path.select(xml).render())
+
     def test_predicate_starts_with_function(self):
         xml = XML('<root><foo>bar</foo></root>')
         path = Path('*[starts-with(name(), "f")]')
Copyright (C) 2012-2017 Edgewall Software