1
try:
2
    from functools import lru_cache
3
except ImportError:
4
    from functools32 import lru_cache
5

6
from cssselect import GenericTranslator as OriginalGenericTranslator
7
from cssselect import HTMLTranslator as OriginalHTMLTranslator
8
from cssselect.xpath import XPathExpr as OriginalXPathExpr
9
from cssselect.xpath import _unicode_safe_getattr, ExpressionError
10
from cssselect.parser import FunctionalPseudoElement
11

12

13
class XPathExpr(OriginalXPathExpr):
14

15
    textnode = False
16
    attribute = None
17

18
    @classmethod
19
    def from_xpath(cls, xpath, textnode=False, attribute=None):
20
        x = cls(path=xpath.path, element=xpath.element, condition=xpath.condition)
21
        x.textnode = textnode
22
        x.attribute = attribute
23
        return x
24

25
    def __str__(self):
26
        path = super(XPathExpr, self).__str__()
27
        if self.textnode:
28
            if path == '*':
29
                path = 'text()'
30
            elif path.endswith('::*/*'):
31
                path = path[:-3] + 'text()'
32
            else:
33
                path += '/text()'
34

35
        if self.attribute is not None:
36
            if path.endswith('::*/*'):
37
                path = path[:-2]
38
            path += '/@%s' % self.attribute
39

40
        return path
41

42
    def join(self, combiner, other):
43
        super(XPathExpr, self).join(combiner, other)
44
        self.textnode = other.textnode
45
        self.attribute = other.attribute
46
        return self
47

48

49
class TranslatorMixin(object):
50
    """This mixin adds support to CSS pseudo elements via dynamic dispatch.
51

52
    Currently supported pseudo-elements are ``::text`` and ``::attr(ATTR_NAME)``.
53
    """
54

55
    def xpath_element(self, selector):
56
        xpath = super(TranslatorMixin, self).xpath_element(selector)
57
        return XPathExpr.from_xpath(xpath)
58

59
    def xpath_pseudo_element(self, xpath, pseudo_element):
60
        """
61
        Dispatch method that transforms XPath to support pseudo-element
62
        """
63
        if isinstance(pseudo_element, FunctionalPseudoElement):
64
            method = 'xpath_%s_functional_pseudo_element' % (
65
                pseudo_element.name.replace('-', '_'))
66
            method = _unicode_safe_getattr(self, method, None)
67
            if not method:
68
                raise ExpressionError(
69
                    "The functional pseudo-element ::%s() is unknown"
70
                    % pseudo_element.name)
71
            xpath = method(xpath, pseudo_element)
72
        else:
73
            method = 'xpath_%s_simple_pseudo_element' % (
74
                pseudo_element.replace('-', '_'))
75
            method = _unicode_safe_getattr(self, method, None)
76
            if not method:
77
                raise ExpressionError(
78
                    "The pseudo-element ::%s is unknown"
79
                    % pseudo_element)
80
            xpath = method(xpath)
81
        return xpath
82

83
    def xpath_attr_functional_pseudo_element(self, xpath, function):
84
        """Support selecting attribute values using ::attr() pseudo-element
85
        """
86
        if function.argument_types() not in (['STRING'], ['IDENT']):
87
            raise ExpressionError(
88
                "Expected a single string or ident for ::attr(), got %r"
89
                % function.arguments)
90
        return XPathExpr.from_xpath(xpath,
91
                                    attribute=function.arguments[0].value)
92

93
    def xpath_text_simple_pseudo_element(self, xpath):
94
        """Support selecting text nodes using ::text pseudo-element"""
95
        return XPathExpr.from_xpath(xpath, textnode=True)
96

97

98
class GenericTranslator(TranslatorMixin, OriginalGenericTranslator):
99
    @lru_cache(maxsize=256)
100
    def css_to_xpath(self, css, prefix='descendant-or-self::'):
101
        return super(GenericTranslator, self).css_to_xpath(css, prefix)
102

103

104
class HTMLTranslator(TranslatorMixin, OriginalHTMLTranslator):
105
    @lru_cache(maxsize=256)
106
    def css_to_xpath(self, css, prefix='descendant-or-self::'):
107
        return super(HTMLTranslator, self).css_to_xpath(css, prefix)
108

109

110
_translator = HTMLTranslator()
111

112

113
def css2xpath(query):
114
    "Return translated XPath version of a given CSS query"
115
    return _translator.css_to_xpath(query)

Read our documentation on viewing source code .

Loading