|
14 | 14 |
|
15 | 15 | import sys
|
16 | 16 | import re
|
| 17 | +import copy |
17 | 18 |
|
18 | 19 | from cssselect.parser import parse, parse_series, SelectorError
|
19 | 20 |
|
@@ -76,13 +77,13 @@ def add_star_prefix(self):
|
76 | 77 | """
|
77 | 78 | self.path += '*/'
|
78 | 79 |
|
79 |
| - def join(self, combiner, other): |
| 80 | + def join(self, combiner, other, closing_combiner=None): |
80 | 81 | path = _unicode(self) + combiner
|
81 | 82 | # Any "star prefix" is redundant when joining.
|
82 | 83 | if other.path != '*/':
|
83 | 84 | path += other.path
|
84 | 85 | self.path = path
|
85 |
| - self.element = other.element |
| 86 | + self.element = other.element + closing_combiner if closing_combiner else other.element |
86 | 87 | self.condition = other.condition
|
87 | 88 | return self
|
88 | 89 |
|
@@ -274,9 +275,14 @@ def xpath_negation(self, negation):
|
274 | 275 |
|
275 | 276 | def xpath_relation(self, relation):
|
276 | 277 | xpath = self.xpath(relation.selector)
|
277 |
| - combinator, subselector = relation.subselector |
278 |
| - method = getattr(self, 'xpath_%s_combinator' % self.combinator_mapping[combinator.value]) |
279 |
| - return method(xpath, self.xpath(subselector)) |
| 278 | + combinator, *subselector = relation.subselector |
| 279 | + if not subselector: |
| 280 | + combinator.value = ' ' |
| 281 | + right = self.xpath(combinator) |
| 282 | + else: |
| 283 | + right = self.xpath(subselector[0]) |
| 284 | + method = getattr(self, 'xpath_relation_%s_combinator' % self.combinator_mapping[combinator.value]) |
| 285 | + return method(xpath, right) |
280 | 286 |
|
281 | 287 | def xpath_function(self, function):
|
282 | 288 | """Translate a functional pseudo-class."""
|
@@ -375,6 +381,29 @@ def xpath_indirect_adjacent_combinator(self, left, right):
|
375 | 381 | """right is a sibling after left, immediately or not"""
|
376 | 382 | return left.join('/following-sibling::', right)
|
377 | 383 |
|
| 384 | + def xpath_relation_descendant_combinator(self, left, right): |
| 385 | + """right is a child, grand-child or further descendant of left; select left""" |
| 386 | + return left.join('/descendant-or-self::', right, closing_combiner='/ancestor-or-self::' + left.element) |
| 387 | + |
| 388 | + def xpath_relation_child_combinator(self, left, right): |
| 389 | + """right is an immediate child of left; select left""" |
| 390 | + return left.join('[./', right, closing_combiner=']') |
| 391 | + |
| 392 | + def xpath_relation_direct_adjacent_combinator(self, left, right): |
| 393 | + """right is a sibling immediately after left; select left""" |
| 394 | + left_copy = copy.copy(left) |
| 395 | + xpath = left.join('/following-sibling::', right) |
| 396 | + xpath.add_name_test() |
| 397 | + xpath.add_condition('position() = 1') |
| 398 | + |
| 399 | + xpath = xpath.join('/preceding-sibling::', left_copy) |
| 400 | + xpath.add_name_test() |
| 401 | + return xpath.add_condition('position() = 1') |
| 402 | + |
| 403 | + def xpath_relation_indirect_adjacent_combinator(self, left, right): |
| 404 | + """right is a sibling after left, immediately or not; select left""" |
| 405 | + return left.join('/following-sibling::', right, closing_combiner='/preceding-sibling::'+left.element) |
| 406 | + |
378 | 407 |
|
379 | 408 | # Function: dispatch by function/pseudo-class name
|
380 | 409 |
|
|
0 commit comments