# 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):