Skip to content

Commit

Permalink
New API:parse, Selector, selector_to_xpath
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonSapin committed Apr 18, 2012
1 parent c37d640 commit 817c1c5
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 30 deletions.
12 changes: 7 additions & 5 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
Changelog
=========

Planned changes
---------------
Version 0.4
-----------

* Add some support for pseudo-elements by separating them from the
rest of the selector.
* Add specificity calculation.
* Add proper support for pseudo-elements
* Add specificity calculation
* Expose the :func:`parse` function and the parsed :class:`Selector` objects
in the API.
* Add the :meth:`~GenericTranslator.selector_to_xpath` method.


Version 0.3
Expand Down
5 changes: 3 additions & 2 deletions cssselect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
"""

from cssselect.parser import SelectorError, SelectorSyntaxError
from cssselect.parser import (parse, Selector, SelectorError,
SelectorSyntaxError)
from cssselect.xpath import GenericTranslator, HTMLTranslator, ExpressionError


VERSION = '0.3'
VERSION = '0.4'
__version__ = VERSION
40 changes: 32 additions & 8 deletions cssselect/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,23 @@ class Selector(object):
"""
def __init__(self, tree, pseudo_element=None):
self._tree = tree
#: A string like ``'after'`` or ``None``
self.pseudo_element = pseudo_element

def __repr__(self):
return '%s[%r::%s]' % (
self.__class__.__name__, self._tree, self.pseudo_element)
if self.pseudo_element:
pseudo_element = '::%s' % self.pseudo_element
else:
pseudo_element = ''
return '%s[%r%s]' % (
self.__class__.__name__, self._tree, pseudo_element)

def specificity(self):
"""Return the specificity_ of this selector as a tuple of 3 integers.
.. _specificity: http://www.w3.org/TR/selectors/#specificity
"""
a, b, c = self._tree.specificity()
if self.pseudo_element:
c += 1
Expand Down Expand Up @@ -229,22 +239,36 @@ def specificity(self):
_class_re = re.compile(r'^\s*(\w*)\.(\w+)\s*$')


def parse(string):
def parse(css):
"""Parse a CSS *group of selectors*.
If you don’t care about pseudo-elements or selector specificity,
you can skip this and use :meth:`~GenericTranslator.css_to_xpath`.
:param css:
A *group of selectors* as an Unicode string.
:raises:
:class:`SelectorSyntaxError` on invalid selectors.
:returns:
A list of parsed :class:`Selector` objects, one for each
selector in the comma-separated group.
"""
# Fast path for simple cases
match = _el_re.match(string)
match = _el_re.match(css)
if match:
return [Selector(Element('*', match.group(1)))]
match = _id_re.match(string)
match = _id_re.match(css)
if match is not None:
return [Selector(Hash(Element(
'*', match.group(1) or '*'), match.group(2)))]
match = _class_re.match(string)
match = _class_re.match(css)
if match is not None:
return [Selector(Class(Element(
'*', match.group(1) or '*'), match.group(2)))]

stream = TokenStream(tokenize(string))
stream.source = string
stream = TokenStream(tokenize(css))
stream.source = css
try:
return list(parse_selector_group(stream))
except SelectorSyntaxError:
Expand Down
5 changes: 3 additions & 2 deletions cssselect/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
import unittest

from lxml import html
from cssselect.parser import tokenize, parse, parse_series, SelectorSyntaxError
from cssselect.xpath import GenericTranslator, HTMLTranslator, ExpressionError
from cssselect import (parse, GenericTranslator, HTMLTranslator,
SelectorSyntaxError, ExpressionError)
from cssselect.parser import tokenize, parse_series


class TestCssselect(unittest.TestCase):
Expand Down
27 changes: 23 additions & 4 deletions cssselect/xpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,27 +117,46 @@ class GenericTranslator(object):
id_attribute = 'id'

def css_to_xpath(self, css, prefix='descendant-or-self::'):
"""Translate a CSS Selector to XPath.
"""Translate a *group of selectors* to XPath.
Pseudo-elements are not supported here.
:param css:
A *group of selectors* as an Unicode string.
:raises:
:class:`SelectorSyntaxError` on invalid selectors,
:class:`ExpressionError` on unknown/unsupported selectors.
:class:`ExpressionError` on unknown/unsupported selectors,
including pseudo-elements.
:returns:
The equivalent XPath 1.0 expression as an Unicode string.
"""
prefix = prefix or ''
selectors = parse(css)
for selector in selectors:
if selector.pseudo_element:
raise ExpressionError('Pseudo-elements are not supported.')

return ' | '.join(
prefix + _unicode(self.xpath(selector._tree))
self.selector_to_xpath(selector, prefix)
for selector in selectors)

def selector_to_xpath(self, selector, prefix='descendant-or-self::'):
"""Translate a parsed selector to XPath.
The :attr:`~Selector.pseudo_element` attribute of the selector
is ignored. It is the caller’s responsibility to reject selectors
with pseudo-elements, or to account for them somehow.
:param selector:
A parsed :class:`Selector` object.
:raises:
:class:`ExpressionError` on unknown/unsupported selectors.
:returns:
The equivalent XPath 1.0 expression as an Unicode string.
"""
return (prefix or '') + _unicode(self.xpath(selector._tree))

@staticmethod
def xpath_literal(s):
s = _unicode(s)
Expand Down
19 changes: 10 additions & 9 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,21 @@ The resulting expression can be used with lxml's `XPath engine`_:
User API
========

Currently, the only public API is the :meth:`~GenericTranslator.css_to_xpath`
method of the translator classes. This API is expected to expand to provide
more information about the parsed selectors, and to allow customization
of the translation.
In CSS3 terms, a `group of selectors`_ is a sequence of comma-separated
selectors. For example, ``div, h1.title + p`` is a group of 2 selectors.

.. _group of selectors: http://www.w3.org/TR/selectors/#grouping

.. autoclass:: GenericTranslator()
:members: css_to_xpath
.. autofunction:: parse
.. autoclass:: Selector()
:members:

.. autoclass:: HTMLTranslator()
.. autoclass:: GenericTranslator
:members: css_to_xpath, selector_to_xpath

.. method:: css_to_xpath(css, prefix='descendant-or-self::')
.. autoclass:: HTMLTranslator

Same as :meth:`GenericTranslator.css_to_xpath`
The API is the same as :class:`GenericTranslator`.

.. autoexception:: SelectorError
.. autoexception:: SelectorSyntaxError
Expand Down

0 comments on commit 817c1c5

Please sign in to comment.