From 7bdde22d68c07ef58f15e381776ffdae34b2eb90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Fedor?= Date: Sun, 5 Jun 2022 01:53:10 +0200 Subject: [PATCH 1/5] Improve detection of element in query --- src/Translator.php | 17 +++++++++++------ test/phpunit/Helper/Helper.php | 23 +++++++++++++++++++++++ test/phpunit/TranslatorTest.php | 24 ++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/Translator.php b/src/Translator.php index 1409090..c1ad7ee 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -61,7 +61,7 @@ protected function convertSingleSelector(string $css):string { $thread = array_values($thread); $xpath = [$this->prefix]; - $prevType = ""; + $hasElement = false; foreach($thread as $threadKey => $currentThreadItem) { $next = isset($thread[$threadKey + 1]) ? $thread[$threadKey + 1] @@ -71,6 +71,7 @@ protected function convertSingleSelector(string $css):string { case "star": case "element": $xpath []= $currentThreadItem['content']; + $hasElement = true; break; case "pseudo": @@ -160,23 +161,26 @@ protected function convertSingleSelector(string $css):string { case "child": array_push($xpath, "/"); + $hasElement = false; break; case "id": array_push( $xpath, - ($prevType != "element" ? '*' : '') + ($hasElement ? '' : '*') . "[@id='{$currentThreadItem['content']}']" ); + $hasElement = true; break; case "class": // https://devhints.io/xpath#class-check array_push( $xpath, - (($prevType != "element" && $prevType != "class") ? '*' : '') + ($hasElement ? '' : '*') . "[contains(concat(' ',normalize-space(@class),' '),' {$currentThreadItem['content']} ')]" ); + $hasElement = true; break; case "sibling": @@ -184,11 +188,13 @@ protected function convertSingleSelector(string $css):string { $xpath, "/following-sibling::*[1]/self::" ); + $hasElement = false; break; case "attribute": - if(!$prevType) { + if(!$hasElement) { array_push($xpath, "*"); + $hasElement = true; } /** @var null|array> $detail */ @@ -257,10 +263,9 @@ protected function convertSingleSelector(string $css):string { case "descendant": array_push($xpath, "//"); + $hasElement = false; break; } - - $prevType = $currentThreadItem["type"]; } return implode("", $xpath); diff --git a/test/phpunit/Helper/Helper.php b/test/phpunit/Helper/Helper.php index 3ab9cf0..fb65de2 100644 --- a/test/phpunit/Helper/Helper.php +++ b/test/phpunit/Helper/Helper.php @@ -156,4 +156,27 @@ class Helper { HTML; + const HTML_SELECTORS = << + + + + + HTML Complex + + + + +
+
First content without ID
+
Content with ID
+
Second content without ID
+
Content with attribute 1
+
Content with attribute 2
+
Third content without ID
+
+ + + +HTML; } diff --git a/test/phpunit/TranslatorTest.php b/test/phpunit/TranslatorTest.php index 82943e6..adc6d6c 100644 --- a/test/phpunit/TranslatorTest.php +++ b/test/phpunit/TranslatorTest.php @@ -397,4 +397,28 @@ public function testSquareBracketsNameAttribute() { self::assertEquals(3, $choiceInputs->length); } + + public function testCombinedSelectors() { + $document = new DOMDocument("1.0", "UTF-8"); + $document->loadHTML(Helper::HTML_SELECTORS); + $xpath = new DOMXPath($document); + + $classIdTranslator = new Translator(".content#content-element"); + $classAttr2Translator = new Translator(".content[data-attr='2']"); + + $titleEl = $xpath->query($classIdTranslator)->item(0); + self::assertEquals("Content with ID", $titleEl->nodeValue); + + $attr2El = $xpath->query($classAttr2Translator)->item(0); + self::assertEquals("Content with attribute 2", $attr2El->nodeValue); + } + + public function testChildWithAttribute() { + $document = new DOMDocument("1.0", "UTF-8"); + $document->loadHTML(Helper::HTML_CHECKBOX); + $xpath = new DOMXPath($document); + $choiceTranslator = new Translator("form [name]"); + $choiceInputs = $xpath->query($choiceTranslator); + self::assertEquals(3, $choiceInputs->length); + } } From 3ff654c7655894cad40d77d6d38e1d7a60c945c3 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Wed, 8 Jun 2022 12:10:50 +0100 Subject: [PATCH 2/5] style: reformat --- test/phpunit/TranslatorTest.php | 48 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/test/phpunit/TranslatorTest.php b/test/phpunit/TranslatorTest.php index adc6d6c..46631bf 100644 --- a/test/phpunit/TranslatorTest.php +++ b/test/phpunit/TranslatorTest.php @@ -228,7 +228,7 @@ public function testClassSelector() { )->item(0); self::assertSame($navElement, $navElement2); - $navElement3 = $xpath->query( + $navElement3 = $xpath->query( new Translator("nav.c-menu.main-selection") )->item(0); self::assertSame($navElement, $navElement3); @@ -398,27 +398,27 @@ public function testSquareBracketsNameAttribute() { self::assertEquals(3, $choiceInputs->length); } - public function testCombinedSelectors() { - $document = new DOMDocument("1.0", "UTF-8"); - $document->loadHTML(Helper::HTML_SELECTORS); - $xpath = new DOMXPath($document); - - $classIdTranslator = new Translator(".content#content-element"); - $classAttr2Translator = new Translator(".content[data-attr='2']"); - - $titleEl = $xpath->query($classIdTranslator)->item(0); - self::assertEquals("Content with ID", $titleEl->nodeValue); - - $attr2El = $xpath->query($classAttr2Translator)->item(0); - self::assertEquals("Content with attribute 2", $attr2El->nodeValue); - } - - public function testChildWithAttribute() { - $document = new DOMDocument("1.0", "UTF-8"); - $document->loadHTML(Helper::HTML_CHECKBOX); - $xpath = new DOMXPath($document); - $choiceTranslator = new Translator("form [name]"); - $choiceInputs = $xpath->query($choiceTranslator); - self::assertEquals(3, $choiceInputs->length); - } + public function testCombinedSelectors() { + $document = new DOMDocument("1.0", "UTF-8"); + $document->loadHTML(Helper::HTML_SELECTORS); + $xpath = new DOMXPath($document); + + $classIdTranslator = new Translator(".content#content-element"); + $classAttr2Translator = new Translator(".content[data-attr='2']"); + + $titleEl = $xpath->query($classIdTranslator)->item(0); + self::assertEquals("Content with ID", $titleEl->nodeValue); + + $attr2El = $xpath->query($classAttr2Translator)->item(0); + self::assertEquals("Content with attribute 2", $attr2El->nodeValue); + } + + public function testChildWithAttribute() { + $document = new DOMDocument("1.0", "UTF-8"); + $document->loadHTML(Helper::HTML_CHECKBOX); + $xpath = new DOMXPath($document); + $choiceTranslator = new Translator("form [name]"); + $choiceInputs = $xpath->query($choiceTranslator); + self::assertEquals(3, $choiceInputs->length); + } } From 73d6fad28e9a39d011aa86131eed5883454bc26d Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Wed, 8 Jun 2022 12:12:03 +0100 Subject: [PATCH 3/5] style: reformat --- test/phpunit/Helper/Helper.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/phpunit/Helper/Helper.php b/test/phpunit/Helper/Helper.php index fb65de2..1f3bfa9 100644 --- a/test/phpunit/Helper/Helper.php +++ b/test/phpunit/Helper/Helper.php @@ -169,11 +169,11 @@ class Helper {
First content without ID
-
Content with ID
-
Second content without ID
-
Content with attribute 1
-
Content with attribute 2
-
Third content without ID
+
Content with ID
+
Second content without ID
+
Content with attribute 1
+
Content with attribute 2
+
Third content without ID
From 5445e83437c948ba7e00f6b711670a48c4cfc2e8 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Wed, 8 Jun 2022 12:14:53 +0100 Subject: [PATCH 4/5] test: multiple named selects --- test/phpunit/TranslatorTest.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/phpunit/TranslatorTest.php b/test/phpunit/TranslatorTest.php index 46631bf..22fada2 100644 --- a/test/phpunit/TranslatorTest.php +++ b/test/phpunit/TranslatorTest.php @@ -421,4 +421,15 @@ public function testChildWithAttribute() { $choiceInputs = $xpath->query($choiceTranslator); self::assertEquals(3, $choiceInputs->length); } + + public function testMultipleNamedElements() { + $document = new DOMDocument("1.0", "UTF-8"); + $document->loadHTML(Helper::HTML_SELECTS); + $xpath = new DOMXPath($document); + $translator = new Translator("form [name='from'], form [name='to']"); + $selectElements = $xpath->query($translator); + self::assertEquals(2, $selectElements->length); + self::assertSame("from", $selectElements->item(0)->getAttribute("name")); + self::assertSame("to", $selectElements->item(1)->getAttribute("name")); + } } From 88747c95b06312028aaf6fd9f08fcfa0de2e74ed Mon Sep 17 00:00:00 2001 From: Chris How Date: Sun, 24 Dec 2023 12:31:43 +0100 Subject: [PATCH 5/5] Implement 'equals starts with' ('^=') and 'equals or starts with hyphenated' ('|=') selectors --- src/Translator.php | 23 ++++++++++++++++++----- test/phpunit/TranslatorTest.php | 24 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/Translator.php b/src/Translator.php index 01ed98b..49493d9 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -12,7 +12,7 @@ class Translator { . '|(#(?P[\w-]*))' . '|(\.(?P[\w-]*))' . '|(?P\s*\+\s*)' - . "|(\[(?P[\w-]*)((?P[=~$*]+)(?P(.+\[\]'?)|[^\]]+))*\])+" + . "|(\[(?P[\w-]*)((?P[=~$|^*]+)(?P(.+\[\]'?)|[^\]]+))*\])+" . '|(?P\s+)' . '/'; @@ -20,7 +20,7 @@ class Translator { const EQUALS_CONTAINS_WORD = "~="; const EQUALS_ENDS_WITH = "$="; const EQUALS_CONTAINS = "*="; - const EQUALS_STARTS_WITH_OR_STARTS_WITH_HYPHENATED = "|="; + const EQUALS_OR_STARTS_WITH_HYPHENATED = "|="; const EQUALS_STARTS_WITH = "^="; /** @var string */ @@ -244,11 +244,24 @@ protected function convertSingleSelector(string $css):string { ); break; - case self::EQUALS_STARTS_WITH_OR_STARTS_WITH_HYPHENATED: - throw new NotYetImplementedException(); + case self::EQUALS_OR_STARTS_WITH_HYPHENATED: + array_push( + $xpath, + "[" + . "@{$currentThreadItem['content']}=\"{$valueString}\" or " + . "starts-with(@{$currentThreadItem['content']}, \"{$valueString}-\")" + . "]" + ); + break; case self::EQUALS_STARTS_WITH: - throw new NotYetImplementedException(); + array_push( + $xpath, + "[starts-with(" + . "@{$currentThreadItem['content']}, \"{$valueString}\"" + . ")]" + ); + break; case self::EQUALS_ENDS_WITH: array_push( diff --git a/test/phpunit/TranslatorTest.php b/test/phpunit/TranslatorTest.php index 899697f..802f6e0 100644 --- a/test/phpunit/TranslatorTest.php +++ b/test/phpunit/TranslatorTest.php @@ -241,6 +241,30 @@ public function testAttributeDollarSelector() { ); } + public function testAttributeEqualsOrStartsWithHypehnatedSelector() { + $document = new DOMDocument("1.0", "UTF-8"); + $document->loadHTML("
"); + $xpath = new DOMXPath($document); + + $selector = new Translator("[class|=en]"); + self::assertEquals( + 3, + $xpath->query($selector)->length + ); + } + + public function testAttributeStartsWithSelector() { + $document = new DOMDocument("1.0", "UTF-8"); + $document->loadHTML("
"); + $xpath = new DOMXPath($document); + + $selector = new Translator("[class^=class1]"); + self::assertEquals( + 2, + $xpath->query($selector)->length + ); + } + public function testClassSelector() { $document = new DOMDocument("1.0", "UTF-8"); $document->loadHTML(Helper::HTML_COMPLEX);