Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[DomCrawler] fix the axes handling in a bc way #11624

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 19, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions src/Symfony/Component/DomCrawler/Crawler.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,9 @@ public function addDocument(\DOMDocument $dom)
public function addNodeList(\DOMNodeList $nodes)
{
foreach ($nodes as $node) {
$this->addNode($node);
if ($node instanceof \DOMNode) {
$this->addNode($node);
}
}
}

Expand Down Expand Up @@ -834,28 +836,35 @@ private function relativize($xpath)
// BC for Symfony 2.4 and lower were elements were adding in a fake _root parent
if (0 === strpos($expression, '/_root/')) {
$expression = './'.substr($expression, 7);
} elseif (0 === strpos($expression, 'self::*/')) {
$expression = './'.substr($expression, 8);
}

// add prefix before absolute element selector
if (empty($expression)) {
$expression = $nonMatchingExpression;
} elseif (0 === strpos($expression, '//')) {
$expression = 'descendant-or-self::' . substr($expression, 2);
$expression = 'descendant-or-self::'.substr($expression, 2);
} elseif (0 === strpos($expression, './/')) {
$expression = 'descendant-or-self::' . substr($expression, 3);
$expression = 'descendant-or-self::'.substr($expression, 3);
} elseif (0 === strpos($expression, './')) {
$expression = 'self::' . substr($expression, 2);
} elseif ('/' === $expression[0]) {
$expression = 'self::'.substr($expression, 2);
} elseif (0 === strpos($expression, 'child::')) {
$expression = 'self::'.substr($expression, 7);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you sure about this normalization (please run your new tests against a 2.3.13 codebase to ensure it works the same)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, child::foo is equivalent to ./foo (which is covered with a similar test in testFilterXpathComplexQueries()).

I applied the tests on Symfony 2.3.13 in this branch. The only tests being skipped are the ones affecting the fake root node and the namespace axes. The results are here.

} elseif ('/' === $expression[0] || 0 === strpos($expression, 'self::')) {
// the only direct child in Symfony 2.4 and lower is _root, which is already handled previously
// so let's drop the expression entirely
$expression = $nonMatchingExpression;
} elseif ('.' === $expression[0]) {
// '.' is the fake root element in Symfony 2.4 and lower, which is excluded from results
$expression = $nonMatchingExpression;
} elseif (0 === strpos($expression, 'descendant::')) {
$expression = 'descendant-or-self::' . substr($expression, strlen('descendant::'));
} elseif (!preg_match('/^(ancestor|ancestor-or-self|attribute|child|descendant-or-self|following|following-sibling|parent|preceding|preceding-sibling|self)::/', $expression)) {
$expression = 'self::' .$expression;
$expression = 'descendant-or-self::'.substr($expression, strlen('descendant::'));
} elseif (preg_match('/^(ancestor|ancestor-or-self|attribute|following|following-sibling|namespace|parent|preceding|preceding-sibling)::/', $expression)) {
// the fake root has no parent, preceding or following nodes and also no attributes (even no namespace attributes)
$expression = $nonMatchingExpression;
} elseif (0 !== strpos($expression, 'descendant-or-self::')) {
$expression = 'self::'.$expression;
}
$expressions[] = $parenthesis.$expression;
}
Expand Down
59 changes: 46 additions & 13 deletions src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,8 @@ public function testFilterXpathComplexQueries()
$this->assertCount(1, $crawler->filterXPath('//body'));
$this->assertCount(1, $crawler->filterXPath('descendant-or-self::body'));
$this->assertCount(1, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('./div'), 'A child selection finds only the current div');
$this->assertCount(2, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('descendant::div'), 'A descendant selector matches the current div and its child');
$this->assertCount(2, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('//div'), 'A descendant selector matches the current div and its child');
$this->assertCount(3, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('descendant::div'), 'A descendant selector matches the current div and its child');
$this->assertCount(3, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('//div'), 'A descendant selector matches the current div and its child');
$this->assertCount(5, $crawler->filterXPath('(//a | //div)//img'));
$this->assertCount(7, $crawler->filterXPath('((//a | //div)//img | //ul)'));
$this->assertCount(7, $crawler->filterXPath('( ( //a | //div )//img | //ul )'));
Expand All @@ -411,72 +411,104 @@ public function testFilterXPath()
$this->assertCount(3, $crawler->filterXPath('//body')->filterXPath('//button')->parents(), '->filterXpath() preserves parents when chained');
}

public function testFilterXPathWithFakeRoot()
{
$crawler = $this->createTestCrawler();
$this->assertCount(0, $crawler->filterXPath('.'), '->filterXPath() returns an empty result if the XPath references the fake root node');
$this->assertCount(0, $crawler->filterXPath('/_root'), '->filterXPath() returns an empty result if the XPath references the fake root node');
$this->assertCount(0, $crawler->filterXPath('self::*'), '->filterXPath() returns an empty result if the XPath references the fake root node');
$this->assertCount(0, $crawler->filterXPath('self::_root'), '->filterXPath() returns an empty result if the XPath references the fake root node');
}

