Merge branch '2.8' into 3.1

* 2.8:
  [VarDumper] Fix source links with latests Twig versions
  [DomCrawler] Optimize DomCrawler::relativize()
  [HttpKernel] Fix source links with latests Twig versions
  [DomCrawler] Allow pipe (|) character in link tags when using Xpath expressions
This commit is contained in:
Nicolas Grekas 2016-10-18 17:46:07 +02:00
commit 34abba65de
7 changed files with 167 additions and 49 deletions

View File

@ -940,29 +940,54 @@ class Crawler implements \Countable, \IteratorAggregate
{ {
$expressions = array(); $expressions = array();
$unionPattern = '/\|(?![^\[]*\])/';
// An expression which will never match to replace expressions which cannot match in the crawler // An expression which will never match to replace expressions which cannot match in the crawler
// We cannot simply drop // We cannot simply drop
$nonMatchingExpression = 'a[name() = "b"]'; $nonMatchingExpression = 'a[name() = "b"]';
// Split any unions into individual expressions. $xpathLen = strlen($xpath);
foreach (preg_split($unionPattern, $xpath) as $expression) { $openedBrackets = 0;
$expression = trim($expression); $startPosition = strspn($xpath, " \t\n\r\0\x0B");
$parenthesis = '';
// If the union is inside some braces, we need to preserve the opening braces and apply for ($i = $startPosition; $i <= $xpathLen; ++$i) {
// the change only inside it. $i += strcspn($xpath, '"\'[]|', $i);
if (preg_match('/^[\(\s*]+/', $expression, $matches)) {
$parenthesis = $matches[0]; if ($i < $xpathLen) {
$expression = substr($expression, strlen($parenthesis)); switch ($xpath[$i]) {
case '"':
case "'":
if (false === $i = strpos($xpath, $xpath[$i], $i + 1)) {
return $xpath; // The XPath expression is invalid
}
continue 2;
case '[':
++$openedBrackets;
continue 2;
case ']':
--$openedBrackets;
continue 2;
}
} }
if ($openedBrackets) {
continue;
}
if ($startPosition < $xpathLen && '(' === $xpath[$startPosition]) {
// If the union is inside some braces, we need to preserve the opening braces and apply
// the change only inside it.
$j = 1 + strspn($xpath, "( \t\n\r\0\x0B", $startPosition + 1);
$parenthesis = substr($xpath, $startPosition, $j);
$startPosition += $j;
} else {
$parenthesis = '';
}
$expression = rtrim(substr($xpath, $startPosition, $i - $startPosition));
if (0 === strpos($expression, 'self::*/')) { if (0 === strpos($expression, 'self::*/')) {
$expression = './'.substr($expression, 8); $expression = './'.substr($expression, 8);
} }
// add prefix before absolute element selector // add prefix before absolute element selector
if (empty($expression)) { if ('' === $expression) {
$expression = $nonMatchingExpression; $expression = $nonMatchingExpression;
} elseif (0 === strpos($expression, '//')) { } elseif (0 === strpos($expression, '//')) {
$expression = 'descendant-or-self::'.substr($expression, 2); $expression = 'descendant-or-self::'.substr($expression, 2);
@ -975,7 +1000,7 @@ class Crawler implements \Countable, \IteratorAggregate
} elseif ('/' === $expression[0] || '.' === $expression[0] || 0 === strpos($expression, 'self::')) { } elseif ('/' === $expression[0] || '.' === $expression[0] || 0 === strpos($expression, 'self::')) {
$expression = $nonMatchingExpression; $expression = $nonMatchingExpression;
} elseif (0 === strpos($expression, 'descendant::')) { } elseif (0 === strpos($expression, 'descendant::')) {
$expression = 'descendant-or-self::'.substr($expression, strlen('descendant::')); $expression = 'descendant-or-self::'.substr($expression, 12);
} elseif (preg_match('/^(ancestor|ancestor-or-self|attribute|following|following-sibling|namespace|parent|preceding|preceding-sibling)::/', $expression)) { } 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) // the fake root has no parent, preceding or following nodes and also no attributes (even no namespace attributes)
$expression = $nonMatchingExpression; $expression = $nonMatchingExpression;
@ -983,9 +1008,16 @@ class Crawler implements \Countable, \IteratorAggregate
$expression = 'self::'.$expression; $expression = 'self::'.$expression;
} }
$expressions[] = $parenthesis.$expression; $expressions[] = $parenthesis.$expression;
if ($i === $xpathLen) {
return implode(' | ', $expressions);
}
$i += strspn($xpath, " \t\n\r\0\x0B", $i + 1);
$startPosition = $i + 1;
} }
return implode(' | ', $expressions); return $xpath; // The XPath expression is invalid
} }
/** /**

View File

@ -430,6 +430,7 @@ EOF
$this->assertCount(5, $crawler->filterXPath('(//a | //div)//img')); $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)'));
$this->assertCount(7, $crawler->filterXPath('( ( //a | //div )//img | //ul )')); $this->assertCount(7, $crawler->filterXPath('( ( //a | //div )//img | //ul )'));
$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'])]"));
} }
public function testFilterXPath() public function testFilterXPath()
@ -596,7 +597,7 @@ EOF
$this->assertCount(0, $crawler->filterXPath('self::a'), 'The fake root node has no "real" element name'); $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(0, $crawler->filterXPath('self::a/img'), 'The fake root node has no "real" element name');
$this->assertCount(9, $crawler->filterXPath('self::*/a')); $this->assertCount(10, $crawler->filterXPath('self::*/a'));
} }
public function testFilter() public function testFilter()
@ -1079,6 +1080,8 @@ HTML;
<a href="?get=param">GetLink</a> <a href="?get=param">GetLink</a>
<a href="/example">Klausi|Claudiu</a>
<form action="foo" id="FooFormId"> <form action="foo" id="FooFormId">
<input type="text" value="TextValue" name="TextName" /> <input type="text" value="TextValue" name="TextName" />
<input type="submit" value="FooValue" name="FooName" id="FooId" /> <input type="submit" value="FooValue" name="FooName" id="FooId" />

