mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
N°2435.5 Manage SwiftMailer lib using composer
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
|
||||
"scssphp/scssphp": "1.0.0",
|
||||
"swiftmailer/swiftmailer": "5.4.9",
|
||||
"pelago/emogrifier": "2.1.0",
|
||||
|
||||
"symfony/console": "3.4.*",
|
||||
"symfony/dotenv": "3.4.*",
|
||||
|
||||
129
composer.lock
generated
129
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "dee6a275ba517b372d3e8d0d3d371a1b",
|
||||
"content-hash": "3009859b319b32e2248fd281abba1df4",
|
||||
"packages": [
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
@@ -55,6 +55,80 @@
|
||||
],
|
||||
"time": "2019-01-03T20:59:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pelago/emogrifier",
|
||||
"version": "v2.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MyIntervals/emogrifier.git",
|
||||
"reference": "40c3d4f475d44ffc7265a760d1dd0e81f579f96f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/40c3d4f475d44ffc7265a760d1dd0e81f579f96f",
|
||||
"reference": "40c3d4f475d44ffc7265a760d1dd0e81f579f96f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*",
|
||||
"php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0",
|
||||
"symfony/css-selector": "^3.4.0 || ^4.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.2.0",
|
||||
"phpmd/phpmd": "^2.6.0",
|
||||
"phpunit/phpunit": "^4.8.0",
|
||||
"squizlabs/php_codesniffer": "^3.3.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Pelago\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "John Reeve",
|
||||
"email": "jreeve@pelagodesign.com"
|
||||
},
|
||||
{
|
||||
"name": "Cameron Brooks"
|
||||
},
|
||||
{
|
||||
"name": "Jaime Prado"
|
||||
},
|
||||
{
|
||||
"name": "Oliver Klee",
|
||||
"email": "github@oliverklee.de"
|
||||
},
|
||||
{
|
||||
"name": "Zoli Szabó",
|
||||
"email": "zoli.szabo+github@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Jake Hotson",
|
||||
"email": "jake@qzdesign.co.uk"
|
||||
}
|
||||
],
|
||||
"description": "Converts CSS styles into inline style attributes in your HTML code",
|
||||
"homepage": "https://www.myintervals.com/emogrifier.php",
|
||||
"keywords": [
|
||||
"css",
|
||||
"email",
|
||||
"pre-processing"
|
||||
],
|
||||
"time": "2018-12-08T13:55:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/cache",
|
||||
"version": "1.0.1",
|
||||
@@ -620,6 +694,59 @@
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2019-07-24T14:46:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
"version": "v3.4.30",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/css-selector.git",
|
||||
"reference": "8ca29297c29b64fb3a1a135e71cb25f67f9fdccf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/8ca29297c29b64fb3a1a135e71cb25f67f9fdccf",
|
||||
"reference": "8ca29297c29b64fb3a1a135e71cb25f67f9fdccf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.5.9|>=7.0.8"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.4-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\CssSelector\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Jean-François Simon",
|
||||
"email": "jeanfrancois.simon@sensiolabs.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony CssSelector Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2019-01-16T09:39:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
"version": "v3.4.30",
|
||||
|
||||
@@ -331,7 +331,6 @@ class EMail
|
||||
{
|
||||
if (($sMimeType === 'text/html') && ($sCustomStyles !== null))
|
||||
{
|
||||
require_once(APPROOT.'lib/emogrifier/Classes/Emogrifier.php');
|
||||
$emogrifier = new \Pelago\Emogrifier($sBody, $sCustomStyles);
|
||||
$sBody = $emogrifier->emogrify(); // Adds html/body tags if not already present
|
||||
}
|
||||
|
||||
@@ -325,6 +325,12 @@ return array(
|
||||
'ParseError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ParseError.php',
|
||||
'ParseyyStackEntry' => $baseDir . '/core/oql/build/PHP/Lempar.php',
|
||||
'ParseyyToken' => $baseDir . '/core/oql/build/PHP/Lempar.php',
|
||||
'Pelago\\Emogrifier' => $vendorDir . '/pelago/emogrifier/src/Emogrifier.php',
|
||||
'Pelago\\Emogrifier\\CssConcatenator' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/CssConcatenator.php',
|
||||
'Pelago\\Emogrifier\\CssInliner' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/CssInliner.php',
|
||||
'Pelago\\Emogrifier\\HtmlProcessor\\AbstractHtmlProcessor' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/AbstractHtmlProcessor.php',
|
||||
'Pelago\\Emogrifier\\HtmlProcessor\\CssToAttributeConverter' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/CssToAttributeConverter.php',
|
||||
'Pelago\\Emogrifier\\HtmlProcessor\\HtmlNormalizer' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/HtmlNormalizer.php',
|
||||
'PortalDispatcher' => $baseDir . '/application/portaldispatcher.class.inc.php',
|
||||
'PortalURLMaker' => $baseDir . '/application/applicationcontext.class.inc.php',
|
||||
'PrintableDataTable' => $baseDir . '/application/datatable.class.inc.php',
|
||||
@@ -839,6 +845,54 @@ return array(
|
||||
'Symfony\\Component\\Console\\Terminal' => $vendorDir . '/symfony/console/Terminal.php',
|
||||
'Symfony\\Component\\Console\\Tester\\ApplicationTester' => $vendorDir . '/symfony/console/Tester/ApplicationTester.php',
|
||||
'Symfony\\Component\\Console\\Tester\\CommandTester' => $vendorDir . '/symfony/console/Tester/CommandTester.php',
|
||||
'Symfony\\Component\\CssSelector\\CssSelectorConverter' => $vendorDir . '/symfony/css-selector/CssSelectorConverter.php',
|
||||
'Symfony\\Component\\CssSelector\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/css-selector/Exception/ExceptionInterface.php',
|
||||
'Symfony\\Component\\CssSelector\\Exception\\ExpressionErrorException' => $vendorDir . '/symfony/css-selector/Exception/ExpressionErrorException.php',
|
||||
'Symfony\\Component\\CssSelector\\Exception\\InternalErrorException' => $vendorDir . '/symfony/css-selector/Exception/InternalErrorException.php',
|
||||
'Symfony\\Component\\CssSelector\\Exception\\ParseException' => $vendorDir . '/symfony/css-selector/Exception/ParseException.php',
|
||||
'Symfony\\Component\\CssSelector\\Exception\\SyntaxErrorException' => $vendorDir . '/symfony/css-selector/Exception/SyntaxErrorException.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\AbstractNode' => $vendorDir . '/symfony/css-selector/Node/AbstractNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\AttributeNode' => $vendorDir . '/symfony/css-selector/Node/AttributeNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\ClassNode' => $vendorDir . '/symfony/css-selector/Node/ClassNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\CombinedSelectorNode' => $vendorDir . '/symfony/css-selector/Node/CombinedSelectorNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\ElementNode' => $vendorDir . '/symfony/css-selector/Node/ElementNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\FunctionNode' => $vendorDir . '/symfony/css-selector/Node/FunctionNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\HashNode' => $vendorDir . '/symfony/css-selector/Node/HashNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\NegationNode' => $vendorDir . '/symfony/css-selector/Node/NegationNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\NodeInterface' => $vendorDir . '/symfony/css-selector/Node/NodeInterface.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\PseudoNode' => $vendorDir . '/symfony/css-selector/Node/PseudoNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\SelectorNode' => $vendorDir . '/symfony/css-selector/Node/SelectorNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\Specificity' => $vendorDir . '/symfony/css-selector/Node/Specificity.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Handler\\CommentHandler' => $vendorDir . '/symfony/css-selector/Parser/Handler/CommentHandler.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Handler\\HandlerInterface' => $vendorDir . '/symfony/css-selector/Parser/Handler/HandlerInterface.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Handler\\HashHandler' => $vendorDir . '/symfony/css-selector/Parser/Handler/HashHandler.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Handler\\IdentifierHandler' => $vendorDir . '/symfony/css-selector/Parser/Handler/IdentifierHandler.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Handler\\NumberHandler' => $vendorDir . '/symfony/css-selector/Parser/Handler/NumberHandler.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Handler\\StringHandler' => $vendorDir . '/symfony/css-selector/Parser/Handler/StringHandler.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Handler\\WhitespaceHandler' => $vendorDir . '/symfony/css-selector/Parser/Handler/WhitespaceHandler.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Parser' => $vendorDir . '/symfony/css-selector/Parser/Parser.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\ParserInterface' => $vendorDir . '/symfony/css-selector/Parser/ParserInterface.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Reader' => $vendorDir . '/symfony/css-selector/Parser/Reader.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Shortcut\\ClassParser' => $vendorDir . '/symfony/css-selector/Parser/Shortcut/ClassParser.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Shortcut\\ElementParser' => $vendorDir . '/symfony/css-selector/Parser/Shortcut/ElementParser.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Shortcut\\EmptyStringParser' => $vendorDir . '/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Shortcut\\HashParser' => $vendorDir . '/symfony/css-selector/Parser/Shortcut/HashParser.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Token' => $vendorDir . '/symfony/css-selector/Parser/Token.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\TokenStream' => $vendorDir . '/symfony/css-selector/Parser/TokenStream.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\Tokenizer' => $vendorDir . '/symfony/css-selector/Parser/Tokenizer/Tokenizer.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerEscaping' => $vendorDir . '/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerPatterns' => $vendorDir . '/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\AbstractExtension' => $vendorDir . '/symfony/css-selector/XPath/Extension/AbstractExtension.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\AttributeMatchingExtension' => $vendorDir . '/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\CombinationExtension' => $vendorDir . '/symfony/css-selector/XPath/Extension/CombinationExtension.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\ExtensionInterface' => $vendorDir . '/symfony/css-selector/XPath/Extension/ExtensionInterface.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\FunctionExtension' => $vendorDir . '/symfony/css-selector/XPath/Extension/FunctionExtension.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\HtmlExtension' => $vendorDir . '/symfony/css-selector/XPath/Extension/HtmlExtension.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\NodeExtension' => $vendorDir . '/symfony/css-selector/XPath/Extension/NodeExtension.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\PseudoClassExtension' => $vendorDir . '/symfony/css-selector/XPath/Extension/PseudoClassExtension.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Translator' => $vendorDir . '/symfony/css-selector/XPath/Translator.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\TranslatorInterface' => $vendorDir . '/symfony/css-selector/XPath/TranslatorInterface.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\XPathExpr' => $vendorDir . '/symfony/css-selector/XPath/XPathExpr.php',
|
||||
'Symfony\\Component\\Debug\\BufferingLogger' => $vendorDir . '/symfony/debug/BufferingLogger.php',
|
||||
'Symfony\\Component\\Debug\\Debug' => $vendorDir . '/symfony/debug/Debug.php',
|
||||
'Symfony\\Component\\Debug\\DebugClassLoader' => $vendorDir . '/symfony/debug/DebugClassLoader.php',
|
||||
|
||||
@@ -23,6 +23,7 @@ return array(
|
||||
'Symfony\\Component\\Dotenv\\' => array($vendorDir . '/symfony/dotenv'),
|
||||
'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\\Console\\' => array($vendorDir . '/symfony/console'),
|
||||
'Symfony\\Component\\Config\\' => array($vendorDir . '/symfony/config'),
|
||||
'Symfony\\Component\\ClassLoader\\' => array($vendorDir . '/symfony/class-loader'),
|
||||
@@ -36,4 +37,5 @@ return array(
|
||||
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
|
||||
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
|
||||
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
|
||||
'Pelago\\' => array($vendorDir . '/pelago/emogrifier/src'),
|
||||
);
|
||||
|
||||
@@ -39,6 +39,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
|
||||
'Symfony\\Component\\Dotenv\\' => 25,
|
||||
'Symfony\\Component\\DependencyInjection\\' => 38,
|
||||
'Symfony\\Component\\Debug\\' => 24,
|
||||
'Symfony\\Component\\CssSelector\\' => 30,
|
||||
'Symfony\\Component\\Console\\' => 26,
|
||||
'Symfony\\Component\\Config\\' => 25,
|
||||
'Symfony\\Component\\ClassLoader\\' => 30,
|
||||
@@ -55,6 +56,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
|
||||
'Psr\\Log\\' => 8,
|
||||
'Psr\\Container\\' => 14,
|
||||
'Psr\\Cache\\' => 10,
|
||||
'Pelago\\' => 7,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -127,6 +129,10 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/debug',
|
||||
),
|
||||
'Symfony\\Component\\CssSelector\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/css-selector',
|
||||
),
|
||||
'Symfony\\Component\\Console\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/console',
|
||||
@@ -179,6 +185,10 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/cache/src',
|
||||
),
|
||||
'Pelago\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/pelago/emogrifier/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixesPsr0 = array (
|
||||
@@ -511,6 +521,12 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
|
||||
'ParseError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ParseError.php',
|
||||
'ParseyyStackEntry' => __DIR__ . '/../..' . '/core/oql/build/PHP/Lempar.php',
|
||||
'ParseyyToken' => __DIR__ . '/../..' . '/core/oql/build/PHP/Lempar.php',
|
||||
'Pelago\\Emogrifier' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier.php',
|
||||
'Pelago\\Emogrifier\\CssConcatenator' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/CssConcatenator.php',
|
||||
'Pelago\\Emogrifier\\CssInliner' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/CssInliner.php',
|
||||
'Pelago\\Emogrifier\\HtmlProcessor\\AbstractHtmlProcessor' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/AbstractHtmlProcessor.php',
|
||||
'Pelago\\Emogrifier\\HtmlProcessor\\CssToAttributeConverter' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/CssToAttributeConverter.php',
|
||||
'Pelago\\Emogrifier\\HtmlProcessor\\HtmlNormalizer' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/HtmlNormalizer.php',
|
||||
'PortalDispatcher' => __DIR__ . '/../..' . '/application/portaldispatcher.class.inc.php',
|
||||
'PortalURLMaker' => __DIR__ . '/../..' . '/application/applicationcontext.class.inc.php',
|
||||
'PrintableDataTable' => __DIR__ . '/../..' . '/application/datatable.class.inc.php',
|
||||
@@ -1025,6 +1041,54 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
|
||||
'Symfony\\Component\\Console\\Terminal' => __DIR__ . '/..' . '/symfony/console/Terminal.php',
|
||||
'Symfony\\Component\\Console\\Tester\\ApplicationTester' => __DIR__ . '/..' . '/symfony/console/Tester/ApplicationTester.php',
|
||||
'Symfony\\Component\\Console\\Tester\\CommandTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandTester.php',
|
||||
'Symfony\\Component\\CssSelector\\CssSelectorConverter' => __DIR__ . '/..' . '/symfony/css-selector/CssSelectorConverter.php',
|
||||
'Symfony\\Component\\CssSelector\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/css-selector/Exception/ExceptionInterface.php',
|
||||
'Symfony\\Component\\CssSelector\\Exception\\ExpressionErrorException' => __DIR__ . '/..' . '/symfony/css-selector/Exception/ExpressionErrorException.php',
|
||||
'Symfony\\Component\\CssSelector\\Exception\\InternalErrorException' => __DIR__ . '/..' . '/symfony/css-selector/Exception/InternalErrorException.php',
|
||||
'Symfony\\Component\\CssSelector\\Exception\\ParseException' => __DIR__ . '/..' . '/symfony/css-selector/Exception/ParseException.php',
|
||||
'Symfony\\Component\\CssSelector\\Exception\\SyntaxErrorException' => __DIR__ . '/..' . '/symfony/css-selector/Exception/SyntaxErrorException.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\AbstractNode' => __DIR__ . '/..' . '/symfony/css-selector/Node/AbstractNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\AttributeNode' => __DIR__ . '/..' . '/symfony/css-selector/Node/AttributeNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\ClassNode' => __DIR__ . '/..' . '/symfony/css-selector/Node/ClassNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\CombinedSelectorNode' => __DIR__ . '/..' . '/symfony/css-selector/Node/CombinedSelectorNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\ElementNode' => __DIR__ . '/..' . '/symfony/css-selector/Node/ElementNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\FunctionNode' => __DIR__ . '/..' . '/symfony/css-selector/Node/FunctionNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\HashNode' => __DIR__ . '/..' . '/symfony/css-selector/Node/HashNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\NegationNode' => __DIR__ . '/..' . '/symfony/css-selector/Node/NegationNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\NodeInterface' => __DIR__ . '/..' . '/symfony/css-selector/Node/NodeInterface.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\PseudoNode' => __DIR__ . '/..' . '/symfony/css-selector/Node/PseudoNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\SelectorNode' => __DIR__ . '/..' . '/symfony/css-selector/Node/SelectorNode.php',
|
||||
'Symfony\\Component\\CssSelector\\Node\\Specificity' => __DIR__ . '/..' . '/symfony/css-selector/Node/Specificity.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Handler\\CommentHandler' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Handler/CommentHandler.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Handler\\HandlerInterface' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Handler/HandlerInterface.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Handler\\HashHandler' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Handler/HashHandler.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Handler\\IdentifierHandler' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Handler/IdentifierHandler.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Handler\\NumberHandler' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Handler/NumberHandler.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Handler\\StringHandler' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Handler/StringHandler.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Handler\\WhitespaceHandler' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Handler/WhitespaceHandler.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Parser' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Parser.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\ParserInterface' => __DIR__ . '/..' . '/symfony/css-selector/Parser/ParserInterface.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Reader' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Reader.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Shortcut\\ClassParser' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Shortcut/ClassParser.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Shortcut\\ElementParser' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Shortcut/ElementParser.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Shortcut\\EmptyStringParser' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Shortcut\\HashParser' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Shortcut/HashParser.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Token' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Token.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\TokenStream' => __DIR__ . '/..' . '/symfony/css-selector/Parser/TokenStream.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\Tokenizer' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Tokenizer/Tokenizer.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerEscaping' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php',
|
||||
'Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerPatterns' => __DIR__ . '/..' . '/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\AbstractExtension' => __DIR__ . '/..' . '/symfony/css-selector/XPath/Extension/AbstractExtension.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\AttributeMatchingExtension' => __DIR__ . '/..' . '/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\CombinationExtension' => __DIR__ . '/..' . '/symfony/css-selector/XPath/Extension/CombinationExtension.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\ExtensionInterface' => __DIR__ . '/..' . '/symfony/css-selector/XPath/Extension/ExtensionInterface.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\FunctionExtension' => __DIR__ . '/..' . '/symfony/css-selector/XPath/Extension/FunctionExtension.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\HtmlExtension' => __DIR__ . '/..' . '/symfony/css-selector/XPath/Extension/HtmlExtension.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\NodeExtension' => __DIR__ . '/..' . '/symfony/css-selector/XPath/Extension/NodeExtension.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Extension\\PseudoClassExtension' => __DIR__ . '/..' . '/symfony/css-selector/XPath/Extension/PseudoClassExtension.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\Translator' => __DIR__ . '/..' . '/symfony/css-selector/XPath/Translator.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\TranslatorInterface' => __DIR__ . '/..' . '/symfony/css-selector/XPath/TranslatorInterface.php',
|
||||
'Symfony\\Component\\CssSelector\\XPath\\XPathExpr' => __DIR__ . '/..' . '/symfony/css-selector/XPath/XPathExpr.php',
|
||||
'Symfony\\Component\\Debug\\BufferingLogger' => __DIR__ . '/..' . '/symfony/debug/BufferingLogger.php',
|
||||
'Symfony\\Component\\Debug\\Debug' => __DIR__ . '/..' . '/symfony/debug/Debug.php',
|
||||
'Symfony\\Component\\Debug\\DebugClassLoader' => __DIR__ . '/..' . '/symfony/debug/DebugClassLoader.php',
|
||||
|
||||
@@ -50,6 +50,82 @@
|
||||
"random"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pelago/emogrifier",
|
||||
"version": "v2.1.0",
|
||||
"version_normalized": "2.1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MyIntervals/emogrifier.git",
|
||||
"reference": "40c3d4f475d44ffc7265a760d1dd0e81f579f96f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/40c3d4f475d44ffc7265a760d1dd0e81f579f96f",
|
||||
"reference": "40c3d4f475d44ffc7265a760d1dd0e81f579f96f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*",
|
||||
"php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0",
|
||||
"symfony/css-selector": "^3.4.0 || ^4.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.2.0",
|
||||
"phpmd/phpmd": "^2.6.0",
|
||||
"phpunit/phpunit": "^4.8.0",
|
||||
"squizlabs/php_codesniffer": "^3.3.2"
|
||||
},
|
||||
"time": "2018-12-08T13:55:46+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1.x-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Pelago\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "John Reeve",
|
||||
"email": "jreeve@pelagodesign.com"
|
||||
},
|
||||
{
|
||||
"name": "Cameron Brooks"
|
||||
},
|
||||
{
|
||||
"name": "Jaime Prado"
|
||||
},
|
||||
{
|
||||
"name": "Oliver Klee",
|
||||
"email": "github@oliverklee.de"
|
||||
},
|
||||
{
|
||||
"name": "Zoli Szabó",
|
||||
"email": "zoli.szabo+github@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Jake Hotson",
|
||||
"email": "jake@qzdesign.co.uk"
|
||||
}
|
||||
],
|
||||
"description": "Converts CSS styles into inline style attributes in your HTML code",
|
||||
"homepage": "https://www.myintervals.com/emogrifier.php",
|
||||
"keywords": [
|
||||
"css",
|
||||
"email",
|
||||
"pre-processing"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "psr/cache",
|
||||
"version": "1.0.1",
|
||||
@@ -635,6 +711,61 @@
|
||||
"description": "Symfony Console Component",
|
||||
"homepage": "https://symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
"version": "v3.4.30",
|
||||
"version_normalized": "3.4.30.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/css-selector.git",
|
||||
"reference": "8ca29297c29b64fb3a1a135e71cb25f67f9fdccf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/8ca29297c29b64fb3a1a135e71cb25f67f9fdccf",
|
||||
"reference": "8ca29297c29b64fb3a1a135e71cb25f67f9fdccf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.5.9|>=7.0.8"
|
||||
},
|
||||
"time": "2019-01-16T09:39:14+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.4-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\CssSelector\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Jean-François Simon",
|
||||
"email": "jeanfrancois.simon@sensiolabs.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony CssSelector Component",
|
||||
"homepage": "https://symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
"version": "v3.4.30",
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
sudo: false
|
||||
|
||||
language: php
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- vendor
|
||||
|
||||
env:
|
||||
global:
|
||||
secure: nOIIWvxRsDlkg+5H21dmVeqvFbweOAk3l3ZiyZO1m5XuGuuZR9yj10oOudee8m0hzJ7e9eoZ+dfB3t8lmK0fTRTB6w0G7RuGiQb89ief3Zhs1vOveYOgS5yfTMRym57iluxsLeCe7AxWmy7+0fWAvx1qL7bKp+THGK9yv/aj9eM=
|
||||
|
||||
php:
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- hhvm
|
||||
|
||||
before_script:
|
||||
- composer install
|
||||
- vendor/bin/phpcs --config-set encoding utf-8
|
||||
- if [ "$GITHUB_COMPOSER_AUTH" ]; then composer config -g github-oauth.github.com $GITHUB_COMPOSER_AUTH; fi
|
||||
|
||||
script:
|
||||
# Run PHP lint on all PHP files.
|
||||
- find Classes/ Tests/ -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l
|
||||
# Check the coding style.
|
||||
- vendor/bin/phpcs --standard=Configuration/PhpCodeSniffer/Standards/Emogrifier/ Classes/ Tests/
|
||||
# Run the unit tests.
|
||||
- vendor/bin/phpunit Tests/
|
||||
@@ -1,92 +0,0 @@
|
||||
# Emogrifier Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
Emogrifier is in a pre-1.0 state. This means that its APIs and behavior are
|
||||
subject to breaking changes without deprecation notices.
|
||||
|
||||
|
||||
## [1.0.0][] (2015-10-15)
|
||||
|
||||
### Added
|
||||
- Add branch alias ([#231](https://github.com/jjriv/emogrifier/pull/231))
|
||||
- Remove media queries which do not impact the document
|
||||
([#217](https://github.com/jjriv/emogrifier/pull/217))
|
||||
- Allow elements to be excluded from emogrification
|
||||
([#215](https://github.com/jjriv/emogrifier/pull/215))
|
||||
- Handle !important ([#214](https://github.com/jjriv/emogrifier/pull/214))
|
||||
- emogrifyBodyContent() method
|
||||
([#206](https://github.com/jjriv/emogrifier/pull/206))
|
||||
- Cache combinedStyles ([#211](https://github.com/jjriv/emogrifier/pull/211))
|
||||
- Allow user to define media types to keep
|
||||
([#200](https://github.com/jjriv/emogrifier/pull/200))
|
||||
- Ignore invalid CSS selectors
|
||||
([#194](https://github.com/jjriv/emogrifier/pull/194))
|
||||
- isRemoveDisplayNoneEnabled option
|
||||
([#162](https://github.com/jjriv/emogrifier/pull/162))
|
||||
- Allow disabling of "inline style" and "style block" parsing
|
||||
([#156](https://github.com/jjriv/emogrifier/pull/156))
|
||||
- Preserve @media if necessary
|
||||
([#62](https://github.com/jjriv/emogrifier/pull/62))
|
||||
- Add extraction of style blocks within the HTML
|
||||
- Add several new pseudo-selectors (first-child, last-child, nth-child,
|
||||
and nth-of-type)
|
||||
|
||||
|
||||
### Changed
|
||||
- Make HTML5 the default document type
|
||||
([#245](https://github.com/jjriv/emogrifier/pull/245))
|
||||
- Make copyCssWithMediaToStyleNode private
|
||||
([#218](https://github.com/jjriv/emogrifier/pull/218))
|
||||
- Stop encoding umlauts and dollar signs
|
||||
([#170](https://github.com/jjriv/emogrifier/pull/170))
|
||||
- Convert the classes to namespaces
|
||||
([#41](https://github.com/jjriv/emogrifier/pull/41))
|
||||
|
||||
|
||||
### Deprecated
|
||||
- Support for PHP 5.4 will be removed in Emogrifier 2.0.
|
||||
|
||||
|
||||
### Removed
|
||||
- Drop support for PHP 5.3
|
||||
([#114](https://github.com/jjriv/emogrifier/pull/114))
|
||||
- Support for character sets other than UTF-8 was removed.
|
||||
|
||||
|
||||
### Fixed
|
||||
- Fix failing tests on Windows due to line endings
|
||||
([#263](https://github.com/jjriv/emogrifier/pull/263))
|
||||
- Parsing CSS declaration blocks
|
||||
([#261](https://github.com/jjriv/emogrifier/pull/261))
|
||||
- Fix first-child and last-child selectors
|
||||
([#257](https://github.com/jjriv/emogrifier/pull/257))
|
||||
- Fix parsing of CSS for data URIs
|
||||
([#243](https://github.com/jjriv/emogrifier/pull/243))
|
||||
- Fix multi-line media queries
|
||||
([#241](https://github.com/jjriv/emogrifier/pull/241))
|
||||
- Keep CSS media queries even if followed by CSS comments
|
||||
([#201](https://github.com/jjriv/emogrifier/pull/201))
|
||||
- Fix CSS selectors with exact attribute only
|
||||
([#197](https://github.com/jjriv/emogrifier/pull/197))
|
||||
- Properly handle UTF-8 characters and entities
|
||||
([#189](https://github.com/jjriv/emogrifier/pull/189))
|
||||
- Add mbstring extension to composer.json
|
||||
([#93](https://github.com/jjriv/emogrifier/pull/93))
|
||||
- Prevent incorrectly capitalized CSS selectors from being stripped
|
||||
([#85](https://github.com/jjriv/emogrifier/pull/85))
|
||||
- Fix CSS selectors with exact attribute only
|
||||
([#197](https://github.com/jjriv/emogrifier/pull/197))
|
||||
- Wrong selector extraction from minified CSS
|
||||
([#69](https://github.com/jjriv/emogrifier/pull/69))
|
||||
- Restore libxml error handler state after clearing
|
||||
([#65](https://github.com/jjriv/emogrifier/pull/65))
|
||||
- Ignore all warnings produced by DOMDocument::loadHTML()
|
||||
([#63](https://github.com/jjriv/emogrifier/pull/63))
|
||||
- Style tags in HTML cause an Xpath invalid query error
|
||||
([#60](https://github.com/jjriv/emogrifier/pull/60))
|
||||
- Fix PHP warnings with PHP 5.5
|
||||
([#26](https://github.com/jjriv/emogrifier/pull/26))
|
||||
- Make removal of invisible nodes operate in a case-insensitive manner
|
||||
- Fix a bug that was overwriting existing inline styles from the original HTML
|
||||
@@ -1,78 +0,0 @@
|
||||
# Contributing to Emogrifier
|
||||
|
||||
Those that wish to contribute bug fixes, new features, refactorings and
|
||||
clean-up to Emogrifier are more than welcome.
|
||||
|
||||
When you contribute, please take the following things into account:
|
||||
|
||||
|
||||
## General workflow
|
||||
|
||||
After you have submitted a pull request, the Emogrifier team will review your
|
||||
changes. This will probably result in quite a few comments on ways to improve
|
||||
your pull request. The Emogrifier project receives contributions from
|
||||
developers around the world, so we need the code to be the most consistent,
|
||||
readable, and maintainable that it can be.
|
||||
|
||||
Please do not feel frustrated by this - instead please view this both as our
|
||||
contribution to your pull request as well as a way to learn more about
|
||||
improving code quality.
|
||||
|
||||
If you would like to know whether an idea would fit in the general strategy of
|
||||
the Emogrifier project or would like to get feedback on the best architecture
|
||||
for your ideas, we propose you open a ticket first and discuss your ideas there
|
||||
first before investing a lot of time in writing code.
|
||||
|
||||
|
||||
## Install the development dependencies
|
||||
|
||||
To install the development dependencies (PHPUnit and PHP_CodeSniffer), please
|
||||
run the following command:
|
||||
|
||||
composer install
|
||||
|
||||
|
||||
## Unit-test your changes
|
||||
|
||||
Please cover all changes with unit tests and make sure that your code does not
|
||||
break any existing tests. We will only merge pull request that include full
|
||||
code coverage of the fixed bugs and the new features.
|
||||
|
||||
To run the existing PHPUnit tests, run this command:
|
||||
|
||||
vendor/bin/phpunit Tests/
|
||||
|
||||
|
||||
## Coding Style
|
||||
|
||||
Please use the same coding style (PSR-2) as the rest of the code. Indentation
|
||||
is four spaces.
|
||||
|
||||
We will only merge pull requests that follow the project's coding style.
|
||||
|
||||
Please check your code with the provided PHP_CodeSniffer standard:
|
||||
|
||||
vendor/bin/phpcs --standard=Configuration/PhpCodeSniffer/Standards/Emogrifier/ Classes/ Tests/
|
||||
|
||||
Please make your code clean, well-readable and easy to understand.
|
||||
|
||||
If you add new methods or fields, please add proper PHPDoc for the new
|
||||
methods/fields. Please use grammatically correct, complete sentences in the
|
||||
code documentation.
|
||||
|
||||
|
||||
## Git commits
|
||||
|
||||
Git commits should have a <= 50 character summary, optionally followed by a
|
||||
blank line and a more in depth description of 79 characters per line.
|
||||
|
||||
[Please squash related commits together](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html).
|
||||
|
||||
If you already have a commit and work on it, you can also
|
||||
[amend the first commit](https://nathanhoad.net/git-amend-your-last-commit).
|
||||
|
||||
Please use grammatically correct, complete sentences in the commit messages.
|
||||
|
||||
Also, please prefix the subject line of the commit message with either
|
||||
[FEATURE], [TASK], [BUGFIX] OR [CLEANUP]. This makes it faster to see what
|
||||
a commit is about.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +0,0 @@
|
||||
Emogrifier is copyright (c) 2008-2014 Pelago and licensed under the MIT license.
|
||||
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
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.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"name": "pelago/emogrifier",
|
||||
"description": "Converts CSS styles into inline style attributes in your HTML code",
|
||||
"tags": ["email", "css", "pre-processing"],
|
||||
"license": "MIT",
|
||||
"homepage": "http://www.pelagodesign.com/sidecar/emogrifier/",
|
||||
"authors": [
|
||||
{
|
||||
"name": "John Reeve",
|
||||
"email": "jreeve@pelagodesign.com"
|
||||
},
|
||||
{
|
||||
"name": "Cameron Brooks"
|
||||
},
|
||||
{
|
||||
"name": "Jaime Prado"
|
||||
},
|
||||
{
|
||||
"name": "Oliver Klee",
|
||||
"email": "typo3-coding@oliverklee.de"
|
||||
},
|
||||
{
|
||||
"name": "Roman Ožana",
|
||||
"email": "ozana@omdesign.cz"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.4.0",
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"squizlabs/php_codesniffer": "2.3.4",
|
||||
"typo3-ci/typo3sniffpool": "2.1.1",
|
||||
"phpunit/phpunit": "4.8.11"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Pelago\\": "Classes/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#
|
||||
# The following source files are not re-distributed with the "build" of the application
|
||||
# since they are used solely for constructing other files during the build process
|
||||
#
|
||||
Test
|
||||
129
lib/pelago/emogrifier/.github/CONTRIBUTING.md
vendored
Normal file
129
lib/pelago/emogrifier/.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
# Contributing to Emogrifier
|
||||
|
||||
Those that wish to contribute bug fixes, new features, refactorings and
|
||||
clean-up to Emogrifier are more than welcome.
|
||||
|
||||
When you contribute, please take the following things into account:
|
||||
|
||||
|
||||
## Contributor Code of Conduct
|
||||
|
||||
Please note that this project is released with a
|
||||
[Contributor Code of Conduct](../CODE_OF_CONDUCT.md). By participating in this
|
||||
project, you agree to abide by its terms.
|
||||
|
||||
|
||||
## General workflow
|
||||
|
||||
This is the workflow for contributing changes to Emogrifier:
|
||||
|
||||
1. [Fork the Emogrifier Git repository](https://guides.github.com/activities/forking/).
|
||||
2. Clone your forked repository and
|
||||
[install the development dependencies](#install-the-development-dependencies).
|
||||
3. Add a local remote "upstream" so you will be able to
|
||||
[synchronize your fork with the original Emogrifier repository](https://help.github.com/articles/syncing-a-fork/).
|
||||
4. Create a local branch for your changes.
|
||||
5. [Add unit tests for your changes](#unit-test-your-changes).
|
||||
These tests should fail without your changes.
|
||||
6. Add your changes. Your added unit tests now should pass, and no other tests
|
||||
should be broken. Check that your changes follow the same
|
||||
[coding style](#coding-style) as the rest of the project.
|
||||
7. Add a changelog entry.
|
||||
8. [Commit](#git-commits) and push your changes.
|
||||
9. [Create a pull request](https://help.github.com/articles/about-pull-requests/)
|
||||
for your changes. Check that the Travis build is green. (If it is not, fix the
|
||||
problems listed by Travis.)
|
||||
10. [Request a review](https://help.github.com/articles/about-pull-request-reviews/)
|
||||
from @oliverklee.
|
||||
11. Together with him, polish your changes until they are ready to be merged.
|
||||
|
||||
|
||||
## About code reviews
|
||||
|
||||
After you have submitted a pull request, the Emogrifier team will review your
|
||||
changes. This will probably result in quite a few comments on ways to improve
|
||||
your pull request. The Emogrifier project receives contributions from
|
||||
developers around the world, so we need the code to be the most consistent,
|
||||
readable, and maintainable that it can be.
|
||||
|
||||
Please do not feel frustrated by this - instead please view this both as our
|
||||
contribution to your pull request as well as a way to learn more about
|
||||
improving code quality.
|
||||
|
||||
If you would like to know whether an idea would fit in the general strategy of
|
||||
the Emogrifier project or would like to get feedback on the best architecture
|
||||
for your ideas, we propose you open a ticket first and discuss your ideas there
|
||||
first before investing a lot of time in writing code.
|
||||
|
||||
|
||||
## Install the development dependencies
|
||||
|
||||
To install the development dependencies (PHPUnit and PHP_CodeSniffer), please
|
||||
run the following commands:
|
||||
|
||||
```shell
|
||||
composer install
|
||||
composer require --dev slevomat/coding-standard:^4.0
|
||||
```
|
||||
|
||||
Note that the development dependencies (in particular, for PHP_CodeSniffer)
|
||||
require PHP 7.0 or later. The second command installs the PHP_CodeSniffer
|
||||
dependencies and should be omitted if specifically testing against an earlier
|
||||
version of PHP, however you will not be able to run the static code analysis.
|
||||
|
||||
|
||||
## Unit-test your changes
|
||||
|
||||
Please cover all changes with unit tests and make sure that your code does not
|
||||
break any existing tests. We will only merge pull requests that include full
|
||||
code coverage of the fixed bugs and the new features.
|
||||
|
||||
To run the existing PHPUnit tests, run this command:
|
||||
|
||||
```shell
|
||||
composer ci:tests:unit
|
||||
```
|
||||
|
||||
|
||||
## Coding Style
|
||||
|
||||
Please use the same coding style (PSR-2) as the rest of the code. Indentation
|
||||
is four spaces.
|
||||
|
||||
We will only merge pull requests that follow the project's coding style.
|
||||
|
||||
Please check your code with the provided static code analysis tools:
|
||||
|
||||
```shell
|
||||
composer ci:static
|
||||
```
|
||||
|
||||
Please make your code clean, well-readable and easy to understand.
|
||||
|
||||
If you add new methods or fields, please add proper PHPDoc for the new
|
||||
methods/fields. Please use grammatically correct, complete sentences in the
|
||||
code documentation.
|
||||
|
||||
You can autoformat your code using the following command:
|
||||
|
||||
```shell
|
||||
composer php:fix
|
||||
```
|
||||
|
||||
|
||||
## Git commits
|
||||
|
||||
Commit message should have a <= 50 character summary, optionally followed by a
|
||||
blank line and a more in depth description of 79 characters per line.
|
||||
|
||||
Please use grammatically correct, complete sentences in the commit messages.
|
||||
|
||||
Also, please prefix the subject line of the commit message with either
|
||||
[FEATURE], [TASK], [BUGFIX] OR [CLEANUP]. This makes it faster to see what
|
||||
a commit is about.
|
||||
|
||||
|
||||
## Creating pull requests (PRs)
|
||||
|
||||
When you create a pull request, please
|
||||
[make your PR editable](https://github.com/blog/2247-improving-collaboration-with-forks).
|
||||
@@ -20,5 +20,6 @@
|
||||
.TemporaryItems
|
||||
.webprj
|
||||
nbproject
|
||||
/.php_cs.cache
|
||||
/vendor/
|
||||
composer.lock
|
||||
65
lib/pelago/emogrifier/.travis.yml
Normal file
65
lib/pelago/emogrifier/.travis.yml
Normal file
@@ -0,0 +1,65 @@
|
||||
sudo: false
|
||||
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- vendor
|
||||
- $HOME/.composer/cache
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- DEPENDENCIES_PREFERENCE="--prefer-lowest"
|
||||
- DEPENDENCIES_PREFERENCE=""
|
||||
|
||||
before_install:
|
||||
- phpenv config-rm xdebug.ini || echo "xdebug not available"
|
||||
|
||||
install:
|
||||
- >
|
||||
export IGNORE_PLATFORM_REQS="$(composer php:version |grep -q '^7.3' && printf -- --ignore-platform-reqs)";
|
||||
echo;
|
||||
echo "Updating the dependencies";
|
||||
composer update $IGNORE_PLATFORM_REQS --with-dependencies $DEPENDENCIES_PREFERENCE;
|
||||
composer show;
|
||||
|
||||
script:
|
||||
- >
|
||||
echo;
|
||||
echo "Validating the composer.json";
|
||||
composer validate --no-check-all --no-check-lock --strict;
|
||||
|
||||
- >
|
||||
echo;
|
||||
echo "Linting all PHP files";
|
||||
composer ci:php:lint;
|
||||
|
||||
- >
|
||||
echo;
|
||||
echo "Running the unit tests";
|
||||
composer ci:tests:unit;
|
||||
|
||||
- >
|
||||
echo;
|
||||
echo "Running PHPMD";
|
||||
composer ci:php:md;
|
||||
|
||||
- >
|
||||
echo;
|
||||
function version_gte() { test "$(printf '%s\n' "$@" | sort -n -t. -r | head -n 1)" = "$1"; };
|
||||
if version_gte $(composer php:version) 7; then
|
||||
echo "Installing slevomat/coding-standard only for PHP 7.x";
|
||||
composer require $IGNORE_PLATFORM_REQS --dev slevomat/coding-standard:^4.0 $DEPENDENCIES_PREFERENCE;
|
||||
echo "Running PHP_CodeSniffer";
|
||||
composer ci:php:sniff;
|
||||
else
|
||||
echo "Skipped PHP_CodeSniffer due to insufficient PHP version: $(composer php:version)";
|
||||
fi;
|
||||
312
lib/pelago/emogrifier/CHANGELOG.md
Normal file
312
lib/pelago/emogrifier/CHANGELOG.md
Normal file
@@ -0,0 +1,312 @@
|
||||
# Emogrifier Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## x.y.z
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Deprecated
|
||||
|
||||
### Removed
|
||||
|
||||
### Fixed
|
||||
|
||||
## 2.1.0
|
||||
|
||||
### Added
|
||||
- PHP 7.3 support
|
||||
([#638](https://github.com/MyIntervals/emogrifier/pull/638))
|
||||
- Allow PHP 7.3 in `composer.json`
|
||||
- Test in Travis for PHP 7.3
|
||||
- Add a `renderBodyContent()` method
|
||||
([#633](https://github.com/MyIntervals/emogrifier/pull/633))
|
||||
- Add a `getDomDocument()` method
|
||||
([#630](https://github.com/MyIntervals/emogrifier/pull/630))
|
||||
- Add a Composer script for PHP CS Fixer
|
||||
([#607](https://github.com/MyIntervals/emogrifier/pull/607))
|
||||
- Copy matching rules with dynamic pseudo-classes or pseudo-elements in
|
||||
selectors to the style element
|
||||
([#280](https://github.com/MyIntervals/emogrifier/issues/280),
|
||||
[#562](https://github.com/MyIntervals/emogrifier/pull/562),
|
||||
[#567](https://github.com/MyIntervals/emogrifier/pull/567))
|
||||
- Add a CssToAttributeConverter
|
||||
([#546](https://github.com/jjriv/emogrifier/pull/546))
|
||||
- Expose the DOMDocument in AbstractHtmlProcessor
|
||||
([#520](https://github.com/jjriv/emogrifier/pull/520))
|
||||
- Add an HtmlNormalizer class
|
||||
([#513](https://github.com/jjriv/emogrifier/pull/513),
|
||||
[#516](https://github.com/jjriv/emogrifier/pull/516))
|
||||
- Add a CssInliner class
|
||||
([#514](https://github.com/jjriv/emogrifier/pull/514),
|
||||
[#522](https://github.com/jjriv/emogrifier/pull/522))
|
||||
- Composer scripts for the various CI build steps
|
||||
- Validate the composer.json on Travis
|
||||
([#476](https://github.com/jjriv/emogrifier/pull/476))
|
||||
|
||||
### Changed
|
||||
- Mark the work-in-progress classes as `@internal`
|
||||
([#640](https://github.com/MyIntervals/emogrifier/pull/640))
|
||||
- Remove the unprocessable tags from the DOM, not from the raw HTML
|
||||
([#627](https://github.com/MyIntervals/emogrifier/pull/627))
|
||||
- Reject empty HTML in `setHtml()`
|
||||
([#622](https://github.com/MyIntervals/emogrifier/pull/622))
|
||||
- Stop passing the DOM document around
|
||||
([#618](https://github.com/MyIntervals/emogrifier/pull/618))
|
||||
- Improve performance by using explicit namespaces for PHP functions
|
||||
([#573](https://github.com/MyIntervals/emogrifier/pull/573),
|
||||
[#576](https://github.com/MyIntervals/emogrifier/pull/576))
|
||||
- Add type hint checking to the code sniffs
|
||||
([#566](https://github.com/MyIntervals/emogrifier/pull/566))
|
||||
- Check the code with PHPMD
|
||||
([#561](https://github.com/jjriv/emogrifier/pull/561))
|
||||
- Add the cyclomatic complexity to the checked code sniffs
|
||||
([#558](https://github.com/jjriv/emogrifier/pull/558))
|
||||
- Use the Symfony CSS selector component
|
||||
([#540](https://github.com/jjriv/emogrifier/pull/540))
|
||||
|
||||
### Deprecated
|
||||
- Support for PHP 5.5 will be removed in Emogrifier 3.0.
|
||||
- Support for PHP 5.6 will be removed in Emogrifier 4.0.
|
||||
- The removal of invisible nodes will be removed in Emogrifier 3.0.
|
||||
([#473](https://github.com/jjriv/emogrifier/pull/473))
|
||||
- Converting CSS styles to (non-CSS) HTML attributes will be removed
|
||||
in Emogrifier 3.0. Please use the new CssToAttributeConverter instead.
|
||||
([#474](https://github.com/jjriv/emogrifier/pull/474))
|
||||
- Emogrifier 3.x.y will be the last release that supports usage without
|
||||
Composer (i.e., you can still require the class file).
|
||||
Starting with version 4.0, Emogrifier will only work with Composer.
|
||||
- The Emogrifier class will be superseded by CssInliner class in
|
||||
Emogrifier 3.0. For this, the Emogrifier class will be deprecated for
|
||||
version 3.0 and removed for version 4.0.
|
||||
|
||||
### Removed
|
||||
- Drop the `@version` PHPDoc annotations
|
||||
([#637](https://github.com/MyIntervals/emogrifier/pull/637))
|
||||
- Drop the destructors
|
||||
([#619](https://github.com/MyIntervals/emogrifier/pull/619))
|
||||
|
||||
### Fixed
|
||||
- Add required XML PHP extension to `composer.json`
|
||||
([#614](https://github.com/MyIntervals/emogrifier/pull/614))
|
||||
- Add required DOM PHP extension to `composer.json`
|
||||
([#595](https://github.com/MyIntervals/emogrifier/pull/595))
|
||||
- Escape hyphens in regular expressions
|
||||
([#588](https://github.com/MyIntervals/emogrifier/pull/588))
|
||||
- Fix Travis for PHP 5.x
|
||||
([#589](https://github.com/MyIntervals/emogrifier/pull/589))
|
||||
- Allow CSS between empty `@media` rule and another `@media` rule
|
||||
([#534](https://github.com/MyIntervals/emogrifier/pull/534))
|
||||
- Allow additional whitespace in media-query-list of disallowed `@media` rules
|
||||
([#532](https://github.com/MyIntervals/emogrifier/pull/532))
|
||||
- Allow multiple minified `@import` rules in the CSS without error (note:
|
||||
`@import`s are currently ignored,
|
||||
[#527](https://github.com/MyIntervals/emogrifier/pull/527))
|
||||
- Style property ordering when multiple mixed individual and shorthand
|
||||
properties apply ([#511](https://github.com/MyIntervals/emogrifier/pull/511),
|
||||
[#508](https://github.com/MyIntervals/emogrifier/issues/508))
|
||||
- Calculation of selector precedence for selectors involving pseudo-classes
|
||||
and/or attributes ([#502](https://github.com/MyIntervals/emogrifier/pull/502))
|
||||
- Allow `@charset` in the CSS without error (note: its value is currently
|
||||
ignored, [#507](https://github.com/MyIntervals/emogrifier/pull/507))
|
||||
- Allow attribute selectors in descendants
|
||||
([#506](https://github.com/MyIntervals/emogrifier/pull/506),
|
||||
[#381](https://github.com/MyIntervals/emogrifier/issues/381))
|
||||
- Allow adjacent sibling CSS selector combinator in minified CSS
|
||||
([#505](https://github.com/MyIntervals/emogrifier/pull/505))
|
||||
- Allow CSS property values containing newlines
|
||||
([#504](https://github.com/MyIntervals/emogrifier/pull/504))
|
||||
|
||||
## 2.0.0
|
||||
|
||||
### Added
|
||||
- Support for CSS :not() selector
|
||||
([#431](https://github.com/jjriv/emogrifier/pull/431))
|
||||
- Automatically remove !important annotations from final inline style declarations
|
||||
([#420](https://github.com/MyIntervals/emogrifier/pull/420))
|
||||
- Automatically move `<style>` block from `<head>` to `<body>`
|
||||
([#396](https://github.com/MyIntervals/emogrifier/pull/396))
|
||||
- PHP 7.2 support ([#398](https://github.com/MyIntervals/emogrifier/pull/398))
|
||||
- Allow PHP 7.2 in `composer.json`, cleaner PHP version constraint
|
||||
- Test in Travis for PHP 7.2
|
||||
- Debug mode. Throw debug exceptions only if debug is active.
|
||||
([#392](https://github.com/MyIntervals/emogrifier/pull/392))
|
||||
|
||||
### Changed
|
||||
- Test with latest and oldest dependencies on Travis
|
||||
([#463](https://github.com/MyIntervals/emogrifier/pull/463))
|
||||
- Always enable the debug mode in the tests
|
||||
([#448](https://github.com/MyIntervals/emogrifier/pull/448))
|
||||
- Optimize the string operations
|
||||
([#430](https://github.com/MyIntervals/emogrifier/pull/430))
|
||||
|
||||
### Deprecated
|
||||
- Support for PHP 5.5 will be removed in Emogrifier 3.0.
|
||||
- Support for PHP 5.6 will be removed in Emogrifier 4.0.
|
||||
|
||||
### Removed
|
||||
- Drop support for PHP 5.4
|
||||
([#422](https://github.com/MyIntervals/emogrifier/pull/422))
|
||||
- Drop support for HHVM
|
||||
([#386](https://github.com/MyIntervals/emogrifier/pull/386))
|
||||
|
||||
### Fixed
|
||||
- Handle invalid/unrecognized selectors in media query blocks
|
||||
([#442](https://github.com/MyIntervals/emogrifier/pull/442))
|
||||
- Throw (the correct) exception for invalid excluded selectors
|
||||
([#437](https://github.com/MyIntervals/emogrifier/pull/437))
|
||||
- emogrifyBody must not encode umlaut entities
|
||||
([#414](https://github.com/MyIntervals/emogrifier/pull/414))
|
||||
- Fix mapped HTML attribute values
|
||||
([#405](https://github.com/MyIntervals/emogrifier/pull/405))
|
||||
- Make sure the HTML always has a BODY element
|
||||
([#410](https://github.com/MyIntervals/emogrifier/pull/410))
|
||||
- Make inline style priority higher than css block priority
|
||||
([#404](https://github.com/MyIntervals/emogrifier/pull/404))
|
||||
- Fix media regex parsing
|
||||
([#402](https://github.com/MyIntervals/emogrifier/pull/402))
|
||||
- Silence purposefully ignored PHP Warnings
|
||||
([#400](https://github.com/MyIntervals/emogrifier/pull/400))
|
||||
|
||||
## 1.2.0 (2017-03-02)
|
||||
|
||||
### Added
|
||||
- Handling invalid xPath expression warnings
|
||||
([#361](https://github.com/MyIntervals/emogrifier/pull/361))
|
||||
|
||||
### Deprecated
|
||||
- Support for PHP 5.5 will be removed in Emogrifier 3.0.
|
||||
- Support for PHP 5.4 will be removed in Emogrifier 2.0.
|
||||
|
||||
### Fixed
|
||||
- Allow colon (`:`) and semi-colon (`;`) when using the `*=` selector
|
||||
([#371](https://github.com/MyIntervals/emogrifier/pull/371))
|
||||
- Ignore "auto" width and height
|
||||
([#365](https://github.com/MyIntervals/emogrifier/pull/365))
|
||||
|
||||
## 1.1.0 (2016-09-18)
|
||||
|
||||
### Added
|
||||
- Add support for PHP 7.1
|
||||
([#342](https://github.com/MyIntervals/emogrifier/pull/342))
|
||||
- Support the attr|=value selector
|
||||
([#337](https://github.com/MyIntervals/emogrifier/pull/337))
|
||||
- Support the attr*=value selector
|
||||
([#330](https://github.com/MyIntervals/emogrifier/pull/330))
|
||||
- Support the attr$=value selector
|
||||
([#329](https://github.com/MyIntervals/emogrifier/pull/329))
|
||||
- Support the attr^=value selector
|
||||
([#324](https://github.com/MyIntervals/emogrifier/pull/324))
|
||||
- Support the attr~=value selector
|
||||
([#323](https://github.com/MyIntervals/emogrifier/pull/323))
|
||||
- Add CSS to HTML attribute mapper
|
||||
([#288](https://github.com/MyIntervals/emogrifier/pull/288))
|
||||
|
||||
### Changed
|
||||
- Remove composer dependency from PHP mbstring extension
|
||||
(Actual code dependency were removed a lot of time ago)
|
||||
([#295](https://github.com/MyIntervals/emogrifier/pull/295))
|
||||
|
||||
### Deprecated
|
||||
- Support for PHP 5.5 will be removed in Emogrifier 3.0.
|
||||
- Support for PHP 5.4 will be removed in Emogrifier 2.0.
|
||||
|
||||
### Fixed
|
||||
- Method emogrifyBodyContent() doesn't keeps utf8 umlauts
|
||||
([#349](https://github.com/MyIntervals/emogrifier/pull/349))
|
||||
- Ignore value with words more than one in the attribute selector
|
||||
([#327](https://github.com/MyIntervals/emogrifier/pull/327))
|
||||
- Ignore spaces around the > in the direct child selector
|
||||
([#322](https://github.com/MyIntervals/emogrifier/pull/322))
|
||||
- Ignore empty media queries
|
||||
([#307](https://github.com/MyIntervals/emogrifier/pull/307))
|
||||
([#237](https://github.com/MyIntervals/emogrifier/issues/237))
|
||||
- Ignore pseudo-class when combined with pseudo-element
|
||||
([#308](https://github.com/MyIntervals/emogrifier/pull/308))
|
||||
- First-child and last-child selectors are broken
|
||||
([#293](https://github.com/MyIntervals/emogrifier/pull/293))
|
||||
- Second !important rule needs to overwrite the first one
|
||||
([#292](https://github.com/MyIntervals/emogrifier/pull/292))
|
||||
|
||||
## 1.0.0 (2015-10-15)
|
||||
|
||||
### Added
|
||||
- Add branch alias ([#231](https://github.com/MyIntervals/emogrifier/pull/231))
|
||||
- Remove media queries which do not impact the document
|
||||
([#217](https://github.com/MyIntervals/emogrifier/pull/217))
|
||||
- Allow elements to be excluded from emogrification
|
||||
([#215](https://github.com/MyIntervals/emogrifier/pull/215))
|
||||
- Handle !important ([#214](https://github.com/MyIntervals/emogrifier/pull/214))
|
||||
- emogrifyBodyContent() method
|
||||
([#206](https://github.com/MyIntervals/emogrifier/pull/206))
|
||||
- Cache combinedStyles ([#211](https://github.com/MyIntervals/emogrifier/pull/211))
|
||||
- Allow user to define media types to keep
|
||||
([#200](https://github.com/MyIntervals/emogrifier/pull/200))
|
||||
- Ignore invalid CSS selectors
|
||||
([#194](https://github.com/MyIntervals/emogrifier/pull/194))
|
||||
- isRemoveDisplayNoneEnabled option
|
||||
([#162](https://github.com/MyIntervals/emogrifier/pull/162))
|
||||
- Allow disabling of "inline style" and "style block" parsing
|
||||
([#156](https://github.com/MyIntervals/emogrifier/pull/156))
|
||||
- Preserve @media if necessary
|
||||
([#62](https://github.com/MyIntervals/emogrifier/pull/62))
|
||||
- Add extraction of style blocks within the HTML
|
||||
- Add several new pseudo-selectors (first-child, last-child, nth-child,
|
||||
and nth-of-type)
|
||||
|
||||
### Changed
|
||||
- Make HTML5 the default document type
|
||||
([#245](https://github.com/MyIntervals/emogrifier/pull/245))
|
||||
- Make copyCssWithMediaToStyleNode private
|
||||
([#218](https://github.com/MyIntervals/emogrifier/pull/218))
|
||||
- Stop encoding umlauts and dollar signs
|
||||
([#170](https://github.com/MyIntervals/emogrifier/pull/170))
|
||||
- Convert the classes to namespaces
|
||||
([#41](https://github.com/MyIntervals/emogrifier/pull/41))
|
||||
|
||||
### Deprecated
|
||||
- Support for PHP 5.4 will be removed in Emogrifier 2.0.
|
||||
|
||||
### Removed
|
||||
- Drop support for PHP 5.3
|
||||
([#114](https://github.com/MyIntervals/emogrifier/pull/114))
|
||||
- Support for character sets other than UTF-8 was removed.
|
||||
|
||||
### Fixed
|
||||
- Fix failing tests on Windows due to line endings
|
||||
([#263](https://github.com/MyIntervals/emogrifier/pull/263))
|
||||
- Parsing CSS declaration blocks
|
||||
([#261](https://github.com/MyIntervals/emogrifier/pull/261))
|
||||
- Fix first-child and last-child selectors
|
||||
([#257](https://github.com/MyIntervals/emogrifier/pull/257))
|
||||
- Fix parsing of CSS for data URIs
|
||||
([#243](https://github.com/MyIntervals/emogrifier/pull/243))
|
||||
- Fix multi-line media queries
|
||||
([#241](https://github.com/MyIntervals/emogrifier/pull/241))
|
||||
- Keep CSS media queries even if followed by CSS comments
|
||||
([#201](https://github.com/MyIntervals/emogrifier/pull/201))
|
||||
- Fix CSS selectors with exact attribute only
|
||||
([#197](https://github.com/MyIntervals/emogrifier/pull/197))
|
||||
- Properly handle UTF-8 characters and entities
|
||||
([#189](https://github.com/MyIntervals/emogrifier/pull/189))
|
||||
- Add mbstring extension to composer.json
|
||||
([#93](https://github.com/MyIntervals/emogrifier/pull/93))
|
||||
- Prevent incorrectly capitalized CSS selectors from being stripped
|
||||
([#85](https://github.com/MyIntervals/emogrifier/pull/85))
|
||||
- Fix CSS selectors with exact attribute only
|
||||
([#197](https://github.com/MyIntervals/emogrifier/pull/197))
|
||||
- Wrong selector extraction from minified CSS
|
||||
([#69](https://github.com/MyIntervals/emogrifier/pull/69))
|
||||
- Restore libxml error handler state after clearing
|
||||
([#65](https://github.com/MyIntervals/emogrifier/pull/65))
|
||||
- Ignore all warnings produced by DOMDocument::loadHTML()
|
||||
([#63](https://github.com/MyIntervals/emogrifier/pull/63))
|
||||
- Style tags in HTML cause an Xpath invalid query error
|
||||
([#60](https://github.com/MyIntervals/emogrifier/pull/60))
|
||||
- Fix PHP warnings with PHP 5.5
|
||||
([#26](https://github.com/MyIntervals/emogrifier/pull/26))
|
||||
- Make removal of invisible nodes operate in a case-insensitive manner
|
||||
- Fix a bug that was overwriting existing inline styles from the original HTML
|
||||
77
lib/pelago/emogrifier/CODE_OF_CONDUCT.md
Normal file
77
lib/pelago/emogrifier/CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age,
|
||||
body size, disability, ethnicity, gender identity and expression, level of
|
||||
experience, nationality, personal appearance, race, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an
|
||||
appointed representative at an online or offline event. Representation of a
|
||||
project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at (emogrifier at myintervals dot com).
|
||||
All complaints will be reviewed and investigated and will result in a response
|
||||
that is deemed necessary and appropriate to the circumstances. The project team
|
||||
is obligated to maintain confidentiality with regard to the reporter of an
|
||||
incident. Further details of specific enforcement policies may be posted
|
||||
separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 1.4, available at
|
||||
[http://contributor-covenant.org/version/1/4/][version].
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
21
lib/pelago/emogrifier/LICENSE
Normal file
21
lib/pelago/emogrifier/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2008-2018 Pelago
|
||||
|
||||
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.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Emogrifier
|
||||
|
||||
[](https://travis-ci.org/jjriv/emogrifier)
|
||||
[](https://travis-ci.org/MyIntervals/emogrifier)
|
||||
[](https://packagist.org/packages/pelago/emogrifier)
|
||||
[](https://packagist.org/packages/pelago/emogrifier)
|
||||
[](https://packagist.org/packages/pelago/emogrifier)
|
||||
@@ -28,14 +28,14 @@ in `<link>` elements. Emogrifier solves this problem by converting CSS styles
|
||||
into inline style attributes in your HTML code.
|
||||
|
||||
- [How it works](#how-it-works)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Options](#options)
|
||||
- [Installing with Composer](#installing-with-composer)
|
||||
- [Usage](#usage)
|
||||
- [Supported CSS selectors](#supported-css-selectors)
|
||||
- [Caveats](#caveats)
|
||||
- [Maintainer](#maintainer)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
- [Processing HTML](#processing-html)
|
||||
- [Maintainers](#maintainers)
|
||||
|
||||
## How it Works
|
||||
|
||||
@@ -43,30 +43,44 @@ Emogrifier automagically transmogrifies your HTML by parsing your CSS and
|
||||
inserting your CSS definitions into tags within your HTML based on your CSS
|
||||
selectors.
|
||||
|
||||
## Installation
|
||||
|
||||
For installing emogrifier, either add pelago/emogrifier to your
|
||||
project's composer.json, or you can use composer as below:
|
||||
|
||||
```bash
|
||||
composer require pelago/emogrifier
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
First, you provide Emogrifier with the HTML and CSS you would like to merge.
|
||||
This can happen directly during instantiation:
|
||||
|
||||
$html = '<html><h1>Hello world!</h1></html>';
|
||||
$css = 'h1 {font-size: 32px;}';
|
||||
$emogrifier = new \Pelago\Emogrifier($html, $css);
|
||||
```php
|
||||
$html = '<html><h1>Hello world!</h1></html>';
|
||||
$css = 'h1 {font-size: 32px;}';
|
||||
$emogrifier = new \Pelago\Emogrifier($html, $css);
|
||||
```
|
||||
|
||||
You could also use the setters for providing this data after instantiation:
|
||||
|
||||
$emogrifier = new \Pelago\Emogrifier();
|
||||
```php
|
||||
$emogrifier = new \Pelago\Emogrifier();
|
||||
|
||||
$html = '<html><h1>Hello world!</h1></html>';
|
||||
$css = 'h1 {font-size: 32px;}';
|
||||
$html = '<html><h1>Hello world!</h1></html>';
|
||||
$css = 'h1 {font-size: 32px;}';
|
||||
|
||||
$emogrifier->setHtml($html);
|
||||
$emogrifier->setCss($css);
|
||||
$emogrifier->setHtml($html);
|
||||
$emogrifier->setCss($css);
|
||||
```
|
||||
|
||||
After you have set the HTML and CSS, you can call the `emogrify` method to
|
||||
merge both:
|
||||
|
||||
$mergedHtml = $emogrifier->emogrify();
|
||||
```php
|
||||
$mergedHtml = $emogrifier->emogrify();
|
||||
```
|
||||
|
||||
Emogrifier automatically adds a Content-Type meta tag to set the charset for
|
||||
the document (if it is not provided).
|
||||
@@ -74,8 +88,9 @@ the document (if it is not provided).
|
||||
If you would like to get back only the content of the BODY element instead of
|
||||
the complete HTML document, you can use the `emogrifyBodyContent` instead:
|
||||
|
||||
$bodyContent = $emogrifier->emogrifyBodyContent();
|
||||
|
||||
```php
|
||||
$bodyContent = $emogrifier->emogrifyBodyContent();
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
@@ -87,7 +102,9 @@ calling the `emogrify` method:
|
||||
"style" attributes to the HTML. The `<style>` blocks will then be removed
|
||||
from the HTML. If you want to disable this functionality so that Emogrifier
|
||||
leaves these `<style>` blocks in the HTML and does not parse them, you should
|
||||
use this option.
|
||||
use this option. If you use this option, the contents of the `<style>` blocks
|
||||
will _not_ be applied as inline styles and any CSS you want Emogrifier to
|
||||
use must be passed in as described in the [Usage section](#usage) above.
|
||||
* `$emogrifier->disableInlineStylesParsing()` - By default, Emogrifier
|
||||
preserves all of the "style" attributes on tags in the HTML you pass to it.
|
||||
However if you want to discard all existing inline styles in the HTML before
|
||||
@@ -95,6 +112,8 @@ calling the `emogrify` method:
|
||||
* `$emogrifier->disableInvisibleNodeRemoval()` - By default, Emogrifier removes
|
||||
elements from the DOM that have the style attribute `display: none;`. If
|
||||
you would like to keep invisible elements in the DOM, use this option.
|
||||
Note: This option will be removed in Emogrifier 3.0. HTML tags with
|
||||
`display: none;` then will always be retained.
|
||||
* `$emogrifier->addAllowedMediaType(string $mediaName)` - By default, Emogrifier
|
||||
will keep only media types `all`, `screen` and `print`. If you want to keep
|
||||
some others, you can use this method to define them.
|
||||
@@ -102,60 +121,78 @@ calling the `emogrify` method:
|
||||
method to remove media types that Emogrifier keeps.
|
||||
* `$emogrifier->addExcludedSelector(string $selector)` - Keeps elements from
|
||||
being affected by emogrification.
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
* PHP from 5.4 to 7.0 (with the mbstring extension)
|
||||
* or HHVM
|
||||
|
||||
* `$emogrifier->enableCssToHtmlMapping()` - Some email clients don't support CSS
|
||||
well, even if inline and prefer HTML attributes. This function allows you to
|
||||
put properties such as height, width, background color and font color in your
|
||||
CSS while the transformed content will have all the available HTML
|
||||
attributes set. This option will be removed in Emogrifier 3.0. Please use the
|
||||
`CssToAttributeConverter` class instead.
|
||||
|
||||
## Installing with Composer
|
||||
|
||||
Download the [`composer.phar`](https://getcomposer.org/composer.phar) locally
|
||||
or install [Composer](https://getcomposer.org/) globally:
|
||||
|
||||
curl -s https://getcomposer.org/installer | php
|
||||
```bash
|
||||
curl -s https://getcomposer.org/installer | php
|
||||
```
|
||||
|
||||
Run the following command for a local installation:
|
||||
|
||||
php composer.phar require pelago/emogrifier:@dev
|
||||
```bash
|
||||
php composer.phar require pelago/emogrifier:^2.1.0
|
||||
```
|
||||
|
||||
Or for a global installation, run the following command:
|
||||
|
||||
composer require pelago/emogrifier:@dev
|
||||
```bash
|
||||
composer require pelago/emogrifier:^2.1.0
|
||||
```
|
||||
|
||||
You can also add follow lines to your `composer.json` and run the
|
||||
`composer update` command:
|
||||
|
||||
"require": {
|
||||
"pelago/emogrifier": "@dev"
|
||||
}
|
||||
```json
|
||||
"require": {
|
||||
"pelago/emogrifier": "^2.1.0"
|
||||
}
|
||||
```
|
||||
|
||||
See https://getcomposer.org/ for more information and documentation.
|
||||
|
||||
|
||||
## Supported CSS selectors
|
||||
|
||||
Emogrifier currently support the following
|
||||
[CSS selectors](http://www.w3.org/TR/CSS2/selector.html):
|
||||
Emogrifier currently supports the following
|
||||
[CSS selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors):
|
||||
|
||||
* ID
|
||||
* class
|
||||
* type
|
||||
* descendant
|
||||
* child
|
||||
* adjacent
|
||||
* attribute presence
|
||||
* attribute value
|
||||
* attribute only
|
||||
* first-child
|
||||
* last-child
|
||||
* [type](https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors)
|
||||
* [class](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors)
|
||||
* [ID](https://developer.mozilla.org/en-US/docs/Web/CSS/ID_selectors)
|
||||
* [universal](https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors):
|
||||
* [attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors):
|
||||
* presence
|
||||
* exact value match
|
||||
* value with `~` (one word within a whitespace-separated list of words)
|
||||
* value with `|` (either exact value match or prefix followed by a hyphen)
|
||||
* value with `^` (prefix match)
|
||||
* value with `$` (suffix match)
|
||||
* value with `*` (substring match)
|
||||
* [adjacent](https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_selectors)
|
||||
* [child](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_selectors)
|
||||
* [descendant](https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_selectors)
|
||||
* [pseudo-classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes):
|
||||
* [first-child](https://developer.mozilla.org/en-US/docs/Web/CSS/:first-child)
|
||||
* [last-child](https://developer.mozilla.org/en-US/docs/Web/CSS/:last-child)
|
||||
* [not()](https://developer.mozilla.org/en-US/docs/Web/CSS/:not)
|
||||
|
||||
The following selectors are not implemented yet:
|
||||
|
||||
* universal
|
||||
|
||||
* [universal](https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors)
|
||||
* [case-insensitive attribute value](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors#case-insensitive)
|
||||
* [general sibling](https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_selectors)
|
||||
* [pseudo-classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes)
|
||||
(some of them will never be supported)
|
||||
* [pseudo-elements](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements)
|
||||
|
||||
## Caveats
|
||||
|
||||
@@ -191,8 +228,67 @@ The following selectors are not implemented yet:
|
||||
works by converting CSS selectors to XPath selectors, and pseudo selectors
|
||||
cannot be converted accurately).
|
||||
|
||||
## Processing HTML
|
||||
|
||||
## Maintainer
|
||||
The Emogrifier package also provides classes for (post-)processing the HTML
|
||||
generated by `emogrify` (and it also works on any other HTML).
|
||||
|
||||
Emogrifier is maintained by the good people at
|
||||
[Pelago](http://www.pelagodesign.com/), info AT pelagodesign DOT com.
|
||||
### Normalizing and cleaning up HTML
|
||||
|
||||
The `HtmlNormalizer` class normalizes the given HTML in the following ways:
|
||||
|
||||
- add a document type (HTML5) if missing
|
||||
- disentangle incorrectly nested tags
|
||||
- add HEAD and BODY elements (if they are missing)
|
||||
- reformat the HTML
|
||||
|
||||
The class can be used like this:
|
||||
|
||||
```php
|
||||
$normalizer = new \Pelago\Emogrifier\HtmlProcessor\HtmlNormalizer($rawHtml);
|
||||
$cleanHtml = $normalizer->render();
|
||||
```
|
||||
|
||||
### Converting CSS styles to visual HTML attributes
|
||||
|
||||
The `CssToAttributeConverter` converts a few style attributes values to visual
|
||||
HTML attributes. This allows to get at least a bit of visual styling for email
|
||||
clients that do not support CSS well. For example, `style="width: 100px"`
|
||||
will be converted to `width="100"`.
|
||||
|
||||
The class can be used like this:
|
||||
|
||||
```php
|
||||
$converter = new \Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter($rawHtml);
|
||||
$visualHtml = $converter->convertCssToVisualAttributes()->render();
|
||||
```
|
||||
|
||||
### Technology preview of new classes
|
||||
|
||||
Currently, a refactoring effort is underway, aiming towards replacing the
|
||||
grown-over-time `Emogrifier` class with the new `CssInliner` class and moving
|
||||
additional HTML processing into separate `CssProcessor` classes (which will
|
||||
inherit from `AbstractHtmlProcessor`). You can try the new classes, but be
|
||||
aware that the APIs of the new classes still are subject to change.
|
||||
|
||||
## Steps to release a new version
|
||||
|
||||
1. Create a pull request "Prepare release of version x.y.z" with the following
|
||||
changes.
|
||||
1. In the [composer.json](composer.json), update the `branch-alias` entry to
|
||||
point to the release _after_ the upcoming release.
|
||||
1. In the [README.md](README.md), update the version numbers in the section
|
||||
[Installing with Composer](#installing-with-composer).
|
||||
1. In the [CHANGELOG.md](CHANGELOG.md), set the version number and remove any
|
||||
empty sections.
|
||||
1. Have the pull request reviewed and merged.
|
||||
1. In the [Releases tab](https://github.com/MyIntervals/emogrifier/releases),
|
||||
create a new release and copy the change log entries to the new release.
|
||||
1. Post about the new release on social media.
|
||||
|
||||
## Maintainers
|
||||
|
||||
* [Oliver Klee](https://github.com/oliverklee)
|
||||
* [Zoli Szabó](https://github.com/zoliszabo)
|
||||
* [Jake Hotson](https://github.com/JakeQZ)
|
||||
* [John Reeve](https://github.com/jjriv)
|
||||
95
lib/pelago/emogrifier/composer.json
Normal file
95
lib/pelago/emogrifier/composer.json
Normal file
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"name": "pelago/emogrifier",
|
||||
"description": "Converts CSS styles into inline style attributes in your HTML code",
|
||||
"keywords": [
|
||||
"email",
|
||||
"css",
|
||||
"pre-processing"
|
||||
],
|
||||
"homepage": "https://www.myintervals.com/emogrifier.php",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oliver Klee",
|
||||
"email": "github@oliverklee.de"
|
||||
},
|
||||
{
|
||||
"name": "Zoli Szabó",
|
||||
"email": "zoli.szabo+github@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "John Reeve",
|
||||
"email": "jreeve@pelagodesign.com"
|
||||
},
|
||||
{
|
||||
"name": "Jake Hotson",
|
||||
"email": "jake@qzdesign.co.uk"
|
||||
},
|
||||
{
|
||||
"name": "Cameron Brooks"
|
||||
},
|
||||
{
|
||||
"name": "Jaime Prado"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/MyIntervals/emogrifier/issues",
|
||||
"source": "https://github.com/MyIntervals/emogrifier"
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0",
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*",
|
||||
"symfony/css-selector": "^3.4.0 || ^4.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.2.0",
|
||||
"squizlabs/php_codesniffer": "^3.3.2",
|
||||
"phpmd/phpmd": "^2.6.0",
|
||||
"phpunit/phpunit": "^4.8.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Pelago\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Pelago\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"config": {
|
||||
"preferred-install": {
|
||||
"*": "dist"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"php:version": "php -v | grep -Po 'PHP\\s++\\K(?:\\d++\\.)*+\\d++(?:-\\w++)?+'",
|
||||
"php:fix": "php-cs-fixer --config=config/php-cs-fixer.php fix config/ src/ tests/",
|
||||
"ci:php:lint": "find config src tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l",
|
||||
"ci:php:sniff": "phpcs config src tests",
|
||||
"ci:php:md": "phpmd src text config/phpmd.xml",
|
||||
"ci:tests:unit": "phpunit tests/",
|
||||
"ci:tests": [
|
||||
"@ci:tests:unit"
|
||||
],
|
||||
"ci:dynamic": [
|
||||
"@ci:tests"
|
||||
],
|
||||
"ci:static": [
|
||||
"@ci:php:lint",
|
||||
"@ci:php:sniff",
|
||||
"@ci:php:md"
|
||||
],
|
||||
"ci": [
|
||||
"@ci:static",
|
||||
"@ci:dynamic"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
93
lib/pelago/emogrifier/config/php-cs-fixer.php
Normal file
93
lib/pelago/emogrifier/config/php-cs-fixer.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
die('This script supports command line usage only. Please check your command.');
|
||||
}
|
||||
|
||||
return \PhpCsFixer\Config::create()
|
||||
->setRiskyAllowed(true)
|
||||
->setRules(
|
||||
[
|
||||
// copied from the TYPO3 Core
|
||||
'@PSR2' => true,
|
||||
'@DoctrineAnnotation' => true,
|
||||
'no_leading_import_slash' => true,
|
||||
'no_trailing_comma_in_singleline_array' => true,
|
||||
'no_singleline_whitespace_before_semicolons' => true,
|
||||
'no_unused_imports' => true,
|
||||
'concat_space' => ['spacing' => 'one'],
|
||||
'no_whitespace_in_blank_line' => true,
|
||||
'ordered_imports' => true,
|
||||
'single_quote' => true,
|
||||
'no_empty_statement' => true,
|
||||
'no_extra_consecutive_blank_lines' => true,
|
||||
'phpdoc_no_package' => true,
|
||||
'phpdoc_scalar' => true,
|
||||
'no_blank_lines_after_phpdoc' => true,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'whitespace_after_comma_in_array' => true,
|
||||
'function_typehint_space' => true,
|
||||
'hash_to_slash_comment' => true,
|
||||
'no_alias_functions' => true,
|
||||
'lowercase_cast' => true,
|
||||
'no_leading_namespace_whitespace' => true,
|
||||
'native_function_casing' => true,
|
||||
'no_short_bool_cast' => true,
|
||||
'no_unneeded_control_parentheses' => true,
|
||||
'phpdoc_trim' => true,
|
||||
'no_superfluous_elseif' => true,
|
||||
'no_useless_else' => true,
|
||||
'phpdoc_types' => true,
|
||||
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
|
||||
'return_type_declaration' => ['space_before' => 'none'],
|
||||
'cast_spaces' => ['space' => 'none'],
|
||||
'declare_equal_normalize' => ['space' => 'single'],
|
||||
'dir_constant' => true,
|
||||
|
||||
// additional rules
|
||||
'combine_consecutive_issets' => true,
|
||||
'combine_consecutive_unsets' => true,
|
||||
'compact_nullable_typehint' => true,
|
||||
// PHP >= 7.0
|
||||
// 'declare_strict_types' => true,
|
||||
'elseif' => true,
|
||||
'encoding' => true,
|
||||
'escape_implicit_backslashes' => ['single_quoted' => true],
|
||||
'is_null' => true,
|
||||
'linebreak_after_opening_tag' => true,
|
||||
'magic_constant_casing' => true,
|
||||
'method_separation' => true,
|
||||
'modernize_types_casting' => true,
|
||||
// not yet, but maybe later to improve performance
|
||||
// 'native_function_invocation' => true,
|
||||
'new_with_braces' => true,
|
||||
'no_blank_lines_after_class_opening' => true,
|
||||
'no_empty_comment' => true,
|
||||
'no_empty_phpdoc' => true,
|
||||
'no_extra_blank_lines' => true,
|
||||
'no_multiline_whitespace_before_semicolons' => true,
|
||||
'no_php4_constructor' => true,
|
||||
'no_short_echo_tag' => true,
|
||||
'no_spaces_after_function_name' => true,
|
||||
'no_spaces_inside_parenthesis' => true,
|
||||
'no_unneeded_curly_braces' => true,
|
||||
'no_useless_return' => true,
|
||||
'no_whitespace_before_comma_in_array' => true,
|
||||
'php_unit_construct' => true,
|
||||
'php_unit_fqcn_annotation' => true,
|
||||
'php_unit_set_up_tear_down_visibility' => true,
|
||||
'phpdoc_add_missing_param_annotation' => true,
|
||||
'phpdoc_indent' => true,
|
||||
'phpdoc_separation' => true,
|
||||
'semicolon_after_instruction' => true,
|
||||
'short_scalar_cast' => true,
|
||||
'space_after_semicolon' => true,
|
||||
'standardize_not_equals' => true,
|
||||
'psr4' => true,
|
||||
'ternary_operator_spaces' => true,
|
||||
// PHP >= 7.0
|
||||
// 'ternary_to_null_coalescing' => true,
|
||||
'trailing_comma_in_multiline_array' => true,
|
||||
'unary_operator_spaces' => true,
|
||||
]
|
||||
);
|
||||
49
lib/pelago/emogrifier/config/phpmd.xml
Normal file
49
lib/pelago/emogrifier/config/phpmd.xml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0"?>
|
||||
<ruleset name="phpList">
|
||||
<description>
|
||||
PHPMD rules for Emogrifier
|
||||
</description>
|
||||
|
||||
<!-- The commented-out rules will be enabled once the code does not generate any warnings anymore. -->
|
||||
|
||||
<rule ref="rulesets/cleancode.xml/BooleanArgumentFlag"/>
|
||||
<rule ref="rulesets/cleancode.xml/StaticAccess"/>
|
||||
|
||||
<rule ref="rulesets/codesize.xml/CyclomaticComplexity"/>
|
||||
<rule ref="rulesets/codesize.xml/NPathComplexity"/>
|
||||
<rule ref="rulesets/codesize.xml/ExcessiveMethodLength"/>
|
||||
<!--<rule ref="rulesets/codesize.xml/ExcessiveClassLength"/>-->
|
||||
<!--<rule ref="rulesets/codesize.xml/ExcessiveParameterList"/>-->
|
||||
<rule ref="rulesets/codesize.xml/ExcessivePublicCount"/>
|
||||
<!--<rule ref="rulesets/codesize.xml/TooManyFields"/>-->
|
||||
<!--<rule ref="rulesets/codesize.xml/TooManyMethods"/>-->
|
||||
<!--<rule ref="rulesets/codesize.xml/TooManyPublicMethods"/>-->
|
||||
<!--<rule ref="rulesets/codesize.xml/ExcessiveClassComplexity"/>-->
|
||||
|
||||
<rule ref="rulesets/controversial.xml/Superglobals"/>
|
||||
<rule ref="rulesets/controversial.xml/CamelCaseClassName"/>
|
||||
<rule ref="rulesets/controversial.xml/CamelCasePropertyName"/>
|
||||
<rule ref="rulesets/controversial.xml/CamelCaseMethodName"/>
|
||||
<rule ref="rulesets/controversial.xml/CamelCaseParameterName"/>
|
||||
<rule ref="rulesets/controversial.xml/CamelCaseVariableName"/>
|
||||
|
||||
<rule ref="rulesets/design.xml/ExitExpression"/>
|
||||
<rule ref="rulesets/design.xml/EvalExpression"/>
|
||||
<rule ref="rulesets/design.xml/GotoStatement"/>
|
||||
<rule ref="rulesets/design.xml/NumberOfChildren"/>
|
||||
<rule ref="rulesets/design.xml/DepthOfInheritance"/>
|
||||
<rule ref="rulesets/design.xml/CouplingBetweenObjects"/>
|
||||
<rule ref="rulesets/design.xml/DevelopmentCodeFragment"/>
|
||||
|
||||
<!--<rule ref="rulesets/naming.xml/ShortVariable"/>-->
|
||||
<!--<rule ref="rulesets/naming.xml/LongVariable"/>-->
|
||||
<rule ref="rulesets/naming.xml/ShortMethodName"/>
|
||||
<rule ref="rulesets/naming.xml/ConstructorWithNameAsEnclosingClass"/>
|
||||
<rule ref="rulesets/naming.xml/ConstantNamingConventions"/>
|
||||
<rule ref="rulesets/naming.xml/BooleanGetMethodName"/>
|
||||
|
||||
<rule ref="rulesets/unusedcode.xml/UnusedPrivateField"/>
|
||||
<rule ref="rulesets/unusedcode.xml/UnusedLocalVariable"/>
|
||||
<!--<rule ref="rulesets/unusedcode.xml/UnusedPrivateMethod"/>-->
|
||||
<!--<rule ref="rulesets/unusedcode.xml/UnusedFormalParameter"/>-->
|
||||
</ruleset>
|
||||
@@ -1,15 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ruleset name="PPW Coding Standard">
|
||||
<description>This is the coding standard used for the Emogrifier code.
|
||||
This standard has been tested with to work with PHP_CodeSniffer >= 2.3.0.
|
||||
<ruleset name="Coding Standard">
|
||||
<description>
|
||||
This standard requires PHP_CodeSniffer >= 3.2.0.
|
||||
</description>
|
||||
|
||||
<config name="installed_paths" value="../../slevomat/coding-standard"/>
|
||||
|
||||
<!--The complete PSR-2 ruleset-->
|
||||
<rule ref="PSR2"/>
|
||||
|
||||
<!-- Arrays -->
|
||||
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
|
||||
<rule ref="Squiz.Arrays.ArrayBracketSpacing"/>
|
||||
<rule ref="Squiz.Arrays.ArrayDeclaration.NoCommaAfterLast"/>
|
||||
|
||||
<!-- Classes -->
|
||||
<rule ref="Generic.Classes.DuplicateClassName"/>
|
||||
@@ -20,6 +23,7 @@
|
||||
|
||||
<!-- Code analysis -->
|
||||
<rule ref="Generic.CodeAnalysis.EmptyStatement"/>
|
||||
<rule ref="Generic.CodeAnalysis.AssignmentInCondition"/>
|
||||
<rule ref="Generic.CodeAnalysis.ForLoopShouldBeWhileLoop"/>
|
||||
<rule ref="Generic.CodeAnalysis.ForLoopWithTestFunctionCall"/>
|
||||
<rule ref="Generic.CodeAnalysis.JumbledIncrementer"/>
|
||||
@@ -34,52 +38,58 @@
|
||||
<rule ref="PEAR.Commenting.InlineComment"/>
|
||||
<rule ref="Squiz.Commenting.DocCommentAlignment"/>
|
||||
<rule ref="Squiz.Commenting.EmptyCatchComment"/>
|
||||
<rule ref="Squiz.Commenting.FunctionComment">
|
||||
<!-- Allow PHP-5-compatible type hinting. -->
|
||||
<exclude name="Squiz.Commenting.FunctionComment.ScalarTypeHintMissing"/>
|
||||
<!-- Allow no comment for self-describing parameter and exception class names. -->
|
||||
<exclude name="Squiz.Commenting.FunctionComment.MissingParamComment"/>
|
||||
<exclude name="Squiz.Commenting.FunctionComment.EmptyThrows"/>
|
||||
<!-- Allow "int" rather than "integer", etc., in PHPDoc. -->
|
||||
<exclude name="Squiz.Commenting.FunctionComment.IncorrectParamVarName"/>
|
||||
<exclude name="Squiz.Commenting.FunctionComment.InvalidReturn"/>
|
||||
<!-- Allow "@return" to be omitted (for methods which do not return a value). -->
|
||||
<exclude name="Squiz.Commenting.FunctionComment.MissingReturn"/>
|
||||
<!-- Allow parameter type, name and comment not all vertically aligned. -->
|
||||
<exclude name="Squiz.Commenting.FunctionComment.SpacingAfterParamType"/>
|
||||
<exclude name="Squiz.Commenting.FunctionComment.SpacingAfterParamName"/>
|
||||
<!-- Allow parameter and exception descriptions which are not full sentences. -->
|
||||
<exclude name="Squiz.Commenting.FunctionComment.ParamCommentNotCapital"/>
|
||||
<exclude name="Squiz.Commenting.FunctionComment.ParamCommentFullStop"/>
|
||||
<exclude name="Squiz.Commenting.FunctionComment.ThrowsNotCapital"/>
|
||||
<exclude name="Squiz.Commenting.FunctionComment.ThrowsNoFullStop"/>
|
||||
</rule>
|
||||
<rule ref="Squiz.Commenting.FunctionCommentThrowTag"/>
|
||||
<rule ref="Squiz.Commenting.PostStatementComment"/>
|
||||
<rule ref="TYPO3SniffPool.Commenting.ClassComment"/>
|
||||
<rule ref="TYPO3SniffPool.Commenting.DoubleSlashCommentsInNewLine"/>
|
||||
<rule ref="TYPO3SniffPool.Commenting.SpaceAfterDoubleSlash"/>
|
||||
|
||||
<!-- Control structures -->
|
||||
<rule ref="PEAR.ControlStructures.ControlSignature"/>
|
||||
<rule ref="TYPO3SniffPool.ControlStructures.DisallowEachInLoopCondition"/>
|
||||
<rule ref="TYPO3SniffPool.ControlStructures.DisallowElseIfConstruct"/>
|
||||
<rule ref="TYPO3SniffPool.ControlStructures.ExtraBracesByAssignmentInLoop"/>
|
||||
<rule ref="TYPO3SniffPool.ControlStructures.SwitchDeclaration"/>
|
||||
<rule ref="TYPO3SniffPool.ControlStructures.TernaryConditionalOperator"/>
|
||||
<rule ref="TYPO3SniffPool.ControlStructures.UnusedVariableInForEachLoop"/>
|
||||
|
||||
<!-- Debug -->
|
||||
<rule ref="Generic.Debug.ClosureLinter"/>
|
||||
<rule ref="TYPO3SniffPool.Debug.DebugCode"/>
|
||||
|
||||
<!-- Files -->
|
||||
<rule ref="Generic.Files.OneClassPerFile"/>
|
||||
<rule ref="Generic.Files.OneInterfacePerFile"/>
|
||||
<rule ref="TYPO3SniffPool.Files.FileExtension"/>
|
||||
<rule ref="TYPO3SniffPool.Files.Filename"/>
|
||||
<rule ref="TYPO3SniffPool.Files.IncludingFile"/>
|
||||
<rule ref="Generic.Files.OneObjectStructurePerFile"/>
|
||||
<rule ref="Zend.Files.ClosingTag"/>
|
||||
|
||||
<!-- Formatting -->
|
||||
<rule ref="Generic.Formatting.SpaceAfterCast"/>
|
||||
<rule ref="Generic.Formatting.NoSpaceAfterCast"/>
|
||||
<rule ref="PEAR.Formatting.MultiLineAssignment"/>
|
||||
|
||||
<!-- Functions -->
|
||||
<rule ref="Generic.Functions.CallTimePassByReference"/>
|
||||
<rule ref="SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalFunctions"/>
|
||||
<rule ref="Squiz.Functions.FunctionDuplicateArgument"/>
|
||||
<rule ref="Squiz.Functions.GlobalFunction"/>
|
||||
|
||||
<!-- Metrics -->
|
||||
<!-- Enable this rule when the cyclomatic complexity of all methods is sufficiently low. -->
|
||||
<!--<rule ref="Generic.Metrics.CyclomaticComplexity"/>-->
|
||||
<rule ref="Generic.Metrics.CyclomaticComplexity"/>
|
||||
<rule ref="Generic.Metrics.NestingLevel"/>
|
||||
|
||||
<!-- Naming conventions -->
|
||||
<rule ref="Generic.NamingConventions.ConstructorName"/>
|
||||
<rule ref="PEAR.NamingConventions.ValidClassName"/>
|
||||
<rule ref="TYPO3SniffPool.NamingConventions.ValidFunctionName"/>
|
||||
<rule ref="TYPO3SniffPool.NamingConventions.ValidVariableName"/>
|
||||
|
||||
<!-- Objects -->
|
||||
<rule ref="Squiz.Objects.ObjectMemberComma"/>
|
||||
@@ -89,9 +99,12 @@
|
||||
<rule ref="Squiz.Operators.ValidLogicalOperators"/>
|
||||
|
||||
<!-- PHP -->
|
||||
<rule ref="Generic.PHP.BacktickOperator"/>
|
||||
<rule ref="Generic.PHP.CharacterBeforePHPOpeningTag"/>
|
||||
<rule ref="Generic.PHP.DeprecatedFunctions"/>
|
||||
<rule ref="Generic.PHP.DisallowAlternativePHPTags"/>
|
||||
<rule ref="Generic.PHP.DisallowShortOpenTag"/>
|
||||
<rule ref="Generic.PHP.DiscourageGoto"/>
|
||||
<rule ref="Generic.PHP.ForbiddenFunctions"/>
|
||||
<rule ref="Generic.PHP.NoSilencedErrors"/>
|
||||
<rule ref="Squiz.PHP.CommentedOutCode">
|
||||
@@ -103,7 +116,6 @@
|
||||
<rule ref="Squiz.PHP.DisallowSizeFunctionsInLoops"/>
|
||||
<rule ref="Squiz.PHP.DiscouragedFunctions"/>
|
||||
<rule ref="Squiz.PHP.Eval"/>
|
||||
<rule ref="Squiz.PHP.ForbiddenFunctions"/>
|
||||
<rule ref="Squiz.PHP.GlobalKeyword"/>
|
||||
<rule ref="Squiz.PHP.Heredoc"/>
|
||||
<rule ref="Squiz.PHP.InnerFunctions"/>
|
||||
@@ -113,14 +125,9 @@
|
||||
<!-- Scope -->
|
||||
<rule ref="Squiz.Scope.MemberVarScope"/>
|
||||
<rule ref="Squiz.Scope.StaticThisUsage"/>
|
||||
<rule ref="TYPO3SniffPool.Scope.AlwaysReturn">
|
||||
<exclude-pattern>*/Tests/*</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<!--Strings-->
|
||||
<rule ref="Squiz.Strings.DoubleQuoteUsage"/>
|
||||
<rule ref="TYPO3SniffPool.Strings.ConcatenationSpacing"/>
|
||||
<rule ref="TYPO3SniffPool.Strings.UnnecessaryStringConcat"/>
|
||||
|
||||
<!-- Whitespace -->
|
||||
<rule ref="PEAR.WhiteSpace.ObjectOperatorIndent"/>
|
||||
@@ -130,7 +137,4 @@
|
||||
<rule ref="Squiz.WhiteSpace.OperatorSpacing"/>
|
||||
<rule ref="Squiz.WhiteSpace.PropertyLabelSpacing"/>
|
||||
<rule ref="Squiz.WhiteSpace.SemicolonSpacing"/>
|
||||
<rule ref="TYPO3SniffPool.WhiteSpace.NoWhitespaceAtInDecrement"/>
|
||||
<rule ref="TYPO3SniffPool.WhiteSpace.ScopeClosingBrace"/>
|
||||
<rule ref="TYPO3SniffPool.WhiteSpace.WhitespaceAfterCommentSigns"/>
|
||||
</ruleset>
|
||||
2006
lib/pelago/emogrifier/src/Emogrifier.php
Normal file
2006
lib/pelago/emogrifier/src/Emogrifier.php
Normal file
File diff suppressed because it is too large
Load Diff
154
lib/pelago/emogrifier/src/Emogrifier/CssConcatenator.php
Normal file
154
lib/pelago/emogrifier/src/Emogrifier/CssConcatenator.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Pelago\Emogrifier;
|
||||
|
||||
/**
|
||||
* Facilitates building a CSS string by appending rule blocks one at a time, checking whether the media query,
|
||||
* selectors, or declarations block are the same as those from the preceding block and combining blocks in such cases.
|
||||
*
|
||||
* Example:
|
||||
* $concatenator = new CssConcatenator();
|
||||
* $concatenator->append(['body'], 'color: blue;');
|
||||
* $concatenator->append(['body'], 'font-size: 16px;');
|
||||
* $concatenator->append(['p'], 'margin: 1em 0;');
|
||||
* $concatenator->append(['ul', 'ol'], 'margin: 1em 0;');
|
||||
* $concatenator->append(['body'], 'font-size: 14px;', '@media screen and (max-width: 400px)');
|
||||
* $concatenator->append(['ul', 'ol'], 'margin: 0.75em 0;', '@media screen and (max-width: 400px)');
|
||||
* $css = $concatenator->getCss();
|
||||
*
|
||||
* `$css` (if unminified) would contain the following CSS:
|
||||
* ` body {
|
||||
* ` color: blue;
|
||||
* ` font-size: 16px;
|
||||
* ` }
|
||||
* ` p, ul, ol {
|
||||
* ` margin: 1em 0;
|
||||
* ` }
|
||||
* ` @media screen and (max-width: 400px) {
|
||||
* ` body {
|
||||
* ` font-size: 14px;
|
||||
* ` }
|
||||
* ` ul, ol {
|
||||
* ` margin: 0.75em 0;
|
||||
* ` }
|
||||
* ` }
|
||||
*
|
||||
* @author Jake Hotson <jake.github@qzdesign.co.uk>
|
||||
*/
|
||||
class CssConcatenator
|
||||
{
|
||||
/**
|
||||
* Array of media rules in order. Each element is an object with the following properties:
|
||||
* - string `media` - The media query string, e.g. "@media screen and (max-width:639px)", or an empty string for
|
||||
* rules not within a media query block;
|
||||
* - \stdClass[] `ruleBlocks` - Array of rule blocks in order, where each element is an object with the following
|
||||
* properties:
|
||||
* - mixed[] `selectorsAsKeys` - Array whose keys are selectors for the rule block (values are of no
|
||||
* significance);
|
||||
* - string `declarationsBlock` - The property declarations, e.g. "margin-top: 0.5em; padding: 0".
|
||||
*
|
||||
* @var \stdClass[]
|
||||
*/
|
||||
private $mediaRules = [];
|
||||
|
||||
/**
|
||||
* Appends a declaration block to the CSS.
|
||||
*
|
||||
* @param string[] $selectors Array of selectors for the rule, e.g. ["ul", "ol", "p:first-child"].
|
||||
* @param string $declarationsBlock The property declarations, e.g. "margin-top: 0.5em; padding: 0".
|
||||
* @param string $media The media query for the rule, e.g. "@media screen and (max-width:639px)",
|
||||
* or an empty string if none.
|
||||
*/
|
||||
public function append(array $selectors, $declarationsBlock, $media = '')
|
||||
{
|
||||
$selectorsAsKeys = \array_flip($selectors);
|
||||
|
||||
$mediaRule = $this->getOrCreateMediaRuleToAppendTo($media);
|
||||
$lastRuleBlock = \end($mediaRule->ruleBlocks);
|
||||
|
||||
$hasSameDeclarationsAsLastRule = $lastRuleBlock !== false
|
||||
&& $declarationsBlock === $lastRuleBlock->declarationsBlock;
|
||||
if ($hasSameDeclarationsAsLastRule) {
|
||||
$lastRuleBlock->selectorsAsKeys += $selectorsAsKeys;
|
||||
} else {
|
||||
$hasSameSelectorsAsLastRule = $lastRuleBlock !== false
|
||||
&& static::hasEquivalentSelectors($selectorsAsKeys, $lastRuleBlock->selectorsAsKeys);
|
||||
if ($hasSameSelectorsAsLastRule) {
|
||||
$lastDeclarationsBlockWithoutSemicolon = \rtrim(\rtrim($lastRuleBlock->declarationsBlock), ';');
|
||||
$lastRuleBlock->declarationsBlock = $lastDeclarationsBlockWithoutSemicolon . ';' . $declarationsBlock;
|
||||
} else {
|
||||
$mediaRule->ruleBlocks[] = (object)\compact('selectorsAsKeys', 'declarationsBlock');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCss()
|
||||
{
|
||||
return \implode('', \array_map([$this, 'getMediaRuleCss'], $this->mediaRules));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $media The media query for rules to be appended, e.g. "@media screen and (max-width:639px)",
|
||||
* or an empty string if none.
|
||||
*
|
||||
* @return \stdClass Object with properties as described for elements of `$mediaRules`.
|
||||
*/
|
||||
private function getOrCreateMediaRuleToAppendTo($media)
|
||||
{
|
||||
$lastMediaRule = \end($this->mediaRules);
|
||||
if ($lastMediaRule !== false && $media === $lastMediaRule->media) {
|
||||
return $lastMediaRule;
|
||||
}
|
||||
|
||||
$newMediaRule = (object)[
|
||||
'media' => $media,
|
||||
'ruleBlocks' => [],
|
||||
];
|
||||
$this->mediaRules[] = $newMediaRule;
|
||||
return $newMediaRule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if two sets of selectors are equivalent (i.e. the same selectors, possibly in a different order).
|
||||
*
|
||||
* @param mixed[] $selectorsAsKeys1 Array in which the selectors are the keys, and the values are of no
|
||||
* significance.
|
||||
* @param mixed[] $selectorsAsKeys2 Another such array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function hasEquivalentSelectors(array $selectorsAsKeys1, array $selectorsAsKeys2)
|
||||
{
|
||||
return \count($selectorsAsKeys1) === \count($selectorsAsKeys2)
|
||||
&& \count($selectorsAsKeys1) === \count($selectorsAsKeys1 + $selectorsAsKeys2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \stdClass $mediaRule Object with properties as described for elements of `$mediaRules`.
|
||||
*
|
||||
* @return string CSS for the media rule.
|
||||
*/
|
||||
private static function getMediaRuleCss(\stdClass $mediaRule)
|
||||
{
|
||||
$css = \implode('', \array_map([static::class, 'getRuleBlockCss'], $mediaRule->ruleBlocks));
|
||||
if ($mediaRule->media !== '') {
|
||||
$css = $mediaRule->media . '{' . $css . '}';
|
||||
}
|
||||
return $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \stdClass $ruleBlock Object with properties as described for elements of the `ruleBlocks` property of
|
||||
* elements of `$mediaRules`.
|
||||
*
|
||||
* @return string CSS for the rule block.
|
||||
*/
|
||||
private static function getRuleBlockCss(\stdClass $ruleBlock)
|
||||
{
|
||||
$selectors = \array_keys($ruleBlock->selectorsAsKeys);
|
||||
return \implode(',', $selectors) . '{' . $ruleBlock->declarationsBlock . '}';
|
||||
}
|
||||
}
|
||||
1332
lib/pelago/emogrifier/src/Emogrifier/CssInliner.php
Normal file
1332
lib/pelago/emogrifier/src/Emogrifier/CssInliner.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
namespace Pelago\Emogrifier\HtmlProcessor;
|
||||
|
||||
/**
|
||||
* Base class for HTML processor that e.g., can remove, add or modify nodes or attributes.
|
||||
*
|
||||
* The "vanilla" subclass is the HtmlNormalizer.
|
||||
*
|
||||
* @internal This class currently is a new technology preview, and its API is still in flux. Don't use it in production.
|
||||
*
|
||||
* @author Oliver Klee <github@oliverklee.de>
|
||||
*/
|
||||
abstract class AbstractHtmlProcessor
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const DEFAULT_DOCUMENT_TYPE = '<!DOCTYPE html>';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const CONTENT_TYPE_META_TAG = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">';
|
||||
|
||||
/**
|
||||
* @var \DOMDocument
|
||||
*/
|
||||
protected $domDocument = null;
|
||||
|
||||
/**
|
||||
* @param string $unprocessedHtml raw HTML, must be UTF-encoded, must not be empty
|
||||
*
|
||||
* @throws \InvalidArgumentException if $unprocessedHtml is anything other than a non-empty string
|
||||
*/
|
||||
public function __construct($unprocessedHtml)
|
||||
{
|
||||
if (!\is_string($unprocessedHtml)) {
|
||||
throw new \InvalidArgumentException('The provided HTML must be a string.', 1515459744);
|
||||
}
|
||||
if ($unprocessedHtml === '') {
|
||||
throw new \InvalidArgumentException('The provided HTML must not be empty.', 1515763647);
|
||||
}
|
||||
|
||||
$this->setHtml($unprocessedHtml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HTML to process.
|
||||
*
|
||||
* @param string $html the HTML to process, must be UTF-8-encoded
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setHtml($html)
|
||||
{
|
||||
$this->createUnifiedDomDocument($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the internal DOMDocument representation of the HTML in its current state.
|
||||
*
|
||||
* @return \DOMDocument
|
||||
*/
|
||||
public function getDomDocument()
|
||||
{
|
||||
return $this->domDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the normalized and processed HTML.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return $this->domDocument->saveHTML();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the content of the BODY element of the normalized and processed HTML.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function renderBodyContent()
|
||||
{
|
||||
$bodyNodeHtml = $this->domDocument->saveHTML($this->getBodyElement());
|
||||
|
||||
return \str_replace(['<body>', '</body>'], '', $bodyNodeHtml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the BODY element.
|
||||
*
|
||||
* This method assumes that there always is a BODY element.
|
||||
*
|
||||
* @return \DOMElement
|
||||
*/
|
||||
private function getBodyElement()
|
||||
{
|
||||
return $this->domDocument->getElementsByTagName('body')->item(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DOM document from the given HTML and stores it in $this->domDocument.
|
||||
*
|
||||
* The DOM document will always have a BODY element and a document type.
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function createUnifiedDomDocument($html)
|
||||
{
|
||||
$this->createRawDomDocument($html);
|
||||
$this->ensureExistenceOfBodyElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DOMDocument instance from the given HTML and stores it in $this->domDocument.
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function createRawDomDocument($html)
|
||||
{
|
||||
$domDocument = new \DOMDocument();
|
||||
$domDocument->strictErrorChecking = false;
|
||||
$domDocument->formatOutput = true;
|
||||
$libXmlState = \libxml_use_internal_errors(true);
|
||||
$domDocument->loadHTML($this->prepareHtmlForDomConversion($html));
|
||||
\libxml_clear_errors();
|
||||
\libxml_use_internal_errors($libXmlState);
|
||||
|
||||
$this->domDocument = $domDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTML with added document type and Content-Type meta tag if needed,
|
||||
* ensuring that the HTML will be good for creating a DOM document from it.
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @return string the unified HTML
|
||||
*/
|
||||
private function prepareHtmlForDomConversion($html)
|
||||
{
|
||||
$htmlWithDocumentType = $this->ensureDocumentType($html);
|
||||
|
||||
return $this->addContentTypeMetaTag($htmlWithDocumentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that the passed HTML has a document type.
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @return string HTML with document type
|
||||
*/
|
||||
private function ensureDocumentType($html)
|
||||
{
|
||||
$hasDocumentType = \stripos($html, '<!DOCTYPE') !== false;
|
||||
if ($hasDocumentType) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
return static::DEFAULT_DOCUMENT_TYPE . $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Content-Type meta tag for the charset.
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @return string the HTML with the meta tag added
|
||||
*/
|
||||
private function addContentTypeMetaTag($html)
|
||||
{
|
||||
$hasContentTypeMetaTag = \stripos($html, 'Content-Type') !== false;
|
||||
if ($hasContentTypeMetaTag) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
// We are trying to insert the meta tag to the right spot in the DOM.
|
||||
// If we just prepended it to the HTML, we would lose attributes set to the HTML tag.
|
||||
$hasHeadTag = \stripos($html, '<head') !== false;
|
||||
$hasHtmlTag = \stripos($html, '<html') !== false;
|
||||
|
||||
if ($hasHeadTag) {
|
||||
$reworkedHtml = \preg_replace('/<head(.*?)>/i', '<head$1>' . static::CONTENT_TYPE_META_TAG, $html);
|
||||
} elseif ($hasHtmlTag) {
|
||||
$reworkedHtml = \preg_replace(
|
||||
'/<html(.*?)>/i',
|
||||
'<html$1><head>' . static::CONTENT_TYPE_META_TAG . '</head>',
|
||||
$html
|
||||
);
|
||||
} else {
|
||||
$reworkedHtml = static::CONTENT_TYPE_META_TAG . $html;
|
||||
}
|
||||
|
||||
return $reworkedHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that $this->domDocument has a BODY element and adds it if it is missing.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function ensureExistenceOfBodyElement()
|
||||
{
|
||||
if ($this->domDocument->getElementsByTagName('body')->item(0) !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$htmlElement = $this->domDocument->getElementsByTagName('html')->item(0);
|
||||
$htmlElement->appendChild($this->domDocument->createElement('body'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
<?php
|
||||
|
||||
namespace Pelago\Emogrifier\HtmlProcessor;
|
||||
|
||||
/**
|
||||
* This HtmlProcessor can convert style HTML attributes to the corresponding other visual HTML attributes,
|
||||
* e.g. it converts style="width: 100px" to width="100".
|
||||
*
|
||||
* It will only add attributes, but leaves the style attribute untouched.
|
||||
*
|
||||
* To trigger the conversion, call the convertCssToVisualAttributes method.
|
||||
*
|
||||
* @internal This class currently is a new technology preview, and its API is still in flux. Don't use it in production.
|
||||
*
|
||||
* @author Oliver Klee <github@oliverklee.de>
|
||||
*/
|
||||
class CssToAttributeConverter extends AbstractHtmlProcessor
|
||||
{
|
||||
/**
|
||||
* This multi-level array contains simple mappings of CSS properties to
|
||||
* HTML attributes. If a mapping only applies to certain HTML nodes or
|
||||
* only for certain values, the mapping is an object with a whitelist
|
||||
* of nodes and values.
|
||||
*
|
||||
* @var mixed[][]
|
||||
*/
|
||||
private $cssToHtmlMap = [
|
||||
'background-color' => [
|
||||
'attribute' => 'bgcolor',
|
||||
],
|
||||
'text-align' => [
|
||||
'attribute' => 'align',
|
||||
'nodes' => ['p', 'div', 'td'],
|
||||
'values' => ['left', 'right', 'center', 'justify'],
|
||||
],
|
||||
'float' => [
|
||||
'attribute' => 'align',
|
||||
'nodes' => ['table', 'img'],
|
||||
'values' => ['left', 'right'],
|
||||
],
|
||||
'border-spacing' => [
|
||||
'attribute' => 'cellspacing',
|
||||
'nodes' => ['table'],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[][]
|
||||
*/
|
||||
private static $parsedCssCache = [];
|
||||
|
||||
/**
|
||||
* Maps the CSS from the style nodes to visual HTML attributes.
|
||||
*
|
||||
* @return CssToAttributeConverter fluent interface
|
||||
*/
|
||||
public function convertCssToVisualAttributes()
|
||||
{
|
||||
/** @var \DOMElement $node */
|
||||
foreach ($this->getAllNodesWithStyleAttribute() as $node) {
|
||||
$inlineStyleDeclarations = $this->parseCssDeclarationsBlock($node->getAttribute('style'));
|
||||
$this->mapCssToHtmlAttributes($inlineStyleDeclarations, $node);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list with all DOM nodes that have a style attribute.
|
||||
*
|
||||
* @return \DOMNodeList
|
||||
*/
|
||||
private function getAllNodesWithStyleAttribute()
|
||||
{
|
||||
$xPath = new \DOMXPath($this->domDocument);
|
||||
|
||||
return $xPath->query('//*[@style]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a CSS declaration block into property name/value pairs.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* The declaration block
|
||||
*
|
||||
* "color: #000; font-weight: bold;"
|
||||
*
|
||||
* will be parsed into the following array:
|
||||
*
|
||||
* "color" => "#000"
|
||||
* "font-weight" => "bold"
|
||||
*
|
||||
* @param string $cssDeclarationsBlock the CSS declarations block without the curly braces, may be empty
|
||||
*
|
||||
* @return string[]
|
||||
* the CSS declarations with the property names as array keys and the property values as array values
|
||||
*/
|
||||
private function parseCssDeclarationsBlock($cssDeclarationsBlock)
|
||||
{
|
||||
if (isset(self::$parsedCssCache[$cssDeclarationsBlock])) {
|
||||
return self::$parsedCssCache[$cssDeclarationsBlock];
|
||||
}
|
||||
|
||||
$properties = [];
|
||||
$declarations = \preg_split('/;(?!base64|charset)/', $cssDeclarationsBlock);
|
||||
|
||||
foreach ($declarations as $declaration) {
|
||||
$matches = [];
|
||||
if (!\preg_match('/^([A-Za-z\\-]+)\\s*:\\s*(.+)$/s', \trim($declaration), $matches)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$propertyName = \strtolower($matches[1]);
|
||||
$propertyValue = $matches[2];
|
||||
$properties[$propertyName] = $propertyValue;
|
||||
}
|
||||
self::$parsedCssCache[$cssDeclarationsBlock] = $properties;
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies $styles to $node.
|
||||
*
|
||||
* This method maps CSS styles to HTML attributes and adds those to the
|
||||
* node.
|
||||
*
|
||||
* @param string[] $styles the new CSS styles taken from the global styles to be applied to this node
|
||||
* @param \DOMElement $node node to apply styles to
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function mapCssToHtmlAttributes(array $styles, \DOMElement $node)
|
||||
{
|
||||
foreach ($styles as $property => $value) {
|
||||
// Strip !important indicator
|
||||
$value = \trim(\str_replace('!important', '', $value));
|
||||
$this->mapCssToHtmlAttribute($property, $value, $node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to apply the CSS style to $node as an attribute.
|
||||
*
|
||||
* This method maps a CSS rule to HTML attributes and adds those to the node.
|
||||
*
|
||||
* @param string $property the name of the CSS property to map
|
||||
* @param string $value the value of the style rule to map
|
||||
* @param \DOMElement $node node to apply styles to
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function mapCssToHtmlAttribute($property, $value, \DOMElement $node)
|
||||
{
|
||||
if (!$this->mapSimpleCssProperty($property, $value, $node)) {
|
||||
$this->mapComplexCssProperty($property, $value, $node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the CSS property in the mapping table and maps it if it matches the conditions.
|
||||
*
|
||||
* @param string $property the name of the CSS property to map
|
||||
* @param string $value the value of the style rule to map
|
||||
* @param \DOMElement $node node to apply styles to
|
||||
*
|
||||
* @return bool true if the property can be mapped using the simple mapping table
|
||||
*/
|
||||
private function mapSimpleCssProperty($property, $value, \DOMElement $node)
|
||||
{
|
||||
if (!isset($this->cssToHtmlMap[$property])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$mapping = $this->cssToHtmlMap[$property];
|
||||
$nodesMatch = !isset($mapping['nodes']) || \in_array($node->nodeName, $mapping['nodes'], true);
|
||||
$valuesMatch = !isset($mapping['values']) || \in_array($value, $mapping['values'], true);
|
||||
if (!$nodesMatch || !$valuesMatch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$node->setAttribute($mapping['attribute'], $value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps CSS properties that need special transformation to an HTML attribute.
|
||||
*
|
||||
* @param string $property the name of the CSS property to map
|
||||
* @param string $value the value of the style rule to map
|
||||
* @param \DOMElement $node node to apply styles to
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function mapComplexCssProperty($property, $value, \DOMElement $node)
|
||||
{
|
||||
switch ($property) {
|
||||
case 'background':
|
||||
$this->mapBackgroundProperty($node, $value);
|
||||
break;
|
||||
case 'width':
|
||||
// intentional fall-through
|
||||
case 'height':
|
||||
$this->mapWidthOrHeightProperty($node, $value, $property);
|
||||
break;
|
||||
case 'margin':
|
||||
$this->mapMarginProperty($node, $value);
|
||||
break;
|
||||
case 'border':
|
||||
$this->mapBorderProperty($node, $value);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DOMElement $node node to apply styles to
|
||||
* @param string $value the value of the style rule to map
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function mapBackgroundProperty(\DOMElement $node, $value)
|
||||
{
|
||||
// parse out the color, if any
|
||||
$styles = \explode(' ', $value);
|
||||
$first = $styles[0];
|
||||
if (!\is_numeric($first[0]) && \strpos($first, 'url') !== 0) {
|
||||
// as this is not a position or image, assume it's a color
|
||||
$node->setAttribute('bgcolor', $first);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DOMElement $node node to apply styles to
|
||||
* @param string $value the value of the style rule to map
|
||||
* @param string $property the name of the CSS property to map
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function mapWidthOrHeightProperty(\DOMElement $node, $value, $property)
|
||||
{
|
||||
// only parse values in px and %, but not values like "auto"
|
||||
if (!\preg_match('/^(\\d+)(px|%)$/', $value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$number = \preg_replace('/[^0-9.%]/', '', $value);
|
||||
$node->setAttribute($property, $number);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DOMElement $node node to apply styles to
|
||||
* @param string $value the value of the style rule to map
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function mapMarginProperty(\DOMElement $node, $value)
|
||||
{
|
||||
if (!$this->isTableOrImageNode($node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$margins = $this->parseCssShorthandValue($value);
|
||||
if ($margins['left'] === 'auto' && $margins['right'] === 'auto') {
|
||||
$node->setAttribute('align', 'center');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DOMElement $node node to apply styles to
|
||||
* @param string $value the value of the style rule to map
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function mapBorderProperty(\DOMElement $node, $value)
|
||||
{
|
||||
if (!$this->isTableOrImageNode($node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($value === 'none' || $value === '0') {
|
||||
$node->setAttribute('border', '0');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DOMElement $node
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isTableOrImageNode(\DOMElement $node)
|
||||
{
|
||||
return $node->nodeName === 'table' || $node->nodeName === 'img';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a shorthand CSS value and splits it into individual values
|
||||
*
|
||||
* @param string $value a string of CSS value with 1, 2, 3 or 4 sizes
|
||||
* For example: padding: 0 auto;
|
||||
* '0 auto' is split into top: 0, left: auto, bottom: 0,
|
||||
* right: auto.
|
||||
*
|
||||
* @return string[] an array of values for top, right, bottom and left (using these as associative array keys)
|
||||
*/
|
||||
private function parseCssShorthandValue($value)
|
||||
{
|
||||
$values = \preg_split('/\\s+/', $value);
|
||||
|
||||
$css = [];
|
||||
$css['top'] = $values[0];
|
||||
$css['right'] = (\count($values) > 1) ? $values[1] : $css['top'];
|
||||
$css['bottom'] = (\count($values) > 2) ? $values[2] : $css['top'];
|
||||
$css['left'] = (\count($values) > 3) ? $values[3] : $css['right'];
|
||||
|
||||
return $css;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Pelago\Emogrifier\HtmlProcessor;
|
||||
|
||||
/**
|
||||
* Normalizes HTML:
|
||||
* - add a document type (HTML5) if missing
|
||||
* - disentangle incorrectly nested tags
|
||||
* - add HEAD and BODY elements (if they are missing)
|
||||
* - reformat the HTML
|
||||
*
|
||||
* @internal This class currently is a new technology preview, and its API is still in flux. Don't use it in production.
|
||||
*
|
||||
* @author Oliver Klee <github@oliverklee.de>
|
||||
*/
|
||||
class HtmlNormalizer extends AbstractHtmlProcessor
|
||||
{
|
||||
}
|
||||
100
lib/pelago/emogrifier/tests/Support/Traits/AssertCss.php
Normal file
100
lib/pelago/emogrifier/tests/Support/Traits/AssertCss.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Pelago\Tests\Support\Traits;
|
||||
|
||||
/**
|
||||
* Provides assertion methods for use with CSS content where whitespace may vary.
|
||||
*
|
||||
* @author Jake Hotson <jake.github@qzdesign.co.uk>
|
||||
*/
|
||||
trait AssertCss
|
||||
{
|
||||
/**
|
||||
* Processing of @media rules may involve removal of some unnecessary whitespace from the CSS placed in the <style>
|
||||
* element added to the docuemnt, due to the way that certain parts are `trim`med. Notably, whitespace either side
|
||||
* of "{", "}" and "," or at the beginning of the CSS may be removed.
|
||||
*
|
||||
* This method helps takes care of that, by converting a search needle for an exact match into a regular expression
|
||||
* that allows for such whitespace removal, so that the tests themselves do not need to be written less humanly
|
||||
* readable and can use inputs containing extra whitespace.
|
||||
*
|
||||
* @param string $needle Needle that would be used with `assertContains` or `assertNotContains`.
|
||||
*
|
||||
* @return string Needle to use with `assertRegExp` or `assertNotRegExp` instead.
|
||||
*/
|
||||
private static function getCssNeedleRegExp($needle)
|
||||
{
|
||||
$needleMatcher = \preg_replace_callback(
|
||||
'/\\s*+([{},])\\s*+|(^\\s++)|(>)\\s*+|(?:(?!\\s*+[{},]|^\\s)[^>])++/',
|
||||
function (array $matches) {
|
||||
if (isset($matches[1]) && $matches[1] !== '') {
|
||||
// matched possibly some whitespace, followed by "{", "}" or ",", then possibly more whitespace
|
||||
return '\\s*+' . \preg_quote($matches[1], '/') . '\\s*+';
|
||||
}
|
||||
if (isset($matches[2]) && $matches[2] !== '') {
|
||||
// matched whitespace at start
|
||||
return '\\s*+';
|
||||
}
|
||||
if (isset($matches[3]) && $matches[3] !== '') {
|
||||
// matched ">" (e.g. end of <style> tag) followed by possibly some whitespace
|
||||
return \preg_quote($matches[3], '/') . '\\s*+';
|
||||
}
|
||||
// matched any other sequence which could not overlap with the above
|
||||
return \preg_quote($matches[0], '/');
|
||||
},
|
||||
$needle
|
||||
);
|
||||
return '/' . $needleMatcher . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `assertContains` but allows for removal of some unnecessary whitespace from the CSS.
|
||||
*
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*/
|
||||
private static function assertContainsCss($needle, $haystack)
|
||||
{
|
||||
static::assertRegExp(
|
||||
static::getCssNeedleRegExp($needle),
|
||||
$haystack,
|
||||
'Plain text needle: "' . $needle . '"'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `assertNotContains` and also enforces the assertion with removal of some unnecessary whitespace from the
|
||||
* CSS.
|
||||
*
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*/
|
||||
private static function assertNotContainsCss($needle, $haystack)
|
||||
{
|
||||
static::assertNotRegExp(
|
||||
static::getCssNeedleRegExp($needle),
|
||||
$haystack,
|
||||
'Plain text needle: "' . $needle . '"'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a string of CSS occurs exactly a certain number of times in the result, allowing for removal of some
|
||||
* unnecessary whitespace.
|
||||
*
|
||||
* @param int $expectedCount
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*/
|
||||
private static function assertContainsCssCount(
|
||||
$expectedCount,
|
||||
$needle,
|
||||
$haystack
|
||||
) {
|
||||
static::assertSame(
|
||||
$expectedCount,
|
||||
\preg_match_all(static::getCssNeedleRegExp($needle), $haystack),
|
||||
'Plain text needle: "' . $needle . "\"\nHaystack: \"" . $haystack . '"'
|
||||
);
|
||||
}
|
||||
}
|
||||
2907
lib/pelago/emogrifier/tests/Unit/CssInlinerTest.php
Normal file
2907
lib/pelago/emogrifier/tests/Unit/CssInlinerTest.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,317 @@
|
||||
<?php
|
||||
|
||||
namespace Pelago\Tests\Unit\Emogrifier;
|
||||
|
||||
use Pelago\Emogrifier\CssConcatenator;
|
||||
|
||||
/**
|
||||
* Test case.
|
||||
*
|
||||
* @author Jake Hotson <jake.github@qzdesign.co.uk>
|
||||
*/
|
||||
class CssConcatenatorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var CssConcatenator
|
||||
*/
|
||||
private $subject = null;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
$this->subject = new CssConcatenator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function getCssInitiallyReturnsEmptyString()
|
||||
{
|
||||
$result = $this->subject->getCss();
|
||||
|
||||
static::assertSame('', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function appendSetsFirstRule()
|
||||
{
|
||||
$this->subject->append(['p'], 'color: green;');
|
||||
|
||||
$result = $this->subject->getCss();
|
||||
|
||||
static::assertSame('p{color: green;}', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function appendWithMediaQuerySetsFirstRuleInMediaRule()
|
||||
{
|
||||
$this->subject->append(['p'], 'color: green;', '@media screen');
|
||||
|
||||
$result = $this->subject->getCss();
|
||||
|
||||
static::assertSame('@media screen{p{color: green;}}', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function equivalentSelectorsDataProvider()
|
||||
{
|
||||
return [
|
||||
'one selector' => [['p'], ['p']],
|
||||
'two selectors' => [
|
||||
['p', 'ul'],
|
||||
['p', 'ul'],
|
||||
],
|
||||
'two selectors in different order' => [
|
||||
['p', 'ul'],
|
||||
['ul', 'p'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string[] $selectors1
|
||||
* @param string[] $selectors2
|
||||
*
|
||||
* @dataProvider equivalentSelectorsDataProvider
|
||||
*/
|
||||
public function appendCombinesRulesWithEquivalentSelectors(array $selectors1, array $selectors2)
|
||||
{
|
||||
$this->subject->append($selectors1, 'color: green;');
|
||||
$this->subject->append($selectors2, 'font-size: 16px;');
|
||||
|
||||
$result = $this->subject->getCss();
|
||||
|
||||
$expectedResult = \implode(',', $selectors1) . '{color: green;font-size: 16px;}';
|
||||
|
||||
static::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function appendInsertsSemicolonCombiningRulesWithoutTrailingSemicolon()
|
||||
{
|
||||
$this->subject->append(['p'], 'color: green');
|
||||
$this->subject->append(['p'], 'font-size: 16px');
|
||||
|
||||
$result = $this->subject->getCss();
|
||||
|
||||
static::assertSame('p{color: green;font-size: 16px}', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function differentSelectorsDataProvider()
|
||||
{
|
||||
return [
|
||||
'single selectors' => [
|
||||
['p'],
|
||||
['ul'],
|
||||
['p', 'ul'],
|
||||
],
|
||||
'single selector and an entirely different pair' => [
|
||||
['p'],
|
||||
['ul', 'ol'],
|
||||
['p', 'ul', 'ol'],
|
||||
],
|
||||
'single selector and a superset pair' => [
|
||||
['p'],
|
||||
['p', 'ul'],
|
||||
['p', 'ul'],
|
||||
],
|
||||
'pair of selectors and an entirely different single' => [
|
||||
['p', 'ul'],
|
||||
['ol'],
|
||||
['p', 'ul', 'ol'],
|
||||
],
|
||||
'pair of selectors and a subset single' => [
|
||||
['p', 'ul'],
|
||||
['ul'],
|
||||
['p', 'ul'],
|
||||
],
|
||||
'entirely different pairs of selectors' => [
|
||||
['p', 'ul'],
|
||||
['ol', 'h1'],
|
||||
['p', 'ul', 'ol', 'h1'],
|
||||
],
|
||||
'pairs of selectors with one common' => [
|
||||
['p', 'ul'],
|
||||
['ul', 'ol'],
|
||||
['p', 'ul', 'ol'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string[] $selectors1
|
||||
* @param string[] $selectors2
|
||||
* @param string[] $combinedSelectors
|
||||
*
|
||||
* @dataProvider differentSelectorsDataProvider
|
||||
*/
|
||||
public function appendCombinesSameRulesWithDifferentSelectors(
|
||||
array $selectors1,
|
||||
array $selectors2,
|
||||
array $combinedSelectors
|
||||
) {
|
||||
$this->subject->append($selectors1, 'color: green;');
|
||||
$this->subject->append($selectors2, 'color: green;');
|
||||
|
||||
$result = $this->subject->getCss();
|
||||
|
||||
$expectedResult = \implode(',', $combinedSelectors) . '{color: green;}';
|
||||
|
||||
static::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string[] $selectors1
|
||||
* @param string[] $selectors2
|
||||
*
|
||||
* @dataProvider differentSelectorsDataProvider
|
||||
*/
|
||||
public function appendNotCombinesDifferentRulesWithDifferentSelectors(array $selectors1, array $selectors2)
|
||||
{
|
||||
$this->subject->append($selectors1, 'color: green;');
|
||||
$this->subject->append($selectors2, 'font-size: 16px;');
|
||||
|
||||
$result = $this->subject->getCss();
|
||||
|
||||
$expectedResult = \implode(',', $selectors1) . '{color: green;}'
|
||||
. \implode(',', $selectors2) . '{font-size: 16px;}';
|
||||
|
||||
static::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function appendCombinesRulesForSameMediaQueryInMediaRule()
|
||||
{
|
||||
$this->subject->append(['p'], 'color: green;', '@media screen');
|
||||
$this->subject->append(['ul'], 'font-size: 16px;', '@media screen');
|
||||
|
||||
$result = $this->subject->getCss();
|
||||
|
||||
static::assertSame('@media screen{p{color: green;}ul{font-size: 16px;}}', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string[] $selectors1
|
||||
* @param string[] $selectors2
|
||||
*
|
||||
* @dataProvider equivalentSelectorsDataProvider
|
||||
*/
|
||||
public function appendCombinesRulesWithEquivalentSelectorsWithinMediaRule(array $selectors1, array $selectors2)
|
||||
{
|
||||
$this->subject->append($selectors1, 'color: green;', '@media screen');
|
||||
$this->subject->append($selectors2, 'font-size: 16px;', '@media screen');
|
||||
|
||||
$result = $this->subject->getCss();
|
||||
|
||||
$expectedResult = '@media screen{' . \implode(',', $selectors1) . '{color: green;font-size: 16px;}}';
|
||||
|
||||
static::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string[] $selectors1
|
||||
* @param string[] $selectors2
|
||||
* @param string[] $combinedSelectors
|
||||
*
|
||||
* @dataProvider differentSelectorsDataProvider
|
||||
*/
|
||||
public function appendCombinesSameRulesWithDifferentSelectorsWithinMediaRule(
|
||||
array $selectors1,
|
||||
array $selectors2,
|
||||
array $combinedSelectors
|
||||
) {
|
||||
$this->subject->append($selectors1, 'color: green;', '@media screen');
|
||||
$this->subject->append($selectors2, 'color: green;', '@media screen');
|
||||
|
||||
$result = $this->subject->getCss();
|
||||
|
||||
$expectedResult = '@media screen{' . \implode(',', $combinedSelectors) . '{color: green;}}';
|
||||
|
||||
static::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function appendNotCombinesRulesForDifferentMediaQueryInMediaRule()
|
||||
{
|
||||
$this->subject->append(['p'], 'color: green;', '@media screen');
|
||||
$this->subject->append(['p'], 'color: green;', '@media print');
|
||||
|
||||
$result = $this->subject->getCss();
|
||||
|
||||
static::assertSame('@media screen{p{color: green;}}@media print{p{color: green;}}', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[][]
|
||||
*/
|
||||
public function combinableRulesDataProvider()
|
||||
{
|
||||
return [
|
||||
'same selectors' => [['p'], 'color: green;', ['p'], 'font-size: 16px;', ''],
|
||||
'same declarations block' => [['p'], 'color: green;', ['ul'], 'color: green;', ''],
|
||||
'same media query' => [['p'], 'color: green;', ['ul'], 'font-size: 16px;', '@media screen'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param array $rule1Selectors
|
||||
* @param string $rule1DeclarationsBlock
|
||||
* @param array $rule2Selectors
|
||||
* @param string $rule2DeclarationsBlock
|
||||
* @param string $media
|
||||
*
|
||||
* @dataProvider combinableRulesDataProvider
|
||||
*/
|
||||
public function appendNotCombinesNonadjacentRules(
|
||||
array $rule1Selectors,
|
||||
$rule1DeclarationsBlock,
|
||||
array $rule2Selectors,
|
||||
$rule2DeclarationsBlock,
|
||||
$media
|
||||
) {
|
||||
$this->subject->append($rule1Selectors, $rule1DeclarationsBlock, $media);
|
||||
$this->subject->append(['.intervening'], '-intervening-property: 0;');
|
||||
$this->subject->append($rule2Selectors, $rule2DeclarationsBlock, $media);
|
||||
|
||||
$result = $this->subject->getCss();
|
||||
|
||||
$expectedRule1Css = \implode(',', $rule1Selectors) . '{' . $rule1DeclarationsBlock . '}';
|
||||
$expectedRule2Css = \implode(',', $rule2Selectors) . '{' . $rule2DeclarationsBlock . '}';
|
||||
if ($media !== '') {
|
||||
$expectedRule1Css = $media . '{' . $expectedRule1Css . '}';
|
||||
$expectedRule2Css = $media . '{' . $expectedRule2Css . '}';
|
||||
}
|
||||
$expectedResult = $expectedRule1Css . '.intervening{-intervening-property: 0;}' . $expectedRule2Css;
|
||||
|
||||
static::assertSame($expectedResult, $result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
<?php
|
||||
|
||||
namespace Pelago\Tests\Unit\Emogrifier\HtmlProcessor;
|
||||
|
||||
use Pelago\Emogrifier\HtmlProcessor\AbstractHtmlProcessor;
|
||||
use Pelago\Tests\Unit\Emogrifier\HtmlProcessor\Fixtures\TestingHtmlProcessor;
|
||||
|
||||
/**
|
||||
* Test case.
|
||||
*
|
||||
* @author Oliver Klee <github@oliverklee.de>
|
||||
*/
|
||||
class AbstractHtmlProcessorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function fixtureIsAbstractHtmlProcessor()
|
||||
{
|
||||
static::assertInstanceOf(AbstractHtmlProcessor::class, new TestingHtmlProcessor('<html></html>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function reformatsHtml()
|
||||
{
|
||||
$rawHtml = '<!DOCTYPE HTML>' .
|
||||
'<html>' .
|
||||
'<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head>' .
|
||||
'<body></body>' .
|
||||
'</html>';
|
||||
$formattedHtml = "<!DOCTYPE HTML>\n" .
|
||||
"<html>\n" .
|
||||
'<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head>' . "\n" .
|
||||
"<body></body>\n" .
|
||||
"</html>\n";
|
||||
|
||||
$subject = new TestingHtmlProcessor($rawHtml);
|
||||
|
||||
static::assertSame($formattedHtml, $subject->render());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
*/
|
||||
public function nonHtmlDataProvider()
|
||||
{
|
||||
return [
|
||||
'empty string' => [''],
|
||||
'null' => [null],
|
||||
'integer' => [2],
|
||||
'float' => [3.14159],
|
||||
'object' => [new \stdClass()],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @expectedException \InvalidArgumentException
|
||||
*
|
||||
* @param mixed $html
|
||||
*
|
||||
* @dataProvider nonHtmlDataProvider
|
||||
*/
|
||||
public function constructorWithNoHtmlDataThrowsException($html)
|
||||
{
|
||||
new TestingHtmlProcessor($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function invalidHtmlDataProvider()
|
||||
{
|
||||
return [
|
||||
'broken nesting gets nested' => ['<b><i></b></i>', '<b><i></i></b>'],
|
||||
'partial opening tag gets closed' => ['<b', '<b></b>'],
|
||||
'only opening tag gets closed' => ['<b>', '<b></b>'],
|
||||
'only closing tag gets removed' => ['foo</b> bar', 'foo bar'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $expectedHtml
|
||||
*
|
||||
* @dataProvider invalidHtmlDataProvider
|
||||
*/
|
||||
public function renderRepairsBrokenHtml($input, $expectedHtml)
|
||||
{
|
||||
$subject = new TestingHtmlProcessor($input);
|
||||
$result = $subject->render();
|
||||
|
||||
static::assertContains($expectedHtml, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function contentWithoutHtmlTagDataProvider()
|
||||
{
|
||||
return [
|
||||
'doctype only' => ['<!DOCTYPE html>'],
|
||||
'body content only' => ['<p>Hello</p>'],
|
||||
'HEAD element' => ['<head></head>'],
|
||||
'BODY element' => ['<body></body>'],
|
||||
'HEAD AND BODY element' => ['<head></head><body></body>'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @dataProvider contentWithoutHtmlTagDataProvider
|
||||
*/
|
||||
public function addsMissingHtmlTag($html)
|
||||
{
|
||||
$subject = new TestingHtmlProcessor($html);
|
||||
|
||||
$result = $subject->render();
|
||||
|
||||
static::assertContains('<html>', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function contentWithoutHeadTagDataProvider()
|
||||
{
|
||||
return [
|
||||
'doctype only' => ['<!DOCTYPE html>'],
|
||||
'body content only' => ['<p>Hello</p>'],
|
||||
'BODY element' => ['<body></body>'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @dataProvider contentWithoutHeadTagDataProvider
|
||||
*/
|
||||
public function addsMissingHeadTag($html)
|
||||
{
|
||||
$subject = new TestingHtmlProcessor($html);
|
||||
|
||||
$result = $subject->render();
|
||||
|
||||
static::assertContains('<head>', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function contentWithoutBodyTagDataProvider()
|
||||
{
|
||||
return [
|
||||
'doctype only' => ['<!DOCTYPE html>'],
|
||||
'HEAD element' => ['<head></head>'],
|
||||
'body content only' => ['<p>Hello</p>'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @dataProvider contentWithoutBodyTagDataProvider
|
||||
*/
|
||||
public function addsMissingBodyTag($html)
|
||||
{
|
||||
$subject = new TestingHtmlProcessor($html);
|
||||
|
||||
$result = $subject->render();
|
||||
|
||||
static::assertContains('<body>', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function putsMissingBodyElementAroundBodyContent()
|
||||
{
|
||||
$subject = new TestingHtmlProcessor('<p>Hello</p>');
|
||||
|
||||
$result = $subject->render();
|
||||
|
||||
static::assertContains('<body><p>Hello</p></body>', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function specialCharactersDataProvider()
|
||||
{
|
||||
return [
|
||||
'template markers with dollar signs & square brackets' => ['$[USER:NAME]$'],
|
||||
'UTF-8 umlauts' => ['Küss die Hand, schöne Frau.'],
|
||||
'HTML entities' => ['a & b > c'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $codeNotToBeChanged
|
||||
*
|
||||
* @dataProvider specialCharactersDataProvider
|
||||
*/
|
||||
public function keepsSpecialCharacters($codeNotToBeChanged)
|
||||
{
|
||||
$html = '<html><p>' . $codeNotToBeChanged . '</p></html>';
|
||||
$subject = new TestingHtmlProcessor($html);
|
||||
|
||||
$result = $subject->render();
|
||||
|
||||
static::assertContains($codeNotToBeChanged, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function addMissingHtml5DocumentType()
|
||||
{
|
||||
$subject = new TestingHtmlProcessor('<html></html>');
|
||||
|
||||
$result = $subject->render();
|
||||
|
||||
static::assertContains('<!DOCTYPE html>', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function documentTypeDataProvider()
|
||||
{
|
||||
return [
|
||||
'HTML5' => ['<!DOCTYPE html>'],
|
||||
'XHTML 1.0 strict' => [
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' .
|
||||
'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
|
||||
],
|
||||
'XHTML 1.0 transitional' => [
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' .
|
||||
'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
|
||||
],
|
||||
'HTML 4 transitional' => [
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" ' .
|
||||
'"http://www.w3.org/TR/REC-html40/loose.dtd">',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $documentType
|
||||
*
|
||||
* @dataProvider documentTypeDataProvider
|
||||
*/
|
||||
public function keepsExistingDocumentType($documentType)
|
||||
{
|
||||
$html = $documentType . '<html></html>';
|
||||
$subject = new TestingHtmlProcessor($html);
|
||||
|
||||
$result = $subject->render();
|
||||
|
||||
static::assertContains($documentType, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function addsMissingContentTypeMetaTag()
|
||||
{
|
||||
$subject = new TestingHtmlProcessor('<p>Hello</p>');
|
||||
|
||||
$result = $subject->render();
|
||||
|
||||
static::assertContains('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notAddsSecondContentTypeMetaTag()
|
||||
{
|
||||
$html = '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head>';
|
||||
$subject = new TestingHtmlProcessor($html);
|
||||
|
||||
$result = $subject->render();
|
||||
|
||||
$numberOfContentTypeMetaTags = \substr_count($result, 'Content-Type');
|
||||
static::assertSame(1, $numberOfContentTypeMetaTags);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $documentType
|
||||
*
|
||||
* @dataProvider documentTypeDataProvider
|
||||
*/
|
||||
public function convertsXmlSelfClosingTagsToNonXmlSelfClosingTag($documentType)
|
||||
{
|
||||
$subject = new TestingHtmlProcessor($documentType . '<html><body><br/></body></html>');
|
||||
|
||||
$result = $subject->render();
|
||||
|
||||
static::assertContains('<body><br></body>', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $documentType
|
||||
*
|
||||
* @dataProvider documentTypeDataProvider
|
||||
*/
|
||||
public function keepsNonXmlSelfClosingTags($documentType)
|
||||
{
|
||||
$subject = new TestingHtmlProcessor($documentType . '<html><body><br></body></html>');
|
||||
|
||||
$result = $subject->render();
|
||||
|
||||
static::assertContains('<body><br></body>', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function renderBodyContentForEmptyBodyReturnsEmptyString()
|
||||
{
|
||||
$subject = new TestingHtmlProcessor('<html><body></body></html>');
|
||||
|
||||
$result = $subject->renderBodyContent();
|
||||
|
||||
static::assertSame('', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function renderBodyContentReturnsBodyContent()
|
||||
{
|
||||
$bodyContent = '<p>Hello world</p>';
|
||||
$subject = new TestingHtmlProcessor('<html><body>' . $bodyContent . '</body></html>');
|
||||
|
||||
$result = $subject->renderBodyContent();
|
||||
|
||||
static::assertSame($bodyContent, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function getDomDocumentReturnsDomDocument()
|
||||
{
|
||||
$subject = new TestingHtmlProcessor('<html></html>');
|
||||
|
||||
static::assertInstanceOf(\DOMDocument::class, $subject->getDomDocument());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function getDomDocumentWithNormalizedHtmlRepresentsTheGivenHtml()
|
||||
{
|
||||
$html = "<!DOCTYPE html>\n<html>\n<head>" .
|
||||
'<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' .
|
||||
"</head>\n<body>\n<br>\n</body>\n</html>\n";
|
||||
$subject = new TestingHtmlProcessor($html);
|
||||
|
||||
$domDocument = $subject->getDomDocument();
|
||||
|
||||
self::assertSame($html, $domDocument->saveHTML());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace Pelago\Tests\Unit\Emogrifier\HtmlProcessor;
|
||||
|
||||
use Pelago\Emogrifier\HtmlProcessor\AbstractHtmlProcessor;
|
||||
use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter;
|
||||
|
||||
/**
|
||||
* Test case.
|
||||
*
|
||||
* @author Oliver Klee <github@oliverklee.de>
|
||||
*/
|
||||
class CssToAttributeConverterTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function classIsAbstractHtmlProcessor()
|
||||
{
|
||||
static::assertInstanceOf(AbstractHtmlProcessor::class, new CssToAttributeConverter('<html></html>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function renderWithoutConvertCssToVisualAttributesCallNotAddsVisuablAttributes()
|
||||
{
|
||||
$html = '<html style="text-align: right;"></html>';
|
||||
$subject = new CssToAttributeConverter($html);
|
||||
|
||||
static::assertContains('<html style="text-align: right;">', $subject->render());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function convertCssToVisualAttributesUsesFluentInterface()
|
||||
{
|
||||
$html = '<html style="text-align: right;"></html>';
|
||||
$subject = new CssToAttributeConverter($html);
|
||||
|
||||
static::assertSame($subject, $subject->convertCssToVisualAttributes());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function matchingCssToHtmlMappingDataProvider()
|
||||
{
|
||||
return [
|
||||
'background-color => bgcolor' => ['<p style="background-color: red;">hi</p>', 'bgcolor="red"'],
|
||||
'background-color with !important => bgcolor' => [
|
||||
'<p style="background-color: red !important;">hi</p>',
|
||||
'bgcolor="red"',
|
||||
],
|
||||
'p.text-align => align' => ['<p style="text-align: left;">hi</p>', 'align="left"'],
|
||||
'div.text-align => align' => ['<div style="text-align: left;">hi</div>', 'align="left"'],
|
||||
'td.text-align => align' => [
|
||||
'<table><tr><td style="text-align: left;">hi</td></tr></table>',
|
||||
'align="left',
|
||||
],
|
||||
'text-align: left => align=left' => ['<p style="text-align: left;">hi</p>', 'align="left"'],
|
||||
'text-align: right => align=right' => ['<p style="text-align: right;">hi</p>', 'align="right"'],
|
||||
'text-align: center => align=center' => ['<p style="text-align: center;">hi</p>', 'align="center"'],
|
||||
'text-align: justify => align:justify' => ['<p style="text-align: justify;">hi</p>', 'align="justify"'],
|
||||
'img.float: right => align=right' => ['<img style="float: right;">', 'align="right"'],
|
||||
'img.float: left => align=left' => ['<img style="float: left;">', 'align="left"'],
|
||||
'table.float: right => align=right' => ['<table style="float: right;"></table>', 'align="right"'],
|
||||
'table.float: left => align=left' => ['<table style="float: left;"></table>', 'align="left"'],
|
||||
'table.border-spacing: 0 => cellspacing=0' => [
|
||||
'<table style="border-spacing: 0;"></table>',
|
||||
'cellspacing="0"',
|
||||
],
|
||||
'background => bgcolor' => ['<p style="background: red top;">Bonjour</p>', 'bgcolor="red"'],
|
||||
'width with px' => ['<p style="width: 100px;">Hi</p>', 'width="100"'],
|
||||
'width with %' => ['<p style="width: 50%;">Hi</p>', 'width="50%"'],
|
||||
'height with px' => ['<p style="height: 100px;">Hi</p>', 'height="100"'],
|
||||
'height with %' => ['<p style="height: 50%;">Hi</p>', 'height="50%"'],
|
||||
'img.margin: 0 auto (horizontal centering) => align=center' => [
|
||||
'<img style="margin: 0 auto;">',
|
||||
'align="center"',
|
||||
],
|
||||
'img.margin: auto (horizontal centering) => align=center' => [
|
||||
'<img style="margin: auto;">',
|
||||
'align="center"',
|
||||
],
|
||||
'img.margin: 10 auto 30 auto (horizontal centering) => align=center' => [
|
||||
'<img style="margin: 10 auto 30 auto;">',
|
||||
'align="center"',
|
||||
],
|
||||
'table.margin: 0 auto (horizontal centering) => align=center' => [
|
||||
'<table style="margin: 0 auto;"></table>',
|
||||
'align="center"',
|
||||
],
|
||||
'table.margin: auto (horizontal centering) => align=center' => [
|
||||
'<table style="margin: auto;"></table>',
|
||||
'align="center"',
|
||||
],
|
||||
'table.margin: 10 auto 30 auto (horizontal centering) => align=center' => [
|
||||
'<table style="margin: 10 auto 30 auto;"></table>',
|
||||
'align="center"',
|
||||
],
|
||||
'img.border: none => border=0' => ['<img style="border: none;">', 'border="0"'],
|
||||
'img.border: 0 => border=0' => ['<img style="border: none;">', 'border="0"'],
|
||||
'table.border: none => border=0' => ['<table style="border: none;"></table>', 'border="0"'],
|
||||
'table.border: 0 => border=0' => ['<table style="border: 0;"></table>', 'border="0"'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $body The HTML
|
||||
* @param string $attributes The attributes that are expected on the element
|
||||
*
|
||||
* @dataProvider matchingCssToHtmlMappingDataProvider
|
||||
*/
|
||||
public function convertCssToVisualAttributesMapsSuitableCssToHtml($body, $attributes)
|
||||
{
|
||||
$subject = new CssToAttributeConverter('<html><body>' . $body . '</body></html>');
|
||||
|
||||
$subject->convertCssToVisualAttributes();
|
||||
$html = $subject->render();
|
||||
|
||||
static::assertContains($attributes, $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function notMatchingCssToHtmlMappingDataProvider()
|
||||
{
|
||||
return [
|
||||
'background URL' => ['<p style="background: url(bg.png);">Hello</p>'],
|
||||
'background URL with position' => ['<p style="background: url(bg.png) top;">Hello</p>'],
|
||||
'p.margin: 10 5 30 auto (no horizontal centering)' => ['<img style="margin: 10 5 30 auto;">'],
|
||||
'p.margin: auto' => ['<p style="margin: auto;">Hi</p>'],
|
||||
'p.border: none' => ['<p style="border: none;">Hi</p>'],
|
||||
'img.border: 1px solid black' => ['<img style="border: 1px solid black;">'],
|
||||
'span.text-align' => ['<span style="text-align: justify;">Hi</span>'],
|
||||
'text-align: inherit' => ['<p style="text-align: inherit;">Hi</p>'],
|
||||
'span.float' => ['<span style="float: right;">Hi</span>'],
|
||||
'float: none' => ['<table style="float: none;"></table>'],
|
||||
'p.border-spacing' => ['<p style="border-spacing: 5px;">Hi</p>'],
|
||||
'height: auto' => ['<img src="logo.png" alt="" style="height: auto;">'],
|
||||
'width: auto' => ['<img src="logo.png" alt="" style="width: auto;">'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $body the HTML
|
||||
*
|
||||
* @dataProvider notMatchingCssToHtmlMappingDataProvider
|
||||
*/
|
||||
public function convertCssToVisualAttributesNotMapsUnsuitableCssToHtml($body)
|
||||
{
|
||||
$subject = new CssToAttributeConverter('<html><body>' . $body . '</body></html>');
|
||||
|
||||
$subject->convertCssToVisualAttributes();
|
||||
$html = $subject->render();
|
||||
|
||||
static::assertContains($body, $html);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Pelago\Tests\Unit\Emogrifier\HtmlProcessor\Fixtures;
|
||||
|
||||
use Pelago\Emogrifier\HtmlProcessor\AbstractHtmlProcessor;
|
||||
|
||||
/**
|
||||
* Fixture class for AbstractHtmlProcessor.
|
||||
*
|
||||
* @author Oliver Klee <github@oliverklee.de>
|
||||
*/
|
||||
class TestingHtmlProcessor extends AbstractHtmlProcessor
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Pelago\Tests\Unit\Emogrifier\HtmlProcessor;
|
||||
|
||||
use Pelago\Emogrifier\HtmlProcessor\AbstractHtmlProcessor;
|
||||
use Pelago\Emogrifier\HtmlProcessor\HtmlNormalizer;
|
||||
|
||||
/**
|
||||
* Test case.
|
||||
*
|
||||
* @author Oliver Klee <github@oliverklee.de>
|
||||
*/
|
||||
class HtmlNormalizerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function classIsAbstractHtmlProcessor()
|
||||
{
|
||||
static::assertInstanceOf(AbstractHtmlProcessor::class, new HtmlNormalizer('<html></html>'));
|
||||
}
|
||||
}
|
||||
3091
lib/pelago/emogrifier/tests/Unit/EmogrifierTest.php
Normal file
3091
lib/pelago/emogrifier/tests/Unit/EmogrifierTest.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
|
||||
namespace Pelago\Tests\Unit\Support\Traits;
|
||||
|
||||
use Pelago\Tests\Support\Traits\AssertCss;
|
||||
|
||||
/**
|
||||
* Test case.
|
||||
*
|
||||
* @author Jake Hotson <jake.github@qzdesign.co.uk>
|
||||
*/
|
||||
class AssertCssTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
use AssertCss;
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function getCssNeedleRegExpEscapesAllSpecialCharacters()
|
||||
{
|
||||
$needle = '.\\+*?[^]$(){}=!<>|:-/';
|
||||
|
||||
$result = static::getCssNeedleRegExp($needle);
|
||||
|
||||
$resultWithWhitespaceMatchersRemoved = \str_replace('\\s*+', '', $result);
|
||||
|
||||
static::assertSame(
|
||||
'/' . \preg_quote($needle, '/') . '/',
|
||||
$resultWithWhitespaceMatchersRemoved
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function getCssNeedleRegExpNotEscapesNonSpecialCharacters()
|
||||
{
|
||||
$needle = \implode('', \array_merge(\range('a', 'z'), \range('A', 'Z'), \range('0 ', '9 ')))
|
||||
. "\r\n\t `¬\"£%&_;'@~,";
|
||||
|
||||
$result = static::getCssNeedleRegExp($needle);
|
||||
|
||||
$resultWithWhitespaceMatchersRemoved = \str_replace('\\s*+', '', $result);
|
||||
|
||||
static::assertSame(
|
||||
'/' . $needle . '/',
|
||||
$resultWithWhitespaceMatchersRemoved
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function contentWithOptionalWhitespaceDataProvider()
|
||||
{
|
||||
return [
|
||||
'"{" alone' => ['{', ''],
|
||||
'"}" alone' => ['}', ''],
|
||||
'"," alone' => [',', ''],
|
||||
'"{" with non-special character' => ['{', 'a'],
|
||||
'"{" with two non-special characters' => ['{', 'a0'],
|
||||
'"{" with special character' => ['{', '.'],
|
||||
'"{" with two special characters' => ['{', '.+'],
|
||||
'"{" with special character and non-special character' => ['{', '.a'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $contentToInsertAround
|
||||
* @param string $otherContent
|
||||
*
|
||||
* @dataProvider contentWithOptionalWhitespaceDataProvider
|
||||
*/
|
||||
public function getCssNeedleRegExpInsertsOptionalWhitespace($contentToInsertAround, $otherContent)
|
||||
{
|
||||
$result = static::getCssNeedleRegExp($otherContent . $contentToInsertAround . $otherContent);
|
||||
|
||||
$quotedOtherContent = \preg_quote($otherContent, '/');
|
||||
$expectedResult = '/' . $quotedOtherContent . '\\s*+' . \preg_quote($contentToInsertAround, '/') . '\\s*+'
|
||||
. $quotedOtherContent . '/';
|
||||
|
||||
static::assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function getCssNeedleRegExpReplacesWhitespaceAtStartWithOptionalWhitespace()
|
||||
{
|
||||
$result = static::getCssNeedleRegExp(' a');
|
||||
|
||||
static::assertSame('/\\s*+a/', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function styleTagDataProvider()
|
||||
{
|
||||
return [
|
||||
'without space after' => ['<style>a'],
|
||||
'one space after' => ['<style> a'],
|
||||
'two spaces after' => ['<style> a'],
|
||||
'linefeed after' => ["<style>\na"],
|
||||
'Windows line ending after' => ["<style>\r\na"],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $needle
|
||||
*
|
||||
* @dataProvider styleTagDataProvider
|
||||
*/
|
||||
public function getCssNeedleRegExpInsertsOptionalWhitespaceAfterStyleTag($needle)
|
||||
{
|
||||
$result = static::getCssNeedleRegExp($needle);
|
||||
|
||||
static::assertSame('/\\<style\\>\\s*+a/', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function needleFoundDataProvider()
|
||||
{
|
||||
$cssStrings = [
|
||||
'unminified CSS' => 'html, body { color: green; }',
|
||||
'minified CSS' => 'html,body{color: green;}',
|
||||
'CSS with extra spaces' => ' html , body { color: green; }',
|
||||
'CSS with linefeeds' => "\nhtml\n,\nbody\n{\ncolor: green;\n}",
|
||||
'CSS with Windows line endings' => "\r\nhtml\r\n,\r\nbody\r\n{\r\ncolor: green;\r\n}",
|
||||
];
|
||||
|
||||
$datasets = [];
|
||||
foreach ($cssStrings as $needleDescription => $needle) {
|
||||
foreach ($cssStrings as $haystackDescription => $haystack) {
|
||||
$description = $needleDescription . ' in ' . $haystackDescription;
|
||||
$datasets[$description] = [$needle, $haystack];
|
||||
$datasets[$description . ' in <style> tag'] = [
|
||||
'<style>' . $needle . '</style>',
|
||||
'<style>' . $haystack . '</style>',
|
||||
];
|
||||
}
|
||||
}
|
||||
return $datasets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*/
|
||||
public function needleNotFoundDataProvider()
|
||||
{
|
||||
return [
|
||||
'CSS part with "{" not in CSS' => ['p {', 'body { color: green; }'],
|
||||
'CSS part with "}" not in CSS' => ['color: red; }', 'body { color: green; }'],
|
||||
'CSS part with "," not in CSS' => ['html, body', 'body { color: green; }'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*
|
||||
* @dataProvider needleFoundDataProvider
|
||||
*/
|
||||
public function assertContainsCssPassesTestIfNeedleFound($needle, $haystack)
|
||||
{
|
||||
static::assertContainsCss($needle, $haystack);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*
|
||||
* @dataProvider needleNotFoundDataProvider
|
||||
*
|
||||
* @expectedException \PHPUnit_Framework_ExpectationFailedException
|
||||
*/
|
||||
public function assertContainsCssFailsTestIfNeedleNotFound($needle, $haystack)
|
||||
{
|
||||
static::assertContainsCss($needle, $haystack);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*
|
||||
* @dataProvider needleNotFoundDataProvider
|
||||
*/
|
||||
public function assertNotContainsCssPassesTestIfNeedleNotFound($needle, $haystack)
|
||||
{
|
||||
static::assertNotContainsCss($needle, $haystack);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*
|
||||
* @dataProvider needleFoundDataProvider
|
||||
*
|
||||
* @expectedException \PHPUnit_Framework_ExpectationFailedException
|
||||
*/
|
||||
public function assertNotContainsCssFailsTestIfNeedleFound($needle, $haystack)
|
||||
{
|
||||
static::assertNotContainsCss($needle, $haystack);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*
|
||||
* @dataProvider needleNotFoundDataProvider
|
||||
*/
|
||||
public function assertContainsCssCountPassesTestExpectingZeroIfNeedleNotFound($needle, $haystack)
|
||||
{
|
||||
static::assertContainsCssCount(0, $needle, $haystack);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*
|
||||
* @dataProvider needleFoundDataProvider
|
||||
*
|
||||
* @expectedException \PHPUnit_Framework_ExpectationFailedException
|
||||
*/
|
||||
public function assertContainsCssCountFailsTestExpectingZeroIfNeedleFound($needle, $haystack)
|
||||
{
|
||||
static::assertContainsCssCount(0, $needle, $haystack);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*
|
||||
* @dataProvider needleFoundDataProvider
|
||||
*/
|
||||
public function assertContainsCssCountPassesTestExpectingOneIfNeedleFound($needle, $haystack)
|
||||
{
|
||||
static::assertContainsCssCount(1, $needle, $haystack);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*
|
||||
* @dataProvider needleNotFoundDataProvider
|
||||
*
|
||||
* @expectedException \PHPUnit_Framework_ExpectationFailedException
|
||||
*/
|
||||
public function assertContainsCssCountFailsTestExpectingOneIfNeedleNotFound($needle, $haystack)
|
||||
{
|
||||
static::assertContainsCssCount(1, $needle, $haystack);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*
|
||||
* @dataProvider needleFoundDataProvider
|
||||
*
|
||||
* @expectedException \PHPUnit_Framework_ExpectationFailedException
|
||||
*/
|
||||
public function assertContainsCssCountFailsTestExpectingOneIfNeedleFoundTwice($needle, $haystack)
|
||||
{
|
||||
static::assertContainsCssCount(1, $needle, $haystack . $haystack);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*
|
||||
* @dataProvider needleFoundDataProvider
|
||||
*/
|
||||
public function assertContainsCssCountPassesTestExpectingTwoIfNeedleFoundTwice($needle, $haystack)
|
||||
{
|
||||
static::assertContainsCssCount(2, $needle, $haystack . $haystack);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*
|
||||
* @dataProvider needleNotFoundDataProvider
|
||||
*
|
||||
* @expectedException \PHPUnit_Framework_ExpectationFailedException
|
||||
*/
|
||||
public function assertContainsCssCountFailsTestExpectingTwoIfNeedleNotFound($needle, $haystack)
|
||||
{
|
||||
static::assertContainsCssCount(2, $needle, $haystack);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
* @param string $needle
|
||||
* @param string $haystack
|
||||
*
|
||||
* @dataProvider needleFoundDataProvider
|
||||
*
|
||||
* @expectedException \PHPUnit_Framework_ExpectationFailedException
|
||||
*/
|
||||
public function assertContainsCssCountFailsTestExpectingTwoIfNeedleFoundOnlyOnce($needle, $haystack)
|
||||
{
|
||||
static::assertContainsCssCount(2, $needle, $haystack);
|
||||
}
|
||||
}
|
||||
3
lib/symfony/css-selector/.gitignore
vendored
Normal file
3
lib/symfony/css-selector/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
13
lib/symfony/css-selector/CHANGELOG.md
Normal file
13
lib/symfony/css-selector/CHANGELOG.md
Normal file
@@ -0,0 +1,13 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
2.8.0
|
||||
-----
|
||||
|
||||
* Added the `CssSelectorConverter` class as a non-static API for the component.
|
||||
* Deprecated the `CssSelector` static API of the component.
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
* none
|
||||
65
lib/symfony/css-selector/CssSelectorConverter.php
Normal file
65
lib/symfony/css-selector/CssSelectorConverter.php
Normal file
@@ -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;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* CssSelectorConverter is the main entry point of the component and can convert CSS
|
||||
* selectors to XPath expressions.
|
||||
*
|
||||
* @author Christophe Coevoet <stof@notk.org>
|
||||
*/
|
||||
class CssSelectorConverter
|
||||
{
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* @param bool $html Whether HTML support should be enabled. Disable it for XML documents
|
||||
*/
|
||||
public function __construct($html = true)
|
||||
{
|
||||
$this->translator = new Translator();
|
||||
|
||||
if ($html) {
|
||||
$this->translator->registerExtension(new HtmlExtension($this->translator));
|
||||
}
|
||||
|
||||
$this->translator
|
||||
->registerParserShortcut(new EmptyStringParser())
|
||||
->registerParserShortcut(new ElementParser())
|
||||
->registerParserShortcut(new ClassParser())
|
||||
->registerParserShortcut(new HashParser())
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a CSS expression to its XPath equivalent.
|
||||
*
|
||||
* Optionally, a prefix can be added to the resulting XPath
|
||||
* expression with the $prefix parameter.
|
||||
*
|
||||
* @param string $cssExpr The CSS expression
|
||||
* @param string $prefix An optional prefix for the XPath expression
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toXPath($cssExpr, $prefix = 'descendant-or-self::')
|
||||
{
|
||||
return $this->translator->cssToXPath($cssExpr, $prefix);
|
||||
}
|
||||
}
|
||||
24
lib/symfony/css-selector/Exception/ExceptionInterface.php
Normal file
24
lib/symfony/css-selector/Exception/ExceptionInterface.php
Normal file
@@ -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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*/
|
||||
interface ExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -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 cssselect 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
|
||||
{
|
||||
}
|
||||
@@ -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 cssselect 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
|
||||
{
|
||||
}
|
||||
24
lib/symfony/css-selector/Exception/ParseException.php
Normal file
24
lib/symfony/css-selector/Exception/ParseException.php
Normal file
@@ -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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ParseException extends \Exception implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
73
lib/symfony/css-selector/Exception/SyntaxErrorException.php
Normal file
73
lib/symfony/css-selector/Exception/SyntaxErrorException.php
Normal file
@@ -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 cssselect 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
|
||||
{
|
||||
/**
|
||||
* @param string $expectedValue
|
||||
* @param Token $foundToken
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
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 self
|
||||
*/
|
||||
public static function pseudoElementFound($pseudoElement, $unexpectedLocation)
|
||||
{
|
||||
return new self(sprintf('Unexpected pseudo-element "::%s" found %s.', $pseudoElement, $unexpectedLocation));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function unclosedString($position)
|
||||
{
|
||||
return new self(sprintf('Unclosed/invalid string at %s.', $position));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public static function nestedNot()
|
||||
{
|
||||
return new self('Got nested ::not().');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public static function stringAsFunctionArgument()
|
||||
{
|
||||
return new self('String not allowed as function argument.');
|
||||
}
|
||||
}
|
||||
19
lib/symfony/css-selector/LICENSE
Normal file
19
lib/symfony/css-selector/LICENSE
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2004-2019 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.
|
||||
42
lib/symfony/css-selector/Node/AbstractNode.php
Normal file
42
lib/symfony/css-selector/Node/AbstractNode.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
107
lib/symfony/css-selector/Node/AttributeNode.php
Normal file
107
lib/symfony/css-selector/Node/AttributeNode.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AttributeNode extends AbstractNode
|
||||
{
|
||||
private $selector;
|
||||
private $namespace;
|
||||
private $attribute;
|
||||
private $operator;
|
||||
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);
|
||||
}
|
||||
}
|
||||
70
lib/symfony/css-selector/Node/ClassNode.php
Normal file
70
lib/symfony/css-selector/Node/ClassNode.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ClassNode extends AbstractNode
|
||||
{
|
||||
private $selector;
|
||||
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);
|
||||
}
|
||||
}
|
||||
83
lib/symfony/css-selector/Node/CombinedSelectorNode.php
Normal file
83
lib/symfony/css-selector/Node/CombinedSelectorNode.php
Normal file
@@ -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\Node;
|
||||
|
||||
/**
|
||||
* Represents a combined node.
|
||||
*
|
||||
* This component is a port of the Python cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class CombinedSelectorNode extends AbstractNode
|
||||
{
|
||||
private $selector;
|
||||
private $combinator;
|
||||
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);
|
||||
}
|
||||
}
|
||||
72
lib/symfony/css-selector/Node/ElementNode.php
Normal file
72
lib/symfony/css-selector/Node/ElementNode.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ElementNode extends AbstractNode
|
||||
{
|
||||
private $namespace;
|
||||
private $element;
|
||||
|
||||
/**
|
||||
* @param string|null $namespace
|
||||
* @param string|null $element
|
||||
*/
|
||||
public function __construct($namespace = null, $element = null)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
$this->element = $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getNamespace()
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
87
lib/symfony/css-selector/Node/FunctionNode.php
Normal file
87
lib/symfony/css-selector/Node/FunctionNode.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\CssSelector\Node;
|
||||
|
||||
use Symfony\Component\CssSelector\Parser\Token;
|
||||
|
||||
/**
|
||||
* Represents a "<selector>:<name>(<arguments>)" node.
|
||||
*
|
||||
* This component is a port of the Python cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FunctionNode extends AbstractNode
|
||||
{
|
||||
private $selector;
|
||||
private $name;
|
||||
private $arguments;
|
||||
|
||||
/**
|
||||
* @param NodeInterface $selector
|
||||
* @param string $name
|
||||
* @param Token[] $arguments
|
||||
*/
|
||||
public function __construct(NodeInterface $selector, $name, array $arguments = [])
|
||||
{
|
||||
$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.']' : '');
|
||||
}
|
||||
}
|
||||
70
lib/symfony/css-selector/Node/HashNode.php
Normal file
70
lib/symfony/css-selector/Node/HashNode.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class HashNode extends AbstractNode
|
||||
{
|
||||
private $selector;
|
||||
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);
|
||||
}
|
||||
}
|
||||
66
lib/symfony/css-selector/Node/NegationNode.php
Normal file
66
lib/symfony/css-selector/Node/NegationNode.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class NegationNode extends AbstractNode
|
||||
{
|
||||
private $selector;
|
||||
private $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);
|
||||
}
|
||||
}
|
||||
46
lib/symfony/css-selector/Node/NodeInterface.php
Normal file
46
lib/symfony/css-selector/Node/NodeInterface.php
Normal file
@@ -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\Node;
|
||||
|
||||
/**
|
||||
* Interface for nodes.
|
||||
*
|
||||
* This component is a port of the Python cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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();
|
||||
}
|
||||
70
lib/symfony/css-selector/Node/PseudoNode.php
Normal file
70
lib/symfony/css-selector/Node/PseudoNode.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PseudoNode extends AbstractNode
|
||||
{
|
||||
private $selector;
|
||||
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);
|
||||
}
|
||||
}
|
||||
70
lib/symfony/css-selector/Node/SelectorNode.php
Normal file
70
lib/symfony/css-selector/Node/SelectorNode.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class SelectorNode extends AbstractNode
|
||||
{
|
||||
private $tree;
|
||||
private $pseudoElement;
|
||||
|
||||
/**
|
||||
* @param NodeInterface $tree
|
||||
* @param string|null $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 string|null
|
||||
*/
|
||||
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 : '');
|
||||
}
|
||||
}
|
||||
88
lib/symfony/css-selector/Node/Specificity.php
Normal file
88
lib/symfony/css-selector/Node/Specificity.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?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 cssselect 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>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Specificity
|
||||
{
|
||||
const A_FACTOR = 100;
|
||||
const B_FACTOR = 10;
|
||||
const C_FACTOR = 1;
|
||||
|
||||
private $a;
|
||||
private $b;
|
||||
private $c;
|
||||
|
||||
/**
|
||||
* @param int $a
|
||||
* @param int $b
|
||||
* @param int $c
|
||||
*/
|
||||
public function __construct($a, $b, $c)
|
||||
{
|
||||
$this->a = $a;
|
||||
$this->b = $b;
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public function plus(self $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns -1 if the object specificity is lower than the argument,
|
||||
* 0 if they are equal, and 1 if the argument is lower.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function compareTo(self $specificity)
|
||||
{
|
||||
if ($this->a !== $specificity->a) {
|
||||
return $this->a > $specificity->a ? 1 : -1;
|
||||
}
|
||||
|
||||
if ($this->b !== $specificity->b) {
|
||||
return $this->b > $specificity->b ? 1 : -1;
|
||||
}
|
||||
|
||||
if ($this->c !== $specificity->c) {
|
||||
return $this->c > $specificity->c ? 1 : -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
48
lib/symfony/css-selector/Parser/Handler/CommentHandler.php
Normal file
48
lib/symfony/css-selector/Parser/Handler/CommentHandler.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
33
lib/symfony/css-selector/Parser/Handler/HandlerInterface.php
Normal file
33
lib/symfony/css-selector/Parser/Handler/HandlerInterface.php
Normal file
@@ -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\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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface HandlerInterface
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function handle(Reader $reader, TokenStream $stream);
|
||||
}
|
||||
58
lib/symfony/css-selector/Parser/Handler/HashHandler.php
Normal file
58
lib/symfony/css-selector/Parser/Handler/HashHandler.php
Normal file
@@ -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\Tokenizer\TokenizerEscaping;
|
||||
use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns;
|
||||
use Symfony\Component\CssSelector\Parser\TokenStream;
|
||||
|
||||
/**
|
||||
* CSS selector comment handler.
|
||||
*
|
||||
* This component is a port of the Python cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class HashHandler implements HandlerInterface
|
||||
{
|
||||
private $patterns;
|
||||
private $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;
|
||||
}
|
||||
}
|
||||
@@ -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\Tokenizer\TokenizerEscaping;
|
||||
use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns;
|
||||
use Symfony\Component\CssSelector\Parser\TokenStream;
|
||||
|
||||
/**
|
||||
* CSS selector comment handler.
|
||||
*
|
||||
* This component is a port of the Python cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class IdentifierHandler implements HandlerInterface
|
||||
{
|
||||
private $patterns;
|
||||
private $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;
|
||||
}
|
||||
}
|
||||
54
lib/symfony/css-selector/Parser/Handler/NumberHandler.php
Normal file
54
lib/symfony/css-selector/Parser/Handler/NumberHandler.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?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\Tokenizer\TokenizerPatterns;
|
||||
use Symfony\Component\CssSelector\Parser\TokenStream;
|
||||
|
||||
/**
|
||||
* CSS selector comment handler.
|
||||
*
|
||||
* This component is a port of the Python cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class NumberHandler implements HandlerInterface
|
||||
{
|
||||
private $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;
|
||||
}
|
||||
}
|
||||
77
lib/symfony/css-selector/Parser/Handler/StringHandler.php
Normal file
77
lib/symfony/css-selector/Parser/Handler/StringHandler.php
Normal file
@@ -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\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\Tokenizer\TokenizerEscaping;
|
||||
use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns;
|
||||
use Symfony\Component\CssSelector\Parser\TokenStream;
|
||||
|
||||
/**
|
||||
* CSS selector comment handler.
|
||||
*
|
||||
* This component is a port of the Python cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class StringHandler implements HandlerInterface
|
||||
{
|
||||
private $patterns;
|
||||
private $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, ["'", '"'])) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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\Token;
|
||||
use Symfony\Component\CssSelector\Parser\TokenStream;
|
||||
|
||||
/**
|
||||
* CSS selector whitespace handler.
|
||||
*
|
||||
* This component is a port of the Python cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
384
lib/symfony/css-selector/Parser/Parser.php
Normal file
384
lib/symfony/css-selector/Parser/Parser.php
Normal file
@@ -0,0 +1,384 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Parser implements ParserInterface
|
||||
{
|
||||
private $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
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws SyntaxErrorException
|
||||
*/
|
||||
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 [2, 1];
|
||||
case 'even' === $joined:
|
||||
return [2, 0];
|
||||
case 'n' === $joined:
|
||||
return [1, 0];
|
||||
case false === strpos($joined, 'n'):
|
||||
return [0, $int($joined)];
|
||||
}
|
||||
|
||||
$split = explode('n', $joined);
|
||||
$first = isset($split[0]) ? $split[0] : null;
|
||||
|
||||
return [
|
||||
$first ? ('-' === $first || '+' === $first ? $int($first.'1') : $int($first)) : 1,
|
||||
isset($split[1]) && $split[1] ? $int($split[1]) : 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses selector nodes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseSelectorList(TokenStream $stream)
|
||||
{
|
||||
$stream->skipWhitespace();
|
||||
$selectors = [];
|
||||
|
||||
while (true) {
|
||||
$selectors[] = $this->parserSelectorNode($stream);
|
||||
|
||||
if ($stream->getPeek()->isDelimiter([','])) {
|
||||
$stream->getNext();
|
||||
$stream->skipWhitespace();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $selectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses next selector or combined node.
|
||||
*
|
||||
* @return Node\SelectorNode
|
||||
*
|
||||
* @throws SyntaxErrorException
|
||||
*/
|
||||
private function parserSelectorNode(TokenStream $stream)
|
||||
{
|
||||
list($result, $pseudoElement) = $this->parseSimpleSelector($stream);
|
||||
|
||||
while (true) {
|
||||
$stream->skipWhitespace();
|
||||
$peek = $stream->getPeek();
|
||||
|
||||
if ($peek->isFileEnd() || $peek->isDelimiter([','])) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (null !== $pseudoElement) {
|
||||
throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector');
|
||||
}
|
||||
|
||||
if ($peek->isDelimiter(['+', '>', '~'])) {
|
||||
$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
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws SyntaxErrorException
|
||||
*/
|
||||
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([',', '+', '>', '~'])
|
||||
|| ($insideNegation && $peek->isDelimiter([')']))
|
||||
) {
|
||||
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(['.'])) {
|
||||
$stream->getNext();
|
||||
$result = new Node\ClassNode($result, $stream->getNextIdentifier());
|
||||
} elseif ($peek->isDelimiter(['['])) {
|
||||
$stream->getNext();
|
||||
$result = $this->parseAttributeNode($result, $stream);
|
||||
} elseif ($peek->isDelimiter([':'])) {
|
||||
$stream->getNext();
|
||||
|
||||
if ($stream->getPeek()->isDelimiter([':'])) {
|
||||
$stream->getNext();
|
||||
$pseudoElement = $stream->getNextIdentifier();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$identifier = $stream->getNextIdentifier();
|
||||
if (\in_array(strtolower($identifier), ['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(['('])) {
|
||||
$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([')'])) {
|
||||
throw SyntaxErrorException::unexpectedToken('")"', $next);
|
||||
}
|
||||
|
||||
$result = new Node\NegationNode($result, $argument);
|
||||
} else {
|
||||
$arguments = [];
|
||||
$next = null;
|
||||
|
||||
while (true) {
|
||||
$stream->skipWhitespace();
|
||||
$next = $stream->getNext();
|
||||
|
||||
if ($next->isIdentifier()
|
||||
|| $next->isString()
|
||||
|| $next->isNumber()
|
||||
|| $next->isDelimiter(['+', '-'])
|
||||
) {
|
||||
$arguments[] = $next;
|
||||
} elseif ($next->isDelimiter([')'])) {
|
||||
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 [$result, $pseudoElement];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses next element node.
|
||||
*
|
||||
* @return Node\ElementNode
|
||||
*/
|
||||
private function parseElementNode(TokenStream $stream)
|
||||
{
|
||||
$peek = $stream->getPeek();
|
||||
|
||||
if ($peek->isIdentifier() || $peek->isDelimiter(['*'])) {
|
||||
if ($peek->isIdentifier()) {
|
||||
$namespace = $stream->getNext()->getValue();
|
||||
} else {
|
||||
$stream->getNext();
|
||||
$namespace = null;
|
||||
}
|
||||
|
||||
if ($stream->getPeek()->isDelimiter(['|'])) {
|
||||
$stream->getNext();
|
||||
$element = $stream->getNextIdentifierOrStar();
|
||||
} else {
|
||||
$element = $namespace;
|
||||
$namespace = null;
|
||||
}
|
||||
} else {
|
||||
$element = $namespace = null;
|
||||
}
|
||||
|
||||
return new Node\ElementNode($namespace, $element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses next attribute node.
|
||||
*
|
||||
* @return Node\AttributeNode
|
||||
*
|
||||
* @throws SyntaxErrorException
|
||||
*/
|
||||
private function parseAttributeNode(Node\NodeInterface $selector, TokenStream $stream)
|
||||
{
|
||||
$stream->skipWhitespace();
|
||||
$attribute = $stream->getNextIdentifierOrStar();
|
||||
|
||||
if (null === $attribute && !$stream->getPeek()->isDelimiter(['|'])) {
|
||||
throw SyntaxErrorException::unexpectedToken('"|"', $stream->getPeek());
|
||||
}
|
||||
|
||||
if ($stream->getPeek()->isDelimiter(['|'])) {
|
||||
$stream->getNext();
|
||||
|
||||
if ($stream->getPeek()->isDelimiter(['='])) {
|
||||
$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([']'])) {
|
||||
return new Node\AttributeNode($selector, $namespace, $attribute, 'exists', null);
|
||||
} elseif ($next->isDelimiter(['='])) {
|
||||
$operator = '=';
|
||||
} elseif ($next->isDelimiter(['^', '$', '*', '~', '|', '!'])
|
||||
&& $stream->getPeek()->isDelimiter(['='])
|
||||
) {
|
||||
$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([']'])) {
|
||||
throw SyntaxErrorException::unexpectedToken('"]"', $next);
|
||||
}
|
||||
|
||||
return new Node\AttributeNode($selector, $namespace, $attribute, $operator, $value->getValue());
|
||||
}
|
||||
}
|
||||
36
lib/symfony/css-selector/Parser/ParserInterface.php
Normal file
36
lib/symfony/css-selector/Parser/ParserInterface.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface ParserInterface
|
||||
{
|
||||
/**
|
||||
* Parses given selector source into an array of tokens.
|
||||
*
|
||||
* @param string $source
|
||||
*
|
||||
* @return SelectorNode[]
|
||||
*/
|
||||
public function parse($source);
|
||||
}
|
||||
114
lib/symfony/css-selector/Parser/Reader.php
Normal file
114
lib/symfony/css-selector/Parser/Reader.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Reader
|
||||
{
|
||||
private $source;
|
||||
private $length;
|
||||
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 array|false
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
51
lib/symfony/css-selector/Parser/Shortcut/ClassParser.php
Normal file
51
lib/symfony/css-selector/Parser/Shortcut/ClassParser.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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=4)
|
||||
// 0 => string 'test|input.ab6bd_field' (length=22)
|
||||
// 1 => string 'test' (length=4)
|
||||
// 2 => string 'input' (length=5)
|
||||
// 3 => string 'ab6bd_field' (length=11)
|
||||
if (preg_match('/^(?:([a-z]++)\|)?+([\w-]++|\*)?+\.([\w-]++)$/i', trim($source), $matches)) {
|
||||
return [
|
||||
new SelectorNode(new ClassNode(new ElementNode($matches[1] ?: null, $matches[2] ?: null), $matches[3])),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
47
lib/symfony/css-selector/Parser/Shortcut/ElementParser.php
Normal file
47
lib/symfony/css-selector/Parser/Shortcut/ElementParser.php
Normal file
@@ -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\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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ElementParser implements ParserInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse($source)
|
||||
{
|
||||
// Matches an optional namespace, required element or `*`
|
||||
// $source = 'testns|testel';
|
||||
// $matches = array (size=3)
|
||||
// 0 => string 'testns|testel' (length=13)
|
||||
// 1 => string 'testns' (length=6)
|
||||
// 2 => string 'testel' (length=6)
|
||||
if (preg_match('/^(?:([a-z]++)\|)?([\w-]++|\*)$/i', trim($source), $matches)) {
|
||||
return [new SelectorNode(new ElementNode($matches[1] ?: null, $matches[2]))];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -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 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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class EmptyStringParser implements ParserInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse($source)
|
||||
{
|
||||
// Matches an empty string
|
||||
if ('' == $source) {
|
||||
return [new SelectorNode(new ElementNode(null, '*'))];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
51
lib/symfony/css-selector/Parser/Shortcut/HashParser.php
Normal file
51
lib/symfony/css-selector/Parser/Shortcut/HashParser.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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=4)
|
||||
// 0 => string 'test|input#ab6bd_field' (length=22)
|
||||
// 1 => string 'test' (length=4)
|
||||
// 2 => string 'input' (length=5)
|
||||
// 3 => string 'ab6bd_field' (length=11)
|
||||
if (preg_match('/^(?:([a-z]++)\|)?+([\w-]++|\*)?+#([\w-]++)$/i', trim($source), $matches)) {
|
||||
return [
|
||||
new SelectorNode(new HashNode(new ElementNode($matches[1] ?: null, $matches[2] ?: null), $matches[3])),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
149
lib/symfony/css-selector/Parser/Token.php
Normal file
149
lib/symfony/css-selector/Parser/Token.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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';
|
||||
|
||||
private $type;
|
||||
private $value;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDelimiter(array $values = [])
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
175
lib/symfony/css-selector/Parser/TokenStream.php
Normal file
175
lib/symfony/css-selector/Parser/TokenStream.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TokenStream
|
||||
{
|
||||
/**
|
||||
* @var Token[]
|
||||
*/
|
||||
private $tokens = [];
|
||||
|
||||
/**
|
||||
* @var Token[]
|
||||
*/
|
||||
private $used = [];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $cursor = 0;
|
||||
|
||||
/**
|
||||
* @var Token|null
|
||||
*/
|
||||
private $peeked;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $peeking = false;
|
||||
|
||||
/**
|
||||
* Pushes a token.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function push(Token $token)
|
||||
{
|
||||
$this->tokens[] = $token;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Freezes stream.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function freeze()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns next token.
|
||||
*
|
||||
* @return Token
|
||||
*
|
||||
* @throws InternalErrorException If there is no more 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.
|
||||
*
|
||||
* @return string The identifier token value
|
||||
*
|
||||
* @throws SyntaxErrorException If next token is not an identifier
|
||||
*/
|
||||
public function getNextIdentifier()
|
||||
{
|
||||
$next = $this->getNext();
|
||||
|
||||
if (!$next->isIdentifier()) {
|
||||
throw SyntaxErrorException::unexpectedToken('identifier', $next);
|
||||
}
|
||||
|
||||
return $next->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns nex identifier or star delimiter token.
|
||||
*
|
||||
* @return string|null The identifier token value or null if star found
|
||||
*
|
||||
* @throws SyntaxErrorException If next token is not an identifier or a star delimiter
|
||||
*/
|
||||
public function getNextIdentifierOrStar()
|
||||
{
|
||||
$next = $this->getNext();
|
||||
|
||||
if ($next->isIdentifier()) {
|
||||
return $next->getValue();
|
||||
}
|
||||
|
||||
if ($next->isDelimiter(['*'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw SyntaxErrorException::unexpectedToken('identifier or "*"', $next);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips next whitespace if any.
|
||||
*/
|
||||
public function skipWhitespace()
|
||||
{
|
||||
$peek = $this->getPeek();
|
||||
|
||||
if ($peek->isWhitespace()) {
|
||||
$this->getNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
75
lib/symfony/css-selector/Parser/Tokenizer/Tokenizer.php
Normal file
75
lib/symfony/css-selector/Parser/Tokenizer/Tokenizer.php
Normal file
@@ -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\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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Tokenizer
|
||||
{
|
||||
/**
|
||||
* @var Handler\HandlerInterface[]
|
||||
*/
|
||||
private $handlers;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$patterns = new TokenizerPatterns();
|
||||
$escaping = new TokenizerEscaping($patterns);
|
||||
|
||||
$this->handlers = [
|
||||
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.
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
@@ -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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TokenizerEscaping
|
||||
{
|
||||
private $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 ($match) {
|
||||
$c = hexdec($match[1]);
|
||||
|
||||
if (0x80 > $c %= 0x200000) {
|
||||
return \chr($c);
|
||||
}
|
||||
if (0x800 > $c) {
|
||||
return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F);
|
||||
}
|
||||
if (0x10000 > $c) {
|
||||
return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F);
|
||||
}
|
||||
}, $value);
|
||||
}
|
||||
}
|
||||
112
lib/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php
Normal file
112
lib/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?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 cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TokenizerPatterns
|
||||
{
|
||||
private $unicodeEscapePattern;
|
||||
private $simpleEscapePattern;
|
||||
private $newLineEscapePattern;
|
||||
private $escapePattern;
|
||||
private $stringEscapePattern;
|
||||
private $nonAsciiPattern;
|
||||
private $nmCharPattern;
|
||||
private $nmStartPattern;
|
||||
private $identifierPattern;
|
||||
private $hashPattern;
|
||||
private $numberPattern;
|
||||
private $quotedStringPattern;
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
20
lib/symfony/css-selector/README.md
Normal file
20
lib/symfony/css-selector/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
CssSelector Component
|
||||
=====================
|
||||
|
||||
The CssSelector component converts CSS selectors to XPath expressions.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Documentation](https://symfony.com/doc/current/components/css_selector.html)
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
||||
This component is a port of the Python cssselect library
|
||||
[v0.7.1](https://github.com/SimonSapin/cssselect/releases/tag/v0.7.1),
|
||||
which is distributed under the BSD license.
|
||||
76
lib/symfony/css-selector/Tests/CssSelectorConverterTest.php
Normal file
76
lib/symfony/css-selector/Tests/CssSelectorConverterTest.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?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 PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\CssSelector\CssSelectorConverter;
|
||||
|
||||
class CssSelectorConverterTest extends TestCase
|
||||
{
|
||||
public function testCssToXPath()
|
||||
{
|
||||
$converter = new CssSelectorConverter();
|
||||
|
||||
$this->assertEquals('descendant-or-self::*', $converter->toXPath(''));
|
||||
$this->assertEquals('descendant-or-self::h1', $converter->toXPath('h1'));
|
||||
$this->assertEquals("descendant-or-self::h1[@id = 'foo']", $converter->toXPath('h1#foo'));
|
||||
$this->assertEquals("descendant-or-self::h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]", $converter->toXPath('h1.foo'));
|
||||
$this->assertEquals('descendant-or-self::foo:h1', $converter->toXPath('foo|h1'));
|
||||
$this->assertEquals('descendant-or-self::h1', $converter->toXPath('H1'));
|
||||
}
|
||||
|
||||
public function testCssToXPathXml()
|
||||
{
|
||||
$converter = new CssSelectorConverter(false);
|
||||
|
||||
$this->assertEquals('descendant-or-self::H1', $converter->toXPath('H1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\CssSelector\Exception\ParseException
|
||||
* @expectedExceptionMessage Expected identifier, but <eof at 3> found.
|
||||
*/
|
||||
public function testParseExceptions()
|
||||
{
|
||||
$converter = new CssSelectorConverter();
|
||||
$converter->toXPath('h1:');
|
||||
}
|
||||
|
||||
/** @dataProvider getCssToXPathWithoutPrefixTestData */
|
||||
public function testCssToXPathWithoutPrefix($css, $xpath)
|
||||
{
|
||||
$converter = new CssSelectorConverter();
|
||||
|
||||
$this->assertEquals($xpath, $converter->toXPath($css, ''), '->parse() parses an input string and returns a node');
|
||||
}
|
||||
|
||||
public function getCssToXPathWithoutPrefixTestData()
|
||||
{
|
||||
return [
|
||||
['h1', 'h1'],
|
||||
['foo|h1', 'foo:h1'],
|
||||
['h1, h2, h3', 'h1 | h2 | h3'],
|
||||
['h1:nth-child(3n+1)', "*/*[(name() = 'h1') and (position() - 1 >= 0 and (position() - 1) mod 3 = 0)]"],
|
||||
['h1 > p', 'h1/p'],
|
||||
['h1#foo', "h1[@id = 'foo']"],
|
||||
['h1.foo', "h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"],
|
||||
['h1[class*="foo bar"]', "h1[@class and contains(@class, 'foo bar')]"],
|
||||
['h1[foo|class*="foo bar"]', "h1[@foo:class and contains(@foo:class, 'foo bar')]"],
|
||||
['h1[class]', 'h1[@class]'],
|
||||
['h1 .foo', "h1/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"],
|
||||
['h1 #foo', "h1/descendant-or-self::*/*[@id = 'foo']"],
|
||||
['h1 [class*=foo]', "h1/descendant-or-self::*/*[@class and contains(@class, 'foo')]"],
|
||||
['div>.foo', "div/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"],
|
||||
['div > .foo', "div/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"],
|
||||
];
|
||||
}
|
||||
}
|
||||
34
lib/symfony/css-selector/Tests/Node/AbstractNodeTest.php
Normal file
34
lib/symfony/css-selector/Tests/Node/AbstractNodeTest.php
Normal file
@@ -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 PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\CssSelector\Node\NodeInterface;
|
||||
|
||||
abstract class AbstractNodeTest extends 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();
|
||||
}
|
||||
37
lib/symfony/css-selector/Tests/Node/AttributeNodeTest.php
Normal file
37
lib/symfony/css-selector/Tests/Node/AttributeNodeTest.php
Normal file
@@ -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 [
|
||||
[new AttributeNode(new ElementNode(), null, 'attribute', 'exists', null), 'Attribute[Element[*][attribute]]'],
|
||||
[new AttributeNode(new ElementNode(), null, 'attribute', '$=', 'value'), "Attribute[Element[*][attribute $= 'value']]"],
|
||||
[new AttributeNode(new ElementNode(), 'namespace', 'attribute', '$=', 'value'), "Attribute[Element[*][namespace|attribute $= 'value']]"],
|
||||
];
|
||||
}
|
||||
|
||||
public function getSpecificityValueTestData()
|
||||
{
|
||||
return [
|
||||
[new AttributeNode(new ElementNode(), null, 'attribute', 'exists', null), 10],
|
||||
[new AttributeNode(new ElementNode(null, 'element'), null, 'attribute', 'exists', null), 11],
|
||||
[new AttributeNode(new ElementNode(), null, 'attribute', '$=', 'value'), 10],
|
||||
[new AttributeNode(new ElementNode(), 'namespace', 'attribute', '$=', 'value'), 10],
|
||||
];
|
||||
}
|
||||
}
|
||||
33
lib/symfony/css-selector/Tests/Node/ClassNodeTest.php
Normal file
33
lib/symfony/css-selector/Tests/Node/ClassNodeTest.php
Normal file
@@ -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 [
|
||||
[new ClassNode(new ElementNode(), 'class'), 'Class[Element[*].class]'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getSpecificityValueTestData()
|
||||
{
|
||||
return [
|
||||
[new ClassNode(new ElementNode(), 'class'), 10],
|
||||
[new ClassNode(new ElementNode(null, 'element'), 'class'), 11],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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 [
|
||||
[new CombinedSelectorNode(new ElementNode(), '>', new ElementNode()), 'CombinedSelector[Element[*] > Element[*]]'],
|
||||
[new CombinedSelectorNode(new ElementNode(), ' ', new ElementNode()), 'CombinedSelector[Element[*] <followed> Element[*]]'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getSpecificityValueTestData()
|
||||
{
|
||||
return [
|
||||
[new CombinedSelectorNode(new ElementNode(), '>', new ElementNode()), 0],
|
||||
[new CombinedSelectorNode(new ElementNode(null, 'element'), '>', new ElementNode()), 1],
|
||||
[new CombinedSelectorNode(new ElementNode(null, 'element'), '>', new ElementNode(null, 'element')), 2],
|
||||
];
|
||||
}
|
||||
}
|
||||
35
lib/symfony/css-selector/Tests/Node/ElementNodeTest.php
Normal file
35
lib/symfony/css-selector/Tests/Node/ElementNodeTest.php
Normal file
@@ -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 [
|
||||
[new ElementNode(), 'Element[*]'],
|
||||
[new ElementNode(null, 'element'), 'Element[element]'],
|
||||
[new ElementNode('namespace', 'element'), 'Element[namespace|element]'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getSpecificityValueTestData()
|
||||
{
|
||||
return [
|
||||
[new ElementNode(), 0],
|
||||
[new ElementNode(null, 'element'), 1],
|
||||
[new ElementNode('namespace', 'element'), 1],
|
||||
];
|
||||
}
|
||||
}
|
||||
47
lib/symfony/css-selector/Tests/Node/FunctionNodeTest.php
Normal file
47
lib/symfony/css-selector/Tests/Node/FunctionNodeTest.php
Normal file
@@ -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 [
|
||||
[new FunctionNode(new ElementNode(), 'function'), 'Function[Element[*]:function()]'],
|
||||
[new FunctionNode(new ElementNode(), 'function', [
|
||||
new Token(Token::TYPE_IDENTIFIER, 'value', 0),
|
||||
]), "Function[Element[*]:function(['value'])]"],
|
||||
[new FunctionNode(new ElementNode(), 'function', [
|
||||
new Token(Token::TYPE_STRING, 'value1', 0),
|
||||
new Token(Token::TYPE_NUMBER, 'value2', 0),
|
||||
]), "Function[Element[*]:function(['value1', 'value2'])]"],
|
||||
];
|
||||
}
|
||||
|
||||
public function getSpecificityValueTestData()
|
||||
{
|
||||
return [
|
||||
[new FunctionNode(new ElementNode(), 'function'), 10],
|
||||
[new FunctionNode(new ElementNode(), 'function', [
|
||||
new Token(Token::TYPE_IDENTIFIER, 'value', 0),
|
||||
]), 10],
|
||||
[new FunctionNode(new ElementNode(), 'function', [
|
||||
new Token(Token::TYPE_STRING, 'value1', 0),
|
||||
new Token(Token::TYPE_NUMBER, 'value2', 0),
|
||||
]), 10],
|
||||
];
|
||||
}
|
||||
}
|
||||
33
lib/symfony/css-selector/Tests/Node/HashNodeTest.php
Normal file
33
lib/symfony/css-selector/Tests/Node/HashNodeTest.php
Normal file
@@ -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\ElementNode;
|
||||
use Symfony\Component\CssSelector\Node\HashNode;
|
||||
|
||||
class HashNodeTest extends AbstractNodeTest
|
||||
{
|
||||
public function getToStringConversionTestData()
|
||||
{
|
||||
return [
|
||||
[new HashNode(new ElementNode(), 'id'), 'Hash[Element[*]#id]'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getSpecificityValueTestData()
|
||||
{
|
||||
return [
|
||||
[new HashNode(new ElementNode(), 'id'), 100],
|
||||
[new HashNode(new ElementNode(null, 'id'), 'class'), 101],
|
||||
];
|
||||
}
|
||||
}
|
||||
33
lib/symfony/css-selector/Tests/Node/NegationNodeTest.php
Normal file
33
lib/symfony/css-selector/Tests/Node/NegationNodeTest.php
Normal file
@@ -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;
|
||||
use Symfony\Component\CssSelector\Node\NegationNode;
|
||||
|
||||
class NegationNodeTest extends AbstractNodeTest
|
||||
{
|
||||
public function getToStringConversionTestData()
|
||||
{
|
||||
return [
|
||||
[new NegationNode(new ElementNode(), new ClassNode(new ElementNode(), 'class')), 'Negation[Element[*]:not(Class[Element[*].class])]'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getSpecificityValueTestData()
|
||||
{
|
||||
return [
|
||||
[new NegationNode(new ElementNode(), new ClassNode(new ElementNode(), 'class')), 10],
|
||||
];
|
||||
}
|
||||
}
|
||||
32
lib/symfony/css-selector/Tests/Node/PseudoNodeTest.php
Normal file
32
lib/symfony/css-selector/Tests/Node/PseudoNodeTest.php
Normal file
@@ -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 [
|
||||
[new PseudoNode(new ElementNode(), 'pseudo'), 'Pseudo[Element[*]:pseudo]'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getSpecificityValueTestData()
|
||||
{
|
||||
return [
|
||||
[new PseudoNode(new ElementNode(), 'pseudo'), 10],
|
||||
];
|
||||
}
|
||||
}
|
||||
34
lib/symfony/css-selector/Tests/Node/SelectorNodeTest.php
Normal file
34
lib/symfony/css-selector/Tests/Node/SelectorNodeTest.php
Normal file
@@ -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 [
|
||||
[new SelectorNode(new ElementNode()), 'Selector[Element[*]]'],
|
||||
[new SelectorNode(new ElementNode(), 'pseudo'), 'Selector[Element[*]::pseudo]'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getSpecificityValueTestData()
|
||||
{
|
||||
return [
|
||||
[new SelectorNode(new ElementNode()), 0],
|
||||
[new SelectorNode(new ElementNode(), 'pseudo'), 1],
|
||||
];
|
||||
}
|
||||
}
|
||||
63
lib/symfony/css-selector/Tests/Node/SpecificityTest.php
Normal file
63
lib/symfony/css-selector/Tests/Node/SpecificityTest.php
Normal file
@@ -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\Tests\Node;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\CssSelector\Node\Specificity;
|
||||
|
||||
class SpecificityTest extends 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 [
|
||||
[new Specificity(0, 0, 0), 0],
|
||||
[new Specificity(0, 0, 2), 2],
|
||||
[new Specificity(0, 3, 0), 30],
|
||||
[new Specificity(4, 0, 0), 400],
|
||||
[new Specificity(4, 3, 2), 432],
|
||||
];
|
||||
}
|
||||
|
||||
/** @dataProvider getCompareTestData */
|
||||
public function testCompareTo(Specificity $a, Specificity $b, $result)
|
||||
{
|
||||
$this->assertEquals($result, $a->compareTo($b));
|
||||
}
|
||||
|
||||
public function getCompareTestData()
|
||||
{
|
||||
return [
|
||||
[new Specificity(0, 0, 0), new Specificity(0, 0, 0), 0],
|
||||
[new Specificity(0, 0, 1), new Specificity(0, 0, 1), 0],
|
||||
[new Specificity(0, 0, 2), new Specificity(0, 0, 1), 1],
|
||||
[new Specificity(0, 0, 2), new Specificity(0, 0, 3), -1],
|
||||
[new Specificity(0, 4, 0), new Specificity(0, 4, 0), 0],
|
||||
[new Specificity(0, 6, 0), new Specificity(0, 5, 11), 1],
|
||||
[new Specificity(0, 7, 0), new Specificity(0, 8, 0), -1],
|
||||
[new Specificity(9, 0, 0), new Specificity(9, 0, 0), 0],
|
||||
[new Specificity(11, 0, 0), new Specificity(10, 11, 0), 1],
|
||||
[new Specificity(12, 11, 0), new Specificity(13, 0, 0), -1],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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 PHPUnit\Framework\TestCase;
|
||||
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 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([], $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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 [
|
||||
// 2nd argument only exists for inherited method compatibility
|
||||
['/* comment */', new Token(null, null, null), ''],
|
||||
['/* comment */foo', new Token(null, null, null), 'foo'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDontHandleValueTestData()
|
||||
{
|
||||
return [
|
||||
['>'],
|
||||
['+'],
|
||||
[' '],
|
||||
];
|
||||
}
|
||||
|
||||
protected function generateHandler()
|
||||
{
|
||||
return new CommentHandler();
|
||||
}
|
||||
}
|
||||
@@ -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\TokenizerEscaping;
|
||||
use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns;
|
||||
|
||||
class HashHandlerTest extends AbstractHandlerTest
|
||||
{
|
||||
public function getHandleValueTestData()
|
||||
{
|
||||
return [
|
||||
['#id', new Token(Token::TYPE_HASH, 'id', 0), ''],
|
||||
['#123', new Token(Token::TYPE_HASH, '123', 0), ''],
|
||||
|
||||
['#id.class', new Token(Token::TYPE_HASH, 'id', 0), '.class'],
|
||||
['#id element', new Token(Token::TYPE_HASH, 'id', 0), ' element'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDontHandleValueTestData()
|
||||
{
|
||||
return [
|
||||
['id'],
|
||||
['123'],
|
||||
['<'],
|
||||
['<'],
|
||||
['#'],
|
||||
];
|
||||
}
|
||||
|
||||
protected function generateHandler()
|
||||
{
|
||||
$patterns = new TokenizerPatterns();
|
||||
|
||||
return new HashHandler($patterns, new TokenizerEscaping($patterns));
|
||||
}
|
||||
}
|
||||
@@ -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\TokenizerEscaping;
|
||||
use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns;
|
||||
|
||||
class IdentifierHandlerTest extends AbstractHandlerTest
|
||||
{
|
||||
public function getHandleValueTestData()
|
||||
{
|
||||
return [
|
||||
['foo', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), ''],
|
||||
['foo|bar', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '|bar'],
|
||||
['foo.class', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '.class'],
|
||||
['foo[attr]', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '[attr]'],
|
||||
['foo bar', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), ' bar'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDontHandleValueTestData()
|
||||
{
|
||||
return [
|
||||
['>'],
|
||||
['+'],
|
||||
[' '],
|
||||
['*|foo'],
|
||||
['/* comment */'],
|
||||
];
|
||||
}
|
||||
|
||||
protected function generateHandler()
|
||||
{
|
||||
$patterns = new TokenizerPatterns();
|
||||
|
||||
return new IdentifierHandler($patterns, new TokenizerEscaping($patterns));
|
||||
}
|
||||
}
|
||||
@@ -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 [
|
||||
['12', new Token(Token::TYPE_NUMBER, '12', 0), ''],
|
||||
['12.34', new Token(Token::TYPE_NUMBER, '12.34', 0), ''],
|
||||
['+12.34', new Token(Token::TYPE_NUMBER, '+12.34', 0), ''],
|
||||
['-12.34', new Token(Token::TYPE_NUMBER, '-12.34', 0), ''],
|
||||
|
||||
['12 arg', new Token(Token::TYPE_NUMBER, '12', 0), ' arg'],
|
||||
['12]', new Token(Token::TYPE_NUMBER, '12', 0), ']'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDontHandleValueTestData()
|
||||
{
|
||||
return [
|
||||
['hello'],
|
||||
['>'],
|
||||
['+'],
|
||||
[' '],
|
||||
['/* comment */'],
|
||||
];
|
||||
}
|
||||
|
||||
protected function generateHandler()
|
||||
{
|
||||
$patterns = new TokenizerPatterns();
|
||||
|
||||
return new NumberHandler($patterns);
|
||||
}
|
||||
}
|
||||
@@ -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\TokenizerEscaping;
|
||||
use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns;
|
||||
|
||||
class StringHandlerTest extends AbstractHandlerTest
|
||||
{
|
||||
public function getHandleValueTestData()
|
||||
{
|
||||
return [
|
||||
['"hello"', new Token(Token::TYPE_STRING, 'hello', 1), ''],
|
||||
['"1"', new Token(Token::TYPE_STRING, '1', 1), ''],
|
||||
['" "', new Token(Token::TYPE_STRING, ' ', 1), ''],
|
||||
['""', new Token(Token::TYPE_STRING, '', 1), ''],
|
||||
["'hello'", new Token(Token::TYPE_STRING, 'hello', 1), ''],
|
||||
|
||||
["'foo'bar", new Token(Token::TYPE_STRING, 'foo', 1), 'bar'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDontHandleValueTestData()
|
||||
{
|
||||
return [
|
||||
['hello'],
|
||||
['>'],
|
||||
['1'],
|
||||
[' '],
|
||||
];
|
||||
}
|
||||
|
||||
protected function generateHandler()
|
||||
{
|
||||
$patterns = new TokenizerPatterns();
|
||||
|
||||
return new StringHandler($patterns, new TokenizerEscaping($patterns));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user