public function testFilterXPathWithAncestorAxis()
{
$crawler = $this->createTestCrawler()->filterXPath('//form');

$this->assertCount(2, $crawler->filterXPath('ancestor::*'));
$this->assertCount(0, $crawler->filterXPath('ancestor::*'), 'The fake root node has no ancestor nodes');
}

public function testFilterXPathWithAncestorOrSelfAxis()
{
$crawler = $this->createTestCrawler()->filterXPath('//form');

$this->assertCount(3, $crawler->filterXPath('ancestor-or-self::*'));
$this->assertCount(0, $crawler->filterXPath('ancestor-or-self::*'), 'The fake root node has no ancestor nodes');
}

public function testFilterXPathWithAttributeAxis()
{
$crawler = $this->createTestCrawler()->filterXPath('//form');

$this->assertCount(2, $crawler->filterXPath('attribute::*'));
$this->assertCount(0, $crawler->filterXPath('attribute::*'), 'The fake root node has no attribute nodes');
}

public function testFilterXPathWithAttributeAxisAfterElementAxis()
{
$this->assertCount(3, $this->createTestCrawler()->filterXPath('//form/button/attribute::*'), '->filterXPath() handles attribute axes properly when they are preceded by an element filtering axis');
}

public function testFilterXPathWithChildAxis()
{
$crawler = $this->createTestCrawler()->filterXPath('//body');
$crawler = $this->createTestCrawler()->filterXPath('//div[@id="parent"]');

$this->assertCount(2, $crawler->filterXPath('child::input'));
$this->assertCount(1, $crawler->filterXPath('child::div'), 'A child selection finds only the current div');
}

public function testFilterXPathWithFollowingAxis()
{
$crawler = $this->createTestCrawler()->filterXPath('//a');

$this->assertCount(3, $crawler->filterXPath('following::div'));
$this->assertCount(0, $crawler->filterXPath('following::div'), 'The fake root node has no following nodes');
}

public function testFilterXPathWithFollowingSiblingAxis()
{
$crawler = $this->createTestCrawler()->filterXPath('//a');

$this->assertCount(2, $crawler->filterXPath('following-sibling::div'));
$this->assertCount(0, $crawler->filterXPath('following-sibling::div'), 'The fake root node has no following nodes');
}

public function testFilterXPathWithNamespaceAxis()
{
$crawler = $this->createTestCrawler()->filterXPath('//button');

$this->assertCount(0, $crawler->filterXPath('namespace::*'), 'The fake root node has no namespace nodes');
}

public function testFilterXPathWithNamespaceAxisAfterElementAxis()
{
$crawler = $this->createTestCrawler()->filterXPath('//div[@id="parent"]/namespace::*');

$this->assertCount(0, $crawler->filterXPath('namespace::*'), 'Namespace axes cannot be requested');
}

public function testFilterXPathWithParentAxis()
{
$crawler = $this->createTestCrawler()->filterXPath('//button');

$this->assertEquals('foo', $crawler->filterXPath('parent::*')->attr('action'));
$this->assertCount(0, $crawler->filterXPath('parent::*'), 'The fake root node has no parent nodes');
}

public function testFilterXPathWithPrecedingAxis()
{
$crawler = $this->createTestCrawler()->filterXPath('//form');

$this->assertCount(13, $crawler->filterXPath('preceding::*'));
$this->assertCount(0, $crawler->filterXPath('preceding::*'), 'The fake root node has no preceding nodes');
}

public function testFilterXPathWithPrecedingSiblingAxis()
{
$crawler = $this->createTestCrawler()->filterXPath('//form');

$this->assertCount(9, $crawler->filterXPath('preceding-sibling::*'));
$this->assertCount(0, $crawler->filterXPath('preceding-sibling::*'), 'The fake root node has no preceding nodes');
}

public function testFilterXPathWithSelfAxes()
{
$this->assertCount(1, $this->createTestCrawler()->filterXPath('self::*'));
$crawler = $this->createTestCrawler()->filterXPath('//a');

$this->assertCount(0, $crawler->filterXPath('self::a'), 'The fake root node has no "real" element name');
$this->assertCount(0, $crawler->filterXPath('self::a/img'), 'The fake root node has no "real" element name');
$this->assertCount(9, $crawler->filterXPath('self::*/a'));
}

/**
Expand Down Expand Up @@ -844,6 +876,7 @@ public function createTestCrawler($uri = null)
</ul>
<div id="parent">
<div id="child"></div>
<div id="child2" xmlns:foo="http://example.com"></div>
</div>
<div id="sibling"><img /></div>
</body>
Expand Down