# HG changeset patch
# User athomas
# Date 1181138260 0
# Node ID 317a7f4e3c69cf1f8d0ac0ea1506d3253f4ee489
# Parent f85ec7f582b612f7ab885ee0a8b2b436e714d219
Implemented XPath sub-expressions.
diff --git a/doc/xpath.txt b/doc/xpath.txt
--- a/doc/xpath.txt
+++ b/doc/xpath.txt
@@ -52,8 +52,8 @@
* ``sum()``
The mathematical operators (``+``, ``-``, ``*``, ``div``, and ``mod``) are not
-yet supported, whereas the various comparison and logical operators should work
-as expected.
+yet supported, whereas sub-expressions and the various comparison and logical
+operators should work as expected.
You can also use XPath variable references (``$var``) inside predicates.
@@ -68,20 +68,28 @@
.. code-block:: pycon
>>> from genshi.input import XML
-
+
>>> doc = XML('''
- ...
- ... -
- ... Foo
- ...
- ... -
- ... Bar
- ...
- ...
+ ...
+ ... -
+ ... Foo
+ ...
+ ... -
+ ... Bar
+ ...
+ ... -
+ ... Baz
+ ...
+ ... -
+ ... Waz
+ ...
+ ...
... ''')
-
- >>> print doc.select('items/item[@status="closed"]/summary/text()')
- Bar
+
+ >>> print doc.select('items/item[@status="closed" and '
+ ... '(@resolution="invalid" or not(@resolution))]/summary/text()')
+ BarBaz
+
---------------------
diff --git a/genshi/path.py b/genshi/path.py
--- a/genshi/path.py
+++ b/genshi/path.py
@@ -15,17 +15,24 @@
>>> from genshi.input import XML
>>> doc = XML('''
-...
+...
... -
... Foo
...
... -
... Bar
...
+... -
+... Baz
+...
+... -
+... Waz
+...
...
... ''')
->>> print doc.select('items/item[@status="closed"]/summary/text()')
-Bar
+>>> print doc.select('items/item[@status="closed" and '
+... '(@resolution="invalid" or not(@resolution))]/summary/text()')
+BarBaz
Because the XPath engine operates on markup streams (as opposed to tree
structures), it only implements a subset of the full XPath 1.0 language.
@@ -476,11 +483,24 @@
return expr
def _relational_expr(self):
- expr = self._primary_expr()
+ expr = self._sub_expr()
while self.cur_token in ('>', '>=', '<', '>='):
op = _operator_map[self.cur_token]
self.next_token()
- expr = op(expr, self._primary_expr())
+ expr = op(expr, self._sub_expr())
+ return expr
+
+ def _sub_expr(self):
+ token = self.cur_token
+ if token != '(':
+ return self._primary_expr()
+ self.next_token()
+ expr = self._or_expr()
+ if self.cur_token != ')':
+ raise PathSyntaxError('Expected ")" to close sub-expression, '
+ 'but found "%s"' % self.cur_token,
+ self.filename, self.lineno)
+ self.next_token()
return expr
def _primary_expr(self):