From 9cb733fe564810b5f5925bf44b052097d062b49b Mon Sep 17 00:00:00 2001 From: matthew Date: Wed, 28 Apr 2010 19:58:29 +0000 Subject: [PATCH 1/2] ZF-9764, ZF-9765: fix CSS -> XPath transforms - Find all attribute identity selectors - Find all attribute word selectors - Remove double asterix git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@22044 44c647ce-9c0f-0410-b52a-842ac1e357ba --- src/Query/Css2Xpath.php | 97 +++++++++++++++++++++++------------- test/Query/Css2XpathTest.php | 81 +++++++++++------------------- test/QueryTest.php | 28 +++++++++++ 3 files changed, 118 insertions(+), 88 deletions(-) diff --git a/src/Query/Css2Xpath.php b/src/Query/Css2Xpath.php index d873643..149b99a 100644 --- a/src/Query/Css2Xpath.php +++ b/src/Query/Css2Xpath.php @@ -58,16 +58,16 @@ public static function transform($path) foreach ($segments as $key => $segment) { $pathSegment = self::_tokenize($segment); if (0 == $key) { - if (0 === strpos($pathSegment, '[contains(@class')) { - $paths[0] .= '*' . $pathSegment; + if (0 === strpos($pathSegment, '[contains(')) { + $paths[0] .= '*' . ltrim($pathSegment, '*'); } else { $paths[0] .= $pathSegment; } continue; } - if (0 === strpos($pathSegment, '[contains(@class')) { + if (0 === strpos($pathSegment, '[contains(')) { foreach ($paths as $key => $xpath) { - $paths[$key] .= '//*' . $pathSegment; + $paths[$key] .= '//*' . ltrim($pathSegment, '*'); $paths[] = $xpath . $pathSegment; } } else { @@ -99,44 +99,71 @@ protected static function _tokenize($expression) $expression = preg_replace('|(?assertEquals("//*[contains(@class, ' foo ')]", $test); + $this->assertEquals("//*[contains(concat(' ', normalize-space(@class), ' '), ' foo ')]", $test); } public function testTransformShouldAssumeSpacesToIndicateRelativeXpathQueries() @@ -112,8 +74,8 @@ public function testTransformShouldAssumeSpacesToIndicateRelativeXpathQueries() $test = Zend_Dom_Query_Css2Xpath::transform('div#foo .bar'); $this->assertContains('|', $test); $expected = array( - "//div[@id='foo']//*[contains(@class, ' bar ')]", - "//div[@id='foo'][contains(@class, ' bar ')]", + "//div[@id='foo']//*[contains(concat(' ', normalize-space(@class), ' '), ' bar ')]", + "//div[@id='foo'][contains(concat(' ', normalize-space(@class), ' '), ' bar ')]", ); foreach ($expected as $path) { $this->assertContains($path, $test); @@ -136,8 +98,8 @@ public function testMultipleComplexCssSpecificationShouldTransformToExpectedXpat $this->assertContains('|', $test); $actual = explode('|', $test); $expected = array( - "//div[@id='foo']//span[contains(@class, ' bar ')]", - "//*[@id='bar']//li[contains(@class, ' baz ')]//a", + "//div[@id='foo']//span[contains(concat(' ', normalize-space(@class), ' '), ' bar ')]", + "//*[@id='bar']//li[contains(concat(' ', normalize-space(@class), ' '), ' baz ')]//a", ); $this->assertEquals(count($expected), count($actual)); foreach ($actual as $path) { @@ -150,10 +112,10 @@ public function testClassNotationWithoutSpecifiedTagShouldResultInMultipleQuerie $test = Zend_Dom_Query_Css2Xpath::transform('div.foo .bar a .baz span'); $this->assertContains('|', $test); $segments = array( - "//div[contains(@class, ' foo ')]//*[contains(@class, ' bar ')]//a//*[contains(@class, ' baz ')]//span", - "//div[contains(@class, ' foo ')]//*[contains(@class, ' bar ')]//a[contains(@class, ' baz ')]//span", - "//div[contains(@class, ' foo ')][contains(@class, ' bar ')]//a//*[contains(@class, ' baz ')]//span", - "//div[contains(@class, ' foo ')][contains(@class, ' bar ')]//a[contains(@class, ' baz ')]//span", + "//div[contains(concat(' ', normalize-space(@class), ' '), ' foo ')]//*[contains(concat(' ', normalize-space(@class), ' '), ' bar ')]//a//*[contains(concat(' ', normalize-space(@class), ' '), ' baz ')]//span", + "//div[contains(concat(' ', normalize-space(@class), ' '), ' foo ')]//*[contains(concat(' ', normalize-space(@class), ' '), ' bar ')]//a[contains(concat(' ', normalize-space(@class), ' '), ' baz ')]//span", + "//div[contains(concat(' ', normalize-space(@class), ' '), ' foo ')][contains(concat(' ', normalize-space(@class), ' '), ' bar ')]//a//*[contains(concat(' ', normalize-space(@class), ' '), ' baz ')]//span", + "//div[contains(concat(' ', normalize-space(@class), ' '), ' foo ')][contains(concat(' ', normalize-space(@class), ' '), ' bar ')]//a[contains(concat(' ', normalize-space(@class), ' '), ' baz ')]//span", ); foreach ($segments as $xpath) { $this->assertContains($xpath, $test); @@ -175,7 +137,7 @@ public function testShouldCastAttributeNamesToLowerCase() public function testShouldAllowContentSubSelectionOfArbitraryAttributes() { $test = Zend_Dom_Query_Css2Xpath::transform('div[foo~="bar"]'); - $this->assertEquals("//div[contains(@foo, ' bar ')]", $test); + $this->assertEquals("//div[contains(concat(' ', normalize-space(@foo), ' '), ' bar ')]", $test); } public function testShouldAllowContentMatchingOfArbitraryAttributes() @@ -201,9 +163,22 @@ public function testShouldAllowWhitespaceInDescendentSelectorExpressions() $test = Zend_Dom_Query_Css2Xpath::transform('child > leaf'); $this->assertEquals("//child/leaf", $test); } -} -// Call Zend_Dom_Query_Css2XpathTest::main() if this source file is executed directly. -if (PHPUnit_MAIN_METHOD == "Zend_Dom_Query_Css2XpathTest::main") { - Zend_Dom_Query_Css2XpathTest::main(); + /** + * @group ZF-9764 + */ + public function testIdSelectorWithAttribute() + { + $test = Zend_Dom_Query_Css2Xpath::transform('#id[attribute="value"]'); + $this->assertEquals("//*[@id='id'][@attribute='value']", $test); + } + + /** + * @group ZF-9764 + */ + public function testIdSelectorWithLeadingAsterix() + { + $test = Zend_Dom_Query_Css2Xpath::transform('*#id'); + $this->assertEquals("//*[@id='id']", $test); + } } diff --git a/test/QueryTest.php b/test/QueryTest.php index b2d8638..7fc30e7 100644 --- a/test/QueryTest.php +++ b/test/QueryTest.php @@ -243,6 +243,34 @@ public function testLoadingDocumentWithErrorsShouldNotRaisePhpErrors() $this->assertTrue(is_array($errors)); $this->assertTrue(0 < count($errors)); } + + /** + * @group ZF-9765 + */ + public function testCssSelectorShouldFindNodesWhenMatchingMultipleAttributes() + { + $html = << + + +
+ + + +
+ + +EOF; + + $this->query->setDocument($html); + $results = $this->query->query('input[type="hidden"][value="1"]'); + $this->assertEquals(2, count($results), $results->getXpathQuery()); + $results = $this->query->query('input[value="1"][type~="hidden"]'); + $this->assertEquals(2, count($results), $results->getXpathQuery()); + $results = $this->query->query('input[type="hidden"][value="0"]'); + $this->assertEquals(1, count($results)); + } } // Call Zend_Dom_QueryTest::main() if this source file is executed directly. From 49e4d7cfab2cacd6e66343a83efed70e181ca802 Mon Sep 17 00:00:00 2001 From: "bradley.holt" Date: Tue, 25 May 2010 15:52:09 +0000 Subject: [PATCH 2/2] ZF-9881: Allow registration of XPath namespaces in Zend_Test_PHPUnit_ControllerTestCase, Zend_Test_PHPUnit_Constraint_DomQuery, and Zend_Dom_Query git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@22291 44c647ce-9c0f-0410-b52a-842ac1e357ba --- src/Query.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Query.php b/src/Query.php index cf05333..8533a72 100644 --- a/src/Query.php +++ b/src/Query.php @@ -64,6 +64,12 @@ class Zend_Dom_Query */ protected $_docType; + /** + * XPath namespaces + * @var array + */ + protected $_xpathNamespaces = array(); + /** * Constructor * @@ -220,6 +226,17 @@ public function queryXpath($xpathQuery, $query = null) return new Zend_Dom_Query_Result($query, $xpathQuery, $domDoc, $nodeList); } + /** + * Register XPath namespaces + * + * @param array $xpathNamespaces + * @return void + */ + public function registerXpathNamespaces($xpathNamespaces) + { + $this->_xpathNamespaces = $xpathNamespaces; + } + /** * Prepare node list * @@ -230,6 +247,9 @@ public function queryXpath($xpathQuery, $query = null) protected function _getNodeList($document, $xpathQuery) { $xpath = new DOMXPath($document); + foreach ($this->_xpathNamespaces as $prefix => $namespaceUri) { + $xpath->registerNamespace($prefix, $namespaceUri); + } $xpathQuery = (string) $xpathQuery; if (preg_match_all('|\[contains\((@[a-z0-9_-]+),\s?\' |i', $xpathQuery, $matches)) { foreach ($matches[1] as $attribute) {