diff --git a/composer.json b/composer.json index f58b5af869f7d2df3965073e9a491b76e51308dc..e100a674cbeb3beb4d1e46863ff4ece7356f60f4 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "php": ">=5.4.2", "sdboyer/gliph": "0.1.*", "symfony/class-loader": "2.4.*", + "symfony/css-selector": "2.4.*", "symfony/dependency-injection": "2.4.*", "symfony/event-dispatcher": "2.4.*", "symfony/http-foundation": "2.4.*", diff --git a/composer.lock b/composer.lock index f8712885d8671df99d0219c7cd69293a8e39bb4b..a99f00eb04fcd973841111eac58462c21bc248d5 100644 --- a/composer.lock +++ b/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "764a98eeaf89d9f52b9c32d343036de0", + "hash": "cc429f39777a4435a2d1415648978f97", "packages": [ { "name": "doctrine/annotations", @@ -1188,6 +1188,59 @@ "homepage": "http://symfony.com", "time": "2013-11-26 16:40:27" }, + { + "name": "symfony/css-selector", + "version": "v2.4.4", + "target-dir": "Symfony/Component/CssSelector", + "source": { + "type": "git", + "url": "https://github.com/symfony/CssSelector.git", + "reference": "479a5b409723f596ffc3b5178034e4d76ce615b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/CssSelector/zipball/479a5b409723f596ffc3b5178034e4d76ce615b3", + "reference": "479a5b409723f596ffc3b5178034e4d76ce615b3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\CssSelector\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "http://symfony.com", + "time": "2014-04-18 20:37:09" + }, { "name": "symfony/debug", "version": "v2.3.4", diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php index a120883ac2e38732b4b67d31d0f9302a9cf8eb2a..ec50d4dd70f367bc02f8a2cfd79c62c5dff85e34 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -24,6 +24,7 @@ use Drupal\Core\Datetime\DrupalDateTime; use Drupal\block\Entity\Block; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\CssSelector\CssSelector; /** * Test case for typical Drupal tests. @@ -2338,6 +2339,22 @@ protected function xpath($xpath, array $arguments = array()) { } } + /** + * Performs a CSS selection based search on the contents of the internal + * browser. The search is relative to the root element (HTML tag normally) of + * the page. + * + * @param $selector string + * CSS selector to use in the search. + * + * @return \SimpleXMLElement + * The return value of the xpath search performed after converting the css + * selector to an XPath selector. + */ + protected function cssSelect($selector) { + return $this->xpath(CssSelector::toXPath($selector)); + } + /** * Get all option elements, including nested options, in a select. * diff --git a/core/modules/tour/src/Tests/TourTest.php b/core/modules/tour/src/Tests/TourTest.php index 5c99036d881ec06f696c9610f4eaab245fbe9011..17e7d148ed6871d88e90a0983114cd849317684b 100644 --- a/core/modules/tour/src/Tests/TourTest.php +++ b/core/modules/tour/src/Tests/TourTest.php @@ -53,12 +53,6 @@ public static function getInfo() { public function testTourFunctionality() { // Navigate to tour-test-1 and verify the tour_test_1 tip is found with appropriate classes. $this->drupalGet('tour-test-1'); - $elements = $this->xpath('//li[@data-id=:data_id and @class=:classes and ./h2[contains(., :text)]]', array( - ':classes' => 'tip-module-tour-test tip-type-text tip-tour-test-1', - ':data_id' => 'tour-test-1', - ':text' => 'The first tip', - )); - $this->assertEqual(count($elements), 1, 'Found English variant of tip 1.'); // Test the TourTestBase class assertTourTips() method. $tips = array(); @@ -75,16 +69,13 @@ public function testTourFunctionality() { )); $this->assertEqual(count($elements), 1, 'Found Token replacement.'); - $elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array( - ':data_id' => 'tour-test-2', - ':text' => 'The quick brown fox', - )); + $elements = $this->cssSelect("li[data-id=tour-test-1] h2:contains('The first tip')"); + $this->assertEqual(count($elements), 1, 'Found English variant of tip 1.'); + + $elements = $this->cssSelect("li[data-id=tour-test-2] h2:contains('The quick brown fox')"); $this->assertNotEqual(count($elements), 1, 'Did not find English variant of tip 2.'); - $elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array( - ':data_id' => 'tour-test-1', - ':text' => 'La pioggia cade in spagna', - )); + $elements = $this->cssSelect("li[data-id=tour-test-1] h2:contains('La pioggia cade in spagna')"); $this->assertNotEqual(count($elements), 1, 'Did not find Italian variant of tip 1.'); // Ensure that plugins work. @@ -93,16 +84,10 @@ public function testTourFunctionality() { // Navigate to tour-test-2/subpath and verify the tour_test_2 tip is found. $this->drupalGet('tour-test-2/subpath'); - $elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array( - ':data_id' => 'tour-test-2', - ':text' => 'The quick brown fox', - )); + $elements = $this->cssSelect("li[data-id=tour-test-2] h2:contains('The quick brown fox')"); $this->assertEqual(count($elements), 1, 'Found English variant of tip 2.'); - $elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array( - ':data_id' => 'tour-test-1', - ':text' => 'The first tip', - )); + $elements = $this->cssSelect("li[data-id=tour-test-1] h2:contains('The first tip')"); $this->assertNotEqual(count($elements), 1, 'Did not find English variant of tip 1.'); // Enable Italian language and navigate to it/tour-test1 and verify italian @@ -110,16 +95,10 @@ public function testTourFunctionality() { language_save(new Language(array('id' => 'it'))); $this->drupalGet('it/tour-test-1'); - $elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array( - ':data_id' => 'tour-test-1', - ':text' => 'La pioggia cade in spagna', - )); + $elements = $this->cssSelect("li[data-id=tour-test-1] h2:contains('La pioggia cade in spagna')"); $this->assertEqual(count($elements), 1, 'Found Italian variant of tip 1.'); - $elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array( - ':data_id' => 'tour-test-1', - ':text' => 'The first tip', - )); + $elements = $this->cssSelect("li[data-id=tour-test-2] h2:contains('The quick brown fox')"); $this->assertNotEqual(count($elements), 1, 'Did not find English variant of tip 1.'); language_save(new Language(array('id' => 'en'))); @@ -174,18 +153,12 @@ public function testTourFunctionality() { // Navigate to tour-test-1 and verify the new tip is found. $this->drupalGet('tour-test-1'); - $elements = $this->xpath('//li[@data-id=:data_id and ./h2[contains(., :text)]]', array( - ':data_id' => 'tour-code-test-1', - ':text' => 'The rain in spain', - )); + $elements = $this->cssSelect("li[data-id=tour-code-test-1] h2:contains('The rain in spain')"); $this->assertEqual(count($elements), 1, 'Found the required tip markup for tip 4'); // Verify that the weight sorting works by ensuring the lower weight item // (tip 4) has the 'End tour' button. - $elements = $this->xpath('//li[@data-id=:data_id and @data-text=:text]', array( - ':data_id' => 'tour-code-test-1', - ':text' => 'End tour', - )); + $elements = $this->cssSelect("li[data-id=tour-code-test-1][data-text='End tour']"); $this->assertEqual(count($elements), 1, 'Found code tip was weighted last and had "End tour".'); // Test hook_tour_alter(). diff --git a/core/vendor/composer/ClassLoader.php b/core/vendor/composer/ClassLoader.php index a7105553143a422546083f2bb49dd6a79a280a6a..f438e319cd51748608389f2fef6149a592b67241 100644 --- a/core/vendor/composer/ClassLoader.php +++ b/core/vendor/composer/ClassLoader.php @@ -266,7 +266,7 @@ public function unregister() public function loadClass($class) { if ($file = $this->findFile($class)) { - includeFile($file); + include $file; return true; } @@ -291,25 +291,8 @@ public function findFile($class) return $this->classMap[$class]; } - $file = $this->findFileWithExtension($class, '.php'); - - // Search for Hack files if we are running on HHVM - if ($file === null && defined('HHVM_VERSION')) { - $file = $this->findFileWithExtension($class, '.hh'); - } - - if ($file === null) { - // Remember that this class does not exist. - return $this->classMap[$class] = false; - } - - return $file; - } - - private function findFileWithExtension($class, $ext) - { // PSR-4 lookup - $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php'; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { @@ -338,7 +321,7 @@ private function findFileWithExtension($class, $ext) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name - $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . '.php'; } if (isset($this->prefixesPsr0[$first])) { @@ -364,15 +347,8 @@ private function findFileWithExtension($class, $ext) if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } - } -} -/** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - */ -function includeFile($file) -{ - include $file; + // Remember that this class does not exist. + return $this->classMap[$class] = false; + } } diff --git a/core/vendor/composer/autoload_files.php b/core/vendor/composer/autoload_files.php index 2ca5d44933d191b4d91a1250d7a9b647ae95c293..c47ff8ce02687b8ee0b1453412a9132bc23c4d19 100644 --- a/core/vendor/composer/autoload_files.php +++ b/core/vendor/composer/autoload_files.php @@ -6,8 +6,8 @@ $baseDir = dirname(dirname($vendorDir)); return array( - $vendorDir . '/guzzlehttp/streams/src/functions.php', $vendorDir . '/kriswallsmith/assetic/src/functions.php', + $vendorDir . '/guzzlehttp/streams/src/functions.php', $vendorDir . '/guzzlehttp/guzzle/src/functions.php', $baseDir . '/core/lib/Drupal.php', ); diff --git a/core/vendor/composer/autoload_namespaces.php b/core/vendor/composer/autoload_namespaces.php index b6e4ab82c25b8a1589e087be5ba368cd1182493d..e32355368463ccc467ab779e79d536c5d16b31d6 100644 --- a/core/vendor/composer/autoload_namespaces.php +++ b/core/vendor/composer/autoload_namespaces.php @@ -22,6 +22,7 @@ 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), 'Symfony\\Component\\DependencyInjection\\' => array($vendorDir . '/symfony/dependency-injection'), 'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'), + 'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'), 'Symfony\\Component\\ClassLoader\\' => array($vendorDir . '/symfony/class-loader'), 'Symfony\\Cmf\\Component\\Routing' => array($vendorDir . '/symfony-cmf/routing'), 'Psr\\Log\\' => array($vendorDir . '/psr/log'), diff --git a/core/vendor/composer/autoload_real.php b/core/vendor/composer/autoload_real.php index 0ecdb36774e7d47ab993702dd89f365e3dad9ddd..233f4c59d5f82e524d8293ac6aac25f9d3cb1224 100644 --- a/core/vendor/composer/autoload_real.php +++ b/core/vendor/composer/autoload_real.php @@ -49,14 +49,9 @@ public static function getLoader() $includeFiles = require __DIR__ . '/autoload_files.php'; foreach ($includeFiles as $file) { - composerRequireDrupal8($file); + require $file; } return $loader; } } - -function composerRequireDrupal8($file) -{ - require $file; -} diff --git a/core/vendor/composer/installed.json b/core/vendor/composer/installed.json index ed8df0cbbb6c34e7f79ecc48292650cf91ac5355..2f33f03248f2e18bac29e43162d8390d2075bf54 100644 --- a/core/vendor/composer/installed.json +++ b/core/vendor/composer/installed.json @@ -825,212 +825,6 @@ "string" ] }, - { - "name": "guzzle/common", - "version": "v3.7.1", - "version_normalized": "3.7.1.0", - "target-dir": "Guzzle/Common", - "source": { - "type": "git", - "url": "https://github.com/guzzle/common.git", - "reference": "v3.7.1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/common/zipball/v3.7.1", - "reference": "v3.7.1", - "shasum": "" - }, - "require": { - "php": ">=5.3.2", - "symfony/event-dispatcher": ">=2.1" - }, - "time": "2013-07-05 20:17:54", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.7-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-0": { - "Guzzle\\Common": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Common libraries used by Guzzle", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "collection", - "common", - "event", - "exception" - ] - }, - { - "name": "guzzle/stream", - "version": "v3.7.1", - "version_normalized": "3.7.1.0", - "target-dir": "Guzzle/Stream", - "source": { - "type": "git", - "url": "https://github.com/guzzle/stream.git", - "reference": "v3.7.1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/stream/zipball/v3.7.1", - "reference": "v3.7.1", - "shasum": "" - }, - "require": { - "guzzle/common": "self.version", - "php": ">=5.3.2" - }, - "suggest": { - "guzzle/http": "To convert Guzzle request objects to PHP streams" - }, - "time": "2013-06-27 00:50:43", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.7-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-0": { - "Guzzle\\Stream": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Guzzle stream wrapper component", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "Guzzle", - "component", - "stream" - ] - }, - { - "name": "guzzle/parser", - "version": "v3.7.1", - "version_normalized": "3.7.1.0", - "target-dir": "Guzzle/Parser", - "source": { - "type": "git", - "url": "https://github.com/guzzle/parser.git", - "reference": "v3.7.1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/parser/zipball/v3.7.1", - "reference": "v3.7.1", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "time": "2013-06-11 00:24:07", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.7-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-0": { - "Guzzle\\Parser": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Interchangeable parsers used by Guzzle", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "URI Template", - "cookie", - "http", - "message", - "url" - ] - }, - { - "name": "guzzle/http", - "version": "v3.7.1", - "version_normalized": "3.7.1.0", - "target-dir": "Guzzle/Http", - "source": { - "type": "git", - "url": "https://github.com/guzzle/http.git", - "reference": "v3.7.1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/http/zipball/v3.7.1", - "reference": "v3.7.1", - "shasum": "" - }, - "require": { - "guzzle/common": "self.version", - "guzzle/parser": "self.version", - "guzzle/stream": "self.version", - "php": ">=5.3.2" - }, - "suggest": { - "ext-curl": "*" - }, - "time": "2013-07-02 19:53:26", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.7-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-0": { - "Guzzle\\Http": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "HTTP libraries used by Guzzle", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "Guzzle", - "client", - "curl", - "http", - "http client" - ] - }, { "name": "symfony/debug", "version": "v2.3.4", @@ -2317,5 +2111,60 @@ "rest", "web service" ] + }, + { + "name": "symfony/css-selector", + "version": "v2.4.4", + "version_normalized": "2.4.4.0", + "target-dir": "Symfony/Component/CssSelector", + "source": { + "type": "git", + "url": "https://github.com/symfony/CssSelector.git", + "reference": "479a5b409723f596ffc3b5178034e4d76ce615b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/CssSelector/zipball/479a5b409723f596ffc3b5178034e4d76ce615b3", + "reference": "479a5b409723f596ffc3b5178034e4d76ce615b3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2014-04-18 20:37:09", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\CssSelector\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "http://symfony.com" } ] diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/.gitignore b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c49a5d8df5c6548379f00c77fe572a7217bce218 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/CHANGELOG.md b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..be10abee924cefbb9c75643bf588a292ba051cb7 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +2.1.0 +----- + + * none diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/CssSelector.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/CssSelector.php new file mode 100644 index 0000000000000000000000000000000000000000..7a12a8b7e1d899c59485ae1f7ee1f0d28e7bb1c5 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/CssSelector.php @@ -0,0 +1,83 @@ +<?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\CssSelector; + +use Symfony\Component\CssSelector\Parser\Shortcut\ClassParser; +use Symfony\Component\CssSelector\Parser\Shortcut\ElementParser; +use Symfony\Component\CssSelector\Parser\Shortcut\EmptyStringParser; +use Symfony\Component\CssSelector\Parser\Shortcut\HashParser; +use Symfony\Component\CssSelector\XPath\Extension\HtmlExtension; +use Symfony\Component\CssSelector\XPath\Translator; + +/** + * CssSelector is the main entry point of the component and can convert CSS + * selectors to XPath expressions. + * + * $xpath = CssSelector::toXpath('h1.foo'); + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @api + */ +class CssSelector +{ + private static $html = true; + + /** + * Translates a CSS expression to its XPath equivalent. + * Optionally, a prefix can be added to the resulting XPath + * expression with the $prefix parameter. + * + * @param mixed $cssExpr The CSS expression. + * @param string $prefix An optional prefix for the XPath expression. + * + * @return string + * + * @api + */ + public static function toXPath($cssExpr, $prefix = 'descendant-or-self::') + { + $translator = new Translator(); + + if (self::$html) { + $translator->registerExtension(new HtmlExtension($translator)); + } + + $translator + ->registerParserShortcut(new EmptyStringParser()) + ->registerParserShortcut(new ElementParser()) + ->registerParserShortcut(new ClassParser()) + ->registerParserShortcut(new HashParser()) + ; + + return $translator->cssToXPath($cssExpr, $prefix); + } + + /** + * Enables the HTML extension. + */ + public static function enableHtmlExtension() + { + self::$html = true; + } + + /** + * Disables the HTML extension. + */ + public static function disableHtmlExtension() + { + self::$html = false; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ExceptionInterface.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..da01c2b271772a7157c16e4dc59443b6c2cb686f --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ExceptionInterface.php @@ -0,0 +1,24 @@ +<?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\CssSelector\Exception; + +/** + * Interface for exceptions. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +interface ExceptionInterface +{ +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ExpressionErrorException.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ExpressionErrorException.php new file mode 100644 index 0000000000000000000000000000000000000000..151dbf0350467a2098472ecf95c9bf8ff11dc26b --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ExpressionErrorException.php @@ -0,0 +1,24 @@ +<?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\CssSelector\Exception; + +/** + * ParseException is thrown when a CSS selector syntax is not valid. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class ExpressionErrorException extends ParseException implements ExceptionInterface +{ +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/InternalErrorException.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/InternalErrorException.php new file mode 100644 index 0000000000000000000000000000000000000000..8a815fb9ea5f0de1078d99b217bda8237f7434bb --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/InternalErrorException.php @@ -0,0 +1,24 @@ +<?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\CssSelector\Exception; + +/** + * ParseException is thrown when a CSS selector syntax is not valid. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class InternalErrorException extends ParseException implements ExceptionInterface +{ +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ParseException.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ParseException.php new file mode 100644 index 0000000000000000000000000000000000000000..9c119f84c6a3757652ad722c5a74b576f5b19def --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ParseException.php @@ -0,0 +1,24 @@ +<?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\CssSelector\Exception; + +/** + * ParseException is thrown when a CSS selector syntax is not valid. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ParseException extends \Exception implements ExceptionInterface +{ +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/SyntaxErrorException.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/SyntaxErrorException.php new file mode 100644 index 0000000000000000000000000000000000000000..529b891a3f5af6336a0afab94c332101eac31c47 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/SyntaxErrorException.php @@ -0,0 +1,73 @@ +<?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\CssSelector\Exception; + +use Symfony\Component\CssSelector\Parser\Token; + +/** + * ParseException is thrown when a CSS selector syntax is not valid. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class SyntaxErrorException extends ParseException implements ExceptionInterface +{ + /** + * @param string $expectedValue + * @param Token $foundToken + * + * @return SyntaxErrorException + */ + public static function unexpectedToken($expectedValue, Token $foundToken) + { + return new self(sprintf('Expected %s, but %s found.', $expectedValue, $foundToken)); + } + + /** + * @param string $pseudoElement + * @param string $unexpectedLocation + * + * @return SyntaxErrorException + */ + public static function pseudoElementFound($pseudoElement, $unexpectedLocation) + { + return new self(sprintf('Unexpected pseudo-element "::%s" found %s.', $pseudoElement, $unexpectedLocation)); + } + + /** + * @param int $position + * + * @return SyntaxErrorException + */ + public static function unclosedString($position) + { + return new self(sprintf('Unclosed/invalid string at %s.', $position)); + } + + /** + * @return SyntaxErrorException + */ + public static function nestedNot() + { + return new self('Got nested ::not().'); + } + + /** + * @return SyntaxErrorException + */ + public static function stringAsFunctionArgument() + { + return new self('String not allowed as function argument.'); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/LICENSE b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..0b3292cf9023507c2f1f93ddfee7dd8e15838048 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2014 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/AbstractNode.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/AbstractNode.php new file mode 100644 index 0000000000000000000000000000000000000000..f5324e191b0d0a955dd341995ddec3ec2814b416 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/AbstractNode.php @@ -0,0 +1,40 @@ +<?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\CssSelector\Node; + +/** + * Abstract base node class. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +abstract class AbstractNode implements NodeInterface +{ + /** + * @var string + */ + private $nodeName; + + /** + * @return string + */ + public function getNodeName() + { + if (null === $this->nodeName) { + $this->nodeName = preg_replace('~.*\\\\([^\\\\]+)Node$~', '$1', get_called_class()); + } + + return $this->nodeName; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/AttributeNode.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/AttributeNode.php new file mode 100644 index 0000000000000000000000000000000000000000..e2fa9a294c513af52e11b41137cdeb3796aec0de --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/AttributeNode.php @@ -0,0 +1,124 @@ +<?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\CssSelector\Node; + +/** + * Represents a "<selector>[<namespace>|<attribute> <operator> <value>]" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class AttributeNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $selector; + + /** + * @var string + */ + private $namespace; + + /** + * @var string + */ + private $attribute; + + /** + * @var string + */ + private $operator; + + /** + * @var string + */ + private $value; + + /** + * @param NodeInterface $selector + * @param string $namespace + * @param string $attribute + * @param string $operator + * @param string $value + */ + public function __construct(NodeInterface $selector, $namespace, $attribute, $operator, $value) + { + $this->selector = $selector; + $this->namespace = $namespace; + $this->attribute = $attribute; + $this->operator = $operator; + $this->value = $value; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * @return string + */ + public function getAttribute() + { + return $this->attribute; + } + + /** + * @return string + */ + public function getOperator() + { + return $this->operator; + } + + /** + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $attribute = $this->namespace ? $this->namespace.'|'.$this->attribute : $this->attribute; + + return 'exists' === $this->operator + ? sprintf('%s[%s[%s]]', $this->getNodeName(), $this->selector, $attribute) + : sprintf("%s[%s[%s %s '%s']]", $this->getNodeName(), $this->selector, $attribute, $this->operator, $this->value); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/ClassNode.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/ClassNode.php new file mode 100644 index 0000000000000000000000000000000000000000..a7a59a33a9f48a1e39b0303a27595667e93c3c2b --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/ClassNode.php @@ -0,0 +1,75 @@ +<?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\CssSelector\Node; + +/** + * Represents a "<selector>.<name>" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class ClassNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $selector; + + /** + * @var string + */ + private $name; + + /** + * @param NodeInterface $selector + * @param string $name + */ + public function __construct(NodeInterface $selector, $name) + { + $this->selector = $selector; + $this->name = $name; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s.%s]', $this->getNodeName(), $this->selector, $this->name); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php new file mode 100644 index 0000000000000000000000000000000000000000..4e085ea4e1a72fb5fa46fee8f72f7704e0693a28 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php @@ -0,0 +1,92 @@ +<?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\CssSelector\Node; + +/** + * Represents a combined node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class CombinedSelectorNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $selector; + + /** + * @var string + */ + private $combinator; + + /** + * @var NodeInterface + */ + private $subSelector; + + /** + * @param NodeInterface $selector + * @param string $combinator + * @param NodeInterface $subSelector + */ + public function __construct(NodeInterface $selector, $combinator, NodeInterface $subSelector) + { + $this->selector = $selector; + $this->combinator = $combinator; + $this->subSelector = $subSelector; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getCombinator() + { + return $this->combinator; + } + + /** + * @return NodeInterface + */ + public function getSubSelector() + { + return $this->subSelector; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $combinator = ' ' === $this->combinator ? '<followed>' : $this->combinator; + + return sprintf('%s[%s %s %s]', $this->getNodeName(), $this->selector, $combinator, $this->subSelector); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/ElementNode.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/ElementNode.php new file mode 100644 index 0000000000000000000000000000000000000000..9ab13c3f243744f1a95427849c313d848989360d --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/ElementNode.php @@ -0,0 +1,77 @@ +<?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\CssSelector\Node; + +/** + * Represents a "<namespace>|<element>" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class ElementNode extends AbstractNode +{ + /** + * @var string|null + */ + private $namespace; + + /** + * @var string|null + */ + private $element; + + /** + * @param string|null $namespace + * @param string|null $element + */ + public function __construct($namespace = null, $element = null) + { + $this->namespace = $namespace; + $this->element = $element; + } + + /** + * @return null|string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * @return null|string + */ + public function getElement() + { + return $this->element; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return new Specificity(0, 0, $this->element ? 1 : 0); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $element = $this->element ?: '*'; + + return sprintf('%s[%s]', $this->getNodeName(), $this->namespace ? $this->namespace.'|'.$element : $element); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/FunctionNode.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/FunctionNode.php new file mode 100644 index 0000000000000000000000000000000000000000..ecd11a50b00d949f9c1a46a4f3f6af483917c182 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/FunctionNode.php @@ -0,0 +1,96 @@ +<?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\CssSelector\Node; + +use Symfony\Component\CssSelector\Parser\Token; + +/** + * Represents a "<selector>:<name>(<arguments>)" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class FunctionNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $selector; + + /** + * @var string + */ + private $name; + + /** + * @var Token[] + */ + private $arguments; + + /** + * @param NodeInterface $selector + * @param string $name + * @param Token[] $arguments + */ + public function __construct(NodeInterface $selector, $name, array $arguments = array()) + { + $this->selector = $selector; + $this->name = strtolower($name); + $this->arguments = $arguments; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return Token[] + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $arguments = implode(', ', array_map(function (Token $token) { + return "'".$token->getValue()."'"; + }, $this->arguments)); + + return sprintf('%s[%s:%s(%s)]', $this->getNodeName(), $this->selector, $this->name, $arguments ? '['.$arguments.']' : ''); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/HashNode.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/HashNode.php new file mode 100644 index 0000000000000000000000000000000000000000..7fb407579b61bda2b1eb268034592052542611d3 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/HashNode.php @@ -0,0 +1,75 @@ +<?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\CssSelector\Node; + +/** + * Represents a "<selector>#<id>" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class HashNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $selector; + + /** + * @var string + */ + private $id; + + /** + * @param NodeInterface $selector + * @param string $id + */ + public function __construct(NodeInterface $selector, $id) + { + $this->selector = $selector; + $this->id = $id; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(1, 0, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s#%s]', $this->getNodeName(), $this->selector, $this->id); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/NegationNode.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/NegationNode.php new file mode 100644 index 0000000000000000000000000000000000000000..2529689095e5b8c65944183911f861ec1bbc68da --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/NegationNode.php @@ -0,0 +1,75 @@ +<?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\CssSelector\Node; + +/** + * Represents a "<selector>:not(<identifier>)" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class NegationNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $selector; + + /** + * @var NodeInterface + */ + private $subSelector; + + /** + * @param NodeInterface $selector + * @param NodeInterface $subSelector + */ + public function __construct(NodeInterface $selector, NodeInterface $subSelector) + { + $this->selector = $selector; + $this->subSelector = $subSelector; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return NodeInterface + */ + public function getSubSelector() + { + return $this->subSelector; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s:not(%s)]', $this->getNodeName(), $this->selector, $this->subSelector); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/NodeInterface.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/NodeInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..1601c33a6ed3032c76eada0ecc85408504df90fe --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/NodeInterface.php @@ -0,0 +1,44 @@ +<?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\CssSelector\Node; + +/** + * Interface for nodes. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +interface NodeInterface +{ + /** + * Returns node's name. + * + * @return string + */ + public function getNodeName(); + + /** + * Returns node's specificity. + * + * @return Specificity + */ + public function getSpecificity(); + + /** + * Returns node's string representation. + * + * @return string + */ + public function __toString(); +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/PseudoNode.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/PseudoNode.php new file mode 100644 index 0000000000000000000000000000000000000000..4f2d538f6f522dcc1b2f14bbea2fc86d59764360 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/PseudoNode.php @@ -0,0 +1,75 @@ +<?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\CssSelector\Node; + +/** + * Represents a "<selector>:<identifier>" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class PseudoNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $selector; + + /** + * @var string + */ + private $identifier; + + /** + * @param NodeInterface $selector + * @param string $identifier + */ + public function __construct(NodeInterface $selector, $identifier) + { + $this->selector = $selector; + $this->identifier = strtolower($identifier); + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getIdentifier() + { + return $this->identifier; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s:%s]', $this->getNodeName(), $this->selector, $this->identifier); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/SelectorNode.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/SelectorNode.php new file mode 100644 index 0000000000000000000000000000000000000000..49f417f2860c2e631157c851e85807e0167e6b35 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/SelectorNode.php @@ -0,0 +1,75 @@ +<?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\CssSelector\Node; + +/** + * Represents a "<selector>(::|:)<pseudoElement>" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class SelectorNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $tree; + + /** + * @var null|string + */ + private $pseudoElement; + + /** + * @param NodeInterface $tree + * @param null|string $pseudoElement + */ + public function __construct(NodeInterface $tree, $pseudoElement = null) + { + $this->tree = $tree; + $this->pseudoElement = $pseudoElement ? strtolower($pseudoElement) : null; + } + + /** + * @return NodeInterface + */ + public function getTree() + { + return $this->tree; + } + + /** + * @return null|string + */ + public function getPseudoElement() + { + return $this->pseudoElement; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->tree->getSpecificity()->plus(new Specificity(0, 0, $this->pseudoElement ? 1 : 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s%s]', $this->getNodeName(), $this->tree, $this->pseudoElement ? '::'.$this->pseudoElement : ''); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/Specificity.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/Specificity.php new file mode 100644 index 0000000000000000000000000000000000000000..96bbd11f515ce2f40783c11135d2f5e36f170b36 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/Specificity.php @@ -0,0 +1,78 @@ +<?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\CssSelector\Node; + +/** + * Represents a node specificity. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @see http://www.w3.org/TR/selectors/#specificity + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class Specificity +{ + const A_FACTOR = 100; + const B_FACTOR = 10; + const C_FACTOR = 1; + + /** + * @var int + */ + private $a; + + /** + * @var int + */ + private $b; + + /** + * @var int + */ + private $c; + + /** + * Constructor. + * + * @param int $a + * @param int $b + * @param int $c + */ + public function __construct($a, $b, $c) + { + $this->a = $a; + $this->b = $b; + $this->c = $c; + } + + /** + * @param Specificity $specificity + * + * @return Specificity + */ + public function plus(Specificity $specificity) + { + return new self($this->a + $specificity->a, $this->b + $specificity->b, $this->c + $specificity->c); + } + + /** + * Returns global specificity value. + * + * @return int + */ + public function getValue() + { + return $this->a * self::A_FACTOR + $this->b * self::B_FACTOR + $this->c * self::C_FACTOR; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/CommentHandler.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/CommentHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..97c3f8dd24c657f9251b5ebf3c81e20fe669e491 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/CommentHandler.php @@ -0,0 +1,46 @@ +<?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\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class CommentHandler implements HandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + if ('/*' !== $reader->getSubstring(2)) { + return false; + } + + $offset = $reader->getOffset('*/'); + + if (false === $offset) { + $reader->moveToEnd(); + } else { + $reader->moveForward($offset + 2); + } + + return true; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/HandlerInterface.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/HandlerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c7a48c0d6eb1b4b5dbd2e3c8da73ae05d3ad7b0d --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/HandlerInterface.php @@ -0,0 +1,34 @@ +<?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\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector handler interface. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +interface HandlerInterface +{ + /** + * @param Reader $reader + * @param TokenStream $stream + * + * @return bool + */ + public function handle(Reader $reader, TokenStream $stream); +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/HashHandler.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/HashHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..2227ea62a6aaed22d900a3a90daec75f843ba12e --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/HashHandler.php @@ -0,0 +1,67 @@ +<?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\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class HashHandler implements HandlerInterface +{ + /** + * @var TokenizerPatterns + */ + private $patterns; + + /** + * @var TokenizerEscaping + */ + private $escaping; + + /** + * @param TokenizerPatterns $patterns + * @param TokenizerEscaping $escaping + */ + public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) + { + $this->patterns = $patterns; + $this->escaping = $escaping; + } + + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $match = $reader->findPattern($this->patterns->getHashPattern()); + + if (!$match) { + return false; + } + + $value = $this->escaping->escapeUnicode($match[1]); + $stream->push(new Token(Token::TYPE_HASH, $value, $reader->getPosition())); + $reader->moveForward(strlen($match[0])); + + return true; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/IdentifierHandler.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/IdentifierHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..346532ec1512141df8bda83225451aff0b4d58cf --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/IdentifierHandler.php @@ -0,0 +1,67 @@ +<?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\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class IdentifierHandler implements HandlerInterface +{ + /** + * @var TokenizerPatterns + */ + private $patterns; + + /** + * @var TokenizerEscaping + */ + private $escaping; + + /** + * @param TokenizerPatterns $patterns + * @param TokenizerEscaping $escaping + */ + public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) + { + $this->patterns = $patterns; + $this->escaping = $escaping; + } + + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $match = $reader->findPattern($this->patterns->getIdentifierPattern()); + + if (!$match) { + return false; + } + + $value = $this->escaping->escapeUnicode($match[0]); + $stream->push(new Token(Token::TYPE_IDENTIFIER, $value, $reader->getPosition())); + $reader->moveForward(strlen($match[0])); + + return true; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/NumberHandler.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/NumberHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..208f83c08fe778653dac3209f0b1168716854ed9 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/NumberHandler.php @@ -0,0 +1,58 @@ +<?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\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class NumberHandler implements HandlerInterface +{ + /** + * @var TokenizerPatterns + */ + private $patterns; + + /** + * @param TokenizerPatterns $patterns + */ + public function __construct(TokenizerPatterns $patterns) + { + $this->patterns = $patterns; + } + + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $match = $reader->findPattern($this->patterns->getNumberPattern()); + + if (!$match) { + return false; + } + + $stream->push(new Token(Token::TYPE_NUMBER, $match[0], $reader->getPosition())); + $reader->moveForward(strlen($match[0])); + + return true; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/StringHandler.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/StringHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..2663fe87b5112e27e86781251604fe2097c99598 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/StringHandler.php @@ -0,0 +1,86 @@ +<?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\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Exception\InternalErrorException; +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class StringHandler implements HandlerInterface +{ + /** + * @var TokenizerPatterns + */ + private $patterns; + + /** + * @var TokenizerEscaping + */ + private $escaping; + + /** + * @param TokenizerPatterns $patterns + * @param TokenizerEscaping $escaping + */ + public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) + { + $this->patterns = $patterns; + $this->escaping = $escaping; + } + + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $quote = $reader->getSubstring(1); + + if (!in_array($quote, array("'", '"'))) { + return false; + } + + $reader->moveForward(1); + $match = $reader->findPattern($this->patterns->getQuotedStringPattern($quote)); + + if (!$match) { + throw new InternalErrorException(sprintf('Should have found at least an empty match at %s.', $reader->getPosition())); + } + + // check unclosed strings + if (strlen($match[0]) === $reader->getRemainingLength()) { + throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); + } + + // check quotes pairs validity + if ($quote !== $reader->getSubstring(1, strlen($match[0]))) { + throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); + } + + $string = $this->escaping->escapeUnicodeAndNewLine($match[0]); + $stream->push(new Token(Token::TYPE_STRING, $string, $reader->getPosition())); + $reader->moveForward(strlen($match[0]) + 1); + + return true; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/WhitespaceHandler.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/WhitespaceHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..806cfbb513520035f9907b7d1e203260fa11a0e5 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/WhitespaceHandler.php @@ -0,0 +1,44 @@ +<?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\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector whitespace handler. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class WhitespaceHandler implements HandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $match = $reader->findPattern('~^[ \t\r\n\f]+~'); + + if (false === $match) { + return false; + } + + $stream->push(new Token(Token::TYPE_WHITESPACE, $match[0], $reader->getPosition())); + $reader->moveForward(strlen($match[0])); + + return true; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Parser.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Parser.php new file mode 100644 index 0000000000000000000000000000000000000000..6c7c3be583ba8c8b869c68e6647713770f7cb91a --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Parser.php @@ -0,0 +1,399 @@ +<?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\CssSelector\Parser; + +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; +use Symfony\Component\CssSelector\Node; +use Symfony\Component\CssSelector\Parser\Tokenizer\Tokenizer; + +/** + * CSS selector parser. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class Parser implements ParserInterface +{ + /** + * @var Tokenizer + */ + private $tokenizer; + + /** + * Constructor. + * + * @param null|Tokenizer $tokenizer + */ + public function __construct(Tokenizer $tokenizer = null) + { + $this->tokenizer = $tokenizer ?: new Tokenizer(); + } + + /** + * {@inheritdoc} + */ + public function parse($source) + { + $reader = new Reader($source); + $stream = $this->tokenizer->tokenize($reader); + + return $this->parseSelectorList($stream); + } + + /** + * Parses the arguments for ":nth-child()" and friends. + * + * @param Token[] $tokens + * + * @throws SyntaxErrorException + * + * @return array + */ + public static function parseSeries(array $tokens) + { + foreach ($tokens as $token) { + if ($token->isString()) { + throw SyntaxErrorException::stringAsFunctionArgument(); + } + } + + $joined = trim(implode('', array_map(function (Token $token) { + return $token->getValue(); + }, $tokens))); + + $int = function ($string) { + if (!is_numeric($string)) { + throw SyntaxErrorException::stringAsFunctionArgument(); + } + + return (int) $string; + }; + + switch (true) { + case 'odd' === $joined: + return array(2, 1); + case 'even' === $joined: + return array(2, 0); + case 'n' === $joined: + return array(1, 0); + case false === strpos($joined, 'n'): + return array(0, $int($joined)); + } + + $split = explode('n', $joined); + $first = isset($split[0]) ? $split[0] : null; + + return array( + $first ? ('-' === $first || '+' === $first ? $int($first.'1') : $int($first)) : 1, + isset($split[1]) && $split[1] ? $int($split[1]) : 0 + ); + } + + /** + * Parses selector nodes. + * + * @param TokenStream $stream + * + * @return array + */ + private function parseSelectorList(TokenStream $stream) + { + $stream->skipWhitespace(); + $selectors = array(); + + while (true) { + $selectors[] = $this->parserSelectorNode($stream); + + if ($stream->getPeek()->isDelimiter(array(','))) { + $stream->getNext(); + $stream->skipWhitespace(); + } else { + break; + } + } + + return $selectors; + } + + /** + * Parses next selector or combined node. + * + * @param TokenStream $stream + * + * @throws SyntaxErrorException + * + * @return Node\SelectorNode + */ + private function parserSelectorNode(TokenStream $stream) + { + list($result, $pseudoElement) = $this->parseSimpleSelector($stream); + + while (true) { + $stream->skipWhitespace(); + $peek = $stream->getPeek(); + + if ($peek->isFileEnd() || $peek->isDelimiter(array(','))) { + break; + } + + if (null !== $pseudoElement) { + throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector'); + } + + if ($peek->isDelimiter(array('+', '>', '~'))) { + $combinator = $stream->getNext()->getValue(); + $stream->skipWhitespace(); + } else { + $combinator = ' '; + } + + list($nextSelector, $pseudoElement) = $this->parseSimpleSelector($stream); + $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector); + } + + return new Node\SelectorNode($result, $pseudoElement); + } + + /** + * Parses next simple node (hash, class, pseudo, negation). + * + * @param TokenStream $stream + * @param bool $insideNegation + * + * @throws SyntaxErrorException + * + * @return array + */ + private function parseSimpleSelector(TokenStream $stream, $insideNegation = false) + { + $stream->skipWhitespace(); + + $selectorStart = count($stream->getUsed()); + $result = $this->parseElementNode($stream); + $pseudoElement = null; + + while (true) { + $peek = $stream->getPeek(); + if ($peek->isWhitespace() + || $peek->isFileEnd() + || $peek->isDelimiter(array(',', '+', '>', '~')) + || ($insideNegation && $peek->isDelimiter(array(')'))) + ) { + break; + } + + if (null !== $pseudoElement) { + throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector'); + } + + if ($peek->isHash()) { + $result = new Node\HashNode($result, $stream->getNext()->getValue()); + } elseif ($peek->isDelimiter(array('.'))) { + $stream->getNext(); + $result = new Node\ClassNode($result, $stream->getNextIdentifier()); + } elseif ($peek->isDelimiter(array('['))) { + $stream->getNext(); + $result = $this->parseAttributeNode($result, $stream); + } elseif ($peek->isDelimiter(array(':'))) { + $stream->getNext(); + + if ($stream->getPeek()->isDelimiter(array(':'))) { + $stream->getNext(); + $pseudoElement = $stream->getNextIdentifier(); + + continue; + } + + $identifier = $stream->getNextIdentifier(); + if (in_array(strtolower($identifier), array('first-line', 'first-letter', 'before', 'after'))) { + // Special case: CSS 2.1 pseudo-elements can have a single ':'. + // Any new pseudo-element must have two. + $pseudoElement = $identifier; + + continue; + } + + if (!$stream->getPeek()->isDelimiter(array('('))) { + $result = new Node\PseudoNode($result, $identifier); + + continue; + } + + $stream->getNext(); + $stream->skipWhitespace(); + + if ('not' === strtolower($identifier)) { + if ($insideNegation) { + throw SyntaxErrorException::nestedNot(); + } + + list($argument, $argumentPseudoElement) = $this->parseSimpleSelector($stream, true); + $next = $stream->getNext(); + + if (null !== $argumentPseudoElement) { + throw SyntaxErrorException::pseudoElementFound($argumentPseudoElement, 'inside ::not()'); + } + + if (!$next->isDelimiter(array(')'))) { + throw SyntaxErrorException::unexpectedToken('")"', $next); + } + + $result = new Node\NegationNode($result, $argument); + } else { + $arguments = array(); + $next = null; + + while (true) { + $stream->skipWhitespace(); + $next = $stream->getNext(); + + if ($next->isIdentifier() + || $next->isString() + || $next->isNumber() + || $next->isDelimiter(array('+', '-')) + ) { + $arguments[] = $next; + } elseif ($next->isDelimiter(array(')'))) { + break; + } else { + throw SyntaxErrorException::unexpectedToken('an argument', $next); + } + } + + if (empty($arguments)) { + throw SyntaxErrorException::unexpectedToken('at least one argument', $next); + } + + $result = new Node\FunctionNode($result, $identifier, $arguments); + } + } else { + throw SyntaxErrorException::unexpectedToken('selector', $peek); + } + } + + if (count($stream->getUsed()) === $selectorStart) { + throw SyntaxErrorException::unexpectedToken('selector', $stream->getPeek()); + } + + return array($result, $pseudoElement); + } + + /** + * Parses next element node. + * + * @param TokenStream $stream + * + * @return Node\ElementNode + */ + private function parseElementNode(TokenStream $stream) + { + $peek = $stream->getPeek(); + + if ($peek->isIdentifier() || $peek->isDelimiter(array('*'))) { + if ($peek->isIdentifier()) { + $namespace = $stream->getNext()->getValue(); + } else { + $stream->getNext(); + $namespace = null; + } + + if ($stream->getPeek()->isDelimiter(array('|'))) { + $stream->getNext(); + $element = $stream->getNextIdentifierOrStar(); + } else { + $element = $namespace; + $namespace = null; + } + } else { + $element = $namespace = null; + } + + return new Node\ElementNode($namespace, $element); + } + + /** + * Parses next attribute node. + * + * @param Node\NodeInterface $selector + * @param TokenStream $stream + * + * @throws SyntaxErrorException + * + * @return Node\AttributeNode + */ + private function parseAttributeNode(Node\NodeInterface $selector, TokenStream $stream) + { + $stream->skipWhitespace(); + $attribute = $stream->getNextIdentifierOrStar(); + + if (null === $attribute && !$stream->getPeek()->isDelimiter(array('|'))) { + throw SyntaxErrorException::unexpectedToken('"|"', $stream->getPeek()); + } + + if ($stream->getPeek()->isDelimiter(array('|'))) { + $stream->getNext(); + + if ($stream->getPeek()->isDelimiter(array('='))) { + $namespace = null; + $stream->getNext(); + $operator = '|='; + } else { + $namespace = $attribute; + $attribute = $stream->getNextIdentifier(); + $operator = null; + } + } else { + $namespace = $operator = null; + } + + if (null === $operator) { + $stream->skipWhitespace(); + $next = $stream->getNext(); + + if ($next->isDelimiter(array(']'))) { + return new Node\AttributeNode($selector, $namespace, $attribute, 'exists', null); + } elseif ($next->isDelimiter(array('='))) { + $operator = '='; + } elseif ($next->isDelimiter(array('^', '$', '*', '~', '|', '!')) + && $stream->getPeek()->isDelimiter(array('=')) + ) { + $operator = $next->getValue().'='; + $stream->getNext(); + } else { + throw SyntaxErrorException::unexpectedToken('operator', $next); + } + } + + $stream->skipWhitespace(); + $value = $stream->getNext(); + + if ($value->isNumber()) { + // if the value is a number, it's casted into a string + $value = new Token(Token::TYPE_STRING, (string) $value->getValue(), $value->getPosition()); + } + + if (!($value->isIdentifier() || $value->isString())) { + throw SyntaxErrorException::unexpectedToken('string or identifier', $value); + } + + $stream->skipWhitespace(); + $next = $stream->getNext(); + + if (!$next->isDelimiter(array(']'))) { + throw SyntaxErrorException::unexpectedToken('"]"', $next); + } + + return new Node\AttributeNode($selector, $namespace, $attribute, $operator, $value->getValue()); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/ParserInterface.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/ParserInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..b27f79f4a6e4addada16c36da3b3699d7ca2e623 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/ParserInterface.php @@ -0,0 +1,34 @@ +<?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\CssSelector\Parser; + +use Symfony\Component\CssSelector\Node\SelectorNode; + +/** + * CSS selector parser interface. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +interface ParserInterface +{ + /** + * Parses given selector source into an array of tokens. + * + * @param string $source + * + * @return SelectorNode[] + */ + public function parse($source); +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Reader.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Reader.php new file mode 100644 index 0000000000000000000000000000000000000000..2a6c4bbd43964be598c930917991fbbfa7df9d24 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Reader.php @@ -0,0 +1,125 @@ +<?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\CssSelector\Parser; + +/** + * CSS selector reader. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class Reader +{ + /** + * @var string + */ + private $source; + + /** + * @var int + */ + private $length; + + /** + * @var int + */ + private $position = 0; + + /** + * @param string $source + */ + public function __construct($source) + { + $this->source = $source; + $this->length = strlen($source); + } + + /** + * @return bool + */ + public function isEOF() + { + return $this->position >= $this->length; + } + + /** + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * @return int + */ + public function getRemainingLength() + { + return $this->length - $this->position; + } + + /** + * @param int $length + * @param int $offset + * + * @return string + */ + public function getSubstring($length, $offset = 0) + { + return substr($this->source, $this->position + $offset, $length); + } + + /** + * @param string $string + * + * @return int + */ + public function getOffset($string) + { + $position = strpos($this->source, $string, $this->position); + + return false === $position ? false : $position - $this->position; + } + + /** + * @param string $pattern + * + * @return bool + */ + public function findPattern($pattern) + { + $source = substr($this->source, $this->position); + + if (preg_match($pattern, $source, $matches)) { + return $matches; + } + + return false; + } + + /** + * @param int $length + */ + public function moveForward($length) + { + $this->position += $length; + } + + /** + */ + public function moveToEnd() + { + $this->position = $this->length; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/ClassParser.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/ClassParser.php new file mode 100644 index 0000000000000000000000000000000000000000..db352334f0a2415fd273be89fb4617fe129d75db --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/ClassParser.php @@ -0,0 +1,50 @@ +<?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\CssSelector\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\ClassNode; +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\ParserInterface; + +/** + * CSS selector class parser shortcut. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class ClassParser implements ParserInterface +{ + /** + * {@inheritdoc} + */ + public function parse($source) + { + // Matches an optional namespace, optional element, and required class + // $source = 'test|input.ab6bd_field'; + // $matches = array (size=5) + // 0 => string 'test:input.ab6bd_field' (length=22) + // 1 => string 'test:' (length=5) + // 2 => string 'test' (length=4) + // 3 => string 'input' (length=5) + // 4 => string 'ab6bd_field' (length=11) + if (preg_match('/^(([a-z]+)\|)?([\w-]+|\*)?\.([\w-]+)$/i', trim($source), $matches)) { + return array( + new SelectorNode(new ClassNode(new ElementNode($matches[2] ?: null, $matches[3] ?: null), $matches[4])) + ); + } + + return array(); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/ElementParser.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/ElementParser.php new file mode 100644 index 0000000000000000000000000000000000000000..eae18b94679fd0959b3630f1009df722e4791f84 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/ElementParser.php @@ -0,0 +1,46 @@ +<?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\CssSelector\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\ParserInterface; + +/** + * CSS selector element parser shortcut. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class ElementParser implements ParserInterface +{ + /** + * {@inheritdoc} + */ + public function parse($source) + { + // Matches an optional namespace, required element or `*` + // $source = 'testns|testel'; + // $matches = array (size=4) + // 0 => string 'testns:testel' (length=13) + // 1 => string 'testns:' (length=7) + // 2 => string 'testns' (length=6) + // 3 => string 'testel' (length=6) + if (preg_match('/^(([a-z]+)\|)?([\w-]+|\*)$/i', trim($source), $matches)) { + return array(new SelectorNode(new ElementNode($matches[2] ?: null, $matches[3]))); + } + + return array(); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/EmptyStringParser.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/EmptyStringParser.php new file mode 100644 index 0000000000000000000000000000000000000000..0031f7cfef3be0c74e488b4813ebde2a401190e4 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/EmptyStringParser.php @@ -0,0 +1,44 @@ +<?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\CssSelector\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\ParserInterface; + +/** + * CSS selector class parser shortcut. + * + * This shortcut ensure compatibility with previous version. + * - The parser fails to parse an empty string. + * - In the previous version, an empty string matches each tags. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class EmptyStringParser implements ParserInterface +{ + /** + * {@inheritdoc} + */ + public function parse($source) + { + // Matches an empty string + if ($source == '') { + return array(new SelectorNode(new ElementNode(null, '*'))); + } + + return array(); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/HashParser.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/HashParser.php new file mode 100644 index 0000000000000000000000000000000000000000..95d7d5f91174f9cb04a0bb03c2ab4d84cdea4ac1 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/HashParser.php @@ -0,0 +1,50 @@ +<?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\CssSelector\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\HashNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\ParserInterface; + +/** + * CSS selector hash parser shortcut. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class HashParser implements ParserInterface +{ + /** + * {@inheritdoc} + */ + public function parse($source) + { + // Matches an optional namespace, optional element, and required id + // $source = 'test|input#ab6bd_field'; + // $matches = array (size=5) + // 0 => string 'test:input#ab6bd_field' (length=22) + // 1 => string 'test:' (length=5) + // 2 => string 'test' (length=4) + // 3 => string 'input' (length=5) + // 4 => string 'ab6bd_field' (length=11) + if (preg_match('/^(([a-z]+)\|)?([\w-]+|\*)?#([\w-]+)$/i', trim($source), $matches)) { + return array( + new SelectorNode(new HashNode(new ElementNode($matches[2] ?: null, $matches[3] ?: null), $matches[4])) + ); + } + + return array(); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Token.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Token.php new file mode 100644 index 0000000000000000000000000000000000000000..4adfca889d866303688a4438bb3ab420cc38a068 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Token.php @@ -0,0 +1,160 @@ +<?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\CssSelector\Parser; + +/** + * CSS selector token. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class Token +{ + const TYPE_FILE_END = 'eof'; + const TYPE_DELIMITER = 'delimiter'; + const TYPE_WHITESPACE = 'whitespace'; + const TYPE_IDENTIFIER = 'identifier'; + const TYPE_HASH = 'hash'; + const TYPE_NUMBER = 'number'; + const TYPE_STRING = 'string'; + + /** + * @var int + */ + private $type; + + /** + * @var string + */ + private $value; + + /** + * @var int + */ + private $position; + + /** + * @param int $type + * @param string $value + * @param int $position + */ + public function __construct($type, $value, $position) + { + $this->type = $type; + $this->value = $value; + $this->position = $position; + } + + /** + * @return int + */ + public function getType() + { + return $this->type; + } + + /** + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * @return bool + */ + public function isFileEnd() + { + return self::TYPE_FILE_END === $this->type; + } + + /** + * @param array $values + * + * @return bool + */ + public function isDelimiter(array $values = array()) + { + if (self::TYPE_DELIMITER !== $this->type) { + return false; + } + + if (empty($values)) { + return true; + } + + return in_array($this->value, $values); + } + + /** + * @return bool + */ + public function isWhitespace() + { + return self::TYPE_WHITESPACE === $this->type; + } + + /** + * @return bool + */ + public function isIdentifier() + { + return self::TYPE_IDENTIFIER === $this->type; + } + + /** + * @return bool + */ + public function isHash() + { + return self::TYPE_HASH === $this->type; + } + + /** + * @return bool + */ + public function isNumber() + { + return self::TYPE_NUMBER === $this->type; + } + + /** + * @return bool + */ + public function isString() + { + return self::TYPE_STRING === $this->type; + } + + /** + * @return string + */ + public function __toString() + { + if ($this->value) { + return sprintf('<%s "%s" at %s>', $this->type, $this->value, $this->position); + } + + return sprintf('<%s at %s>', $this->type, $this->position); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/TokenStream.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/TokenStream.php new file mode 100644 index 0000000000000000000000000000000000000000..05d715b961a8391c555fa8b64a859f06d01d8276 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/TokenStream.php @@ -0,0 +1,182 @@ +<?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\CssSelector\Parser; + +use Symfony\Component\CssSelector\Exception\InternalErrorException; +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; + +/** + * CSS selector token stream. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class TokenStream +{ + /** + * @var Token[] + */ + private $tokens = array(); + + /** + * @var bool + */ + private $frozen = false; + + /** + * @var Token[] + */ + private $used = array(); + + /** + * @var int + */ + private $cursor = 0; + + /** + * @var Token|null + */ + private $peeked = null; + + /** + * @var bool + */ + private $peeking = false; + + /** + * Pushes a token. + * + * @param Token $token + * + * @return TokenStream + */ + public function push(Token $token) + { + $this->tokens[] = $token; + + return $this; + } + + /** + * Freezes stream. + * + * @return TokenStream + */ + public function freeze() + { + $this->frozen = true; + + return $this; + } + + /** + * Returns next token. + * + * @throws InternalErrorException If there is no more token + * + * @return Token + */ + public function getNext() + { + if ($this->peeking) { + $this->peeking = false; + $this->used[] = $this->peeked; + + return $this->peeked; + } + + if (!isset($this->tokens[$this->cursor])) { + throw new InternalErrorException('Unexpected token stream end.'); + } + + return $this->tokens[$this->cursor ++]; + } + + /** + * Returns peeked token. + * + * @return Token + */ + public function getPeek() + { + if (!$this->peeking) { + $this->peeked = $this->getNext(); + $this->peeking = true; + } + + return $this->peeked; + } + + /** + * Returns used tokens. + * + * @return Token[] + */ + public function getUsed() + { + return $this->used; + } + + /** + * Returns nex identifier token. + * + * @throws SyntaxErrorException If next token is not an identifier + * + * @return string The identifier token value + */ + public function getNextIdentifier() + { + $next = $this->getNext(); + + if (!$next->isIdentifier()) { + throw SyntaxErrorException::unexpectedToken('identifier', $next); + } + + return $next->getValue(); + } + + /** + * Returns nex identifier or star delimiter token. + * + * @throws SyntaxErrorException If next token is not an identifier or a star delimiter + * + * @return null|string The identifier token value or null if star found + */ + public function getNextIdentifierOrStar() + { + $next = $this->getNext(); + + if ($next->isIdentifier()) { + return $next->getValue(); + } + + if ($next->isDelimiter(array('*'))) { + return; + } + + throw SyntaxErrorException::unexpectedToken('identifier or "*"', $next); + } + + /** + * Skips next whitespace if any. + */ + public function skipWhitespace() + { + $peek = $this->getPeek(); + + if ($peek->isWhitespace()) { + $this->getNext(); + } + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/Tokenizer.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/Tokenizer.php new file mode 100644 index 0000000000000000000000000000000000000000..c850276728c749b57787e6960ff363d38ffe4ab4 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/Tokenizer.php @@ -0,0 +1,78 @@ +<?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\CssSelector\Parser\Tokenizer; + +use Symfony\Component\CssSelector\Parser\Handler; +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector tokenizer. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class Tokenizer +{ + /** + * @var Handler\HandlerInterface[] + */ + private $handlers; + + /** + * Constructor. + */ + public function __construct() + { + $patterns = new TokenizerPatterns(); + $escaping = new TokenizerEscaping($patterns); + + $this->handlers = array( + new Handler\WhitespaceHandler(), + new Handler\IdentifierHandler($patterns, $escaping), + new Handler\HashHandler($patterns, $escaping), + new Handler\StringHandler($patterns, $escaping), + new Handler\NumberHandler($patterns), + new Handler\CommentHandler(), + ); + } + + /** + * Tokenize selector source code. + * + * @param Reader $reader + * + * @return TokenStream + */ + public function tokenize(Reader $reader) + { + $stream = new TokenStream(); + + while (!$reader->isEOF()) { + foreach ($this->handlers as $handler) { + if ($handler->handle($reader, $stream)) { + continue 2; + } + } + + $stream->push(new Token(Token::TYPE_DELIMITER, $reader->getSubstring(1), $reader->getPosition())); + $reader->moveForward(1); + } + + return $stream + ->push(new Token(Token::TYPE_FILE_END, null, $reader->getPosition())) + ->freeze(); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerEscaping.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerEscaping.php new file mode 100644 index 0000000000000000000000000000000000000000..c90fc1044d3a9c5d43e29f190ddd6b21b27fad16 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerEscaping.php @@ -0,0 +1,78 @@ +<?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\CssSelector\Parser\Tokenizer; + +/** + * CSS selector tokenizer escaping applier. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class TokenizerEscaping +{ + /** + * @var TokenizerPatterns + */ + private $patterns; + + /** + * @param TokenizerPatterns $patterns + */ + public function __construct(TokenizerPatterns $patterns) + { + $this->patterns = $patterns; + } + + /** + * @param string $value + * + * @return string + */ + public function escapeUnicode($value) + { + $value = $this->replaceUnicodeSequences($value); + + return preg_replace($this->patterns->getSimpleEscapePattern(), '$1', $value); + } + + /** + * @param string $value + * + * @return string + */ + public function escapeUnicodeAndNewLine($value) + { + $value = preg_replace($this->patterns->getNewLineEscapePattern(), '', $value); + + return $this->escapeUnicode($value); + } + + /** + * @param string $value + * + * @return string + */ + private function replaceUnicodeSequences($value) + { + return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), function (array $match) { + $code = $match[1]; + + if (bin2hex($code) > 0xFFFD) { + $code = '\\FFFD'; + } + + return mb_convert_encoding(pack('H*', $code), 'UTF-8', 'UCS-2BE'); + }, $value); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerPatterns.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerPatterns.php new file mode 100644 index 0000000000000000000000000000000000000000..6fc98b71e12658f037b07adefde47b391e690d68 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerPatterns.php @@ -0,0 +1,160 @@ +<?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\CssSelector\Parser\Tokenizer; + +/** + * CSS selector tokenizer patterns builder. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class TokenizerPatterns +{ + /** + * @var string + */ + private $unicodeEscapePattern; + + /** + * @var string + */ + private $simpleEscapePattern; + + /** + * @var string + */ + private $newLineEscapePattern; + + /** + * @var string + */ + private $escapePattern; + + /** + * @var string + */ + private $stringEscapePattern; + + /** + * @var string + */ + private $nonAsciiPattern; + + /** + * @var string + */ + private $nmCharPattern; + + /** + * @var string + */ + private $nmStartPattern; + + /** + * @var string + */ + private $identifierPattern; + + /** + * @var string + */ + private $hashPattern; + + /** + * @var string + */ + private $numberPattern; + + /** + * @var string + */ + private $quotedStringPattern; + + /** + * Constructor. + */ + public function __construct() + { + $this->unicodeEscapePattern = '\\\\([0-9a-f]{1,6})(?:\r\n|[ \n\r\t\f])?'; + $this->simpleEscapePattern = '\\\\(.)'; + $this->newLineEscapePattern = '\\\\(?:\n|\r\n|\r|\f)'; + $this->escapePattern = $this->unicodeEscapePattern.'|\\\\[^\n\r\f0-9a-f]'; + $this->stringEscapePattern = $this->newLineEscapePattern.'|'.$this->escapePattern; + $this->nonAsciiPattern = '[^\x00-\x7F]'; + $this->nmCharPattern = '[_a-z0-9-]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; + $this->nmStartPattern = '[_a-z]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; + $this->identifierPattern = '(?:'.$this->nmStartPattern.')(?:'.$this->nmCharPattern.')*'; + $this->hashPattern = '#((?:'.$this->nmCharPattern.')+)'; + $this->numberPattern = '[+-]?(?:[0-9]*\.[0-9]+|[0-9]+)'; + $this->quotedStringPattern = '([^\n\r\f%s]|'.$this->stringEscapePattern.')*'; + } + + /** + * @return string + */ + public function getNewLineEscapePattern() + { + return '~^'.$this->newLineEscapePattern.'~'; + } + + /** + * @return string + */ + public function getSimpleEscapePattern() + { + return '~^'.$this->simpleEscapePattern.'~'; + } + + /** + * @return string + */ + public function getUnicodeEscapePattern() + { + return '~^'.$this->unicodeEscapePattern.'~i'; + } + + /** + * @return string + */ + public function getIdentifierPattern() + { + return '~^'.$this->identifierPattern.'~i'; + } + + /** + * @return string + */ + public function getHashPattern() + { + return '~^'.$this->hashPattern.'~i'; + } + + /** + * @return string + */ + public function getNumberPattern() + { + return '~^'.$this->numberPattern.'~'; + } + + /** + * @param string $quote + * + * @return string + */ + public function getQuotedStringPattern($quote) + { + return '~^'.sprintf($this->quotedStringPattern, $quote).'~i'; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/README.md b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/README.md new file mode 100644 index 0000000000000000000000000000000000000000..623649bedbb23678cbac55434b43d1468e5366a3 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/README.md @@ -0,0 +1,44 @@ +CssSelector Component +===================== + +CssSelector converts CSS selectors to XPath expressions. + +The component only goal is to convert CSS selectors to their XPath +equivalents: + + use Symfony\Component\CssSelector\CssSelector; + + print CssSelector::toXPath('div.item > h4 > a'); + +HTML and XML are different +-------------------------- + +The `CssSelector` component comes with an `HTML` extension which is enabled by +default. If you need to use this component with `XML` documents, you have to +disable this `HTML` extension. That's because, `HTML` tag & attribute names +are always lower-cased, but case-sensitive in `XML`: + + // disable `HTML` extension: + CssSelector::disableHtmlExtension(); + + // re-enable `HTML` extension: + CssSelector::enableHtmlExtension(); + +When the `HTML` extension is enabled, tag names are lower-cased, attribute +names are lower-cased, the following extra pseudo-classes are supported: +`checked`, `link`, `disabled`, `enabled`, `selected`, `invalid`, `hover`, +`visited`, and the `lang()` function is also added. + +Resources +--------- + +This component is a port of the Python lxml library, which is copyright Infrae +and distributed under the BSD license. + +Current code is a port of https://github.com/SimonSapin/cssselect@v0.7.1 + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/CssSelector/ + $ composer.phar install + $ phpunit diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/CssSelectorTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/CssSelectorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..50daeccd1d27819f4edff05235c489cd5f344926 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/CssSelectorTest.php @@ -0,0 +1,64 @@ +<?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\CssSelector\Tests; + +use Symfony\Component\CssSelector\CssSelector; + +class CssSelectorTest extends \PHPUnit_Framework_TestCase +{ + public function testCssToXPath() + { + $this->assertEquals('descendant-or-self::*', CssSelector::toXPath('')); + $this->assertEquals('descendant-or-self::h1', CssSelector::toXPath('h1')); + $this->assertEquals("descendant-or-self::h1[@id = 'foo']", CssSelector::toXPath('h1#foo')); + $this->assertEquals("descendant-or-self::h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]", CssSelector::toXPath('h1.foo')); + $this->assertEquals('descendant-or-self::foo:h1', CssSelector::toXPath('foo|h1')); + } + + /** @dataProvider getCssToXPathWithoutPrefixTestData */ + public function testCssToXPathWithoutPrefix($css, $xpath) + { + $this->assertEquals($xpath, CssSelector::toXPath($css, ''), '->parse() parses an input string and returns a node'); + } + + public function testParseExceptions() + { + try { + CssSelector::toXPath('h1:'); + $this->fail('->parse() throws an Exception if the css selector is not valid'); + } catch (\Exception $e) { + $this->assertInstanceOf('\Symfony\Component\CssSelector\Exception\ParseException', $e, '->parse() throws an Exception if the css selector is not valid'); + $this->assertEquals("Expected identifier, but <eof at 3> found.", $e->getMessage(), '->parse() throws an Exception if the css selector is not valid'); + } + } + + public function getCssToXPathWithoutPrefixTestData() + { + return array( + array('h1', "h1"), + array('foo|h1', "foo:h1"), + array('h1, h2, h3', "h1 | h2 | h3"), + array('h1:nth-child(3n+1)', "*/*[name() = 'h1' and (position() - 1 >= 0 and (position() - 1) mod 3 = 0)]"), + array('h1 > p', "h1/p"), + array('h1#foo', "h1[@id = 'foo']"), + array('h1.foo', "h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + array('h1[class*="foo bar"]', "h1[@class and contains(@class, 'foo bar')]"), + array('h1[foo|class*="foo bar"]', "h1[@foo:class and contains(@foo:class, 'foo bar')]"), + array('h1[class]', "h1[@class]"), + array('h1 .foo', "h1/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + array('h1 #foo', "h1/descendant-or-self::*/*[@id = 'foo']"), + array('h1 [class*=foo]', "h1/descendant-or-self::*/*[@class and contains(@class, 'foo')]"), + array('div>.foo', "div/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + array('div > .foo', "div/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/AbstractNodeTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/AbstractNodeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..16a3a34d6497d2ecb92c1c880d23fdfa72828312 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/AbstractNodeTest.php @@ -0,0 +1,32 @@ +<?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\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\NodeInterface; + +abstract class AbstractNodeTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getToStringConversionTestData */ + public function testToStringConversion(NodeInterface $node, $representation) + { + $this->assertEquals($representation, (string) $node); + } + + /** @dataProvider getSpecificityValueTestData */ + public function testSpecificityValue(NodeInterface $node, $value) + { + $this->assertEquals($value, $node->getSpecificity()->getValue()); + } + + abstract public function getToStringConversionTestData(); + abstract public function getSpecificityValueTestData(); +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/AttributeNodeTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/AttributeNodeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1fd090f5a6e8c1f1109282f65d85f24f28f29289 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/AttributeNodeTest.php @@ -0,0 +1,37 @@ +<?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\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\AttributeNode; +use Symfony\Component\CssSelector\Node\ElementNode; + +class AttributeNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new AttributeNode(new ElementNode(), null, 'attribute', 'exists', null), 'Attribute[Element[*][attribute]]'), + array(new AttributeNode(new ElementNode(), null, 'attribute', '$=', 'value'), "Attribute[Element[*][attribute $= 'value']]"), + array(new AttributeNode(new ElementNode(), 'namespace', 'attribute', '$=', 'value'), "Attribute[Element[*][namespace|attribute $= 'value']]"), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new AttributeNode(new ElementNode(), null, 'attribute', 'exists', null), 10), + array(new AttributeNode(new ElementNode(null, 'element'), null, 'attribute', 'exists', null), 11), + array(new AttributeNode(new ElementNode(), null, 'attribute', '$=', 'value'), 10), + array(new AttributeNode(new ElementNode(), 'namespace', 'attribute', '$=', 'value'), 10), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/ClassNodeTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/ClassNodeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e0ab45accc31de8f51013ab80e37f239d16657fd --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/ClassNodeTest.php @@ -0,0 +1,33 @@ +<?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\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ClassNode; +use Symfony\Component\CssSelector\Node\ElementNode; + +class ClassNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new ClassNode(new ElementNode(), 'class'), 'Class[Element[*].class]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new ClassNode(new ElementNode(), 'class'), 10), + array(new ClassNode(new ElementNode(null, 'element'), 'class'), 11), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/CombinedSelectorNodeTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/CombinedSelectorNodeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9547298a6fdf3faee1450dee4539d80f049b5cd5 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/CombinedSelectorNodeTest.php @@ -0,0 +1,35 @@ +<?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\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\CombinedSelectorNode; +use Symfony\Component\CssSelector\Node\ElementNode; + +class CombinedSelectorNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new CombinedSelectorNode(new ElementNode(), '>', new ElementNode()), 'CombinedSelector[Element[*] > Element[*]]'), + array(new CombinedSelectorNode(new ElementNode(), ' ', new ElementNode()), 'CombinedSelector[Element[*] <followed> Element[*]]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new CombinedSelectorNode(new ElementNode(), '>', new ElementNode()), 0), + array(new CombinedSelectorNode(new ElementNode(null, 'element'), '>', new ElementNode()), 1), + array(new CombinedSelectorNode(new ElementNode(null, 'element'), '>', new ElementNode(null, 'element')), 2), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/ElementNodeTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/ElementNodeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1db6a591a2f5c5047b818f10e8eb723620530539 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/ElementNodeTest.php @@ -0,0 +1,35 @@ +<?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\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ElementNode; + +class ElementNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new ElementNode(), 'Element[*]'), + array(new ElementNode(null, 'element'), 'Element[element]'), + array(new ElementNode('namespace', 'element'), 'Element[namespace|element]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new ElementNode(), 0), + array(new ElementNode(null, 'element'), 1), + array(new ElementNode('namespace', 'element'),1), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/FunctionNodeTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/FunctionNodeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ee3ce51ba549954ecf473d313fa96638e8ab98fd --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/FunctionNodeTest.php @@ -0,0 +1,47 @@ +<?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\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\Parser\Token; + +class FunctionNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new FunctionNode(new ElementNode(), 'function'), 'Function[Element[*]:function()]'), + array(new FunctionNode(new ElementNode(), 'function', array( + new Token(Token::TYPE_IDENTIFIER, 'value', 0), + )), "Function[Element[*]:function(['value'])]"), + array(new FunctionNode(new ElementNode(), 'function', array( + new Token(Token::TYPE_STRING, 'value1', 0), + new Token(Token::TYPE_NUMBER, 'value2', 0), + )), "Function[Element[*]:function(['value1', 'value2'])]"), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new FunctionNode(new ElementNode(), 'function'), 10), + array(new FunctionNode(new ElementNode(), 'function', array( + new Token(Token::TYPE_IDENTIFIER, 'value', 0), + )), 10), + array(new FunctionNode(new ElementNode(), 'function', array( + new Token(Token::TYPE_STRING, 'value1', 0), + new Token(Token::TYPE_NUMBER, 'value2', 0), + )), 10), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/HashNodeTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/HashNodeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8554b226d6c7fe3fb00ad21784826f3595c5bbeb --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/HashNodeTest.php @@ -0,0 +1,33 @@ +<?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\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\HashNode; +use Symfony\Component\CssSelector\Node\ElementNode; + +class HashNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new HashNode(new ElementNode(), 'id'), 'Hash[Element[*]#id]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new HashNode(new ElementNode(), 'id'), 100), + array(new HashNode(new ElementNode(null, 'id'), 'class'), 101), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/NegationNodeTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/NegationNodeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..edf4552bac8b8dadae6cb6c7e5c76688903770d3 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/NegationNodeTest.php @@ -0,0 +1,33 @@ +<?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\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ClassNode; +use Symfony\Component\CssSelector\Node\NegationNode; +use Symfony\Component\CssSelector\Node\ElementNode; + +class NegationNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new NegationNode(new ElementNode(), new ClassNode(new ElementNode(), 'class')), 'Negation[Element[*]:not(Class[Element[*].class])]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new NegationNode(new ElementNode(), new ClassNode(new ElementNode(), 'class')), 10), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/PseudoNodeTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/PseudoNodeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bc57813cc8fe16300834a2ab1e5c100d6e2870ae --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/PseudoNodeTest.php @@ -0,0 +1,32 @@ +<?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\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\PseudoNode; + +class PseudoNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new PseudoNode(new ElementNode(), 'pseudo'), 'Pseudo[Element[*]:pseudo]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new PseudoNode(new ElementNode(), 'pseudo'), 10), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/SelectorNodeTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/SelectorNodeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5badf71d16d1bb9f92714e24414f91ca541607e3 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/SelectorNodeTest.php @@ -0,0 +1,34 @@ +<?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\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\SelectorNode; + +class SelectorNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new SelectorNode(new ElementNode()), 'Selector[Element[*]]'), + array(new SelectorNode(new ElementNode(), 'pseudo'), 'Selector[Element[*]::pseudo]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new SelectorNode(new ElementNode()), 0), + array(new SelectorNode(new ElementNode(), 'pseudo'), 1), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/SpecificityTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/SpecificityTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1f200cffe4f0447e13b5db8bdaee1458f7b8be72 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/SpecificityTest.php @@ -0,0 +1,40 @@ +<?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\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\Specificity; + +class SpecificityTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getValueTestData */ + public function testValue(Specificity $specificity, $value) + { + $this->assertEquals($value, $specificity->getValue()); + } + + /** @dataProvider getValueTestData */ + public function testPlusValue(Specificity $specificity, $value) + { + $this->assertEquals($value + 123, $specificity->plus(new Specificity(1, 2, 3))->getValue()); + } + + public function getValueTestData() + { + return array( + array(new Specificity(0, 0, 0), 0), + array(new Specificity(0, 0, 2), 2), + array(new Specificity(0, 3, 0), 30), + array(new Specificity(4, 0, 0), 400), + array(new Specificity(4, 3, 2), 432), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/AbstractHandlerTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/AbstractHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a06dca013bae787e5ec14790dfcf87b07b1fc49f --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/AbstractHandlerTest.php @@ -0,0 +1,67 @@ +<?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\CssSelector\Tests\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * @author Jean-François Simon <contact@jfsimon.fr> + */ +abstract class AbstractHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getHandleValueTestData */ + public function testHandleValue($value, Token $expectedToken, $remainingContent) + { + $reader = new Reader($value); + $stream = new TokenStream(); + + $this->assertTrue($this->generateHandler()->handle($reader, $stream)); + $this->assertEquals($expectedToken, $stream->getNext()); + $this->assertRemainingContent($reader, $remainingContent); + } + + /** @dataProvider getDontHandleValueTestData */ + public function testDontHandleValue($value) + { + $reader = new Reader($value); + $stream = new TokenStream(); + + $this->assertFalse($this->generateHandler()->handle($reader, $stream)); + $this->assertStreamEmpty($stream); + $this->assertRemainingContent($reader, $value); + } + + abstract public function getHandleValueTestData(); + abstract public function getDontHandleValueTestData(); + abstract protected function generateHandler(); + + protected function assertStreamEmpty(TokenStream $stream) + { + $property = new \ReflectionProperty($stream, 'tokens'); + $property->setAccessible(true); + + $this->assertEquals(array(), $property->getValue($stream)); + } + + protected function assertRemainingContent(Reader $reader, $remainingContent) + { + if ('' === $remainingContent) { + $this->assertEquals(0, $reader->getRemainingLength()); + $this->assertTrue($reader->isEOF()); + } else { + $this->assertEquals(strlen($remainingContent), $reader->getRemainingLength()); + $this->assertEquals(0, $reader->getOffset($remainingContent)); + } + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/CommentHandlerTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/CommentHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3961bf7d55ad9ee48d6356cd3ea02a34ff9c6eca --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/CommentHandlerTest.php @@ -0,0 +1,55 @@ +<?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\CssSelector\Tests\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\CommentHandler; +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +class CommentHandlerTest extends AbstractHandlerTest +{ + /** @dataProvider getHandleValueTestData */ + public function testHandleValue($value, Token $unusedArgument, $remainingContent) + { + $reader = new Reader($value); + $stream = new TokenStream(); + + $this->assertTrue($this->generateHandler()->handle($reader, $stream)); + // comments are ignored (not pushed as token in stream) + $this->assertStreamEmpty($stream); + $this->assertRemainingContent($reader, $remainingContent); + } + + public function getHandleValueTestData() + { + return array( + // 2nd argument only exists for inherited method compatibility + array('/* comment */', new Token(null, null, null), ''), + array('/* comment */foo', new Token(null, null, null), 'foo'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('>'), + array('+'), + array(' '), + ); + } + + protected function generateHandler() + { + return new CommentHandler(); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/HashHandlerTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/HashHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b7fa00a2255eed45b8420cd3bb22d955f50dae98 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/HashHandlerTest.php @@ -0,0 +1,49 @@ +<?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\CssSelector\Tests\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\HashHandler; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; + +class HashHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array('#id', new Token(Token::TYPE_HASH, 'id', 0), ''), + array('#123', new Token(Token::TYPE_HASH, '123', 0), ''), + + array('#id.class', new Token(Token::TYPE_HASH, 'id', 0), '.class'), + array('#id element', new Token(Token::TYPE_HASH, 'id', 0), ' element'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('id'), + array('123'), + array('<'), + array('<'), + array('#'), + ); + } + + protected function generateHandler() + { + $patterns = new TokenizerPatterns(); + + return new HashHandler($patterns, new TokenizerEscaping($patterns)); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/IdentifierHandlerTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/IdentifierHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..44d35749845ab724364517fd32861f34ac155dbd --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/IdentifierHandlerTest.php @@ -0,0 +1,49 @@ +<?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\CssSelector\Tests\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\IdentifierHandler; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; + +class IdentifierHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array('foo', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), ''), + array('foo|bar', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '|bar'), + array('foo.class', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '.class'), + array('foo[attr]', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '[attr]'), + array('foo bar', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), ' bar'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('>'), + array('+'), + array(' '), + array('*|foo'), + array('/* comment */'), + ); + } + + protected function generateHandler() + { + $patterns = new TokenizerPatterns(); + + return new IdentifierHandler($patterns, new TokenizerEscaping($patterns)); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/NumberHandlerTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/NumberHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..675fd05b43ef895ab439939ef0c04deb2bb46349 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/NumberHandlerTest.php @@ -0,0 +1,50 @@ +<?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\CssSelector\Tests\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\NumberHandler; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; + +class NumberHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array('12', new Token(Token::TYPE_NUMBER, '12', 0), ''), + array('12.34', new Token(Token::TYPE_NUMBER, '12.34', 0), ''), + array('+12.34', new Token(Token::TYPE_NUMBER, '+12.34', 0), ''), + array('-12.34', new Token(Token::TYPE_NUMBER, '-12.34', 0), ''), + + array('12 arg', new Token(Token::TYPE_NUMBER, '12', 0), ' arg'), + array('12]', new Token(Token::TYPE_NUMBER, '12', 0), ']'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('hello'), + array('>'), + array('+'), + array(' '), + array('/* comment */'), + ); + } + + protected function generateHandler() + { + $patterns = new TokenizerPatterns(); + + return new NumberHandler($patterns); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/StringHandlerTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/StringHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..89eff8bd2825bb02f8af446d605cb812deba6bed --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/StringHandlerTest.php @@ -0,0 +1,50 @@ +<?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\CssSelector\Tests\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\StringHandler; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; + +class StringHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array('"hello"', new Token(Token::TYPE_STRING, 'hello', 1), ''), + array('"1"', new Token(Token::TYPE_STRING, '1', 1), ''), + array('" "', new Token(Token::TYPE_STRING, ' ', 1), ''), + array('""', new Token(Token::TYPE_STRING, '', 1), ''), + array("'hello'", new Token(Token::TYPE_STRING, 'hello', 1), ''), + + array("'foo'bar", new Token(Token::TYPE_STRING, 'foo', 1), 'bar'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('hello'), + array('>'), + array('1'), + array(' '), + ); + } + + protected function generateHandler() + { + $patterns = new TokenizerPatterns(); + + return new StringHandler($patterns, new TokenizerEscaping($patterns)); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/WhitespaceHandlerTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/WhitespaceHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f5f9e71dc228782051938ca231c5d61589419ef6 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/WhitespaceHandlerTest.php @@ -0,0 +1,44 @@ +<?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\CssSelector\Tests\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\WhitespaceHandler; +use Symfony\Component\CssSelector\Parser\Token; + +class WhitespaceHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array(' ', new Token(Token::TYPE_WHITESPACE, ' ', 0), ''), + array("\n", new Token(Token::TYPE_WHITESPACE, "\n", 0), ''), + array("\t", new Token(Token::TYPE_WHITESPACE, "\t", 0), ''), + + array(' foo', new Token(Token::TYPE_WHITESPACE, ' ', 0), 'foo'), + array(' .foo', new Token(Token::TYPE_WHITESPACE, ' ', 0), '.foo'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('>'), + array('1'), + array('a'), + ); + } + + protected function generateHandler() + { + return new WhitespaceHandler(); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0454d9faa6f75d28aba8660bb2517885c817bce4 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php @@ -0,0 +1,248 @@ +<?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\CssSelector\Tests\Parser; + +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Parser; +use Symfony\Component\CssSelector\Parser\Token; + +class ParserTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getParserTestData */ + public function testParser($source, $representation) + { + $parser = new Parser(); + + $this->assertEquals($representation, array_map(function (SelectorNode $node) { + return (string) $node->getTree(); + }, $parser->parse($source))); + } + + /** @dataProvider getParserExceptionTestData */ + public function testParserException($source, $message) + { + $parser = new Parser(); + + try { + $parser->parse($source); + $this->fail('Parser should throw a SyntaxErrorException.'); + } catch (SyntaxErrorException $e) { + $this->assertEquals($message, $e->getMessage()); + } + } + + /** @dataProvider getPseudoElementsTestData */ + public function testPseudoElements($source, $element, $pseudo) + { + $parser = new Parser(); + $selectors = $parser->parse($source); + $this->assertCount(1, $selectors); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($element, (string) $selector->getTree()); + $this->assertEquals($pseudo, (string) $selector->getPseudoElement()); + } + + /** @dataProvider getSpecificityTestData */ + public function testSpecificity($source, $value) + { + $parser = new Parser(); + $selectors = $parser->parse($source); + $this->assertCount(1, $selectors); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($value, $selector->getSpecificity()->getValue()); + } + + /** @dataProvider getParseSeriesTestData */ + public function testParseSeries($series, $a, $b) + { + $parser = new Parser(); + $selectors = $parser->parse(sprintf(':nth-child(%s)', $series)); + $this->assertCount(1, $selectors); + + /** @var FunctionNode $function */ + $function = $selectors[0]->getTree(); + $this->assertEquals(array($a, $b), Parser::parseSeries($function->getArguments())); + } + + /** @dataProvider getParseSeriesExceptionTestData */ + public function testParseSeriesException($series) + { + $parser = new Parser(); + $selectors = $parser->parse(sprintf(':nth-child(%s)', $series)); + $this->assertCount(1, $selectors); + + /** @var FunctionNode $function */ + $function = $selectors[0]->getTree(); + $this->setExpectedException('Symfony\Component\CssSelector\Exception\SyntaxErrorException'); + Parser::parseSeries($function->getArguments()); + } + + public function getParserTestData() + { + return array( + array('*', array('Element[*]')), + array('*|*', array('Element[*]')), + array('*|foo', array('Element[foo]')), + array('foo|*', array('Element[foo|*]')), + array('foo|bar', array('Element[foo|bar]')), + array('#foo#bar', array('Hash[Hash[Element[*]#foo]#bar]')), + array('div>.foo', array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array('div> .foo', array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array('div >.foo', array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array('div > .foo', array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array("div \n> \t \t .foo", array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array('td.foo,.bar', array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array('td.foo, .bar', array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array("td.foo\t\r\n\f ,\t\r\n\f .bar", array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array('td.foo,.bar', array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array('td.foo, .bar', array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array("td.foo\t\r\n\f ,\t\r\n\f .bar", array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array('div, td.foo, div.bar span', array('Element[div]', 'Class[Element[td].foo]', 'CombinedSelector[Class[Element[div].bar] <followed> Element[span]]')), + array('div > p', array('CombinedSelector[Element[div] > Element[p]]')), + array('td:first', array('Pseudo[Element[td]:first]')), + array('td :first', array('CombinedSelector[Element[td] <followed> Pseudo[Element[*]:first]]')), + array('a[name]', array('Attribute[Element[a][name]]')), + array("a[ name\t]", array('Attribute[Element[a][name]]')), + array('a [name]', array('CombinedSelector[Element[a] <followed> Attribute[Element[*][name]]]')), + array('a[rel="include"]', array("Attribute[Element[a][rel = 'include']]")), + array('a[rel = include]', array("Attribute[Element[a][rel = 'include']]")), + array("a[hreflang |= 'en']", array("Attribute[Element[a][hreflang |= 'en']]")), + array('a[hreflang|=en]', array("Attribute[Element[a][hreflang |= 'en']]")), + array('div:nth-child(10)', array("Function[Element[div]:nth-child(['10'])]")), + array(':nth-child(2n+2)', array("Function[Element[*]:nth-child(['2', 'n', '+2'])]")), + array('div:nth-of-type(10)', array("Function[Element[div]:nth-of-type(['10'])]")), + array('div div:nth-of-type(10) .aclass', array("CombinedSelector[CombinedSelector[Element[div] <followed> Function[Element[div]:nth-of-type(['10'])]] <followed> Class[Element[*].aclass]]")), + array('label:only', array('Pseudo[Element[label]:only]')), + array('a:lang(fr)', array("Function[Element[a]:lang(['fr'])]")), + array('div:contains("foo")', array("Function[Element[div]:contains(['foo'])]")), + array('div#foobar', array('Hash[Element[div]#foobar]')), + array('div:not(div.foo)', array('Negation[Element[div]:not(Class[Element[div].foo])]')), + array('td ~ th', array('CombinedSelector[Element[td] ~ Element[th]]')), + array('.foo[data-bar][data-baz=0]', array("Attribute[Attribute[Class[Element[*].foo][data-bar]][data-baz = '0']]")), + ); + } + + public function getParserExceptionTestData() + { + return array( + array('attributes(href)/html/body/a', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '(', 10))->getMessage()), + array('attributes(href)', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '(', 10))->getMessage()), + array('html/body/a', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '/', 4))->getMessage()), + array(' ', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_FILE_END, '', 1))->getMessage()), + array('div, ', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_FILE_END, '', 5))->getMessage()), + array(' , div', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, ',', 1))->getMessage()), + array('p, , div', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, ',', 3))->getMessage()), + array('div > ', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_FILE_END, '', 6))->getMessage()), + array(' > div', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '>', 2))->getMessage()), + array('foo|#bar', SyntaxErrorException::unexpectedToken('identifier or "*"', new Token(Token::TYPE_HASH, 'bar', 4))->getMessage()), + array('#.foo', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '#', 0))->getMessage()), + array('.#foo', SyntaxErrorException::unexpectedToken('identifier', new Token(Token::TYPE_HASH, 'foo', 1))->getMessage()), + array(':#foo', SyntaxErrorException::unexpectedToken('identifier', new Token(Token::TYPE_HASH, 'foo', 1))->getMessage()), + array('[*]', SyntaxErrorException::unexpectedToken('"|"', new Token(Token::TYPE_DELIMITER, ']', 2))->getMessage()), + array('[foo|]', SyntaxErrorException::unexpectedToken('identifier', new Token(Token::TYPE_DELIMITER, ']', 5))->getMessage()), + array('[#]', SyntaxErrorException::unexpectedToken('identifier or "*"', new Token(Token::TYPE_DELIMITER, '#', 1))->getMessage()), + array('[foo=#]', SyntaxErrorException::unexpectedToken('string or identifier', new Token(Token::TYPE_DELIMITER, '#', 5))->getMessage()), + array(':nth-child()', SyntaxErrorException::unexpectedToken('at least one argument', new Token(Token::TYPE_DELIMITER, ')', 11))->getMessage()), + array('[href]a', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_IDENTIFIER, 'a', 6))->getMessage()), + array('[rel:stylesheet]', SyntaxErrorException::unexpectedToken('operator', new Token(Token::TYPE_DELIMITER, ':', 4))->getMessage()), + array('[rel=stylesheet', SyntaxErrorException::unexpectedToken('"]"', new Token(Token::TYPE_FILE_END, '', 15))->getMessage()), + array(':lang(fr', SyntaxErrorException::unexpectedToken('an argument', new Token(Token::TYPE_FILE_END, '', 8))->getMessage()), + array(':contains("foo', SyntaxErrorException::unclosedString(10)->getMessage()), + array('foo!', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '!', 3))->getMessage()), + ); + } + + public function getPseudoElementsTestData() + { + return array( + array('foo', 'Element[foo]', ''), + array('*', 'Element[*]', ''), + array(':empty', 'Pseudo[Element[*]:empty]', ''), + array(':BEfore', 'Element[*]', 'before'), + array(':aftER', 'Element[*]', 'after'), + array(':First-Line', 'Element[*]', 'first-line'), + array(':First-Letter', 'Element[*]', 'first-letter'), + array('::befoRE', 'Element[*]', 'before'), + array('::AFter', 'Element[*]', 'after'), + array('::firsT-linE', 'Element[*]', 'first-line'), + array('::firsT-letteR', 'Element[*]', 'first-letter'), + array('::Selection', 'Element[*]', 'selection'), + array('foo:after', 'Element[foo]', 'after'), + array('foo::selection', 'Element[foo]', 'selection'), + array('lorem#ipsum ~ a#b.c[href]:empty::selection', 'CombinedSelector[Hash[Element[lorem]#ipsum] ~ Pseudo[Attribute[Class[Hash[Element[a]#b].c][href]]:empty]]', 'selection'), + ); + } + + public function getSpecificityTestData() + { + return array( + array('*', 0), + array(' foo', 1), + array(':empty ', 10), + array(':before', 1), + array('*:before', 1), + array(':nth-child(2)', 10), + array('.bar', 10), + array('[baz]', 10), + array('[baz="4"]', 10), + array('[baz^="4"]', 10), + array('#lipsum', 100), + array(':not(*)', 0), + array(':not(foo)', 1), + array(':not(.foo)', 10), + array(':not([foo])', 10), + array(':not(:empty)', 10), + array(':not(#foo)', 100), + array('foo:empty', 11), + array('foo:before', 2), + array('foo::before', 2), + array('foo:empty::before', 12), + array('#lorem + foo#ipsum:first-child > bar:first-line', 213), + ); + } + + public function getParseSeriesTestData() + { + return array( + array('1n+3', 1, 3), + array('1n +3', 1, 3), + array('1n + 3', 1, 3), + array('1n+ 3', 1, 3), + array('1n-3', 1, -3), + array('1n -3', 1, -3), + array('1n - 3', 1, -3), + array('1n- 3', 1, -3), + array('n-5', 1, -5), + array('odd', 2, 1), + array('even', 2, 0), + array('3n', 3, 0), + array('n', 1, 0), + array('+n', 1, 0), + array('-n', -1, 0), + array('5', 0, 5), + ); + } + + public function getParseSeriesExceptionTestData() + { + return array( + array('foo'), + array('n+'), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/ReaderTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/ReaderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..03c054eaaeeb797d2d36865c74cf0e6b5d2a9070 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/ReaderTest.php @@ -0,0 +1,101 @@ +<?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\CssSelector\Tests\Parser; + +use Symfony\Component\CssSelector\Parser\Reader; + +class ReaderTest extends \PHPUnit_Framework_TestCase +{ + public function testIsEOF() + { + $reader = new Reader(''); + $this->assertTrue($reader->isEOF()); + + $reader = new Reader('hello'); + $this->assertFalse($reader->isEOF()); + + $this->assignPosition($reader, 2); + $this->assertFalse($reader->isEOF()); + + $this->assignPosition($reader, 5); + $this->assertTrue($reader->isEOF()); + } + + public function testGetRemainingLength() + { + $reader = new Reader('hello'); + $this->assertEquals(5, $reader->getRemainingLength()); + + $this->assignPosition($reader, 2); + $this->assertEquals(3, $reader->getRemainingLength()); + + $this->assignPosition($reader, 5); + $this->assertEquals(0, $reader->getRemainingLength()); + } + + public function testGetSubstring() + { + $reader = new Reader('hello'); + $this->assertEquals('he', $reader->getSubstring(2)); + $this->assertEquals('el', $reader->getSubstring(2, 1)); + + $this->assignPosition($reader, 2); + $this->assertEquals('ll', $reader->getSubstring(2)); + $this->assertEquals('lo', $reader->getSubstring(2, 1)); + } + + public function testGetOffset() + { + $reader = new Reader('hello'); + $this->assertEquals(2, $reader->getOffset('ll')); + $this->assertFalse($reader->getOffset('w')); + + $this->assignPosition($reader, 2); + $this->assertEquals(0, $reader->getOffset('ll')); + $this->assertFalse($reader->getOffset('he')); + } + + public function testFindPattern() + { + $reader = new Reader('hello'); + + $this->assertFalse($reader->findPattern('/world/')); + $this->assertEquals(array('hello', 'h'), $reader->findPattern('/^([a-z]).*/')); + + $this->assignPosition($reader, 2); + $this->assertFalse($reader->findPattern('/^h.*/')); + $this->assertEquals(array('llo'), $reader->findPattern('/^llo$/')); + } + + public function testMoveForward() + { + $reader = new Reader('hello'); + $this->assertEquals(0, $reader->getPosition()); + + $reader->moveForward(2); + $this->assertEquals(2, $reader->getPosition()); + } + + public function testToEnd() + { + $reader = new Reader('hello'); + $reader->moveToEnd(); + $this->assertTrue($reader->isEOF()); + } + + private function assignPosition(Reader $reader, $value) + { + $position = new \ReflectionProperty($reader, 'position'); + $position->setAccessible(true); + $position->setValue($reader, $value); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/ClassParserTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/ClassParserTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6efdd67657630f5aacfbb3895a47d82b7e442d1d --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/ClassParserTest.php @@ -0,0 +1,44 @@ +<?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\CssSelector\Tests\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Shortcut\ClassParser; + +/** + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class ClassParserTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getParseTestData */ + public function testParse($source, $representation) + { + $parser = new ClassParser(); + $selectors = $parser->parse($source); + $this->assertCount(1, $selectors); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($representation, (string) $selector->getTree()); + } + + public function getParseTestData() + { + return array( + array('.testclass', 'Class[Element[*].testclass]'), + array('testel.testclass', 'Class[Element[testel].testclass]'), + array('testns|.testclass', 'Class[Element[testns|*].testclass]'), + array('testns|*.testclass', 'Class[Element[testns|*].testclass]'), + array('testns|testel.testclass', 'Class[Element[testns|testel].testclass]'), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/ElementParserTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/ElementParserTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b30b5ee7acd1fed1ae34bf7724034bd995bdbe69 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/ElementParserTest.php @@ -0,0 +1,43 @@ +<?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\CssSelector\Tests\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Shortcut\ElementParser; + +/** + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class ElementParserTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getParseTestData */ + public function testParse($source, $representation) + { + $parser = new ElementParser(); + $selectors = $parser->parse($source); + $this->assertCount(1, $selectors); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($representation, (string) $selector->getTree()); + } + + public function getParseTestData() + { + return array( + array('*', 'Element[*]'), + array('testel', 'Element[testel]'), + array('testns|*', 'Element[testns|*]'), + array('testns|testel', 'Element[testns|testel]'), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/EmptyStringParserTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/EmptyStringParserTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b7c3539c67a97f1041a06d1dff549a5e169e9bc7 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/EmptyStringParserTest.php @@ -0,0 +1,35 @@ +<?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\CssSelector\Tests\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Shortcut\EmptyStringParser; + +/** + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class EmptyStringParserTest extends \PHPUnit_Framework_TestCase +{ + public function testParse() + { + $parser = new EmptyStringParser(); + $selectors = $parser->parse(''); + $this->assertCount(1, $selectors); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals('Element[*]', (string) $selector->getTree()); + + $selectors = $parser->parse('this will produce an empty array'); + $this->assertCount(0, $selectors); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/HashParserTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/HashParserTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d2ce891ec8206ddc49720caf1fe99951b44214f1 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/HashParserTest.php @@ -0,0 +1,44 @@ +<?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\CssSelector\Tests\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Shortcut\HashParser; + +/** + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class HashParserTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getParseTestData */ + public function testParse($source, $representation) + { + $parser = new HashParser(); + $selectors = $parser->parse($source); + $this->assertCount(1, $selectors); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($representation, (string) $selector->getTree()); + } + + public function getParseTestData() + { + return array( + array('#testid', 'Hash[Element[*]#testid]'), + array('testel#testid', 'Hash[Element[testel]#testid]'), + array('testns|#testid', 'Hash[Element[testns|*]#testid]'), + array('testns|*#testid', 'Hash[Element[testns|*]#testid]'), + array('testns|testel#testid', 'Hash[Element[testns|testel]#testid]'), + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/TokenStreamTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/TokenStreamTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8f3253a7d59f5953dff20ce9dabb383523f12f87 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/TokenStreamTest.php @@ -0,0 +1,95 @@ +<?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\CssSelector\Tests\Parser; + +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +class TokenStreamTest extends \PHPUnit_Framework_TestCase +{ + public function testGetNext() + { + $stream = new TokenStream(); + $stream->push($t1 = new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + $stream->push($t2 = new Token(Token::TYPE_DELIMITER, '.', 2)); + $stream->push($t3 = new Token(Token::TYPE_IDENTIFIER, 'title', 3)); + + $this->assertSame($t1, $stream->getNext()); + $this->assertSame($t2, $stream->getNext()); + $this->assertSame($t3, $stream->getNext()); + } + + public function testGetPeek() + { + $stream = new TokenStream(); + $stream->push($t1 = new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + $stream->push($t2 = new Token(Token::TYPE_DELIMITER, '.', 2)); + $stream->push($t3 = new Token(Token::TYPE_IDENTIFIER, 'title', 3)); + + $this->assertSame($t1, $stream->getPeek()); + $this->assertSame($t1, $stream->getNext()); + $this->assertSame($t2, $stream->getPeek()); + $this->assertSame($t2, $stream->getPeek()); + $this->assertSame($t2, $stream->getNext()); + } + + public function testGetNextIdentifier() + { + $stream = new TokenStream(); + $stream->push(new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + + $this->assertEquals('h1', $stream->getNextIdentifier()); + } + + public function testFailToGetNextIdentifier() + { + $this->setExpectedException('Symfony\Component\CssSelector\Exception\SyntaxErrorException'); + + $stream = new TokenStream(); + $stream->push(new Token(Token::TYPE_DELIMITER, '.', 2)); + $stream->getNextIdentifier(); + } + + public function testGetNextIdentifierOrStar() + { + $stream = new TokenStream(); + + $stream->push(new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + $this->assertEquals('h1', $stream->getNextIdentifierOrStar()); + + $stream->push(new Token(Token::TYPE_DELIMITER, '*', 0)); + $this->assertNull($stream->getNextIdentifierOrStar()); + } + + public function testFailToGetNextIdentifierOrStar() + { + $this->setExpectedException('Symfony\Component\CssSelector\Exception\SyntaxErrorException'); + + $stream = new TokenStream(); + $stream->push(new Token(Token::TYPE_DELIMITER, '.', 2)); + $stream->getNextIdentifierOrStar(); + } + + public function testSkipWhitespace() + { + $stream = new TokenStream(); + $stream->push($t1 = new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + $stream->push($t2 = new Token(Token::TYPE_WHITESPACE, ' ', 2)); + $stream->push($t3 = new Token(Token::TYPE_IDENTIFIER, 'h1', 3)); + + $stream->skipWhitespace(); + $this->assertSame($t1, $stream->getNext()); + + $stream->skipWhitespace(); + $this->assertSame($t3, $stream->getNext()); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/ids.html b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/ids.html new file mode 100644 index 0000000000000000000000000000000000000000..5799fad25ecfb33bca8ab42c615253160c061636 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/ids.html @@ -0,0 +1,48 @@ +<html id="html"><head> + <link id="link-href" href="foo" /> + <link id="link-nohref" /> +</head><body> +<div id="outer-div"> + <a id="name-anchor" name="foo"></a> + <a id="tag-anchor" rel="tag" href="http://localhost/foo">link</a> + <a id="nofollow-anchor" rel="nofollow" href="https://example.org"> + link</a> + <ol id="first-ol" class="a b c"> + <li id="first-li">content</li> + <li id="second-li" lang="En-us"> + <div id="li-div"> + </div> + </li> + <li id="third-li" class="ab c"></li> + <li id="fourth-li" class="ab +c"></li> + <li id="fifth-li"></li> + <li id="sixth-li"></li> + <li id="seventh-li"> </li> + </ol> + <p id="paragraph"> + <b id="p-b">hi</b> <em id="p-em">there</em> + <b id="p-b2">guy</b> + <input type="checkbox" id="checkbox-unchecked" /> + <input type="checkbox" id="checkbox-disabled" disabled="" /> + <input type="text" id="text-checked" checked="checked" /> + <input type="hidden" /> + <input type="hidden" disabled="disabled" /> + <input type="checkbox" id="checkbox-checked" checked="checked" /> + <input type="checkbox" id="checkbox-disabled-checked" + disabled="disabled" checked="checked" /> + <fieldset id="fieldset" disabled="disabled"> + <input type="checkbox" id="checkbox-fieldset-disabled" /> + <input type="hidden" /> + </fieldset> + </p> + <ol id="second-ol"> + </ol> + <map name="dummymap"> + <area shape="circle" coords="200,250,25" href="foo.html" id="area-href" /> + <area shape="default" id="area-nohref" /> + </map> +</div> +<div id="foobar-div" foobar="ab bc +cde"><span id="foobar-span"></span></div> +</body></html> diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/lang.xml b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/lang.xml new file mode 100644 index 0000000000000000000000000000000000000000..14f8dbed681f6f52eb9c7f7bbc021ccdee4533bb --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/lang.xml @@ -0,0 +1,11 @@ +<test> + <a id="first" xml:lang="en">a</a> + <b id="second" xml:lang="en-US">b</b> + <c id="third" xml:lang="en-Nz">c</c> + <d id="fourth" xml:lang="En-us">d</d> + <e id="fifth" xml:lang="fr">e</e> + <f id="sixth" xml:lang="ru">f</f> + <g id="seventh" xml:lang="de"> + <h id="eighth" xml:lang="zh"/> + </g> +</test> diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/shakespear.html b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/shakespear.html new file mode 100644 index 0000000000000000000000000000000000000000..15d1ad33a3192dd9e141a5ac006465fd7f4315ba --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/shakespear.html @@ -0,0 +1,308 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" debug="true"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> +</head> +<body> + <div id="test"> + <div class="dialog"> + <h2>As You Like It</h2> + <div id="playwright"> + by William Shakespeare + </div> + <div class="dialog scene thirdClass" id="scene1"> + <h3>ACT I, SCENE III. A room in the palace.</h3> + <div class="dialog"> + <div class="direction">Enter CELIA and ROSALIND</div> + </div> + <div id="speech1" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.1">Why, cousin! why, Rosalind! Cupid have mercy! not a word?</div> + </div> + <div id="speech2" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.2">Not one to throw at a dog.</div> + </div> + <div id="speech3" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.3">No, thy words are too precious to be cast away upon</div> + <div id="scene1.3.4">curs; throw some of them at me; come, lame me with reasons.</div> + </div> + <div id="speech4" class="character">ROSALIND</div> + <div id="speech5" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.8">But is all this for your father?</div> + </div> + <div class="dialog"> + <div id="scene1.3.5">Then there were two cousins laid up; when the one</div> + <div id="scene1.3.6">should be lamed with reasons and the other mad</div> + <div id="scene1.3.7">without any.</div> + </div> + <div id="speech6" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.9">No, some of it is for my child's father. O, how</div> + <div id="scene1.3.10">full of briers is this working-day world!</div> + </div> + <div id="speech7" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.11">They are but burs, cousin, thrown upon thee in</div> + <div id="scene1.3.12">holiday foolery: if we walk not in the trodden</div> + <div id="scene1.3.13">paths our very petticoats will catch them.</div> + </div> + <div id="speech8" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.14">I could shake them off my coat: these burs are in my heart.</div> + </div> + <div id="speech9" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.15">Hem them away.</div> + </div> + <div id="speech10" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.16">I would try, if I could cry 'hem' and have him.</div> + </div> + <div id="speech11" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.17">Come, come, wrestle with thy affections.</div> + </div> + <div id="speech12" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.18">O, they take the part of a better wrestler than myself!</div> + </div> + <div id="speech13" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.19">O, a good wish upon you! you will try in time, in</div> + <div id="scene1.3.20">despite of a fall. But, turning these jests out of</div> + <div id="scene1.3.21">service, let us talk in good earnest: is it</div> + <div id="scene1.3.22">possible, on such a sudden, you should fall into so</div> + <div id="scene1.3.23">strong a liking with old Sir Rowland's youngest son?</div> + </div> + <div id="speech14" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.24">The duke my father loved his father dearly.</div> + </div> + <div id="speech15" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.25">Doth it therefore ensue that you should love his son</div> + <div id="scene1.3.26">dearly? By this kind of chase, I should hate him,</div> + <div id="scene1.3.27">for my father hated his father dearly; yet I hate</div> + <div id="scene1.3.28">not Orlando.</div> + </div> + <div id="speech16" class="character">ROSALIND</div> + <div title="wtf" class="dialog"> + <div id="scene1.3.29">No, faith, hate him not, for my sake.</div> + </div> + <div id="speech17" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.30">Why should I not? doth he not deserve well?</div> + </div> + <div id="speech18" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.31">Let me love him for that, and do you love him</div> + <div id="scene1.3.32">because I do. Look, here comes the duke.</div> + </div> + <div id="speech19" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.33">With his eyes full of anger.</div> + <div class="direction">Enter DUKE FREDERICK, with Lords</div> + </div> + <div id="speech20" class="character">DUKE FREDERICK</div> + <div class="dialog"> + <div id="scene1.3.34">Mistress, dispatch you with your safest haste</div> + <div id="scene1.3.35">And get you from our court.</div> + </div> + <div id="speech21" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.36">Me, uncle?</div> + </div> + <div id="speech22" class="character">DUKE FREDERICK</div> + <div class="dialog"> + <div id="scene1.3.37">You, cousin</div> + <div id="scene1.3.38">Within these ten days if that thou be'st found</div> + <div id="scene1.3.39">So near our public court as twenty miles,</div> + <div id="scene1.3.40">Thou diest for it.</div> + </div> + <div id="speech23" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.41"> I do beseech your grace,</div> + <div id="scene1.3.42">Let me the knowledge of my fault bear with me:</div> + <div id="scene1.3.43">If with myself I hold intelligence</div> + <div id="scene1.3.44">Or have acquaintance with mine own desires,</div> + <div id="scene1.3.45">If that I do not dream or be not frantic,--</div> + <div id="scene1.3.46">As I do trust I am not--then, dear uncle,</div> + <div id="scene1.3.47">Never so much as in a thought unborn</div> + <div id="scene1.3.48">Did I offend your highness.</div> + </div> + <div id="speech24" class="character">DUKE FREDERICK</div> + <div class="dialog"> + <div id="scene1.3.49">Thus do all traitors:</div> + <div id="scene1.3.50">If their purgation did consist in words,</div> + <div id="scene1.3.51">They are as innocent as grace itself:</div> + <div id="scene1.3.52">Let it suffice thee that I trust thee not.</div> + </div> + <div id="speech25" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.53">Yet your mistrust cannot make me a traitor:</div> + <div id="scene1.3.54">Tell me whereon the likelihood depends.</div> + </div> + <div id="speech26" class="character">DUKE FREDERICK</div> + <div class="dialog"> + <div id="scene1.3.55">Thou art thy father's daughter; there's enough.</div> + </div> + <div id="speech27" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.56">So was I when your highness took his dukedom;</div> + <div id="scene1.3.57">So was I when your highness banish'd him:</div> + <div id="scene1.3.58">Treason is not inherited, my lord;</div> + <div id="scene1.3.59">Or, if we did derive it from our friends,</div> + <div id="scene1.3.60">What's that to me? my father was no traitor:</div> + <div id="scene1.3.61">Then, good my liege, mistake me not so much</div> + <div id="scene1.3.62">To think my poverty is treacherous.</div> + </div> + <div id="speech28" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.63">Dear sovereign, hear me speak.</div> + </div> + <div id="speech29" class="character">DUKE FREDERICK</div> + <div class="dialog"> + <div id="scene1.3.64">Ay, Celia; we stay'd her for your sake,</div> + <div id="scene1.3.65">Else had she with her father ranged along.</div> + </div> + <div id="speech30" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.66">I did not then entreat to have her stay;</div> + <div id="scene1.3.67">It was your pleasure and your own remorse:</div> + <div id="scene1.3.68">I was too young that time to value her;</div> + <div id="scene1.3.69">But now I know her: if she be a traitor,</div> + <div id="scene1.3.70">Why so am I; we still have slept together,</div> + <div id="scene1.3.71">Rose at an instant, learn'd, play'd, eat together,</div> + <div id="scene1.3.72">And wheresoever we went, like Juno's swans,</div> + <div id="scene1.3.73">Still we went coupled and inseparable.</div> + </div> + <div id="speech31" class="character">DUKE FREDERICK</div> + <div class="dialog"> + <div id="scene1.3.74">She is too subtle for thee; and her smoothness,</div> + <div id="scene1.3.75">Her very silence and her patience</div> + <div id="scene1.3.76">Speak to the people, and they pity her.</div> + <div id="scene1.3.77">Thou art a fool: she robs thee of thy name;</div> + <div id="scene1.3.78">And thou wilt show more bright and seem more virtuous</div> + <div id="scene1.3.79">When she is gone. Then open not thy lips:</div> + <div id="scene1.3.80">Firm and irrevocable is my doom</div> + <div id="scene1.3.81">Which I have pass'd upon her; she is banish'd.</div> + </div> + <div id="speech32" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.82">Pronounce that sentence then on me, my liege:</div> + <div id="scene1.3.83">I cannot live out of her company.</div> + </div> + <div id="speech33" class="character">DUKE FREDERICK</div> + <div class="dialog"> + <div id="scene1.3.84">You are a fool. You, niece, provide yourself:</div> + <div id="scene1.3.85">If you outstay the time, upon mine honour,</div> + <div id="scene1.3.86">And in the greatness of my word, you die.</div> + <div class="direction">Exeunt DUKE FREDERICK and Lords</div> + </div> + <div id="speech34" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.87">O my poor Rosalind, whither wilt thou go?</div> + <div id="scene1.3.88">Wilt thou change fathers? I will give thee mine.</div> + <div id="scene1.3.89">I charge thee, be not thou more grieved than I am.</div> + </div> + <div id="speech35" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.90">I have more cause.</div> + </div> + <div id="speech36" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.91"> Thou hast not, cousin;</div> + <div id="scene1.3.92">Prithee be cheerful: know'st thou not, the duke</div> + <div id="scene1.3.93">Hath banish'd me, his daughter?</div> + </div> + <div id="speech37" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.94">That he hath not.</div> + </div> + <div id="speech38" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.95">No, hath not? Rosalind lacks then the love</div> + <div id="scene1.3.96">Which teacheth thee that thou and I am one:</div> + <div id="scene1.3.97">Shall we be sunder'd? shall we part, sweet girl?</div> + <div id="scene1.3.98">No: let my father seek another heir.</div> + <div id="scene1.3.99">Therefore devise with me how we may fly,</div> + <div id="scene1.3.100">Whither to go and what to bear with us;</div> + <div id="scene1.3.101">And do not seek to take your change upon you,</div> + <div id="scene1.3.102">To bear your griefs yourself and leave me out;</div> + <div id="scene1.3.103">For, by this heaven, now at our sorrows pale,</div> + <div id="scene1.3.104">Say what thou canst, I'll go along with thee.</div> + </div> + <div id="speech39" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.105">Why, whither shall we go?</div> + </div> + <div id="speech40" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.106">To seek my uncle in the forest of Arden.</div> + </div> + <div id="speech41" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.107">Alas, what danger will it be to us,</div> + <div id="scene1.3.108">Maids as we are, to travel forth so far!</div> + <div id="scene1.3.109">Beauty provoketh thieves sooner than gold.</div> + </div> + <div id="speech42" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.110">I'll put myself in poor and mean attire</div> + <div id="scene1.3.111">And with a kind of umber smirch my face;</div> + <div id="scene1.3.112">The like do you: so shall we pass along</div> + <div id="scene1.3.113">And never stir assailants.</div> + </div> + <div id="speech43" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.114">Were it not better,</div> + <div id="scene1.3.115">Because that I am more than common tall,</div> + <div id="scene1.3.116">That I did suit me all points like a man?</div> + <div id="scene1.3.117">A gallant curtle-axe upon my thigh,</div> + <div id="scene1.3.118">A boar-spear in my hand; and--in my heart</div> + <div id="scene1.3.119">Lie there what hidden woman's fear there will--</div> + <div id="scene1.3.120">We'll have a swashing and a martial outside,</div> + <div id="scene1.3.121">As many other mannish cowards have</div> + <div id="scene1.3.122">That do outface it with their semblances.</div> + </div> + <div id="speech44" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.123">What shall I call thee when thou art a man?</div> + </div> + <div id="speech45" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.124">I'll have no worse a name than Jove's own page;</div> + <div id="scene1.3.125">And therefore look you call me Ganymede.</div> + <div id="scene1.3.126">But what will you be call'd?</div> + </div> + <div id="speech46" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.127">Something that hath a reference to my state</div> + <div id="scene1.3.128">No longer Celia, but Aliena.</div> + </div> + <div id="speech47" class="character">ROSALIND</div> + <div class="dialog"> + <div id="scene1.3.129">But, cousin, what if we assay'd to steal</div> + <div id="scene1.3.130">The clownish fool out of your father's court?</div> + <div id="scene1.3.131">Would he not be a comfort to our travel?</div> + </div> + <div id="speech48" class="character">CELIA</div> + <div class="dialog"> + <div id="scene1.3.132">He'll go along o'er the wide world with me;</div> + <div id="scene1.3.133">Leave me alone to woo him. Let's away,</div> + <div id="scene1.3.134">And get our jewels and our wealth together,</div> + <div id="scene1.3.135">Devise the fittest time and safest way</div> + <div id="scene1.3.136">To hide us from pursuit that will be made</div> + <div id="scene1.3.137">After my flight. Now go we in content</div> + <div id="scene1.3.138">To liberty and not to banishment.</div> + <div class="direction">Exeunt</div> + </div> + </div> + </div> +</div> +</body> +</html> diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..30f7189f06f18ba248e02af0515a12b0dbd99b6e --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php @@ -0,0 +1,324 @@ +<?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\CssSelector\Tests\XPath; + +use Symfony\Component\CssSelector\XPath\Extension\HtmlExtension; +use Symfony\Component\CssSelector\XPath\Translator; + +class TranslatorTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getXpathLiteralTestData */ + public function testXpathLiteral($value, $literal) + { + $this->assertEquals($literal, Translator::getXpathLiteral($value)); + } + + /** @dataProvider getCssToXPathTestData */ + public function testCssToXPath($css, $xpath) + { + $translator = new Translator(); + $translator->registerExtension(new HtmlExtension($translator)); + $this->assertEquals($xpath, $translator->cssToXPath($css, '')); + } + + /** @dataProvider getXmlLangTestData */ + public function testXmlLang($css, array $elementsId) + { + $translator = new Translator(); + $document = new \SimpleXMLElement(file_get_contents(__DIR__.'/Fixtures/lang.xml')); + $elements = $document->xpath($translator->cssToXPath($css)); + $this->assertEquals(count($elementsId), count($elements)); + foreach ($elements as $element) { + $this->assertTrue(in_array($element->attributes()->id, $elementsId)); + } + } + + /** @dataProvider getHtmlIdsTestData */ + public function testHtmlIds($css, array $elementsId) + { + $translator = new Translator(); + $translator->registerExtension(new HtmlExtension($translator)); + $document = new \DOMDocument(); + $document->strictErrorChecking = false; + $internalErrors = libxml_use_internal_errors(true); + $document->loadHTMLFile(__DIR__.'/Fixtures/ids.html'); + $document = simplexml_import_dom($document); + $elements = $document->xpath($translator->cssToXPath($css)); + $this->assertCount(count($elementsId), $elementsId); + foreach ($elements as $element) { + if (null !== $element->attributes()->id) { + $this->assertTrue(in_array($element->attributes()->id, $elementsId)); + } + } + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + } + + /** @dataProvider getHtmlShakespearTestData */ + public function testHtmlShakespear($css, $count) + { + $translator = new Translator(); + $translator->registerExtension(new HtmlExtension($translator)); + $document = new \DOMDocument(); + $document->strictErrorChecking = false; + $document->loadHTMLFile(__DIR__.'/Fixtures/shakespear.html'); + $document = simplexml_import_dom($document); + $bodies = $document->xpath('//body'); + $elements = $bodies[0]->xpath($translator->cssToXPath($css)); + $this->assertEquals($count, count($elements)); + } + + public function getXpathLiteralTestData() + { + return array( + array('foo', "'foo'"), + array("foo's bar", '"foo\'s bar"'), + array("foo's \"middle\" bar", 'concat(\'foo\', "\'", \'s "middle" bar\')'), + array("foo's 'middle' \"bar\"", 'concat(\'foo\', "\'", \'s \', "\'", \'middle\', "\'", \' "bar"\')'), + ); + } + + public function getCssToXPathTestData() + { + return array( + array('*', "*"), + array('e', "e"), + array('*|e', "e"), + array('e|f', "e:f"), + array('e[foo]', "e[@foo]"), + array('e[foo|bar]', "e[@foo:bar]"), + array('e[foo="bar"]', "e[@foo = 'bar']"), + array('e[foo~="bar"]', "e[@foo and contains(concat(' ', normalize-space(@foo), ' '), ' bar ')]"), + array('e[foo^="bar"]', "e[@foo and starts-with(@foo, 'bar')]"), + array('e[foo$="bar"]', "e[@foo and substring(@foo, string-length(@foo)-2) = 'bar']"), + array('e[foo*="bar"]', "e[@foo and contains(@foo, 'bar')]"), + array('e[hreflang|="en"]', "e[@hreflang and (@hreflang = 'en' or starts-with(@hreflang, 'en-'))]"), + array('e:nth-child(1)', "*/*[name() = 'e' and (position() = 1)]"), + array('e:nth-last-child(1)', "*/*[name() = 'e' and (position() = last() - 0)]"), + array('e:nth-last-child(2n+2)', "*/*[name() = 'e' and (last() - position() - 1 >= 0 and (last() - position() - 1) mod 2 = 0)]"), + array('e:nth-of-type(1)', "*/e[position() = 1]"), + array('e:nth-last-of-type(1)', "*/e[position() = last() - 0]"), + array('div e:nth-last-of-type(1) .aclass', "div/descendant-or-self::*/e[position() = last() - 0]/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' aclass ')]"), + array('e:first-child', "*/*[name() = 'e' and (position() = 1)]"), + array('e:last-child', "*/*[name() = 'e' and (position() = last())]"), + array('e:first-of-type', "*/e[position() = 1]"), + array('e:last-of-type', "*/e[position() = last()]"), + array('e:only-child', "*/*[name() = 'e' and (last() = 1)]"), + array('e:only-of-type', "e[last() = 1]"), + array('e:empty', "e[not(*) and not(string-length())]"), + array('e:EmPTY', "e[not(*) and not(string-length())]"), + array('e:root', "e[not(parent::*)]"), + array('e:hover', "e[0]"), + array('e:contains("foo")', "e[contains(string(.), 'foo')]"), + array('e:ConTains(foo)', "e[contains(string(.), 'foo')]"), + array('e.warning', "e[@class and contains(concat(' ', normalize-space(@class), ' '), ' warning ')]"), + array('e#myid', "e[@id = 'myid']"), + array('e:not(:nth-child(odd))', "e[not(position() - 1 >= 0 and (position() - 1) mod 2 = 0)]"), + array('e:nOT(*)', "e[0]"), + array('e f', "e/descendant-or-self::*/f"), + array('e > f', "e/f"), + array('e + f', "e/following-sibling::*[name() = 'f' and (position() = 1)]"), + array('e ~ f', "e/following-sibling::f"), + array('div#container p', "div[@id = 'container']/descendant-or-self::*/p"), + ); + } + + public function getXmlLangTestData() + { + return array( + array(':lang("EN")', array('first', 'second', 'third', 'fourth')), + array(':lang("en-us")', array('second', 'fourth')), + array(':lang(en-nz)', array('third')), + array(':lang(fr)', array('fifth')), + array(':lang(ru)', array('sixth')), + array(":lang('ZH')", array('eighth')), + array(':lang(de) :lang(zh)', array('eighth')), + array(':lang(en), :lang(zh)', array('first', 'second', 'third', 'fourth', 'eighth')), + array(':lang(es)', array()), + ); + } + + public function getHtmlIdsTestData() + { + return array( + array('div', array('outer-div', 'li-div', 'foobar-div')), + array('DIV', array('outer-div', 'li-div', 'foobar-div')), // case-insensitive in HTML + array('div div', array('li-div')), + array('div, div div', array('outer-div', 'li-div', 'foobar-div')), + array('a[name]', array('name-anchor')), + array('a[NAme]', array('name-anchor')), // case-insensitive in HTML: + array('a[rel]', array('tag-anchor', 'nofollow-anchor')), + array('a[rel="tag"]', array('tag-anchor')), + array('a[href*="localhost"]', array('tag-anchor')), + array('a[href*=""]', array()), + array('a[href^="http"]', array('tag-anchor', 'nofollow-anchor')), + array('a[href^="http:"]', array('tag-anchor')), + array('a[href^=""]', array()), + array('a[href$="org"]', array('nofollow-anchor')), + array('a[href$=""]', array()), + array('div[foobar~="bc"]', array('foobar-div')), + array('div[foobar~="cde"]', array('foobar-div')), + array('[foobar~="ab bc"]', array('foobar-div')), + array('[foobar~=""]', array()), + array('[foobar~=" \t"]', array()), + array('div[foobar~="cd"]', array()), + array('*[lang|="En"]', array('second-li')), + array('[lang|="En-us"]', array('second-li')), + // Attribute values are case sensitive + array('*[lang|="en"]', array()), + array('[lang|="en-US"]', array()), + array('*[lang|="e"]', array()), + // ... :lang() is not. + array(':lang("EN")', array('second-li', 'li-div')), + array('*:lang(en-US)', array('second-li', 'li-div')), + array(':lang("e")', array()), + array('li:nth-child(3)', array('third-li')), + array('li:nth-child(10)', array()), + array('li:nth-child(2n)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-child(even)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-child(2n+0)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-child(+2n+1)', array('first-li', 'third-li', 'fifth-li', 'seventh-li')), + array('li:nth-child(odd)', array('first-li', 'third-li', 'fifth-li', 'seventh-li')), + array('li:nth-child(2n+4)', array('fourth-li', 'sixth-li')), + array('li:nth-child(3n+1)', array('first-li', 'fourth-li', 'seventh-li')), + array('li:nth-child(n)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-child(n-1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-child(n+1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-child(n+3)', array('third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-child(-n)', array()), + array('li:nth-child(-n-1)', array()), + array('li:nth-child(-n+1)', array('first-li')), + array('li:nth-child(-n+3)', array('first-li', 'second-li', 'third-li')), + array('li:nth-last-child(0)', array()), + array('li:nth-last-child(2n)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-last-child(even)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-last-child(2n+2)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-last-child(n)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-last-child(n-1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-last-child(n-3)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-last-child(n+1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-last-child(n+3)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li')), + array('li:nth-last-child(-n)', array()), + array('li:nth-last-child(-n-1)', array()), + array('li:nth-last-child(-n+1)', array('seventh-li')), + array('li:nth-last-child(-n+3)', array('fifth-li', 'sixth-li', 'seventh-li')), + array('ol:first-of-type', array('first-ol')), + array('ol:nth-child(1)', array('first-ol')), + array('ol:nth-of-type(2)', array('second-ol')), + array('ol:nth-last-of-type(1)', array('second-ol')), + array('span:only-child', array('foobar-span')), + array('li div:only-child', array('li-div')), + array('div *:only-child', array('li-div', 'foobar-span')), + array('p:only-of-type', array('paragraph')), + array('a:empty', array('name-anchor')), + array('a:EMpty', array('name-anchor')), + array('li:empty', array('third-li', 'fourth-li', 'fifth-li', 'sixth-li')), + array(':root', array('html')), + array('html:root', array('html')), + array('li:root', array()), + array('* :root', array()), + array('*:contains("link")', array('html', 'outer-div', 'tag-anchor', 'nofollow-anchor')), + array(':CONtains("link")', array('html', 'outer-div', 'tag-anchor', 'nofollow-anchor')), + array('*:contains("LInk")', array()), // case sensitive + array('*:contains("e")', array('html', 'nil', 'outer-div', 'first-ol', 'first-li', 'paragraph', 'p-em')), + array('*:contains("E")', array()), // case-sensitive + array('.a', array('first-ol')), + array('.b', array('first-ol')), + array('*.a', array('first-ol')), + array('ol.a', array('first-ol')), + array('.c', array('first-ol', 'third-li', 'fourth-li')), + array('*.c', array('first-ol', 'third-li', 'fourth-li')), + array('ol *.c', array('third-li', 'fourth-li')), + array('ol li.c', array('third-li', 'fourth-li')), + array('li ~ li.c', array('third-li', 'fourth-li')), + array('ol > li.c', array('third-li', 'fourth-li')), + array('#first-li', array('first-li')), + array('li#first-li', array('first-li')), + array('*#first-li', array('first-li')), + array('li div', array('li-div')), + array('li > div', array('li-div')), + array('div div', array('li-div')), + array('div > div', array()), + array('div>.c', array('first-ol')), + array('div > .c', array('first-ol')), + array('div + div', array('foobar-div')), + array('a ~ a', array('tag-anchor', 'nofollow-anchor')), + array('a[rel="tag"] ~ a', array('nofollow-anchor')), + array('ol#first-ol li:last-child', array('seventh-li')), + array('ol#first-ol *:last-child', array('li-div', 'seventh-li')), + array('#outer-div:first-child', array('outer-div')), + array('#outer-div :first-child', array('name-anchor', 'first-li', 'li-div', 'p-b', 'checkbox-fieldset-disabled', 'area-href')), + array('a[href]', array('tag-anchor', 'nofollow-anchor')), + array(':not(*)', array()), + array('a:not([href])', array('name-anchor')), + array('ol :Not(li[class])', array('first-li', 'second-li', 'li-div', 'fifth-li', 'sixth-li', 'seventh-li')), + // HTML-specific + array(':link', array('link-href', 'tag-anchor', 'nofollow-anchor', 'area-href')), + array(':visited', array()), + array(':enabled', array('link-href', 'tag-anchor', 'nofollow-anchor', 'checkbox-unchecked', 'text-checked', 'checkbox-checked', 'area-href')), + array(':disabled', array('checkbox-disabled', 'checkbox-disabled-checked', 'fieldset', 'checkbox-fieldset-disabled')), + array(':checked', array('checkbox-checked', 'checkbox-disabled-checked')), + ); + } + + public function getHtmlShakespearTestData() + { + return array( + array('*', 246), + array('div:contains(CELIA)', 26), + array('div:only-child', 22), // ? + array('div:nth-child(even)', 106), + array('div:nth-child(2n)', 106), + array('div:nth-child(odd)', 137), + array('div:nth-child(2n+1)', 137), + array('div:nth-child(n)', 243), + array('div:last-child', 53), + array('div:first-child', 51), + array('div > div', 242), + array('div + div', 190), + array('div ~ div', 190), + array('body', 1), + array('body div', 243), + array('div', 243), + array('div div', 242), + array('div div div', 241), + array('div, div, div', 243), + array('div, a, span', 243), + array('.dialog', 51), + array('div.dialog', 51), + array('div .dialog', 51), + array('div.character, div.dialog', 99), + array('div.direction.dialog', 0), + array('div.dialog.direction', 0), + array('div.dialog.scene', 1), + array('div.scene.scene', 1), + array('div.scene .scene', 0), + array('div.direction .dialog ', 0), + array('div .dialog .direction', 4), + array('div.dialog .dialog .direction', 4), + array('#speech5', 1), + array('div#speech5', 1), + array('div #speech5', 1), + array('div.scene div.dialog', 49), + array('div#scene1 div.dialog div', 142), + array('#scene1 #speech1', 1), + array('div[class]', 103), + array('div[class=dialog]', 50), + array('div[class^=dia]', 51), + array('div[class$=log]', 50), + array('div[class*=sce]', 1), + array('div[class|=dialog]', 50), // ? Seems right + array('div[class!=madeup]', 243), // ? Seems right + array('div[class~=dialog]', 51), // ? Seems right + ); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/AbstractExtension.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/AbstractExtension.php new file mode 100644 index 0000000000000000000000000000000000000000..1b147e9ecf6e8bbccd752f053b608458acc7fb35 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/AbstractExtension.php @@ -0,0 +1,63 @@ +<?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\CssSelector\XPath\Extension; + +/** + * XPath expression translator abstract extension. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +abstract class AbstractExtension implements ExtensionInterface +{ + /** + * {@inheritdoc} + */ + public function getNodeTranslators() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getCombinationTranslators() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getFunctionTranslators() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getPseudoClassTranslators() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getAttributeMatchingTranslators() + { + return array(); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/AttributeMatchingExtension.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/AttributeMatchingExtension.php new file mode 100644 index 0000000000000000000000000000000000000000..1b1f00f2863e84b7cfafe08d380cf7d824d7f08b --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/AttributeMatchingExtension.php @@ -0,0 +1,173 @@ +<?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\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\XPath\Translator; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator attribute extension. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class AttributeMatchingExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getAttributeMatchingTranslators() + { + return array( + 'exists' => array($this, 'translateExists'), + '=' => array($this, 'translateEquals'), + '~=' => array($this, 'translateIncludes'), + '|=' => array($this, 'translateDashMatch'), + '^=' => array($this, 'translatePrefixMatch'), + '$=' => array($this, 'translateSuffixMatch'), + '*=' => array($this, 'translateSubstringMatch'), + '!=' => array($this, 'translateDifferent'), + ); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateExists(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($attribute); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateEquals(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition(sprintf('%s = %s', $attribute, Translator::getXpathLiteral($value))); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateIncludes(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($value ? sprintf( + '%1$s and contains(concat(\' \', normalize-space(%1$s), \' \'), %2$s)', + $attribute, + Translator::getXpathLiteral(' '.$value.' ') + ) : '0'); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateDashMatch(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition(sprintf( + '%1$s and (%1$s = %2$s or starts-with(%1$s, %3$s))', + $attribute, + Translator::getXpathLiteral($value), + Translator::getXpathLiteral($value.'-') + )); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translatePrefixMatch(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($value ? sprintf( + '%1$s and starts-with(%1$s, %2$s)', + $attribute, + Translator::getXpathLiteral($value) + ) : '0'); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateSuffixMatch(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($value ? sprintf( + '%1$s and substring(%1$s, string-length(%1$s)-%2$s) = %3$s', + $attribute, + strlen($value) - 1, + Translator::getXpathLiteral($value) + ) : '0'); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateSubstringMatch(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($value ? sprintf( + '%1$s and contains(%1$s, %2$s)', + $attribute, + Translator::getXpathLiteral($value) + ) : '0'); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateDifferent(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition(sprintf( + $value ? 'not(%1$s) or %1$s != %2$s' : '%s != %s', + $attribute, + Translator::getXpathLiteral($value) + )); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'attribute-matching'; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/CombinationExtension.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/CombinationExtension.php new file mode 100644 index 0000000000000000000000000000000000000000..639e9249521904690e987d0eaa67bb7d5db81586 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/CombinationExtension.php @@ -0,0 +1,93 @@ +<?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\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator combination extension. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class CombinationExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getCombinationTranslators() + { + return array( + ' ' => array($this, 'translateDescendant'), + '>' => array($this, 'translateChild'), + '+' => array($this, 'translateDirectAdjacent'), + '~' => array($this, 'translateIndirectAdjacent'), + ); + } + + /** + * @param XPathExpr $xpath + * @param XPathExpr $combinedXpath + * + * @return XPathExpr + */ + public function translateDescendant(XPathExpr $xpath, XPathExpr $combinedXpath) + { + return $xpath->join('/descendant-or-self::*/', $combinedXpath); + } + + /** + * @param XPathExpr $xpath + * @param XPathExpr $combinedXpath + * + * @return XPathExpr + */ + public function translateChild(XPathExpr $xpath, XPathExpr $combinedXpath) + { + return $xpath->join('/', $combinedXpath); + } + + /** + * @param XPathExpr $xpath + * @param XPathExpr $combinedXpath + * + * @return XPathExpr + */ + public function translateDirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath) + { + return $xpath + ->join('/following-sibling::', $combinedXpath) + ->addNameTest() + ->addCondition('position() = 1'); + } + + /** + * @param XPathExpr $xpath + * @param XPathExpr $combinedXpath + * + * @return XPathExpr + */ + public function translateIndirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath) + { + return $xpath->join('/following-sibling::', $combinedXpath); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'combination'; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/ExtensionInterface.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/ExtensionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..65ab287770f27ac4f87790caec74246e0ccaffe4 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/ExtensionInterface.php @@ -0,0 +1,65 @@ +<?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\CssSelector\XPath\Extension; + +/** + * XPath expression translator extension interface. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +interface ExtensionInterface +{ + /** + * Returns node translators. + * + * @return callable[] + */ + public function getNodeTranslators(); + + /** + * Returns combination translators. + * + * @return callable[] + */ + public function getCombinationTranslators(); + + /** + * Returns function translators. + * + * @return callable[] + */ + public function getFunctionTranslators(); + + /** + * Returns pseudo-class translators. + * + * @return callable[] + */ + public function getPseudoClassTranslators(); + + /** + * Returns attribute operation translators. + * + * @return callable[] + */ + public function getAttributeMatchingTranslators(); + + /** + * Returns extension name. + * + * @return string + */ + public function getName(); +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/FunctionExtension.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/FunctionExtension.php new file mode 100644 index 0000000000000000000000000000000000000000..ff8f333ada06903c2a60a8dd3750a654dd395410 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/FunctionExtension.php @@ -0,0 +1,209 @@ +<?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\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\Exception\ExpressionErrorException; +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\Parser\Parser; +use Symfony\Component\CssSelector\XPath\Translator; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator function extension. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class FunctionExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getFunctionTranslators() + { + return array( + 'nth-child' => array($this, 'translateNthChild'), + 'nth-last-child' => array($this, 'translateNthLastChild'), + 'nth-of-type' => array($this, 'translateNthOfType'), + 'nth-last-of-type' => array($this, 'translateNthLastOfType'), + 'contains' => array($this, 'translateContains'), + 'lang' => array($this, 'translateLang'), + ); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * @param bool $last + * @param bool $addNameTest + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateNthChild(XPathExpr $xpath, FunctionNode $function, $last = false, $addNameTest = true) + { + try { + list($a, $b) = Parser::parseSeries($function->getArguments()); + } catch (SyntaxErrorException $e) { + throw new ExpressionErrorException(sprintf('Invalid series: %s', implode(', ', $function->getArguments())), 0, $e); + } + + $xpath->addStarPrefix(); + if ($addNameTest) { + $xpath->addNameTest(); + } + + if (0 === $a) { + return $xpath->addCondition('position() = '.($last ? 'last() - '.($b - 1) : $b)); + } + + if ($a < 0) { + if ($b < 1) { + return $xpath->addCondition('false()'); + } + + $sign = '<='; + } else { + $sign = '>='; + } + + $expr = 'position()'; + + if ($last) { + $expr = 'last() - '.$expr; + $b--; + } + + if (0 !== $b) { + $expr .= ' - '.$b; + } + + $conditions = array(sprintf('%s %s 0', $expr, $sign)); + + if (1 !== $a && -1 !== $a) { + $conditions[] = sprintf('(%s) mod %d = 0', $expr, $a); + } + + return $xpath->addCondition(implode(' and ', $conditions)); + + // todo: handle an+b, odd, even + // an+b means every-a, plus b, e.g., 2n+1 means odd + // 0n+b means b + // n+0 means a=1, i.e., all elements + // an means every a elements, i.e., 2n means even + // -n means -1n + // -1n+6 means elements 6 and previous + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * + * @return XPathExpr + */ + public function translateNthLastChild(XPathExpr $xpath, FunctionNode $function) + { + return $this->translateNthChild($xpath, $function, true); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * + * @return XPathExpr + */ + public function translateNthOfType(XPathExpr $xpath, FunctionNode $function) + { + return $this->translateNthChild($xpath, $function, false, false); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function) + { + if ('*' === $xpath->getElement()) { + throw new ExpressionErrorException('"*:nth-of-type()" is not implemented.'); + } + + return $this->translateNthChild($xpath, $function, true, false); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateContains(XPathExpr $xpath, FunctionNode $function) + { + $arguments = $function->getArguments(); + foreach ($arguments as $token) { + if (!($token->isString() || $token->isIdentifier())) { + throw new ExpressionErrorException( + 'Expected a single string or identifier for :contains(), got ' + .implode(', ', $arguments) + ); + } + } + + return $xpath->addCondition(sprintf( + 'contains(string(.), %s)', + Translator::getXpathLiteral($arguments[0]->getValue()) + )); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateLang(XPathExpr $xpath, FunctionNode $function) + { + $arguments = $function->getArguments(); + foreach ($arguments as $token) { + if (!($token->isString() || $token->isIdentifier())) { + throw new ExpressionErrorException( + 'Expected a single string or identifier for :lang(), got ' + .implode(', ', $arguments) + ); + } + } + + return $xpath->addCondition(sprintf( + 'lang(%s)', + Translator::getXpathLiteral($arguments[0]->getValue()) + )); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'function'; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/HtmlExtension.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/HtmlExtension.php new file mode 100644 index 0000000000000000000000000000000000000000..aef80523dd0910b192239caeeb681e88d6ff3f5e --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/HtmlExtension.php @@ -0,0 +1,238 @@ +<?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\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\Exception\ExpressionErrorException; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\XPath\Translator; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator HTML extension. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class HtmlExtension extends AbstractExtension +{ + /** + * Constructor. + * + * @param Translator $translator + */ + public function __construct(Translator $translator) + { + $translator + ->getExtension('node') + ->setFlag(NodeExtension::ELEMENT_NAME_IN_LOWER_CASE, true) + ->setFlag(NodeExtension::ATTRIBUTE_NAME_IN_LOWER_CASE, true); + } + + /** + * {@inheritdoc} + */ + public function getPseudoClassTranslators() + { + return array( + 'checked' => array($this, 'translateChecked'), + 'link' => array($this, 'translateLink'), + 'disabled' => array($this, 'translateDisabled'), + 'enabled' => array($this, 'translateEnabled'), + 'selected' => array($this, 'translateSelected'), + 'invalid' => array($this, 'translateInvalid'), + 'hover' => array($this, 'translateHover'), + 'visited' => array($this, 'translateVisited'), + ); + } + + /** + * {@inheritdoc} + */ + public function getFunctionTranslators() + { + return array( + 'lang' => array($this, 'translateLang'), + ); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateChecked(XPathExpr $xpath) + { + return $xpath->addCondition( + '(@checked ' + ."and (name(.) = 'input' or name(.) = 'command')" + ."and (@type = 'checkbox' or @type = 'radio'))" + ); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateLink(XPathExpr $xpath) + { + return $xpath->addCondition("@href and (name(.) = 'a' or name(.) = 'link' or name(.) = 'area')"); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateDisabled(XPathExpr $xpath) + { + return $xpath->addCondition( + "(" + ."@disabled and" + ."(" + ."(name(.) = 'input' and @type != 'hidden')" + ." or name(.) = 'button'" + ." or name(.) = 'select'" + ." or name(.) = 'textarea'" + ." or name(.) = 'command'" + ." or name(.) = 'fieldset'" + ." or name(.) = 'optgroup'" + ." or name(.) = 'option'" + .")" + .") or (" + ."(name(.) = 'input' and @type != 'hidden')" + ." or name(.) = 'button'" + ." or name(.) = 'select'" + ." or name(.) = 'textarea'" + .")" + ." and ancestor::fieldset[@disabled]" + ); + // todo: in the second half, add "and is not a descendant of that fieldset element's first legend element child, if any." + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateEnabled(XPathExpr $xpath) + { + return $xpath->addCondition( + '(' + .'@href and (' + ."name(.) = 'a'" + ." or name(.) = 'link'" + ." or name(.) = 'area'" + .')' + .') or (' + .'(' + ."name(.) = 'command'" + ." or name(.) = 'fieldset'" + ." or name(.) = 'optgroup'" + .')' + .' and not(@disabled)' + .') or (' + .'(' + ."(name(.) = 'input' and @type != 'hidden')" + ." or name(.) = 'button'" + ." or name(.) = 'select'" + ." or name(.) = 'textarea'" + ." or name(.) = 'keygen'" + .')' + ." and not (@disabled or ancestor::fieldset[@disabled])" + .') or (' + ."name(.) = 'option' and not(" + ."@disabled or ancestor::optgroup[@disabled]" + .')' + .')' + ); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateLang(XPathExpr $xpath, FunctionNode $function) + { + $arguments = $function->getArguments(); + foreach ($arguments as $token) { + if (!($token->isString() || $token->isIdentifier())) { + throw new ExpressionErrorException( + 'Expected a single string or identifier for :lang(), got ' + .implode(', ', $arguments) + ); + } + } + + return $xpath->addCondition(sprintf( + 'ancestor-or-self::*[@lang][1][starts-with(concat(' + ."translate(@%s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '-')" + .', %s)]', + 'lang', + Translator::getXpathLiteral(strtolower($arguments[0]->getValue()).'-') + )); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateSelected(XPathExpr $xpath) + { + return $xpath->addCondition("(@selected and name(.) = 'option')"); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateInvalid(XPathExpr $xpath) + { + return $xpath->addCondition('0'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateHover(XPathExpr $xpath) + { + return $xpath->addCondition('0'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateVisited(XPathExpr $xpath) + { + return $xpath->addCondition('0'); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'html'; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/NodeExtension.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/NodeExtension.php new file mode 100644 index 0000000000000000000000000000000000000000..f86f2b967266adae658206a1bb1fabbd4b2065be --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/NodeExtension.php @@ -0,0 +1,270 @@ +<?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\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\Node; +use Symfony\Component\CssSelector\XPath\Translator; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator node extension. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class NodeExtension extends AbstractExtension +{ + const ELEMENT_NAME_IN_LOWER_CASE = 1; + const ATTRIBUTE_NAME_IN_LOWER_CASE = 2; + const ATTRIBUTE_VALUE_IN_LOWER_CASE = 4; + + /** + * @var Translator + */ + private $translator; + + /** + * @var int + */ + private $flags; + + /** + * Constructor. + * + * @param Translator $translator + * @param int $flags + */ + public function __construct(Translator $translator, $flags = 0) + { + $this->translator = $translator; + $this->flags = $flags; + } + + /** + * @param int $flag + * @param bool $on + * + * @return NodeExtension + */ + public function setFlag($flag, $on) + { + if ($on && !$this->hasFlag($flag)) { + $this->flags += $flag; + } + + if (!$on && $this->hasFlag($flag)) { + $this->flags -= $flag; + } + + return $this; + } + + /** + * @param int $flag + * + * @return bool + */ + public function hasFlag($flag) + { + return $this->flags & $flag; + } + + /** + * {@inheritdoc} + */ + public function getNodeTranslators() + { + return array( + 'Selector' => array($this, 'translateSelector'), + 'CombinedSelector' => array($this, 'translateCombinedSelector'), + 'Negation' => array($this, 'translateNegation'), + 'Function' => array($this, 'translateFunction'), + 'Pseudo' => array($this, 'translatePseudo'), + 'Attribute' => array($this, 'translateAttribute'), + 'Class' => array($this, 'translateClass'), + 'Hash' => array($this, 'translateHash'), + 'Element' => array($this, 'translateElement'), + ); + } + + /** + * @param Node\SelectorNode $node + * + * @return XPathExpr + */ + public function translateSelector(Node\SelectorNode $node) + { + return $this->translator->nodeToXPath($node->getTree()); + } + + /** + * @param Node\CombinedSelectorNode $node + * + * @return XPathExpr + */ + public function translateCombinedSelector(Node\CombinedSelectorNode $node) + { + return $this->translator->addCombination($node->getCombinator(), $node->getSelector(), $node->getSubSelector()); + } + + /** + * @param Node\NegationNode $node + * + * @return XPathExpr + */ + public function translateNegation(Node\NegationNode $node) + { + $xpath = $this->translator->nodeToXPath($node->getSelector()); + $subXpath = $this->translator->nodeToXPath($node->getSubSelector()); + $subXpath->addNameTest(); + + if ($subXpath->getCondition()) { + return $xpath->addCondition(sprintf('not(%s)', $subXpath->getCondition())); + } + + return $xpath->addCondition('0'); + } + + /** + * @param Node\FunctionNode $node + * + * @return XPathExpr + */ + public function translateFunction(Node\FunctionNode $node) + { + $xpath = $this->translator->nodeToXPath($node->getSelector()); + + return $this->translator->addFunction($xpath, $node); + } + + /** + * @param Node\PseudoNode $node + * + * @return XPathExpr + */ + public function translatePseudo(Node\PseudoNode $node) + { + $xpath = $this->translator->nodeToXPath($node->getSelector()); + + return $this->translator->addPseudoClass($xpath, $node->getIdentifier()); + } + + /** + * @param Node\AttributeNode $node + * + * @return XPathExpr + */ + public function translateAttribute(Node\AttributeNode $node) + { + $name = $node->getAttribute(); + $safe = $this->isSafeName($name); + + if ($this->hasFlag(self::ATTRIBUTE_NAME_IN_LOWER_CASE)) { + $name = strtolower($name); + } + + if ($node->getNamespace()) { + $name = sprintf('%s:%s', $node->getNamespace(), $name); + $safe = $safe && $this->isSafeName($node->getNamespace()); + } + + $attribute = $safe ? '@'.$name : sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name)); + $value = $node->getValue(); + $xpath = $this->translator->nodeToXPath($node->getSelector()); + + if ($this->hasFlag(self::ATTRIBUTE_VALUE_IN_LOWER_CASE)) { + $value = strtolower($value); + } + + return $this->translator->addAttributeMatching($xpath, $node->getOperator(), $attribute, $value); + } + + /** + * @param Node\ClassNode $node + * + * @return XPathExpr + */ + public function translateClass(Node\ClassNode $node) + { + $xpath = $this->translator->nodeToXPath($node->getSelector()); + + return $this->translator->addAttributeMatching($xpath, '~=', '@class', $node->getName()); + } + + /** + * @param Node\HashNode $node + * + * @return XPathExpr + */ + public function translateHash(Node\HashNode $node) + { + $xpath = $this->translator->nodeToXPath($node->getSelector()); + + return $this->translator->addAttributeMatching($xpath, '=', '@id', $node->getId()); + } + + /** + * @param Node\ElementNode $node + * + * @return XPathExpr + */ + public function translateElement(Node\ElementNode $node) + { + $element = $node->getElement(); + + if ($this->hasFlag(self::ELEMENT_NAME_IN_LOWER_CASE)) { + $element = strtolower($element); + } + + if ($element) { + $safe = $this->isSafeName($element); + } else { + $element = '*'; + $safe = true; + } + + if ($node->getNamespace()) { + $element = sprintf('%s:%s', $node->getNamespace(), $element); + $safe = $safe && $this->isSafeName($node->getNamespace()); + } + + $xpath = new XPathExpr('', $element); + + if (!$safe) { + $xpath->addNameTest(); + } + + return $xpath; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'node'; + } + + /** + * Tests if given name is safe. + * + * @param string $name + * + * @return bool + */ + private function isSafeName($name) + { + return 0 < preg_match('~^[a-zA-Z_][a-zA-Z0-9_.-]*$~', $name); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/PseudoClassExtension.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/PseudoClassExtension.php new file mode 100644 index 0000000000000000000000000000000000000000..d230dd7c483f97fd4475c74e9ccde9fe20745caf --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/PseudoClassExtension.php @@ -0,0 +1,162 @@ +<?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\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\Exception\ExpressionErrorException; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator pseudo-class extension. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class PseudoClassExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getPseudoClassTranslators() + { + return array( + 'root' => array($this, 'translateRoot'), + 'first-child' => array($this, 'translateFirstChild'), + 'last-child' => array($this, 'translateLastChild'), + 'first-of-type' => array($this, 'translateFirstOfType'), + 'last-of-type' => array($this, 'translateLastOfType'), + 'only-child' => array($this, 'translateOnlyChild'), + 'only-of-type' => array($this, 'translateOnlyOfType'), + 'empty' => array($this, 'translateEmpty'), + ); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateRoot(XPathExpr $xpath) + { + return $xpath->addCondition('not(parent::*)'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateFirstChild(XPathExpr $xpath) + { + return $xpath + ->addStarPrefix() + ->addNameTest() + ->addCondition('position() = 1'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateLastChild(XPathExpr $xpath) + { + return $xpath + ->addStarPrefix() + ->addNameTest() + ->addCondition('position() = last()'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateFirstOfType(XPathExpr $xpath) + { + if ('*' === $xpath->getElement()) { + throw new ExpressionErrorException('"*:first-of-type" is not implemented.'); + } + + return $xpath + ->addStarPrefix() + ->addCondition('position() = 1'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateLastOfType(XPathExpr $xpath) + { + if ('*' === $xpath->getElement()) { + throw new ExpressionErrorException('"*:last-of-type" is not implemented.'); + } + + return $xpath + ->addStarPrefix() + ->addCondition('position() = last()'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateOnlyChild(XPathExpr $xpath) + { + return $xpath + ->addStarPrefix() + ->addNameTest() + ->addCondition('last() = 1'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateOnlyOfType(XPathExpr $xpath) + { + if ('*' === $xpath->getElement()) { + throw new ExpressionErrorException('"*:only-of-type" is not implemented.'); + } + + return $xpath->addCondition('last() = 1'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateEmpty(XPathExpr $xpath) + { + return $xpath->addCondition('not(*) and not(string-length())'); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pseudo-class'; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Translator.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Translator.php new file mode 100644 index 0000000000000000000000000000000000000000..5a8eb99017a1b58149f267319c5ae939f736beac --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Translator.php @@ -0,0 +1,301 @@ +<?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\CssSelector\XPath; + +use Symfony\Component\CssSelector\Exception\ExpressionErrorException; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\Node\NodeInterface; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Parser; +use Symfony\Component\CssSelector\Parser\ParserInterface; + +/** + * XPath expression translator interface. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class Translator implements TranslatorInterface +{ + /** + * @var ParserInterface + */ + private $mainParser; + + /** + * @var ParserInterface[] + */ + private $shortcutParsers = array(); + + /** + * @var Extension\ExtensionInterface + */ + private $extensions = array(); + + /** + * @var array + */ + private $nodeTranslators = array(); + + /** + * @var array + */ + private $combinationTranslators = array(); + + /** + * @var array + */ + private $functionTranslators = array(); + + /** + * @var array + */ + private $pseudoClassTranslators = array(); + + /** + * @var array + */ + private $attributeMatchingTranslators = array(); + + /** + * Constructor. + */ + public function __construct(ParserInterface $parser = null) + { + $this->mainParser = $parser ?: new Parser(); + + $this + ->registerExtension(new Extension\NodeExtension($this)) + ->registerExtension(new Extension\CombinationExtension()) + ->registerExtension(new Extension\FunctionExtension()) + ->registerExtension(new Extension\PseudoClassExtension()) + ->registerExtension(new Extension\AttributeMatchingExtension()) + ; + } + + /** + * @param string $element + * + * @return string + */ + public static function getXpathLiteral($element) + { + if (false === strpos($element, "'")) { + return "'".$element."'"; + } + + if (false === strpos($element, '"')) { + return '"'.$element.'"'; + } + + $string = $element; + $parts = array(); + while (true) { + if (false !== $pos = strpos($string, "'")) { + $parts[] = sprintf("'%s'", substr($string, 0, $pos)); + $parts[] = "\"'\""; + $string = substr($string, $pos + 1); + } else { + $parts[] = "'$string'"; + break; + } + } + + return sprintf('concat(%s)', implode($parts, ', ')); + } + + /** + * {@inheritdoc} + */ + public function cssToXPath($cssExpr, $prefix = 'descendant-or-self::') + { + $selectors = $this->parseSelectors($cssExpr); + + /** @var SelectorNode $selector */ + foreach ($selectors as $selector) { + if (null !== $selector->getPseudoElement()) { + throw new ExpressionErrorException('Pseudo-elements are not supported.'); + } + } + + $translator = $this; + + return implode(' | ', array_map(function (SelectorNode $selector) use ($translator, $prefix) { + return $translator->selectorToXPath($selector, $prefix); + }, $selectors)); + } + + /** + * {@inheritdoc} + */ + public function selectorToXPath(SelectorNode $selector, $prefix = 'descendant-or-self::') + { + return ($prefix ?: '').$this->nodeToXPath($selector); + } + + /** + * Registers an extension. + * + * @param Extension\ExtensionInterface $extension + * + * @return Translator + */ + public function registerExtension(Extension\ExtensionInterface $extension) + { + $this->extensions[$extension->getName()] = $extension; + + $this->nodeTranslators = array_merge($this->nodeTranslators, $extension->getNodeTranslators()); + $this->combinationTranslators = array_merge($this->combinationTranslators, $extension->getCombinationTranslators()); + $this->functionTranslators = array_merge($this->functionTranslators, $extension->getFunctionTranslators()); + $this->pseudoClassTranslators = array_merge($this->pseudoClassTranslators, $extension->getPseudoClassTranslators()); + $this->attributeMatchingTranslators = array_merge($this->attributeMatchingTranslators, $extension->getAttributeMatchingTranslators()); + + return $this; + } + + /** + * @param string $name + * + * @return Extension\ExtensionInterface + * + * @throws ExpressionErrorException + */ + public function getExtension($name) + { + if (!isset($this->extensions[$name])) { + throw new ExpressionErrorException(sprintf('Extension "%s" not registered.', $name)); + } + + return $this->extensions[$name]; + } + + /** + * Registers a shortcut parser. + * + * @param ParserInterface $shortcut + * + * @return Translator + */ + public function registerParserShortcut(ParserInterface $shortcut) + { + $this->shortcutParsers[] = $shortcut; + + return $this; + } + + /** + * @param NodeInterface $node + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function nodeToXPath(NodeInterface $node) + { + if (!isset($this->nodeTranslators[$node->getNodeName()])) { + throw new ExpressionErrorException(sprintf('Node "%s" not supported.', $node->getNodeName())); + } + + return call_user_func($this->nodeTranslators[$node->getNodeName()], $node); + } + + /** + * @param string $combiner + * @param NodeInterface $xpath + * @param NodeInterface $combinedXpath + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function addCombination($combiner, NodeInterface $xpath, NodeInterface $combinedXpath) + { + if (!isset($this->combinationTranslators[$combiner])) { + throw new ExpressionErrorException(sprintf('Combiner "%s" not supported.', $combiner)); + } + + return call_user_func($this->combinationTranslators[$combiner], $this->nodeToXPath($xpath), $this->nodeToXPath($combinedXpath)); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function addFunction(XPathExpr $xpath, FunctionNode $function) + { + if (!isset($this->functionTranslators[$function->getName()])) { + throw new ExpressionErrorException(sprintf('Function "%s" not supported.', $function->getName())); + } + + return call_user_func($this->functionTranslators[$function->getName()], $xpath, $function); + } + + /** + * @param XPathExpr $xpath + * @param string $pseudoClass + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function addPseudoClass(XPathExpr $xpath, $pseudoClass) + { + if (!isset($this->pseudoClassTranslators[$pseudoClass])) { + throw new ExpressionErrorException(sprintf('Pseudo-class "%s" not supported.', $pseudoClass)); + } + + return call_user_func($this->pseudoClassTranslators[$pseudoClass], $xpath); + } + + /** + * @param XPathExpr $xpath + * @param string $operator + * @param string $attribute + * @param string $value + * + * @throws ExpressionErrorException + * + * @return XPathExpr + */ + public function addAttributeMatching(XPathExpr $xpath, $operator, $attribute, $value) + { + if (!isset($this->attributeMatchingTranslators[$operator])) { + throw new ExpressionErrorException(sprintf('Attribute matcher operator "%s" not supported.', $operator)); + } + + return call_user_func($this->attributeMatchingTranslators[$operator], $xpath, $attribute, $value); + } + + /** + * @param string $css + * + * @return SelectorNode[] + */ + private function parseSelectors($css) + { + foreach ($this->shortcutParsers as $shortcut) { + $tokens = $shortcut->parse($css); + + if (!empty($tokens)) { + return $tokens; + } + } + + return $this->mainParser->parse($css); + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/TranslatorInterface.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/TranslatorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..b26cf5b0c399184d9a621939a0706429cd20a988 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/TranslatorInterface.php @@ -0,0 +1,45 @@ +<?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\CssSelector\XPath; + +use Symfony\Component\CssSelector\Node\SelectorNode; + +/** + * XPath expression translator interface. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +interface TranslatorInterface +{ + /** + * Translates a CSS selector to an XPath expression. + * + * @param string $cssExpr + * @param string $prefix + * + * @return XPathExpr + */ + public function cssToXPath($cssExpr, $prefix = 'descendant-or-self::'); + + /** + * Translates a parsed selector node to an XPath expression + * + * @param SelectorNode $selector + * @param string $prefix + * + * @return XPathExpr + */ + public function selectorToXPath(SelectorNode $selector, $prefix = 'descendant-or-self::'); +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/XPathExpr.php b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/XPathExpr.php new file mode 100644 index 0000000000000000000000000000000000000000..f0f2f987678ed719c86342a46b31a1682cc976f0 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/XPathExpr.php @@ -0,0 +1,140 @@ +<?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\CssSelector\XPath; + +/** + * XPath expression translator interface. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> + */ +class XPathExpr +{ + /** + * @var string + */ + private $path; + + /** + * @var string + */ + private $element; + + /** + * @var string + */ + private $condition; + + /** + * @param string $path + * @param string $element + * @param string $condition + * @param bool $starPrefix + */ + public function __construct($path = '', $element = '*', $condition = '', $starPrefix = false) + { + $this->path = $path; + $this->element = $element; + $this->condition = $condition; + + if ($starPrefix) { + $this->addStarPrefix(); + } + } + + /** + * @return string + */ + public function getElement() + { + return $this->element; + } + + /** + * @param $condition + * + * @return XPathExpr + */ + public function addCondition($condition) + { + $this->condition = $this->condition ? sprintf('%s and (%s)', $this->condition, $condition) : $condition; + + return $this; + } + + /** + * @return string + */ + public function getCondition() + { + return $this->condition; + } + + /** + * @return XPathExpr + */ + public function addNameTest() + { + if ('*' !== $this->element) { + $this->addCondition('name() = '.Translator::getXpathLiteral($this->element)); + $this->element = '*'; + } + + return $this; + } + + /** + * @return XPathExpr + */ + public function addStarPrefix() + { + $this->path .= '*/'; + + return $this; + } + + /** + * Joins another XPathExpr with a combiner. + * + * @param string $combiner + * @param XPathExpr $expr + * + * @return XPathExpr + */ + public function join($combiner, XPathExpr $expr) + { + $path = $this->__toString().$combiner; + + if ('*/' !== $expr->path) { + $path .= $expr->path; + } + + $this->path = $path; + $this->element = $expr->element; + $this->condition = $expr->condition; + + return $this; + } + + /** + * @return string + */ + public function __toString() + { + $path = $this->path.$this->element; + $condition = null === $this->condition || '' === $this->condition ? '' : '['.$this->condition.']'; + + return $path.$condition; + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/composer.json b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..1ab133f3181456690bab77bfa0180f66c4ff0a99 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/css-selector", + "type": "library", + "description": "Symfony CssSelector Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\CssSelector\\": "" } + }, + "target-dir": "Symfony/Component/CssSelector", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + } +} diff --git a/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/phpunit.xml.dist b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/phpunit.xml.dist new file mode 100644 index 0000000000000000000000000000000000000000..a19dc00b348f1a680903cfaf34ecea5a77633084 --- /dev/null +++ b/core/vendor/symfony/css-selector/Symfony/Component/CssSelector/phpunit.xml.dist @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<phpunit backupGlobals="false" + backupStaticAttributes="false" + colors="true" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + processIsolation="false" + stopOnFailure="false" + syntaxCheck="false" + bootstrap="vendor/autoload.php" +> + <testsuites> + <testsuite name="Symfony CssSelector Component Test Suite"> + <directory>./Tests/</directory> + </testsuite> + </testsuites> + + <filter> + <whitelist> + <directory>./</directory> + <exclude> + <directory>./Resources</directory> + <directory>./Tests</directory> + <directory>./vendor</directory> + </exclude> + </whitelist> + </filter> +</phpunit>