1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\CssSelector\XPath\Extension;
13
14use Symfony\Component\CssSelector\XPath\Translator;
15use Symfony\Component\CssSelector\XPath\XPathExpr;
16
17/**
18 * XPath expression translator attribute extension.
19 *
20 * This component is a port of the Python cssselect library,
21 * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
22 *
23 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
24 *
25 * @internal
26 */
27class AttributeMatchingExtension extends AbstractExtension
28{
29    /**
30     * {@inheritdoc}
31     */
32    public function getAttributeMatchingTranslators(): array
33    {
34        return [
35            'exists' => [$this, 'translateExists'],
36            '=' => [$this, 'translateEquals'],
37            '~=' => [$this, 'translateIncludes'],
38            '|=' => [$this, 'translateDashMatch'],
39            '^=' => [$this, 'translatePrefixMatch'],
40            '$=' => [$this, 'translateSuffixMatch'],
41            '*=' => [$this, 'translateSubstringMatch'],
42            '!=' => [$this, 'translateDifferent'],
43        ];
44    }
45
46    public function translateExists(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
47    {
48        return $xpath->addCondition($attribute);
49    }
50
51    public function translateEquals(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
52    {
53        return $xpath->addCondition(sprintf('%s = %s', $attribute, Translator::getXpathLiteral($value)));
54    }
55
56    public function translateIncludes(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
57    {
58        return $xpath->addCondition($value ? sprintf(
59            '%1$s and contains(concat(\' \', normalize-space(%1$s), \' \'), %2$s)',
60            $attribute,
61            Translator::getXpathLiteral(' '.$value.' ')
62        ) : '0');
63    }
64
65    public function translateDashMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
66    {
67        return $xpath->addCondition(sprintf(
68            '%1$s and (%1$s = %2$s or starts-with(%1$s, %3$s))',
69            $attribute,
70            Translator::getXpathLiteral($value),
71            Translator::getXpathLiteral($value.'-')
72        ));
73    }
74
75    public function translatePrefixMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
76    {
77        return $xpath->addCondition($value ? sprintf(
78            '%1$s and starts-with(%1$s, %2$s)',
79            $attribute,
80            Translator::getXpathLiteral($value)
81        ) : '0');
82    }
83
84    public function translateSuffixMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
85    {
86        return $xpath->addCondition($value ? sprintf(
87            '%1$s and substring(%1$s, string-length(%1$s)-%2$s) = %3$s',
88            $attribute,
89            \strlen($value) - 1,
90            Translator::getXpathLiteral($value)
91        ) : '0');
92    }
93
94    public function translateSubstringMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
95    {
96        return $xpath->addCondition($value ? sprintf(
97            '%1$s and contains(%1$s, %2$s)',
98            $attribute,
99            Translator::getXpathLiteral($value)
100        ) : '0');
101    }
102
103    public function translateDifferent(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
104    {
105        return $xpath->addCondition(sprintf(
106            $value ? 'not(%1$s) or %1$s != %2$s' : '%s != %s',
107            $attribute,
108            Translator::getXpathLiteral($value)
109        ));
110    }
111
112    /**
113     * {@inheritdoc}
114     */
115    public function getName(): string
116    {
117        return 'attribute-matching';
118    }
119}
120