| @@ -22,18 +22,14 @@ | ||
| 22 | 22 | * @internal |
| 23 | 23 | */ |
| 24 | 24 | class XPathExpr |
| 25 | 25 | { |
| 26 | - private string $path; | |
| 27 | - private string $element; | |
| 28 | - private string $condition; | |
| 29 | - | |
| 30 | - public function __construct(string $path = '', string $element = '*', string $condition = '', bool $starPrefix = false) | |
| 31 | - { | |
| 32 | - $this->path = $path; | |
| 33 | - $this->element = $element; | |
| 34 | - $this->condition = $condition; | |
| 35 | - | |
| 26 | + public function __construct( | |
| 27 | + private string $path = '', | |
| 28 | + private string $element = '*', | |
| 29 | + private string $condition = '', | |
| 30 | + bool $starPrefix = false, | |
| 31 | + ) { | |
| 36 | 32 | if ($starPrefix) { |
| 37 | 33 | $this->addStarPrefix(); |
| 38 | 34 | } |
| 39 | 35 | } |
| @@ -45,11 +41,11 @@ | ||
| 45 | 41 | |
| 46 | 42 | /** |
| 47 | 43 | * @return $this |
| 48 | 44 | */ |
| 49 | - public function addCondition(string $condition): static | |
| 45 | + public function addCondition(string $condition, string $operator = 'and'): static | |
| 50 | 46 | { |
| 51 | - $this->condition = $this->condition ? \sprintf('(%s) and (%s)', $this->condition, $condition) : $condition; | |
| 47 | + $this->condition = $this->condition ? \sprintf('(%s) %s (%s)', $this->condition, $operator, $condition) : $condition; | |
| 52 | 48 | |
| 53 | 49 | return $this; |
| 54 | 50 | } |
| 55 | 51 | |
| @@ -83,11 +79,15 @@ | ||
| 83 | 79 | |
| 84 | 80 | /** |
| 85 | 81 | * Joins another XPathExpr with a combiner. |
| 86 | 82 | * |
| 83 | + * When $hasInnerConditions is true, $expr->condition is folded into $expr->element as a | |
| 84 | + * "[...]" predicate rather than onto $this->condition, so subsequent addCondition() calls | |
| 85 | + * on $this are not AND'd with it. Needed for relative selectors inside :has(). | |
| 86 | + * | |
| 87 | 87 | * @return $this |
| 88 | 88 | */ |
| 89 | - public function join(string $combiner, self $expr): static | |
| 89 | + public function join(string $combiner, self $expr, ?string $closingCombiner = null, bool $hasInnerConditions = false): static | |
| 90 | 90 | { |
| 91 | 91 | $path = $this->__toString().$combiner; |
| 92 | 92 | |
| 93 | 93 | if ('*/' !== $expr->path) { |
| @@ -94,11 +94,22 @@ | ||
| 94 | 94 | $path .= $expr->path; |
| 95 | 95 | } |
| 96 | 96 | |
| 97 | 97 | $this->path = $path; |
| 98 | - $this->element = $expr->element; | |
| 99 | - $this->condition = $expr->condition; | |
| 100 | 98 | |
| 99 | + if (!$hasInnerConditions) { | |
| 100 | + $this->element = $expr->element.($closingCombiner ?? ''); | |
| 101 | + $this->condition = $expr->condition; | |
| 102 | + } else { | |
| 103 | + $this->element = $expr->element; | |
| 104 | + if ($expr->condition) { | |
| 105 | + $this->element .= '['.$expr->condition.']'; | |
| 106 | + } | |
| 107 | + if ($closingCombiner) { | |
| 108 | + $this->element .= $closingCombiner; | |
| 109 | + } | |
| 110 | + } | |
| 111 | + | |
| 101 | 112 | return $this; |
| 102 | 113 | } |
| 103 | 114 | |
| 104 | 115 | public function __toString(): string |
| @@ -103,9 +114,9 @@ | ||
| 103 | 114 | |
| 104 | 115 | public function __toString(): string |
| 105 | 116 | { |
| 106 | 117 | $path = $this->path.$this->element; |
| 107 | - $condition = null === $this->condition || '' === $this->condition ? '' : '['.$this->condition.']'; | |
| 118 | + $condition = '' === $this->condition ? '' : '['.$this->condition.']'; | |
| 108 | 119 | |
| 109 | 120 | return $path.$condition; |
| 110 | 121 | } |
| 111 | 122 | } |