View File

@ -94,11 +94,11 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
} elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof \Twig_Template) { } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof \Twig_Template) {
$template = $trace[$i]['object']; $template = $trace[$i]['object'];
$name = $template->getTemplateName(); $name = $template->getTemplateName();
$file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : false;
$src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false); $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false);
$info = $template->getDebugInfo(); $info = $template->getDebugInfo();
if (null !== $src && isset($info[$trace[$i - 1]['line']])) { if (isset($info[$trace[$i - 1]['line']])) {
$line = $info[$trace[$i - 1]['line']]; $line = $info[$trace[$i - 1]['line']];
$file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : false;
if ($src) { if ($src) {
$src = explode("\n", $src); $src = explode("\n", $src);

View File

@ -150,15 +150,19 @@ class ExceptionCaster
if (!empty($f['class']) && is_subclass_of($f['class'], 'Twig_Template') && method_exists($f['class'], 'getDebugInfo')) { if (!empty($f['class']) && is_subclass_of($f['class'], 'Twig_Template') && method_exists($f['class'], 'getDebugInfo')) {
$template = isset($f['object']) ? $f['object'] : new $f['class'](new \Twig_Environment(new \Twig_Loader_Filesystem())); $template = isset($f['object']) ? $f['object'] : new $f['class'](new \Twig_Environment(new \Twig_Loader_Filesystem()));
$templateName = $template->getTemplateName();
try { $templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : '');
$templateName = $template->getTemplateName(); $templateInfo = $template->getDebugInfo();
$templateSrc = explode("\n", method_exists($template, 'getSource') ? $template->getSource() : $template->getEnvironment()->getLoader()->getSource($templateName)); if (isset($templateInfo[$f['line']])) {
$templateInfo = $template->getDebugInfo(); if (method_exists($template, 'getSourceContext')) {
if (isset($templateInfo[$f['line']])) { $templateName = $template->getSourceContext()->getPath() ?: $templateName;
$src[$templateName.':'.$templateInfo[$f['line']]] = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext); }
if ($templateSrc) {
$templateSrc = explode("\n", $templateSrc);
$src[$templateName.':'.$templateInfo[$f['line']]] = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext);
} else {
$src[$templateName] = $templateInfo[$f['line']];
} }
} catch (\Twig_Error_Loader $e) {
} }
} }
} else { } else {

View File

@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarDumper\Tests\Caster;
use Symfony\Component\VarDumper\Caster\FrameStub;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
class ExceptionCasterTest extends \PHPUnit_Framework_TestCase
{
use VarDumperTestTrait;
/**
* @requires function Twig_Template::getSourceContext
*/
public function testFrameWithTwig()
{
require_once dirname(__DIR__).'/Fixtures/Twig.php';
$f = array(
new FrameStub(array(
'file' => dirname(__DIR__).'/Fixtures/Twig.php',
'line' => 19,
'class' => '__TwigTemplate_VarDumperFixture_u75a09',
'object' => new \__TwigTemplate_VarDumperFixture_u75a09(new \Twig_Environment(new \Twig_Loader_Filesystem())),
)),
new FrameStub(array(
'file' => dirname(__DIR__).'/Fixtures/Twig.php',
'line' => 19,
'class' => '__TwigTemplate_VarDumperFixture_u75a09',
'object' => new \__TwigTemplate_VarDumperFixture_u75a09(new \Twig_Environment(new \Twig_Loader_Filesystem()), null),
)),
);
$expectedDump = <<<'EODUMP'
array:2 [
0 => {
class: "__TwigTemplate_VarDumperFixture_u75a09"
object: __TwigTemplate_VarDumperFixture_u75a09 {
%A
}
src: {
%sTwig.php:19: """
// line 2\n
throw new \Exception('Foobar');\n
}\n
"""
bar.twig:2: """
foo bar\n
twig source\n
\n
"""
}
}
1 => {
class: "__TwigTemplate_VarDumperFixture_u75a09"
object: __TwigTemplate_VarDumperFixture_u75a09 {
%A
}
src: {
%sTwig.php:19: """
// line 2\n
throw new \Exception('Foobar');\n
}\n
"""
foo.twig:2: """
foo bar\n
twig source\n
\n
"""
}
}
]
EODUMP;
$this->assertDumpMatchesFormat($expectedDump, $f);
}
}

View File

@ -228,6 +228,9 @@ EOTXT
putenv('DUMP_STRING_LENGTH='); putenv('DUMP_STRING_LENGTH=');
} }
/**
* @requires function Twig_Template::getSourceContext
*/
public function testThrowingCaster() public function testThrowingCaster()
{ {
$out = fopen('php://memory', 'r+b'); $out = fopen('php://memory', 'r+b');
@ -262,19 +265,6 @@ EOTXT
rewind($out); rewind($out);
$out = stream_get_contents($out); $out = stream_get_contents($out);
if (method_exists($twig, 'getSource')) {
$twig = <<<EOTXT
foo.twig:2: """
foo bar\\n
twig source\\n
\\n
"""
EOTXT;
} else {
$twig = '';
}
$r = defined('HHVM_VERSION') ? '' : '#%d'; $r = defined('HHVM_VERSION') ? '' : '#%d';
$this->assertStringMatchesFormat( $this->assertStringMatchesFormat(
<<<EOTXT <<<EOTXT
@ -296,7 +286,12 @@ stream resource {@{$ref}
throw new \Exception('Foobar');\\n throw new \Exception('Foobar');\\n
}\\n }\\n
""" """
{$twig} } bar.twig:2: """
foo bar\\n
twig source\\n
\\n
"""
}
} }
%d. Twig_Template->displayWithErrorHandling() ==> __TwigTemplate_VarDumperFixture_u75a09->doDisplay(): { %d. Twig_Template->displayWithErrorHandling() ==> __TwigTemplate_VarDumperFixture_u75a09->doDisplay(): {
src: { src: {

View File

@ -3,14 +3,14 @@
/* foo.twig */ /* foo.twig */
class __TwigTemplate_VarDumperFixture_u75a09 extends Twig_Template class __TwigTemplate_VarDumperFixture_u75a09 extends Twig_Template
{ {
public function __construct(Twig_Environment $env) private $filename;
public function __construct(Twig_Environment $env, $filename = 'bar.twig')
{ {
parent::__construct($env); parent::__construct($env);
$this->parent = false; $this->parent = false;
$this->blocks = array();
$this->blocks = array( $this->filename = $filename;
);
} }
protected function doDisplay(array $context, array $blocks = array()) protected function doDisplay(array $context, array $blocks = array())
@ -26,14 +26,11 @@ class __TwigTemplate_VarDumperFixture_u75a09 extends Twig_Template
public function getDebugInfo() public function getDebugInfo()
{ {
return array (19 => 2); return array(19 => 2);
} }
public function getSource() public function getSourceContext()
{ {
return " foo bar return new Twig_Source(" foo bar\n twig source\n\n", 'foo.twig', $this->filename);
twig source
";
} }
} }