From 5b26e332610ed665137d8b63f43a61ec9d8432b6 Mon Sep 17 00:00:00 2001 From: Klaus Purer Date: Sun, 16 Oct 2016 22:10:53 +0200 Subject: [PATCH 1/4] [DomCrawler] Allow pipe (|) character in link tags when using Xpath expressions --- src/Symfony/Component/DomCrawler/Crawler.php | 44 ++++++++++++++++++- .../DomCrawler/Tests/CrawlerTest.php | 5 ++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php index 37822e53c2..a1ddffd797 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -856,13 +856,12 @@ class Crawler extends \SplObjectStorage { $expressions = array(); - $unionPattern = '/\|(?![^\[]*\])/'; // An expression which will never match to replace expressions which cannot match in the crawler // We cannot simply drop $nonMatchingExpression = 'a[name() = "b"]'; // Split any unions into individual expressions. - foreach (preg_split($unionPattern, $xpath) as $expression) { + foreach ($this->splitUnionParts($xpath) as $expression) { $expression = trim($expression); $parenthesis = ''; @@ -912,6 +911,47 @@ class Crawler extends \SplObjectStorage return implode(' | ', $expressions); } + /** + * Splits the XPath into parts that are separated by the union operator. + * + * @param string $xpath + * + * @return string[] + */ + private function splitUnionParts($xpath) + { + // Split any unions into individual expressions. We need to iterate + // through the string to correctly parse opening/closing quotes and + // braces which is not possible with regular expressions. + $unionParts = array(); + $inSingleQuotedString = false; + $inDoubleQuotedString = false; + $openedBrackets = 0; + $lastUnion = 0; + $xpathLength = strlen($xpath); + for ($i = 0; $i < $xpathLength; ++$i) { + $char = $xpath[$i]; + + if ($char === "'" && !$inDoubleQuotedString) { + $inSingleQuotedString = !$inSingleQuotedString; + } elseif ($char === '"' && !$inSingleQuotedString) { + $inDoubleQuotedString = !$inDoubleQuotedString; + } elseif (!$inSingleQuotedString && !$inDoubleQuotedString) { + if ($char === '[') { + ++$openedBrackets; + } elseif ($char === ']') { + --$openedBrackets; + } elseif ($char === '|' && $openedBrackets === 0) { + $unionParts[] = substr($xpath, $lastUnion, $i - $lastUnion); + $lastUnion = $i + 1; + } + } + } + $unionParts[] = substr($xpath, $lastUnion); + + return $unionParts; + } + /** * @param int $position * diff --git a/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php b/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php index 45bbb2f8e5..65e2a90e87 100755 --- a/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php @@ -387,6 +387,7 @@ EOF $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(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() @@ -548,7 +549,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/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() @@ -969,6 +970,8 @@ HTML; GetLink + Klausi|Claudiu +
From 3c216176e85c5a4f6055d67994439fd5eec95179 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 6 Oct 2016 15:54:54 +0200 Subject: [PATCH 2/4] [HttpKernel] Fix source links with latests Twig versions --- .../Component/HttpKernel/DataCollector/DumpDataCollector.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php index 3a445f45bb..538d73c783 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php @@ -99,11 +99,11 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof \Twig_Template) { $template = $trace[$i]['object']; $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); $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']]; + $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : false; if ($src) { $src = explode("\n", $src); From 17757d8114e255283ab131071b46293f2ff75a2e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 18 Oct 2016 09:12:23 +0200 Subject: [PATCH 3/4] [DomCrawler] Optimize DomCrawler::relativize() --- src/Symfony/Component/DomCrawler/Crawler.php | 96 +++++++++----------- 1 file changed, 44 insertions(+), 52 deletions(-) diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php index a1ddffd797..6f329d3892 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -860,17 +860,43 @@ class Crawler extends \SplObjectStorage // We cannot simply drop $nonMatchingExpression = 'a[name() = "b"]'; - // Split any unions into individual expressions. - foreach ($this->splitUnionParts($xpath) as $expression) { - $expression = trim($expression); - $parenthesis = ''; + $xpathLen = strlen($xpath); + $openedBrackets = 0; + $startPosition = strspn($xpath, " \t\n\r\0\x0B"); - // If the union is inside some braces, we need to preserve the opening braces and apply - // the change only inside it. - if (preg_match('/^[\(\s*]+/', $expression, $matches)) { - $parenthesis = $matches[0]; - $expression = substr($expression, strlen($parenthesis)); + for ($i = $startPosition; $i <= $xpathLen; ++$i) { + $i += strcspn($xpath, '"\'[]|', $i); + + if ($i < $xpathLen) { + 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)); // BC for Symfony 2.4 and lower were elements were adding in a fake _root parent if (0 === strpos($expression, '/_root/')) { @@ -880,7 +906,7 @@ class Crawler extends \SplObjectStorage } // add prefix before absolute element selector - if (empty($expression)) { + if ('' === $expression) { $expression = $nonMatchingExpression; } elseif (0 === strpos($expression, '//')) { $expression = 'descendant-or-self::'.substr($expression, 2); @@ -898,7 +924,7 @@ class Crawler extends \SplObjectStorage // '.' 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::')); + $expression = 'descendant-or-self::'.substr($expression, 12); } 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; @@ -906,50 +932,16 @@ class Crawler extends \SplObjectStorage $expression = 'self::'.$expression; } $expressions[] = $parenthesis.$expression; - } - return implode(' | ', $expressions); - } - - /** - * Splits the XPath into parts that are separated by the union operator. - * - * @param string $xpath - * - * @return string[] - */ - private function splitUnionParts($xpath) - { - // Split any unions into individual expressions. We need to iterate - // through the string to correctly parse opening/closing quotes and - // braces which is not possible with regular expressions. - $unionParts = array(); - $inSingleQuotedString = false; - $inDoubleQuotedString = false; - $openedBrackets = 0; - $lastUnion = 0; - $xpathLength = strlen($xpath); - for ($i = 0; $i < $xpathLength; ++$i) { - $char = $xpath[$i]; - - if ($char === "'" && !$inDoubleQuotedString) { - $inSingleQuotedString = !$inSingleQuotedString; - } elseif ($char === '"' && !$inSingleQuotedString) { - $inDoubleQuotedString = !$inDoubleQuotedString; - } elseif (!$inSingleQuotedString && !$inDoubleQuotedString) { - if ($char === '[') { - ++$openedBrackets; - } elseif ($char === ']') { - --$openedBrackets; - } elseif ($char === '|' && $openedBrackets === 0) { - $unionParts[] = substr($xpath, $lastUnion, $i - $lastUnion); - $lastUnion = $i + 1; - } + if ($i === $xpathLen) { + return implode(' | ', $expressions); } - } - $unionParts[] = substr($xpath, $lastUnion); - return $unionParts; + $i += strspn($xpath, " \t\n\r\0\x0B", $i + 1); + $startPosition = $i + 1; + } + + return $xpath; // The XPath expression is invalid } /** From f3b09d9ca5de33886f03834685c41f9db1461f68 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 6 Oct 2016 16:01:55 +0200 Subject: [PATCH 4/4] [VarDumper] Fix source links with latests Twig versions --- .../VarDumper/Caster/ExceptionCaster.php | 20 +++-- .../Tests/Caster/ExceptionCasterTest.php | 85 +++++++++++++++++++ .../VarDumper/Tests/CliDumperTest.php | 23 ++--- .../VarDumper/Tests/Fixtures/Twig.php | 19 ++--- 4 files changed, 114 insertions(+), 33 deletions(-) create mode 100644 src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php diff --git a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php index 513352e374..173d79dd41 100644 --- a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php @@ -150,15 +150,19 @@ class ExceptionCaster 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())); - - try { - $templateName = $template->getTemplateName(); - $templateSrc = explode("\n", method_exists($template, 'getSource') ? $template->getSource() : $template->getEnvironment()->getLoader()->getSource($templateName)); - $templateInfo = $template->getDebugInfo(); - if (isset($templateInfo[$f['line']])) { - $src[$templateName.':'.$templateInfo[$f['line']]] = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext); + $templateName = $template->getTemplateName(); + $templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : ''); + $templateInfo = $template->getDebugInfo(); + if (isset($templateInfo[$f['line']])) { + if (method_exists($template, 'getSourceContext')) { + $templateName = $template->getSourceContext()->getPath() ?: $templateName; + } + 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 { diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php new file mode 100644 index 0000000000..4c72eb1f75 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php @@ -0,0 +1,85 @@ + + * + * 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\VarDumperTestCase; + +class ExceptionCasterTest extends VarDumperTestCase +{ + /** + * @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); + } +} diff --git a/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php b/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php index 11de511dd0..36d45b6b5b 100644 --- a/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php @@ -201,6 +201,9 @@ EOTXT ); } + /** + * @requires function Twig_Template::getSourceContext + */ public function testThrowingCaster() { $out = fopen('php://memory', 'r+b'); @@ -235,19 +238,6 @@ EOTXT rewind($out); $out = stream_get_contents($out); - if (method_exists($twig, 'getSource')) { - $twig = <<assertStringMatchesFormat( <<displayWithErrorHandling() ==> __TwigTemplate_VarDumperFixture_u75a09->doDisplay(): { src: { diff --git a/src/Symfony/Component/VarDumper/Tests/Fixtures/Twig.php b/src/Symfony/Component/VarDumper/Tests/Fixtures/Twig.php index f25aac6553..7ffdd2bd54 100644 --- a/src/Symfony/Component/VarDumper/Tests/Fixtures/Twig.php +++ b/src/Symfony/Component/VarDumper/Tests/Fixtures/Twig.php @@ -3,14 +3,14 @@ /* foo.twig */ 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); - $this->parent = false; - - $this->blocks = array( - ); + $this->blocks = array(); + $this->filename = $filename; } protected function doDisplay(array $context, array $blocks = array()) @@ -26,14 +26,11 @@ class __TwigTemplate_VarDumperFixture_u75a09 extends Twig_Template public function getDebugInfo() { - return array (19 => 2); + return array(19 => 2); } - public function getSource() + public function getSourceContext() { - return " foo bar - twig source - -"; + return new Twig_Source(" foo bar\n twig source\n\n", 'foo.twig', $this->filename); } }