bug #33777 Fix the :only-of-type pseudo class selector (jakzal)

This PR was squashed before being merged into the 3.4 branch (closes #33777).

Discussion
----------

Fix the :only-of-type pseudo class selector

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #33773
| License       | MIT
| Doc PR        | -

Commits
-------

c2a9bf08f1 Fix the :only-of-type pseudo class selector
This commit is contained in:
Fabien Potencier 2019-10-01 13:57:42 +02:00
commit d2b66ff592
2 changed files with 34 additions and 5 deletions

View File

@ -98,7 +98,7 @@ class TranslatorTest extends TestCase
$elements = $document->xpath($translator->cssToXPath($css)); $elements = $document->xpath($translator->cssToXPath($css));
$this->assertCount(\count($elementsId), $elements); $this->assertCount(\count($elementsId), $elements);
foreach ($elements as $element) { foreach ($elements as $element) {
$this->assertTrue(\in_array($element->attributes()->id, $elementsId)); $this->assertContains((string) $element->attributes()->id, $elementsId);
} }
} }
@ -116,7 +116,7 @@ class TranslatorTest extends TestCase
$this->assertCount(\count($elementsId), $elementsId); $this->assertCount(\count($elementsId), $elementsId);
foreach ($elements as $element) { foreach ($elements as $element) {
if (null !== $element->attributes()->id) { if (null !== $element->attributes()->id) {
$this->assertTrue(\in_array($element->attributes()->id, $elementsId)); $this->assertContains((string) $element->attributes()->id, $elementsId);
} }
} }
libxml_clear_errors(); libxml_clear_errors();
@ -137,6 +137,33 @@ class TranslatorTest extends TestCase
$this->assertCount($count, $elements); $this->assertCount($count, $elements);
} }
public function testOnlyOfTypeFindsSingleChildrenOfGivenType()
{
$translator = new Translator();
$translator->registerExtension(new HtmlExtension($translator));
$document = new \DOMDocument();
$document->loadHTML(<<<'HTML'
<html>
<body>
<p>
<span>A</span>
</p>
<p>
<span>B</span>
<span>C</span>
</p>
</body>
</html>
HTML
);
$xpath = new \DOMXPath($document);
$nodeList = $xpath->query($translator->cssToXPath('span:only-of-type'));
$this->assertSame(1, $nodeList->length);
$this->assertSame('A', $nodeList->item(0)->textContent);
}
public function getXpathLiteralTestData() public function getXpathLiteralTestData()
{ {
return [ return [
@ -175,7 +202,7 @@ class TranslatorTest extends TestCase
['e:first-of-type', '*/e[position() = 1]'], ['e:first-of-type', '*/e[position() = 1]'],
['e:last-of-type', '*/e[position() = last()]'], ['e:last-of-type', '*/e[position() = last()]'],
['e:only-child', "*/*[(name() = 'e') and (last() = 1)]"], ['e:only-child', "*/*[(name() = 'e') and (last() = 1)]"],
['e:only-of-type', 'e[last() = 1]'], ['e:only-of-type', 'e[count(preceding-sibling::e)=0 and count(following-sibling::e)=0]'],
['e:empty', 'e[not(*) and not(string-length())]'], ['e:empty', 'e[not(*) and not(string-length())]'],
['e:EmPTY', 'e[not(*) and not(string-length())]'], ['e:EmPTY', 'e[not(*) and not(string-length())]'],
['e:root', 'e[not(parent::*)]'], ['e:root', 'e[not(parent::*)]'],

View File

@ -123,11 +123,13 @@ class PseudoClassExtension extends AbstractExtension
*/ */
public function translateOnlyOfType(XPathExpr $xpath) public function translateOnlyOfType(XPathExpr $xpath)
{ {
if ('*' === $xpath->getElement()) { $element = $xpath->getElement();
if ('*' === $element) {
throw new ExpressionErrorException('"*:only-of-type" is not implemented.'); throw new ExpressionErrorException('"*:only-of-type" is not implemented.');
} }
return $xpath->addCondition('last() = 1'); return $xpath->addCondition(sprintf('count(preceding-sibling::%s)=0 and count(following-sibling::%s)=0', $element, $element));
} }
/** /**