Skip to content

Commit 5b566da

Browse files
Merge branch '2.7' into 2.8
* 2.7: [DomCrawler] Optimize DomCrawler::relativize() [HttpKernel] Fix source links with latests Twig versions [DomCrawler] Allow pipe (|) character in link tags when using Xpath expressions
2 parents 4744c3d + 8dee4be commit 5b566da

File tree

3 files changed

+51
-16
lines changed

3 files changed

+51
-16
lines changed

src/Symfony/Component/DomCrawler/Crawler.php

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -990,22 +990,47 @@ private function relativize($xpath)
990990
{
991991
$expressions = array();
992992

993-
$unionPattern = '/\|(?![^\[]*\])/';
994993
// An expression which will never match to replace expressions which cannot match in the crawler
995994
// We cannot simply drop
996995
$nonMatchingExpression = 'a[name() = "b"]';
997996

998-
// Split any unions into individual expressions.
999-
foreach (preg_split($unionPattern, $xpath) as $expression) {
1000-
$expression = trim($expression);
1001-
$parenthesis = '';
997+
$xpathLen = strlen($xpath);
998+
$openedBrackets = 0;
999+
$startPosition = strspn($xpath, " \t\n\r\0\x0B");
1000+
1001+
for ($i = $startPosition; $i <= $xpathLen; ++$i) {
1002+
$i += strcspn($xpath, '"\'[]|', $i);
1003+
1004+
if ($i < $xpathLen) {
1005+
switch ($xpath[$i]) {
1006+
case '"':
1007+
case "'":
1008+
if (false === $i = strpos($xpath, $xpath[$i], $i + 1)) {
1009+
return $xpath; // The XPath expression is invalid
1010+
}
1011+
continue 2;
1012+
case '[':
1013+
++$openedBrackets;
1014+
continue 2;
1015+
case ']':
1016+
--$openedBrackets;
1017+
continue 2;
1018+
}
1019+
}
1020+
if ($openedBrackets) {
1021+
continue;
1022+
}
10021023

1003-
// If the union is inside some braces, we need to preserve the opening braces and apply
1004-
// the change only inside it.
1005-
if (preg_match('/^[\(\s*]+/', $expression, $matches)) {
1006-
$parenthesis = $matches[0];
1007-
$expression = substr($expression, strlen($parenthesis));
1024+
if ($startPosition < $xpathLen && '(' === $xpath[$startPosition]) {
1025+
// If the union is inside some braces, we need to preserve the opening braces and apply
1026+
// the change only inside it.
1027+
$j = 1 + strspn($xpath, "( \t\n\r\0\x0B", $startPosition + 1);
1028+
$parenthesis = substr($xpath, $startPosition, $j);
1029+
$startPosition += $j;
1030+
} else {
1031+
$parenthesis = '';
10081032
}
1033+
$expression = rtrim(substr($xpath, $startPosition, $i - $startPosition));
10091034

10101035
// BC for Symfony 2.4 and lower were elements were adding in a fake _root parent
10111036
if (0 === strpos($expression, '/_root/')) {
@@ -1017,7 +1042,7 @@ private function relativize($xpath)
10171042
}
10181043

10191044
// add prefix before absolute element selector
1020-
if (empty($expression)) {
1045+
if ('' === $expression) {
10211046
$expression = $nonMatchingExpression;
10221047
} elseif (0 === strpos($expression, '//')) {
10231048
$expression = 'descendant-or-self::'.substr($expression, 2);
@@ -1035,17 +1060,24 @@ private function relativize($xpath)
10351060
// '.' is the fake root element in Symfony 2.4 and lower, which is excluded from results
10361061
$expression = $nonMatchingExpression;
10371062
} elseif (0 === strpos($expression, 'descendant::')) {
1038-
$expression = 'descendant-or-self::'.substr($expression, strlen('descendant::'));
1063+
$expression = 'descendant-or-self::'.substr($expression, 12);
10391064
} elseif (preg_match('/^(ancestor|ancestor-or-self|attribute|following|following-sibling|namespace|parent|preceding|preceding-sibling)::/', $expression)) {
10401065
// the fake root has no parent, preceding or following nodes and also no attributes (even no namespace attributes)
10411066
$expression = $nonMatchingExpression;
10421067
} elseif (0 !== strpos($expression, 'descendant-or-self::')) {
10431068
$expression = 'self::'.$expression;
10441069
}
10451070
$expressions[] = $parenthesis.$expression;
1071+
1072+
if ($i === $xpathLen) {
1073+
return implode(' | ', $expressions);
1074+
}
1075+
1076+
$i += strspn($xpath, " \t\n\r\0\x0B", $i + 1);
1077+
$startPosition = $i + 1;
10461078
}
10471079

1048-
return implode(' | ', $expressions);
1080+
return $xpath; // The XPath expression is invalid
10491081
}
10501082

10511083
/**

src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ public function testFilterXpathComplexQueries()
406406
$this->assertCount(5, $crawler->filterXPath('(//a | //div)//img'));
407407
$this->assertCount(7, $crawler->filterXPath('((//a | //div)//img | //ul)'));
408408
$this->assertCount(7, $crawler->filterXPath('( ( //a | //div )//img | //ul )'));
409+
$this->assertCount(1, $crawler->filterXPath("//a[./@href][((./@id = 'Klausi|Claudiu' or normalize-space(string(.)) = 'Klausi|Claudiu' or ./@title = 'Klausi|Claudiu' or ./@rel = 'Klausi|Claudiu') or .//img[./@alt = 'Klausi|Claudiu'])]"));
409410
}
410411

411412
public function testFilterXPath()
@@ -582,7 +583,7 @@ public function testFilterXPathWithSelfAxes()
582583

583584
$this->assertCount(0, $crawler->filterXPath('self::a'), 'The fake root node has no "real" element name');
584585
$this->assertCount(0, $crawler->filterXPath('self::a/img'), 'The fake root node has no "real" element name');
585-
$this->assertCount(9, $crawler->filterXPath('self::*/a'));
586+
$this->assertCount(10, $crawler->filterXPath('self::*/a'));
586587
}
587588

588589
public function testFilter()
@@ -1029,6 +1030,8 @@ public function createTestCrawler($uri = null)
10291030
10301031
<a href="?get=param">GetLink</a>
10311032
1033+
<a href="/example">Klausi|Claudiu</a>
1034+
10321035
<form action="foo" id="FooFormId">
10331036
<input type="text" value="TextValue" name="TextName" />
10341037
<input type="submit" value="FooValue" name="FooName" id="FooId" />

src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,11 @@ public function dump(Data $data)
9999
} elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof \Twig_Template) {
100100
$template = $trace[$i]['object'];
101101
$name = $template->getTemplateName();
102-
$file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : false;
103102
$src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false);
104103
$info = $template->getDebugInfo();
105-
if (null !== $src && isset($info[$trace[$i - 1]['line']])) {
104+
if (isset($info[$trace[$i - 1]['line']])) {
106105
$line = $info[$trace[$i - 1]['line']];
106+
$file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : false;
107107

108108
if ($src) {
109109
$src = explode("\n", $src);

0 commit comments

Comments
 (0)