diff --git a/.gitignore b/.gitignore index 841798764..b0d9fc2ca 100644 --- a/.gitignore +++ b/.gitignore @@ -16,12 +16,14 @@ bin */*/composer.json */*/composer.lock # ignore README and UPGRADE -*/*/README.md -*/*/README.rst -*/*/UPGRADE.md -*/*/UPGRADE-*.md -*/*/CONTRIBUTING.md -*/*/SECURITY.md +*/**/README.md +*/**/README.rst +*/**/UPGRADE.md +*/**/UPGRADING.md +*/**/UPGRADE-*.md +*/**/CONTRIBUTING.md +*/**/SECURITY.md +*/**/RELEASE.md # ignore codestyle tools config */*/psalm.xml */*/psalm.baseline.xml @@ -71,10 +73,11 @@ giggsey/locale/CLDR-VERSION.txt guzzlehttp/guzzle/build/ guzzlehttp/guzzle/docs/ guzzlehttp/guzzle/MAKEFILE -guzzlehttp/guzzle/UPGRADING.md justinrainbow/json-schema/demo +masterminds/html5/CREDITS + mexitek/phpcolors/demo nikic/php-parser/bin/ @@ -83,10 +86,6 @@ nikic/php-parser/test/ nikic/php-parser/test_old/ nikic/php-parser/grammar/analyze.php nikic/php-parser/grammar/rebuildParser.php -nikic/php-parser/UPGRADE-1.0.md -nikic/php-parser/UPGRADE-2.0.md -nikic/php-parser/UPGRADE-3.0.md -nikic/php-parser/UPGRADE-4.0.md nikic/php-parser/grammar/parser.template nikic/php-parser/grammar/php5.y nikic/php-parser/grammar/php7.y diff --git a/composer.json b/composer.json index c6317374c..e29153d24 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "deepdiver1975/tarstreamer": "^2.1.0", "doctrine/dbal": "^3.7.0", "egulias/email-validator": "^3.2.6", - "fusonic/opengraph": "^2.2", + "fusonic/opengraph": "^2.3.0", "giggsey/libphonenumber-for-php-lite": "^8.13.12", "guzzlehttp/guzzle": "^7.8.1", "icewind/searchdav": "^3.1.0", diff --git a/composer.lock b/composer.lock index 7c32b323c..c620e58b9 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "603c28381b3cf9fdb8208123b2b64b60", + "content-hash": "369f52466ea42e09a8e1152d1f7e4e98", "packages": [ { "name": "aws/aws-crt-php", @@ -1045,69 +1045,22 @@ "abandoned": true, "time": "2021-04-24T19:01:55+00:00" }, - { - "name": "fusonic/linq", - "version": "v1.1.0", - "source": { - "type": "git", - "url": "https://github.com/fusonic/linq.git", - "reference": "63520ef1470ca771acbd26871efb945dd4a7a5d8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fusonic/linq/zipball/63520ef1470ca771acbd26871efb945dd4a7a5d8", - "reference": "63520ef1470ca771acbd26871efb945dd4a7a5d8", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "type": "library", - "autoload": { - "psr-0": { - "Fusonic\\Linq": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fusonic", - "homepage": "http://www.fusonic.net" - } - ], - "description": "LINQ 2 objects class for PHP", - "homepage": "http://fusonic.github.io/fusonic-linq/", - "keywords": [ - "linq", - "linq2objects" - ], - "support": { - "issues": "https://github.com/fusonic/linq/issues", - "source": "https://github.com/fusonic/linq/tree/master" - }, - "abandoned": true, - "time": "2015-02-26T22:49:17+00:00" - }, { "name": "fusonic/opengraph", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/fusonic/opengraph.git", - "reference": "a63b588fbe56c175ae06e158f1513642653ee3c1" + "reference": "09dca70b04b4221c9dc664e1689e876a3faa50c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fusonic/opengraph/zipball/a63b588fbe56c175ae06e158f1513642653ee3c1", - "reference": "a63b588fbe56c175ae06e158f1513642653ee3c1", + "url": "https://api.github.com/repos/fusonic/opengraph/zipball/09dca70b04b4221c9dc664e1689e876a3faa50c2", + "reference": "09dca70b04b4221c9dc664e1689e876a3faa50c2", "shasum": "" }, "require": { "ext-dom": "*", - "fusonic/linq": "^1.0", "php": "^7.4|^8.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", @@ -1146,9 +1099,9 @@ ], "support": { "issues": "https://github.com/fusonic/opengraph/issues", - "source": "https://github.com/fusonic/opengraph/tree/v2.2.0" + "source": "https://github.com/fusonic/opengraph/tree/v2.3.0" }, - "time": "2022-01-20T05:47:36+00:00" + "time": "2023-12-05T14:26:51+00:00" }, { "name": "giggsey/libphonenumber-for-php-lite", @@ -2053,6 +2006,73 @@ ], "time": "2020-10-31T13:45:51+00:00" }, + { + "name": "masterminds/html5", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + }, + "time": "2024-03-31T07:05:07+00:00" + }, { "name": "mexitek/phpcolors", "version": "v1.0.4", @@ -4566,21 +4586,20 @@ }, { "name": "symfony/css-selector", - "version": "v5.4.11", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "c1681789f059ab756001052164726ae88512ae3d" + "reference": "ee0f7ed5cf298cc019431bb3b3977ebc52b86229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/c1681789f059ab756001052164726ae88512ae3d", - "reference": "c1681789f059ab756001052164726ae88512ae3d", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ee0f7ed5cf298cc019431bb3b3977ebc52b86229", + "reference": "ee0f7ed5cf298cc019431bb3b3977ebc52b86229", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" }, "type": "library", "autoload": { @@ -4612,7 +4631,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.4.11" + "source": "https://github.com/symfony/css-selector/tree/v6.4.3" }, "funding": [ { @@ -4628,7 +4647,7 @@ "type": "tidelift" } ], - "time": "2022-06-27T16:58:25+00:00" + "time": "2024-01-23T14:51:35+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4699,34 +4718,26 @@ }, { "name": "symfony/dom-crawler", - "version": "v5.4.11", + "version": "v6.4.4", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "0b900ca5576ecd59e08c76127e616667cfe427a7" + "reference": "f0e7ec3fa17000e2d0cb4557b4b47c88a6a63531" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/0b900ca5576ecd59e08c76127e616667cfe427a7", - "reference": "0b900ca5576ecd59e08c76127e616667cfe427a7", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/f0e7ec3fa17000e2d0cb4557b4b47c88a6a63531", + "reference": "f0e7ec3fa17000e2d0cb4557b4b47c88a6a63531", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "masterminds/html5": "^2.6", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "masterminds/html5": "<2.6" + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "masterminds/html5": "^2.6", - "symfony/css-selector": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/css-selector": "" + "symfony/css-selector": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -4754,7 +4765,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v5.4.11" + "source": "https://github.com/symfony/dom-crawler/tree/v6.4.4" }, "funding": [ { @@ -4770,7 +4781,7 @@ "type": "tidelift" } ], - "time": "2022-06-27T16:58:25+00:00" + "time": "2024-02-07T09:17:57+00:00" }, { "name": "symfony/event-dispatcher", @@ -5174,16 +5185,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", "shasum": "" }, "require": { @@ -5197,9 +5208,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -5236,7 +5244,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" }, "funding": [ { @@ -5252,7 +5260,7 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -5508,16 +5516,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", "shasum": "" }, "require": { @@ -5531,9 +5539,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -5571,7 +5576,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" }, "funding": [ { @@ -5587,7 +5592,7 @@ "type": "tidelift" } ], - "time": "2023-07-28T09:04:16+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php72", @@ -5746,16 +5751,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { @@ -5763,9 +5768,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -5809,7 +5811,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -5825,7 +5827,7 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/process", diff --git a/composer/autoload_classmap.php b/composer/autoload_classmap.php index 19834d353..86a2e4c92 100644 --- a/composer/autoload_classmap.php +++ b/composer/autoload_classmap.php @@ -1536,18 +1536,6 @@ 'FG\\X509\\SAN\\DNSName' => $vendorDir . '/fgrosse/phpasn1/lib/X509/SAN/DNSName.php', 'FG\\X509\\SAN\\IPAddress' => $vendorDir . '/fgrosse/phpasn1/lib/X509/SAN/IPAddress.php', 'FG\\X509\\SAN\\SubjectAlternativeNames' => $vendorDir . '/fgrosse/phpasn1/lib/X509/SAN/SubjectAlternativeNames.php', - 'Fusonic\\Linq\\GroupedLinq' => $vendorDir . '/fusonic/linq/src/Fusonic/Linq/GroupedLinq.php', - 'Fusonic\\Linq\\Helper\\LinqHelper' => $vendorDir . '/fusonic/linq/src/Fusonic/Linq/Helper/LinqHelper.php', - 'Fusonic\\Linq\\Iterator\\DistinctIterator' => $vendorDir . '/fusonic/linq/src/Fusonic/Linq/Iterator/DistinctIterator.php', - 'Fusonic\\Linq\\Iterator\\ExceptIterator' => $vendorDir . '/fusonic/linq/src/Fusonic/Linq/Iterator/ExceptIterator.php', - 'Fusonic\\Linq\\Iterator\\GroupIterator' => $vendorDir . '/fusonic/linq/src/Fusonic/Linq/Iterator/GroupIterator.php', - 'Fusonic\\Linq\\Iterator\\IntersectIterator' => $vendorDir . '/fusonic/linq/src/Fusonic/Linq/Iterator/IntersectIterator.php', - 'Fusonic\\Linq\\Iterator\\OfTypeIterator' => $vendorDir . '/fusonic/linq/src/Fusonic/Linq/Iterator/OfTypeIterator.php', - 'Fusonic\\Linq\\Iterator\\OrderIterator' => $vendorDir . '/fusonic/linq/src/Fusonic/Linq/Iterator/OrderIterator.php', - 'Fusonic\\Linq\\Iterator\\SelectIterator' => $vendorDir . '/fusonic/linq/src/Fusonic/Linq/Iterator/SelectIterator.php', - 'Fusonic\\Linq\\Iterator\\SelectManyIterator' => $vendorDir . '/fusonic/linq/src/Fusonic/Linq/Iterator/SelectManyIterator.php', - 'Fusonic\\Linq\\Iterator\\WhereIterator' => $vendorDir . '/fusonic/linq/src/Fusonic/Linq/Iterator/WhereIterator.php', - 'Fusonic\\Linq\\Linq' => $vendorDir . '/fusonic/linq/src/Fusonic/Linq/Linq.php', 'Fusonic\\OpenGraph\\Consumer' => $vendorDir . '/fusonic/opengraph/src/Consumer.php', 'Fusonic\\OpenGraph\\Elements\\Audio' => $vendorDir . '/fusonic/opengraph/src/Elements/Audio.php', 'Fusonic\\OpenGraph\\Elements\\ElementBase' => $vendorDir . '/fusonic/opengraph/src/Elements/ElementBase.php', @@ -1793,6 +1781,26 @@ 'League\\Uri\\UriTemplate\\Template' => $vendorDir . '/league/uri/src/UriTemplate/Template.php', 'League\\Uri\\UriTemplate\\VarSpecifier' => $vendorDir . '/league/uri/src/UriTemplate/VarSpecifier.php', 'League\\Uri\\UriTemplate\\VariableBag' => $vendorDir . '/league/uri/src/UriTemplate/VariableBag.php', + 'Masterminds\\HTML5' => $vendorDir . '/masterminds/html5/src/HTML5.php', + 'Masterminds\\HTML5\\Elements' => $vendorDir . '/masterminds/html5/src/HTML5/Elements.php', + 'Masterminds\\HTML5\\Entities' => $vendorDir . '/masterminds/html5/src/HTML5/Entities.php', + 'Masterminds\\HTML5\\Exception' => $vendorDir . '/masterminds/html5/src/HTML5/Exception.php', + 'Masterminds\\HTML5\\InstructionProcessor' => $vendorDir . '/masterminds/html5/src/HTML5/InstructionProcessor.php', + 'Masterminds\\HTML5\\Parser\\CharacterReference' => $vendorDir . '/masterminds/html5/src/HTML5/Parser/CharacterReference.php', + 'Masterminds\\HTML5\\Parser\\DOMTreeBuilder' => $vendorDir . '/masterminds/html5/src/HTML5/Parser/DOMTreeBuilder.php', + 'Masterminds\\HTML5\\Parser\\EventHandler' => $vendorDir . '/masterminds/html5/src/HTML5/Parser/EventHandler.php', + 'Masterminds\\HTML5\\Parser\\FileInputStream' => $vendorDir . '/masterminds/html5/src/HTML5/Parser/FileInputStream.php', + 'Masterminds\\HTML5\\Parser\\InputStream' => $vendorDir . '/masterminds/html5/src/HTML5/Parser/InputStream.php', + 'Masterminds\\HTML5\\Parser\\ParseError' => $vendorDir . '/masterminds/html5/src/HTML5/Parser/ParseError.php', + 'Masterminds\\HTML5\\Parser\\Scanner' => $vendorDir . '/masterminds/html5/src/HTML5/Parser/Scanner.php', + 'Masterminds\\HTML5\\Parser\\StringInputStream' => $vendorDir . '/masterminds/html5/src/HTML5/Parser/StringInputStream.php', + 'Masterminds\\HTML5\\Parser\\Tokenizer' => $vendorDir . '/masterminds/html5/src/HTML5/Parser/Tokenizer.php', + 'Masterminds\\HTML5\\Parser\\TreeBuildingRules' => $vendorDir . '/masterminds/html5/src/HTML5/Parser/TreeBuildingRules.php', + 'Masterminds\\HTML5\\Parser\\UTF8Utils' => $vendorDir . '/masterminds/html5/src/HTML5/Parser/UTF8Utils.php', + 'Masterminds\\HTML5\\Serializer\\HTML5Entities' => $vendorDir . '/masterminds/html5/src/HTML5/Serializer/HTML5Entities.php', + 'Masterminds\\HTML5\\Serializer\\OutputRules' => $vendorDir . '/masterminds/html5/src/HTML5/Serializer/OutputRules.php', + 'Masterminds\\HTML5\\Serializer\\RulesInterface' => $vendorDir . '/masterminds/html5/src/HTML5/Serializer/RulesInterface.php', + 'Masterminds\\HTML5\\Serializer\\Traverser' => $vendorDir . '/masterminds/html5/src/HTML5/Serializer/Traverser.php', 'Mexitek\\PHPColors\\Color' => $vendorDir . '/mexitek/phpcolors/src/Mexitek/PHPColors/Color.php', 'MicrosoftAzure\\Storage\\Blob\\BlobRestProxy' => $vendorDir . '/microsoft/azure-storage-blob/src/Blob/BlobRestProxy.php', 'MicrosoftAzure\\Storage\\Blob\\BlobSharedAccessSignatureHelper' => $vendorDir . '/microsoft/azure-storage-blob/src/Blob/BlobSharedAccessSignatureHelper.php', diff --git a/composer/autoload_files.php b/composer/autoload_files.php index b20dade95..6843f465f 100644 --- a/composer/autoload_files.php +++ b/composer/autoload_files.php @@ -7,8 +7,8 @@ return array( '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', - 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', '383eaff206634a77a1be54e64e6459c7' => $vendorDir . '/sabre/uri/lib/functions.php', '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', diff --git a/composer/autoload_namespaces.php b/composer/autoload_namespaces.php index c43ac4882..225f7eb6a 100644 --- a/composer/autoload_namespaces.php +++ b/composer/autoload_namespaces.php @@ -7,7 +7,6 @@ return array( 'Pimple' => array($vendorDir . '/pimple/pimple/src'), - 'Fusonic\\Linq' => array($vendorDir . '/fusonic/linq/src'), 'Console' => array($vendorDir . '/pear/console_getopt'), 'Archive_Tar' => array($vendorDir . '/pear/archive_tar'), '' => array($vendorDir . '/pear/pear-core-minimal/src'), diff --git a/composer/autoload_psr4.php b/composer/autoload_psr4.php index c5fa437f9..9f64e2531 100644 --- a/composer/autoload_psr4.php +++ b/composer/autoload_psr4.php @@ -62,6 +62,7 @@ 'Nextcloud\\LogNormalizer\\' => array($vendorDir . '/nextcloud/lognormalizer/src'), 'MicrosoftAzure\\Storage\\Common\\' => array($vendorDir . '/microsoft/azure-storage-common/src/Common'), 'MicrosoftAzure\\Storage\\Blob\\' => array($vendorDir . '/microsoft/azure-storage-blob/src/Blob'), + 'Masterminds\\' => array($vendorDir . '/masterminds/html5/src'), 'League\\Uri\\' => array($vendorDir . '/league/uri/src', $vendorDir . '/league/uri-interfaces/src'), 'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'), 'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'), diff --git a/composer/autoload_static.php b/composer/autoload_static.php index 1dc699d53..f69f0f288 100644 --- a/composer/autoload_static.php +++ b/composer/autoload_static.php @@ -8,8 +8,8 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 { public static $files = array ( '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', - 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '383eaff206634a77a1be54e64e6459c7' => __DIR__ . '/..' . '/sabre/uri/lib/functions.php', '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', @@ -225,6 +225,7 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 array ( 'MicrosoftAzure\\Storage\\Common\\' => 30, 'MicrosoftAzure\\Storage\\Blob\\' => 28, + 'Masterminds\\' => 12, ), 'L' => array ( @@ -516,6 +517,10 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 array ( 0 => __DIR__ . '/..' . '/microsoft/azure-storage-blob/src/Blob', ), + 'Masterminds\\' => + array ( + 0 => __DIR__ . '/..' . '/masterminds/html5/src', + ), 'League\\Uri\\' => array ( 0 => __DIR__ . '/..' . '/league/uri/src', @@ -635,13 +640,6 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 0 => __DIR__ . '/..' . '/pimple/pimple/src', ), ), - 'F' => - array ( - 'Fusonic\\Linq' => - array ( - 0 => __DIR__ . '/..' . '/fusonic/linq/src', - ), - ), 'C' => array ( 'Console' => @@ -2193,18 +2191,6 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'FG\\X509\\SAN\\DNSName' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/X509/SAN/DNSName.php', 'FG\\X509\\SAN\\IPAddress' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/X509/SAN/IPAddress.php', 'FG\\X509\\SAN\\SubjectAlternativeNames' => __DIR__ . '/..' . '/fgrosse/phpasn1/lib/X509/SAN/SubjectAlternativeNames.php', - 'Fusonic\\Linq\\GroupedLinq' => __DIR__ . '/..' . '/fusonic/linq/src/Fusonic/Linq/GroupedLinq.php', - 'Fusonic\\Linq\\Helper\\LinqHelper' => __DIR__ . '/..' . '/fusonic/linq/src/Fusonic/Linq/Helper/LinqHelper.php', - 'Fusonic\\Linq\\Iterator\\DistinctIterator' => __DIR__ . '/..' . '/fusonic/linq/src/Fusonic/Linq/Iterator/DistinctIterator.php', - 'Fusonic\\Linq\\Iterator\\ExceptIterator' => __DIR__ . '/..' . '/fusonic/linq/src/Fusonic/Linq/Iterator/ExceptIterator.php', - 'Fusonic\\Linq\\Iterator\\GroupIterator' => __DIR__ . '/..' . '/fusonic/linq/src/Fusonic/Linq/Iterator/GroupIterator.php', - 'Fusonic\\Linq\\Iterator\\IntersectIterator' => __DIR__ . '/..' . '/fusonic/linq/src/Fusonic/Linq/Iterator/IntersectIterator.php', - 'Fusonic\\Linq\\Iterator\\OfTypeIterator' => __DIR__ . '/..' . '/fusonic/linq/src/Fusonic/Linq/Iterator/OfTypeIterator.php', - 'Fusonic\\Linq\\Iterator\\OrderIterator' => __DIR__ . '/..' . '/fusonic/linq/src/Fusonic/Linq/Iterator/OrderIterator.php', - 'Fusonic\\Linq\\Iterator\\SelectIterator' => __DIR__ . '/..' . '/fusonic/linq/src/Fusonic/Linq/Iterator/SelectIterator.php', - 'Fusonic\\Linq\\Iterator\\SelectManyIterator' => __DIR__ . '/..' . '/fusonic/linq/src/Fusonic/Linq/Iterator/SelectManyIterator.php', - 'Fusonic\\Linq\\Iterator\\WhereIterator' => __DIR__ . '/..' . '/fusonic/linq/src/Fusonic/Linq/Iterator/WhereIterator.php', - 'Fusonic\\Linq\\Linq' => __DIR__ . '/..' . '/fusonic/linq/src/Fusonic/Linq/Linq.php', 'Fusonic\\OpenGraph\\Consumer' => __DIR__ . '/..' . '/fusonic/opengraph/src/Consumer.php', 'Fusonic\\OpenGraph\\Elements\\Audio' => __DIR__ . '/..' . '/fusonic/opengraph/src/Elements/Audio.php', 'Fusonic\\OpenGraph\\Elements\\ElementBase' => __DIR__ . '/..' . '/fusonic/opengraph/src/Elements/ElementBase.php', @@ -2450,6 +2436,26 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'League\\Uri\\UriTemplate\\Template' => __DIR__ . '/..' . '/league/uri/src/UriTemplate/Template.php', 'League\\Uri\\UriTemplate\\VarSpecifier' => __DIR__ . '/..' . '/league/uri/src/UriTemplate/VarSpecifier.php', 'League\\Uri\\UriTemplate\\VariableBag' => __DIR__ . '/..' . '/league/uri/src/UriTemplate/VariableBag.php', + 'Masterminds\\HTML5' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5.php', + 'Masterminds\\HTML5\\Elements' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Elements.php', + 'Masterminds\\HTML5\\Entities' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Entities.php', + 'Masterminds\\HTML5\\Exception' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Exception.php', + 'Masterminds\\HTML5\\InstructionProcessor' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/InstructionProcessor.php', + 'Masterminds\\HTML5\\Parser\\CharacterReference' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Parser/CharacterReference.php', + 'Masterminds\\HTML5\\Parser\\DOMTreeBuilder' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Parser/DOMTreeBuilder.php', + 'Masterminds\\HTML5\\Parser\\EventHandler' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Parser/EventHandler.php', + 'Masterminds\\HTML5\\Parser\\FileInputStream' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Parser/FileInputStream.php', + 'Masterminds\\HTML5\\Parser\\InputStream' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Parser/InputStream.php', + 'Masterminds\\HTML5\\Parser\\ParseError' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Parser/ParseError.php', + 'Masterminds\\HTML5\\Parser\\Scanner' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Parser/Scanner.php', + 'Masterminds\\HTML5\\Parser\\StringInputStream' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Parser/StringInputStream.php', + 'Masterminds\\HTML5\\Parser\\Tokenizer' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Parser/Tokenizer.php', + 'Masterminds\\HTML5\\Parser\\TreeBuildingRules' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Parser/TreeBuildingRules.php', + 'Masterminds\\HTML5\\Parser\\UTF8Utils' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Parser/UTF8Utils.php', + 'Masterminds\\HTML5\\Serializer\\HTML5Entities' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Serializer/HTML5Entities.php', + 'Masterminds\\HTML5\\Serializer\\OutputRules' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Serializer/OutputRules.php', + 'Masterminds\\HTML5\\Serializer\\RulesInterface' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Serializer/RulesInterface.php', + 'Masterminds\\HTML5\\Serializer\\Traverser' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Serializer/Traverser.php', 'Mexitek\\PHPColors\\Color' => __DIR__ . '/..' . '/mexitek/phpcolors/src/Mexitek/PHPColors/Color.php', 'MicrosoftAzure\\Storage\\Blob\\BlobRestProxy' => __DIR__ . '/..' . '/microsoft/azure-storage-blob/src/Blob/BlobRestProxy.php', 'MicrosoftAzure\\Storage\\Blob\\BlobSharedAccessSignatureHelper' => __DIR__ . '/..' . '/microsoft/azure-storage-blob/src/Blob/BlobSharedAccessSignatureHelper.php', diff --git a/composer/installed.json b/composer/installed.json index 34231e2a5..483266cda 100644 --- a/composer/installed.json +++ b/composer/installed.json @@ -181,6 +181,10 @@ "MIT" ], "description": "Convenience wrapper around ini_get()", + "support": { + "issues": "https://github.com/bantuXorg/php-ini-get-wrapper/issues", + "source": "https://github.com/bantuXorg/php-ini-get-wrapper/tree/v1.0.1" + }, "install-path": "../bantu/ini-get-wrapper" }, { @@ -218,12 +222,12 @@ "type": "library", "installation-source": "dist", "autoload": { - "psr-4": { - "Assert\\": "lib/Assert" - }, "files": [ "lib/Assert/functions.php" - ] + ], + "psr-4": { + "Assert\\": "lib/Assert" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1077,74 +1081,26 @@ "issues": "https://github.com/fgrosse/PHPASN1/issues", "source": "https://github.com/fgrosse/PHPASN1/tree/v2.3.0" }, + "abandoned": true, "install-path": "../fgrosse/phpasn1" }, - { - "name": "fusonic/linq", - "version": "v1.1.0", - "version_normalized": "1.1.0.0", - "source": { - "type": "git", - "url": "https://github.com/fusonic/linq.git", - "reference": "63520ef1470ca771acbd26871efb945dd4a7a5d8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fusonic/linq/zipball/63520ef1470ca771acbd26871efb945dd4a7a5d8", - "reference": "63520ef1470ca771acbd26871efb945dd4a7a5d8", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "time": "2015-02-26T22:49:17+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-0": { - "Fusonic\\Linq": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fusonic", - "homepage": "http://www.fusonic.net" - } - ], - "description": "LINQ 2 objects class for PHP", - "homepage": "http://fusonic.github.io/fusonic-linq/", - "keywords": [ - "linq", - "linq2objects" - ], - "support": { - "issues": "https://github.com/fusonic/linq/issues", - "source": "https://github.com/fusonic/linq/tree/master" - }, - "install-path": "../fusonic/linq" - }, { "name": "fusonic/opengraph", - "version": "v2.2.0", - "version_normalized": "2.2.0.0", + "version": "v2.3.0", + "version_normalized": "2.3.0.0", "source": { "type": "git", "url": "https://github.com/fusonic/opengraph.git", - "reference": "a63b588fbe56c175ae06e158f1513642653ee3c1" + "reference": "09dca70b04b4221c9dc664e1689e876a3faa50c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fusonic/opengraph/zipball/a63b588fbe56c175ae06e158f1513642653ee3c1", - "reference": "a63b588fbe56c175ae06e158f1513642653ee3c1", + "url": "https://api.github.com/repos/fusonic/opengraph/zipball/09dca70b04b4221c9dc664e1689e876a3faa50c2", + "reference": "09dca70b04b4221c9dc664e1689e876a3faa50c2", "shasum": "" }, "require": { "ext-dom": "*", - "fusonic/linq": "^1.0", "php": "^7.4|^8.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", @@ -1160,7 +1116,7 @@ "nyholm/psr7": "^1.2", "symfony/http-client": "^5.0" }, - "time": "2022-01-20T05:47:36+00:00", + "time": "2023-12-05T14:26:51+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1185,7 +1141,7 @@ ], "support": { "issues": "https://github.com/fusonic/opengraph/issues", - "source": "https://github.com/fusonic/opengraph/tree/v2.2.0" + "source": "https://github.com/fusonic/opengraph/tree/v2.3.0" }, "install-path": "../fusonic/opengraph" }, @@ -2128,6 +2084,76 @@ ], "install-path": "../league/uri-interfaces" }, + { + "name": "masterminds/html5", + "version": "2.9.0", + "version_normalized": "2.9.0.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "time": "2024-03-31T07:05:07+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + }, + "install-path": "../masterminds/html5" + }, { "name": "mexitek/phpcolors", "version": "v1.0.4", @@ -2481,6 +2507,10 @@ "log", "normalizer" ], + "support": { + "issues": "https://github.com/nextcloud/lognormalizer/issues", + "source": "https://github.com/nextcloud/lognormalizer/tree/v1.0.0" + }, "install-path": "../nextcloud/lognormalizer" }, { @@ -3879,12 +3909,12 @@ }, "installation-source": "dist", "autoload": { - "psr-4": { - "Ramsey\\Uuid\\": "src/" - }, "files": [ "src/functions.php" - ] + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3906,6 +3936,10 @@ { "url": "https://github.com/ramsey", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" } ], "install-path": "../ramsey/uuid" @@ -4645,6 +4679,10 @@ } ], "description": "Automatic BASH completion for Symfony Console Component based applications.", + "support": { + "issues": "https://github.com/stecman/symfony-console-completion/issues", + "source": "https://github.com/stecman/symfony-console-completion/tree/0.11.0" + }, "install-path": "../stecman/symfony-console-completion" }, { @@ -4751,24 +4789,23 @@ }, { "name": "symfony/css-selector", - "version": "v5.4.11", - "version_normalized": "5.4.11.0", + "version": "v6.4.3", + "version_normalized": "6.4.3.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "c1681789f059ab756001052164726ae88512ae3d" + "reference": "ee0f7ed5cf298cc019431bb3b3977ebc52b86229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/c1681789f059ab756001052164726ae88512ae3d", - "reference": "c1681789f059ab756001052164726ae88512ae3d", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ee0f7ed5cf298cc019431bb3b3977ebc52b86229", + "reference": "ee0f7ed5cf298cc019431bb3b3977ebc52b86229", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" }, - "time": "2022-06-27T16:58:25+00:00", + "time": "2024-01-23T14:51:35+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -4800,7 +4837,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.4.11" + "source": "https://github.com/symfony/css-selector/tree/v6.4.3" }, "funding": [ { @@ -4890,37 +4927,29 @@ }, { "name": "symfony/dom-crawler", - "version": "v5.4.11", - "version_normalized": "5.4.11.0", + "version": "v6.4.4", + "version_normalized": "6.4.4.0", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "0b900ca5576ecd59e08c76127e616667cfe427a7" + "reference": "f0e7ec3fa17000e2d0cb4557b4b47c88a6a63531" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/0b900ca5576ecd59e08c76127e616667cfe427a7", - "reference": "0b900ca5576ecd59e08c76127e616667cfe427a7", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/f0e7ec3fa17000e2d0cb4557b4b47c88a6a63531", + "reference": "f0e7ec3fa17000e2d0cb4557b4b47c88a6a63531", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "masterminds/html5": "^2.6", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "masterminds/html5": "<2.6" + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "masterminds/html5": "^2.6", - "symfony/css-selector": "^4.4|^5.0|^6.0" + "symfony/css-selector": "^5.4|^6.0|^7.0" }, - "suggest": { - "symfony/css-selector": "" - }, - "time": "2022-06-27T16:58:25+00:00", + "time": "2024-02-07T09:17:57+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -4948,7 +4977,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v5.4.11" + "source": "https://github.com/symfony/dom-crawler/tree/v6.4.4" }, "funding": [ { @@ -5383,17 +5412,17 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.28.0", - "version_normalized": "1.28.0.0", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", "shasum": "" }, "require": { @@ -5405,12 +5434,9 @@ "suggest": { "ext-ctype": "For best performance" }, - "time": "2023-01-26T09:26:14+00:00", + "time": "2024-01-29T20:11:03+00:00", "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -5448,7 +5474,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" }, "funding": [ { @@ -5729,17 +5755,17 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", - "version_normalized": "1.28.0.0", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", "shasum": "" }, "require": { @@ -5751,12 +5777,9 @@ "suggest": { "ext-mbstring": "For best performance" }, - "time": "2023-07-28T09:04:16+00:00", + "time": "2024-01-29T20:11:03+00:00", "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -5795,7 +5818,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" }, "funding": [ { @@ -5976,28 +5999,25 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.28.0", - "version_normalized": "1.28.0.0", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { "php": ">=7.1" }, - "time": "2023-01-26T09:26:14+00:00", + "time": "2024-01-29T20:11:03+00:00", "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -6042,7 +6062,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -6604,13 +6624,6 @@ }, "installation-source": "dist", "autoload": { - "psr-4": { - "Safe\\": [ - "lib/", - "deprecated/", - "generated/" - ] - }, "files": [ "deprecated/apc.php", "deprecated/libevent.php", @@ -6701,7 +6714,14 @@ "generated/yaz.php", "generated/zip.php", "generated/zlib.php" - ] + ], + "psr-4": { + "Safe\\": [ + "lib/", + "deprecated/", + "generated/" + ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ diff --git a/composer/installed.php b/composer/installed.php index 02cd6d6f0..abef2c443 100644 --- a/composer/installed.php +++ b/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'nextcloud/3rdparty', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => 'fe17c4147f7aa5ae661c99e6a52a5cf5e1a994bb', + 'reference' => '35894785112a2404cadb68d40a52158c76791b7c', 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), @@ -145,19 +145,10 @@ 'aliases' => array(), 'dev_requirement' => false, ), - 'fusonic/linq' => array( - 'pretty_version' => 'v1.1.0', - 'version' => '1.1.0.0', - 'reference' => '63520ef1470ca771acbd26871efb945dd4a7a5d8', - 'type' => 'library', - 'install_path' => __DIR__ . '/../fusonic/linq', - 'aliases' => array(), - 'dev_requirement' => false, - ), 'fusonic/opengraph' => array( - 'pretty_version' => 'v2.2.0', - 'version' => '2.2.0.0', - 'reference' => 'a63b588fbe56c175ae06e158f1513642653ee3c1', + 'pretty_version' => 'v2.3.0', + 'version' => '2.3.0.0', + 'reference' => '09dca70b04b4221c9dc664e1689e876a3faa50c2', 'type' => 'library', 'install_path' => __DIR__ . '/../fusonic/opengraph', 'aliases' => array(), @@ -271,6 +262,15 @@ 'aliases' => array(), 'dev_requirement' => false, ), + 'masterminds/html5' => array( + 'pretty_version' => '2.9.0', + 'version' => '2.9.0.0', + 'reference' => 'f5ac2c0b0a2eefca70b2ce32a5809992227e75a6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../masterminds/html5', + 'aliases' => array(), + 'dev_requirement' => false, + ), 'mexitek/phpcolors' => array( 'pretty_version' => 'v1.0.4', 'version' => '1.0.4.0', @@ -319,7 +319,7 @@ 'nextcloud/3rdparty' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => 'fe17c4147f7aa5ae661c99e6a52a5cf5e1a994bb', + 'reference' => '35894785112a2404cadb68d40a52158c76791b7c', 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), @@ -698,9 +698,9 @@ 'dev_requirement' => false, ), 'symfony/css-selector' => array( - 'pretty_version' => 'v5.4.11', - 'version' => '5.4.11.0', - 'reference' => 'c1681789f059ab756001052164726ae88512ae3d', + 'pretty_version' => 'v6.4.3', + 'version' => '6.4.3.0', + 'reference' => 'ee0f7ed5cf298cc019431bb3b3977ebc52b86229', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/css-selector', 'aliases' => array(), @@ -716,9 +716,9 @@ 'dev_requirement' => false, ), 'symfony/dom-crawler' => array( - 'pretty_version' => 'v5.4.11', - 'version' => '5.4.11.0', - 'reference' => '0b900ca5576ecd59e08c76127e616667cfe427a7', + 'pretty_version' => 'v6.4.4', + 'version' => '6.4.4.0', + 'reference' => 'f0e7ec3fa17000e2d0cb4557b4b47c88a6a63531', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/dom-crawler', 'aliases' => array(), @@ -776,9 +776,9 @@ 'dev_requirement' => false, ), 'symfony/polyfill-ctype' => array( - 'pretty_version' => 'v1.28.0', - 'version' => '1.28.0.0', - 'reference' => 'ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb', + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => 'ef4d7e442ca910c4764bce785146269b30cb5fc4', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 'aliases' => array(), @@ -812,9 +812,9 @@ 'dev_requirement' => false, ), 'symfony/polyfill-mbstring' => array( - 'pretty_version' => 'v1.28.0', - 'version' => '1.28.0.0', - 'reference' => '42292d99c55abe617799667f454222c54c60e229', + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => '9773676c8a1bb1f8d4340a62efe641cf76eda7ec', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', 'aliases' => array(), @@ -839,9 +839,9 @@ 'dev_requirement' => false, ), 'symfony/polyfill-php80' => array( - 'pretty_version' => 'v1.28.0', - 'version' => '1.28.0.0', - 'reference' => '6caa57379c4aec19c0a12a38b59b26487dcfe4b5', + 'pretty_version' => 'v1.29.0', + 'version' => '1.29.0.0', + 'reference' => '87b68208d5c1188808dd7839ee1e6c8ec3b02f1b', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'aliases' => array(), diff --git a/fusonic/linq/LICENSE b/fusonic/linq/LICENSE deleted file mode 100644 index 1617dcba4..000000000 --- a/fusonic/linq/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2014 Fusonic GmbH (http://www.fusonic.net) - -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. \ No newline at end of file diff --git a/fusonic/linq/src/Fusonic/Linq/GroupedLinq.php b/fusonic/linq/src/Fusonic/Linq/GroupedLinq.php deleted file mode 100644 index 2a2bb5107..000000000 --- a/fusonic/linq/src/Fusonic/Linq/GroupedLinq.php +++ /dev/null @@ -1,35 +0,0 @@ -groupKey = $groupKey; - } - - public function key() - { - return $this->groupKey; - } -} \ No newline at end of file diff --git a/fusonic/linq/src/Fusonic/Linq/Helper/LinqHelper.php b/fusonic/linq/src/Fusonic/Linq/Helper/LinqHelper.php deleted file mode 100644 index f3e8c9aea..000000000 --- a/fusonic/linq/src/Fusonic/Linq/Helper/LinqHelper.php +++ /dev/null @@ -1,67 +0,0 @@ -getIterator(); - } - else if($value instanceof \Iterator) { - return $value; - } - - throw new \UnexpectedValueException("Value must be an array, or implement either the \IteratorAggregate or \Iterator interface"); - } - - public static function isIterable($param) - { - return is_array($param) - || $param instanceof \IteratorAggregate - || $param instanceof \Iterator; - } -} \ No newline at end of file diff --git a/fusonic/linq/src/Fusonic/Linq/Iterator/DistinctIterator.php b/fusonic/linq/src/Fusonic/Linq/Iterator/DistinctIterator.php deleted file mode 100644 index 7651fbfd3..000000000 --- a/fusonic/linq/src/Fusonic/Linq/Iterator/DistinctIterator.php +++ /dev/null @@ -1,62 +0,0 @@ -iterator = $iterator; - } - - public function current() - { - return $this->distinct->current(); - } - - public function next() - { - $this->distinct->next(); - } - - public function key() - { - return $this->distinct->key(); - } - - public function valid() - { - return $this->distinct->valid(); - } - - public function rewind() - { - if ($this->distinct === null) { - $this->getDistincts(); - } - - $this->distinct->rewind(); - } - - private function getDistincts() - { - $data = iterator_to_array($this->iterator); - $distinct = array_unique($data); - $this->distinct = new \ArrayIterator($distinct); - } -} diff --git a/fusonic/linq/src/Fusonic/Linq/Iterator/ExceptIterator.php b/fusonic/linq/src/Fusonic/Linq/Iterator/ExceptIterator.php deleted file mode 100644 index 18d3c2ea0..000000000 --- a/fusonic/linq/src/Fusonic/Linq/Iterator/ExceptIterator.php +++ /dev/null @@ -1,65 +0,0 @@ -first = $first; - $this->second = $second; - } - - public function current() - { - return $this->result->current(); - } - - public function next() - { - $this->result->next(); - } - - public function key() - { - return $this->result->key(); - } - - public function valid() - { - return $this->result->valid(); - } - - public function rewind() - { - if ($this->result === null) { - $this->getResult(); - } - - $this->result->rewind(); - } - - private function getResult() - { - $firstArray = iterator_to_array($this->first); - $secondArray = iterator_to_array($this->second); - $this->result = new ArrayIterator(array_diff($firstArray, $secondArray)); - } -} diff --git a/fusonic/linq/src/Fusonic/Linq/Iterator/GroupIterator.php b/fusonic/linq/src/Fusonic/Linq/Iterator/GroupIterator.php deleted file mode 100644 index 0b2501790..000000000 --- a/fusonic/linq/src/Fusonic/Linq/Iterator/GroupIterator.php +++ /dev/null @@ -1,74 +0,0 @@ -iterator = $iterator; - $this->keySelector = $keySelector; - } - - public function current() - { - $current = $this->grouped->current(); - return new GroupedLinq($current['key'], new \ArrayIterator($current['values'])); - } - - public function next() - { - $this->grouped->next(); - } - - public function key() - { - return $this->grouped->key(); - } - - public function valid() - { - return $this->grouped->valid(); - } - - public function rewind() - { - if ($this->grouped === null) { - $this->doGroup(); - } - - $this->grouped->rewind(); - } - - private function doGroup() - { - $keySelector = $this->keySelector; - $this->grouped = new \ArrayIterator(array()); - foreach ($this->iterator as $value) { - $key = $keySelector($value); - if (!isset($this->grouped[$key])) { - $this->grouped[$key] = array('key' => $key, 'values'=> array()); - } - - $this->grouped[$key]['values'][] = $value; - } - } -} diff --git a/fusonic/linq/src/Fusonic/Linq/Iterator/IntersectIterator.php b/fusonic/linq/src/Fusonic/Linq/Iterator/IntersectIterator.php deleted file mode 100644 index d08c2efe1..000000000 --- a/fusonic/linq/src/Fusonic/Linq/Iterator/IntersectIterator.php +++ /dev/null @@ -1,65 +0,0 @@ -first = $first; - $this->second = $second; - } - - public function current() - { - return $this->intersections->current(); - } - - public function next() - { - $this->intersections->next(); - } - - public function key() - { - return $this->intersections->key(); - } - - public function valid() - { - return $this->intersections->valid(); - } - - public function rewind() - { - if ($this->intersections === null) { - $this->calcIntersections(); - } - - $this->intersections->rewind(); - } - - private function calcIntersections() - { - $firstArray = iterator_to_array($this->first); - $secondArray = iterator_to_array($this->second); - $this->intersections = new ArrayIterator(array_intersect($firstArray, $secondArray)); - } -} diff --git a/fusonic/linq/src/Fusonic/Linq/Iterator/OfTypeIterator.php b/fusonic/linq/src/Fusonic/Linq/Iterator/OfTypeIterator.php deleted file mode 100644 index 46a59482f..000000000 --- a/fusonic/linq/src/Fusonic/Linq/Iterator/OfTypeIterator.php +++ /dev/null @@ -1,94 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Fusonic\Linq\Iterator; - -use FilterIterator; -use Fusonic\Linq\Helper; -use Iterator; - -/** - * Iterator for filtering the Linq query with a specified type. - * @package Fusonic\Linq\Iterator - */ -final class OfTypeIterator - extends - \FilterIterator -{ - /** - * @var callable $acceptCallback - */ - private $acceptCallback; - - /** - * Initializes an instance of OfTypeIterator. - * - * @param Iterator $iterator - * @param string $type - */ - public function __construct(Iterator $iterator, $type) - { - parent::__construct($iterator); - - switch (strtolower($type)) - { - case 'int': - case 'integer': - $this->acceptCallback = function ($current) - { - return is_int($current); - }; - break; - case 'float': - case 'double': - $this->acceptCallback = function ($current) - { - return is_float($current); - }; - break; - case 'string': - $this->acceptCallback = function ($current) - { - return is_string($current); - }; - break; - case 'bool': - case 'boolean': - $this->acceptCallback = function ($current) - { - return is_bool($current); - }; - break; - - default: - $this->acceptCallback = function ($current) use ($type) - { - return $current instanceof $type; - }; - } - } - - /** - * (PHP 5 >= 5.1.0)
- * Check whether the current element of the iterator is acceptable - * @link http://php.net/manual/en/filteriterator.accept.php - * @return bool true if the current element is acceptable, otherwise false. - */ - public function accept() - { - /** @var mixed $current */ - $current = $this->current(); - /** @var callable $func */ - $func = $this->acceptCallback; - - return $func($current); - } -} \ No newline at end of file diff --git a/fusonic/linq/src/Fusonic/Linq/Iterator/OrderIterator.php b/fusonic/linq/src/Fusonic/Linq/Iterator/OrderIterator.php deleted file mode 100644 index 732e79eb2..000000000 --- a/fusonic/linq/src/Fusonic/Linq/Iterator/OrderIterator.php +++ /dev/null @@ -1,113 +0,0 @@ -iterator = $items; - $this->direction = $direction; - $this->orderKeyFunc = $orderKeyFunc; - } - - public function current() - { - return $this->orderedIterator->current(); - } - - public function next() - { - $this->orderedIterator->next(); - } - - public function key() - { - return $this->orderedIterator->key(); - } - - public function valid() - { - return $this->orderedIterator->valid(); - } - - public function rewind() - { - if ($this->orderedIterator == null) { - $this->orderItems(); - } - $this->orderedIterator->rewind(); - } - - public function orderItems() - { - $orderKeyFunc = $this->orderKeyFunc; - $direction = $this->direction; - - $itemIterator = $this->iterator; - $itemIterator->rewind(); - if (!$itemIterator->valid()) { - $this->orderedIterator = new ArrayIterator(); - return; - } - - $firstOrderKey = $orderKeyFunc($itemIterator->current()); - - $sortType = Helper\LinqHelper::LINQ_ORDER_TYPE_NUMERIC; - - if ($firstOrderKey instanceof \DateTime) { - $sortType = Helper\LinqHelper::LINQ_ORDER_TYPE_DATETIME; - } elseif (!is_numeric($firstOrderKey)) { - $sortType = Helper\LinqHelper::LINQ_ORDER_TYPE_ALPHANUMERIC; - } - - $keyMap = array(); - $valueMap = array(); - - foreach ($itemIterator as $value) { - $orderKey = $orderKeyFunc != null ? $orderKeyFunc($value) : $value; - if ($sortType == Helper\LinqHelper::LINQ_ORDER_TYPE_DATETIME) { - $orderKey = $orderKey->getTimeStamp(); - } - $keyMap[] = $orderKey; - $valueMap[] = $value; - } - - if ($sortType == Helper\LinqHelper::LINQ_ORDER_TYPE_DATETIME) { - $sortType = Helper\LinqHelper::LINQ_ORDER_TYPE_NUMERIC; - } - - if ($direction == Helper\LinqHelper::LINQ_ORDER_ASC) { - asort($keyMap, $sortType == Helper\LinqHelper::LINQ_ORDER_TYPE_NUMERIC ? SORT_NUMERIC : SORT_LOCALE_STRING); - } else { - arsort($keyMap, $sortType == Helper\LinqHelper::LINQ_ORDER_TYPE_NUMERIC ? SORT_NUMERIC : SORT_LOCALE_STRING); - } - - $sorted = new ArrayIterator(array()); - foreach ($keyMap as $key => $value) { - $sorted[] = $valueMap[$key]; - } - - $this->orderedIterator = $sorted; - } -} diff --git a/fusonic/linq/src/Fusonic/Linq/Iterator/SelectIterator.php b/fusonic/linq/src/Fusonic/Linq/Iterator/SelectIterator.php deleted file mode 100644 index aa698b3d2..000000000 --- a/fusonic/linq/src/Fusonic/Linq/Iterator/SelectIterator.php +++ /dev/null @@ -1,37 +0,0 @@ -selector = $selector; - } - - public function current() - { - $selector = $this->selector; - return $selector(parent::current()); - } -} diff --git a/fusonic/linq/src/Fusonic/Linq/Iterator/SelectManyIterator.php b/fusonic/linq/src/Fusonic/Linq/Iterator/SelectManyIterator.php deleted file mode 100644 index 310b8bdb6..000000000 --- a/fusonic/linq/src/Fusonic/Linq/Iterator/SelectManyIterator.php +++ /dev/null @@ -1,84 +0,0 @@ -iterator = $iterator; - } - - public function current() - { - if ($this->currentIterator != null) { - return $this->currentIterator->current(); - } - - return null; - } - - public function next() - { - if ($this->currentIterator != null) { - $this->currentIterator->next(); - - if (!$this->currentIterator->valid()) { - $this->iterator->next(); - if ($this->iterator->valid()) { - $this->currentIterator = Helper\LinqHelper::getIteratorOrThrow($this->iterator->current()); - if ($this->currentIterator != null) { - $this->currentIterator->rewind(); - $this->key++; - } - } - } else { - $this->key++; - } - } - } - - public function key() - { - return $this->key; - } - - public function valid() - { - $current = $this->currentIterator; - return $current != null && $current->valid(); - } - - public function rewind() - { - $this->iterator->rewind(); - if ($this->iterator->valid()) { - $current = $this->iterator->current(); - $this->currentIterator = Helper\LinqHelper::getIteratorOrThrow($current); - if ($this->currentIterator != null) { - $this->currentIterator->rewind(); - } - } else { - $this->currentIterator = null; - } - - $this->key = 0; - } -} \ No newline at end of file diff --git a/fusonic/linq/src/Fusonic/Linq/Iterator/WhereIterator.php b/fusonic/linq/src/Fusonic/Linq/Iterator/WhereIterator.php deleted file mode 100644 index 1a757e008..000000000 --- a/fusonic/linq/src/Fusonic/Linq/Iterator/WhereIterator.php +++ /dev/null @@ -1,34 +0,0 @@ -func = $func; - } - - public function accept() - { - $func = $this->func; - $current = $this->current(); - return Helper\LinqHelper::getBoolOrThrowException($func($current)); - } -} \ No newline at end of file diff --git a/fusonic/linq/src/Fusonic/Linq/Linq.php b/fusonic/linq/src/Fusonic/Linq/Linq.php deleted file mode 100644 index f9a568105..000000000 --- a/fusonic/linq/src/Fusonic/Linq/Linq.php +++ /dev/null @@ -1,732 +0,0 @@ -. - */ -class Linq implements IteratorAggregate, Countable -{ - private $iterator; - - /** - * Creates a new Linq object using the provided dataSource. - * - * @param array|\Iterator|IteratorAggregate $dataSource A Traversable sequence as data source. - */ - public function __construct($dataSource) - { - LinqHelper::assertArgumentIsIterable($dataSource, "dataSource"); - $dataSource = LinqHelper::getIteratorOrThrow($dataSource); - - $this->iterator = $dataSource; - } - - /** - * Creates a new Linq object using the provided dataDataSource. - * This is the recommended way for getting a new Linq instance. - * - * @param array|\Iterator|IteratorAggregate $dataSource A Traversable sequence as data source. - * @return Linq - */ - public static function from($dataSource) - { - return new Linq($dataSource); - } - - /** - * Generates a sequence of integral numbers within a specified range. - * - * @param $start The value of the first integer in the sequence. - * @param $count The number of sequential integers to generate. - * @return Linq An sequence that contains a range of sequential int numbers. - * @throws \OutOfRangeException - */ - public static function range($start, $count) - { - if ($count < 0) { - throw new OutOfRangeException('$count must be not be negative.'); - } - - return new Linq(range($start, $start + $count - 1)); - } - - /** - * Filters the Linq object according to func return result. - * - * @param callback $func A func that returns boolean - * @return Linq Filtered results according to $func - */ - public function where($func) - { - return new Linq(new WhereIterator($this->iterator, $func)); - } - - /** - * Filters the Linq object according to type. - * - * @param string $type - * - * @return Linq Filtered results according to $func - */ - public function ofType($type) - { - return new Linq(new OfTypeIterator($this->iterator, $type)); - } - - /** - * Bypasses a specified number of elements and then returns the remaining elements. - * - * @param int $count The number of elements to skip before returning the remaining elements. - * @return Linq A Linq object that contains the elements that occur after the specified index. - */ - public function skip($count) - { - // If its an array iterator we must check the arrays bounds are greater than the skip count. - // This is because the LimitIterator will use the seek() method which will throw an exception if $count > array.bounds. - $innerIterator = $this->iterator; - if ($innerIterator instanceof \ArrayIterator) { - if ($count >= $innerIterator->count()) { - return new Linq(array()); - } - } - - return new Linq(new \LimitIterator($innerIterator, $count, -1)); - } - - /** - * Returns a specified number of contiguous elements from the start of a sequence - * - * @param int $count The number of elements to return. - * @return Linq A Linq object that contains the specified number of elements from the start. - */ - public function take($count) - { - if ($count == 0) { - return new Linq(array()); - } - - return new Linq(new \LimitIterator($this->iterator, 0, $count)); - } - - /** - * Applies an accumulator function over a sequence. - * The aggregate method makes it simple to perform a calculation over a sequence of values. - * This method works by calling $func one time for each element. - * The first element of source is used as the initial aggregate value if $seed parameter is not specified. - * If $seed is specified, this value will be used as the first value. - * - * @param callback $func An accumulator function to be invoked on each element. - * @param mixed $seed - * @throws \RuntimeException if the input sequence contains no elements. - * @return mixed Returns the final result of $func. - */ - public function aggregate($func, $seed = null) - { - $result = null; - $first = true; - - if ($seed !== null) { - $result = $seed; - $first = false; - } - - foreach ($this->iterator as $current) { - if (!$first) { - $result = $func($result, $current); - } else { - $result = $current; - $first = false; - } - } - if ($first) { - throw new \RuntimeException("The input sequence contains no elements."); - } - return $result; - } - - /** - * Splits the sequence in chunks according to $chunksize. - * - * @param $chunksize Specifies how many elements are grouped together per chunk. - * @throws \InvalidArgumentException - * @return Linq - */ - public function chunk($chunksize) - { - if ($chunksize < 1) { - throw new \InvalidArgumentException("chunksize", $chunksize); - } - - $i = -1; - return $this->select( - function ($x) use (&$i) { - $i++; - return array("index" => $i, "value" => $x); - } - ) - ->groupBy( - function ($pair) use ($chunksize) { - return $pair["index"] / $chunksize; - } - ) - ->select( - function (GroupedLinq $group) { - return $group->select( - function ($v) { - return $v["value"]; - } - ); - } - ); - } - - /** - * Determines whether all elements satisfy a condition. - * - * @param callback $func A function to test each element for a condition. - * @return bool True if every element passes the test in the specified func, or if the sequence is empty; otherwise, false. - */ - public function all($func) - { - foreach ($this->iterator as $current) { - $match = LinqHelper::getBoolOrThrowException($func($current)); - if (!$match) { - return false; - } - } - return true; - } - - /** - * Determines whether any element exists or satisfies a condition by invoking $func. - * - * @param callback $func A function to test each element for a condition or NULL to determine if any element exists. - * @return bool True if no $func given and the source sequence contains any elements or True if any elements passed the test in the specified func; otherwise, false. - */ - public function any($func = null) - { - foreach ($this->iterator as $current) { - if ($func === null) { - return true; - } - - $match = LinqHelper::getBoolOrThrowException($func($current)); - if ($match) { - return true; - } - } - return false; - } - - /** - * Counts the elements of this Linq sequence. - * @return int - */ - public function count() - { - if ($this->iterator instanceof Countable) { - return $this->iterator->count(); - } - - return iterator_count($this->iterator); - } - - /** - * Computes the average of all numeric values. Uses $func to obtain the value on each element. - * - * @param callback $func A func that returns any numeric type (int, float etc.) - * @throws \UnexpectedValueException if an item of the sequence is not a numeric value. - * @return double Average of items - */ - public function average($func = null) - { - $resultTotal = 0; - $itemCount = 0; - - $source = $this->getSelectIteratorOrInnerIterator($func); - - foreach ($source as $item) { - if (!is_numeric($item)) { - throw new UnexpectedValueException("Cannot calculate an average on a none numeric value"); - } - - $resultTotal += $item; - $itemCount++; - } - return $itemCount == 0 ? 0 : ($resultTotal / $itemCount); - } - - /** - * Sorts the elements in ascending order according to a key provided by $func. - * - * @param callback $func A function to extract a key from an element. - * @return Linq A new Linq instance whose elements are sorted ascending according to a key. - */ - public function orderBy($func) - { - return $this->order($func, LinqHelper::LINQ_ORDER_ASC); - } - - /** - * Sorts the elements in descending order according to a key provided by $func. - * - * @param callback $func A function to extract a key from an element. - * @return Linq A new Linq instance whose elements are sorted descending according to a key. - */ - public function orderByDescending($func) - { - return $this->order($func, LinqHelper::LINQ_ORDER_DESC); - } - - private function order($func, $direction = LinqHelper::LINQ_ORDER_ASC) - { - return new Linq(new OrderIterator($this->iterator, $func, $direction)); - } - - /** - * Gets the sum of all items or by invoking a transform function on each item to get a numeric value. - * - * @param callback $func A func that returns any numeric type (int, float etc.) from the given element, or NULL to use the element itself. - * @throws \UnexpectedValueException if any element is not a numeric value. - * @return double The sum of all items. - */ - public function sum($func = null) - { - $sum = 0; - $iterator = $this->getSelectIteratorOrInnerIterator($func); - foreach ($iterator as $value) { - if (!is_numeric($value)) { - throw new UnexpectedValueException("sum() only works on numeric values."); - } - - $sum += $value; - } - return $sum; - } - - /** - * Gets the minimum item value of all items or by invoking a transform function on each item to get a numeric value. - * - * @param callback $func A func that returns any numeric type (int, float etc.) from the given element, or NULL to use the element itself. - * @throws \RuntimeException if the sequence contains no elements - * @throws \UnexpectedValueException - * @return double Minimum item value - */ - public function min($func = null) - { - $min = null; - $iterator = $this->getSelectIteratorOrInnerIterator($func); - foreach ($iterator as $value) { - if (!is_numeric($value) && !is_string($value) && !($value instanceof \DateTime)) { - throw new UnexpectedValueException("min() only works on numeric values, strings and DateTime objects."); - } - - if (is_null($min)) { - $min = $value; - } elseif ($min > $value) { - $min = $value; - } - } - - if ($min === null) { - throw new \RuntimeException("Cannot calculate min() as the Linq sequence contains no elements."); - } - - return $min; - } - - /** - * Returns the maximum item value according to $func - * - * @param callback $func A func that returns any numeric type (int, float etc.) - * @throws \RuntimeException if the sequence contains no elements - * @throws \UnexpectedValueException if any element is not a numeric value or a string. - * @return double Maximum item value - */ - public function max($func = null) - { - $max = null; - $iterator = $this->getSelectIteratorOrInnerIterator($func); - foreach ($iterator as $value) { - if (!is_numeric($value) && !is_string($value) && !($value instanceof \DateTime)) { - throw new UnexpectedValueException("max() only works on numeric values, strings and DateTime objects."); - } - - if (is_null($max)) { - $max = $value; - } elseif ($max < $value) { - $max = $value; - } - } - - if ($max === null) { - throw new \RuntimeException("Cannot calculate max() as the Linq sequence contains no elements."); - } - - return $max; - } - - /** - * Projects each element into a new form by invoking the selector function. - * - * @param callback $func A transform function to apply to each element. - * @return Linq A new Linq object whose elements are the result of invoking the transform function on each element of the original Linq object. - */ - public function select($func) - { - return new Linq(new SelectIterator($this->iterator, $func)); - } - - /** - * Projects each element of a sequence to a new Linq and flattens the resulting sequences into one sequence. - * - * @param callback $func A func that returns a sequence (array, Linq, Iterator). - * @throws \UnexpectedValueException if an element is not a traversable sequence. - * @return Linq A new Linq object whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. - */ - public function selectMany($func) - { - return new Linq(new SelectManyIterator(new SelectIterator($this->iterator, $func))); - } - - /** - * Performs the specified action on each element of the Linq sequence and returns the Linq sequence. - * @param callback $func A func that will be evaluated for each item in the linq sequence. - * @return Linq The original Linq sequence that was used to perform the foreach. - */ - public function each($func) - { - foreach ($this->iterator as $item) { - $func($item); - } - return $this; - } - - /** - * Determines whether a sequence contains a specified element. - * This function will use php strict comparison (===). If you need custom comparison use the Linq::any($func) method. - * - * @param mixed $value The value to locate in the sequence. - * @return bool True if $value is found within the sequence; otherwise false. - */ - public function contains($value) - { - return $this->any( - function ($x) use ($value) { - return $x === $value; - } - ); - } - - /** - * Concatenates this Linq object with the given sequence. - * - * @param array|\Iterator $second A sequence which will be concatenated with this Linq object. - * @throws InvalidArgumentException if the given sequence is not traversable. - * @return Linq A new Linq object that contains the concatenated elements of the input sequence and the original Linq sequence. - */ - public function concat($second) - { - LinqHelper::assertArgumentIsIterable($second, "second"); - - $allItems = new \ArrayIterator(array($this->iterator, $second)); - - return new Linq(new SelectManyIterator($allItems)); - } - - /** - * Returns distinct item values of this - * - * @param callback $func - * @return Linq Distinct item values of this - */ - public function distinct($func = null) - { - return new Linq(new DistinctIterator($this->getSelectIteratorOrInnerIterator($func))); - } - - /** - * Intersects the Linq sequence with second Iterable sequence. - * - * @param \Iterator|array An iterator to intersect with: - * @return Linq intersected items - */ - public function intersect($second) - { - LinqHelper::assertArgumentIsIterable($second, "second"); - return new Linq(new IntersectIterator($this->iterator, LinqHelper::getIteratorOrThrow($second))); - } - - /** - * Returns all elements except the ones of the given sequence. - * - * @param array|\Iterator $second - * @return Linq Returns all items of this not occuring in $second - */ - public function except($second) - { - LinqHelper::assertArgumentIsIterable($second, "second"); - return new Linq(new ExceptIterator($this->iterator, LinqHelper::getIteratorOrThrow($second))); - } - - /** - * Returns the element at a specified index. - * This method throws an exception if index is out of range. - * To instead return NULL when the specified index is out of range, use the elementAtOrNull method. - * - * @throws \OutOfRangeException if index is less than 0 or greater than or equal to the number of elements in the sequence. - * @param int $index - * @return mixed Item at $index - */ - public function elementAt($index) - { - return $this->getValueAt($index, true); - } - - /** - * Returns the element at a specified index or NULL if the index is out of range. - * - * @param $index - * @return mixed Item at $index - */ - public function elementAtOrNull($index) - { - return $this->getValueAt($index, false); - } - - private function getValueAt($index, $throwEx) - { - $i = 0; - foreach ($this->iterator as $value) { - if ($i == $index) { - return $value; - } - $i++; - } - - if ($throwEx) { - throw new OutOfRangeException("Index is less than 0 or greater than or equal to the number of elements in the sequence."); - } - - return null; - } - - /** - * Groups the object according to the $func generated key - * - * @param callback $keySelector a func that returns an item as key, item can be any type. - * @return GroupedLinq - */ - public function groupBy($keySelector) - { - return new Linq(new GroupIterator($this->iterator, $keySelector)); - } - - /** - * Returns the last element that satisfies a specified condition. - * @throws \RuntimeException if no element satisfies the condition in predicate or the source sequence is empty. - * - * @param callback $func a func that returns boolean. - * @return Object Last item in this - */ - public function last($func = null) - { - return $this->getLast($func, true); - } - - /** - * Returns the last element that satisfies a condition or NULL if no such element is found. - * - * @param callback $func a func that returns boolean. - * @return mixed - */ - public function lastOrNull($func = null) - { - return $this->getLast($func, false); - } - - /** - * Returns the first element that satisfies a specified condition - * @throws \RuntimeException if no element satisfies the condition in predicate -or- the source sequence is empty / does not match any elements. - * - * @param callback $func a func that returns boolean. - * @return mixed - */ - public function first($func = null) - { - return $this->getFirst($func, true); - } - - /** - * Returns the first element, or NULL if the sequence contains no elements. - * - * @param callback $func a func that returns boolean. - * @return mixed - */ - public function firstOrNull($func = null) - { - return $this->getFirst($func, false); - } - - /** - * Returns the only element that satisfies a specified condition. - * - * @throws \RuntimeException if no element exists or if more than one element exists. - * @param callback $func a func that returns boolean. - * @return mixed - */ - public function single($func = null) - { - return $this->getSingle($func, true); - } - - /** - * Returns the only element that satisfies a specified condition or NULL if no such element exists. - * - * @throws \RuntimeException if more than one element satisfies the condition. - * @param callback $func a func that returns boolean. - * @return mixed - */ - public function singleOrNull($func = null) - { - return $this->getSingle($func, false); - } - - - private function getWhereIteratorOrInnerIterator($func) - { - return $func === null ? $this->iterator : new WhereIterator($this->iterator, $func); - } - - private function getSelectIteratorOrInnerIterator($func) - { - return $func === null ? $this->iterator : new SelectIterator($this->iterator, $func); - } - - private function getSingle($func, $throw) - { - $source = $this->getWhereIteratorOrInnerIterator($func); - - $count = 0; - $single = null; - - foreach ($source as $stored) { - $count++; - - if ($count > 1) { - throw new \RuntimeException("The input sequence contains more than 1 elements."); - } - - $single = $stored; - } - - if ($count == 0 && $throw) { - throw new \RuntimeException("The input sequence contains no matching element."); - } - - return $single; - } - - private function getFirst($func, $throw) - { - $source = $this->getWhereIteratorOrInnerIterator($func); - - $count = 0; - $first = null; - - foreach ($source as $stored) { - $count++; - $first = $stored; - break; - } - - if ($count == 0 && $throw) { - throw new \RuntimeException("The input sequence contains no matching element."); - } - - return $first; - } - - private function getLast($func, $throw) - { - $source = $this->getWhereIteratorOrInnerIterator($func); - - $count = 0; - $last = null; - - foreach ($source as $stored) { - $count++; - $last = $stored; - } - - if ($count == 0 && $throw) { - throw new \RuntimeException("The input sequence contains no matching element."); - } - - return $last; - } - - /** - * Creates an Array from this Linq object with key/value selector(s). - * - * @param callback $keySelector a func that returns the array-key for each element. - * @param callback $valueSelector a func that returns the array-value for each element. - * - * @return Array An array with all values. - */ - public function toArray($keySelector = null, $valueSelector = null) - { - if ($keySelector === null && $valueSelector === null) { - return iterator_to_array($this, false); - } elseif ($keySelector == null) { - return iterator_to_array(new SelectIterator($this->getIterator(), $valueSelector), false); - } else { - $array = array(); - foreach ($this as $value) { - $key = $keySelector($value); - $array[$key] = $valueSelector == null ? $value : $valueSelector($value); - } - return $array; - } - } - - /** - * Retrieves the iterator of this Linq class. - * @link http://php.net/manual/en/iteratoraggregate.getiterator.php - * @return Traversable An instance of an object implementing Iterator or - * Traversable - */ - public function getIterator() - { - return $this->iterator; - } -} \ No newline at end of file diff --git a/fusonic/opengraph/src/Consumer.php b/fusonic/opengraph/src/Consumer.php index 25b03febc..74aec6a64 100644 --- a/fusonic/opengraph/src/Consumer.php +++ b/fusonic/opengraph/src/Consumer.php @@ -2,8 +2,6 @@ namespace Fusonic\OpenGraph; -use DOMElement; -use Fusonic\Linq\Linq; use Fusonic\OpenGraph\Objects\ObjectBase; use Fusonic\OpenGraph\Objects\Website; use LogicException; @@ -97,32 +95,20 @@ private function extractOpenGraphData(string $content): ObjectBase { // Get all meta-tags starting with "og:" $ogMetaTags = $crawler->filter("meta[{$t}^='og:']"); + // Create clean property array - $props = Linq::from($ogMetaTags) - ->select( - function (DOMElement $tag) use ($t) { - $name = strtolower(trim($tag->getAttribute($t))); - $value = trim($tag->getAttribute("content")); - return new Property($name, $value); - } - ) - ->toArray(); + $props = []; + foreach ($ogMetaTags as $tag) { + $name = strtolower(trim($tag->getAttribute($t))); + $value = trim($tag->getAttribute("content")); + $props[] = new Property($name, $value); + } + $properties = array_merge($properties, $props); - } - // Create new object of the correct type - $typeProperty = Linq::from($properties) - ->firstOrNull( - function (Property $property) { - return $property->key === Property::TYPE; - } - ); - switch ($typeProperty !== null ? $typeProperty->value : null) { - default: - $object = new Website(); - break; - } + // Create new object + $object = new Website(); // Assign all properties to the object $object->assignProperties($properties, $this->debug); diff --git a/masterminds/html5/LICENSE.txt b/masterminds/html5/LICENSE.txt new file mode 100644 index 000000000..3c275b54a --- /dev/null +++ b/masterminds/html5/LICENSE.txt @@ -0,0 +1,66 @@ +## HTML5-PHP License + +Copyright (c) 2013 The Authors of HTML5-PHP + +Matt Butcher - mattbutcher@google.com +Matt Farina - matt@mattfarina.com +Asmir Mustafic - goetas@gmail.com + +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. + +## HTML5Lib License + +Portions of this are based on html5lib's PHP version, which was a +sub-project of html5lib. The following is the list of contributors from +html5lib: + +html5lib: + +Copyright (c) 2006-2009 The Authors + +Contributors: +James Graham - jg307@cam.ac.uk +Anne van Kesteren - annevankesteren@gmail.com +Lachlan Hunt - lachlan.hunt@lachy.id.au +Matt McDonald - kanashii@kanashii.ca +Sam Ruby - rubys@intertwingly.net +Ian Hickson (Google) - ian@hixie.ch +Thomas Broyer - t.broyer@ltgt.net +Jacques Distler - distler@golem.ph.utexas.edu +Henri Sivonen - hsivonen@iki.fi +Adam Barth - abarth@webkit.org +Eric Seidel - eric@webkit.org +The Mozilla Foundation (contributions from Henri Sivonen since 2008) +David Flanagan (Mozilla) - dflanagan@mozilla.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/masterminds/html5/src/HTML5.php b/masterminds/html5/src/HTML5.php new file mode 100644 index 000000000..c857145fb --- /dev/null +++ b/masterminds/html5/src/HTML5.php @@ -0,0 +1,246 @@ + false, + + // Prevents the parser from automatically assigning the HTML5 namespace to the DOM document. + 'disable_html_ns' => false, + ); + + protected $errors = array(); + + public function __construct(array $defaultOptions = array()) + { + $this->defaultOptions = array_merge($this->defaultOptions, $defaultOptions); + } + + /** + * Get the current default options. + * + * @return array + */ + public function getOptions() + { + return $this->defaultOptions; + } + + /** + * Load and parse an HTML file. + * + * This will apply the HTML5 parser, which is tolerant of many + * varieties of HTML, including XHTML 1, HTML 4, and well-formed HTML + * 3. Note that in these cases, not all of the old data will be + * preserved. For example, XHTML's XML declaration will be removed. + * + * The rules governing parsing are set out in the HTML 5 spec. + * + * @param string|resource $file The path to the file to parse. If this is a resource, it is + * assumed to be an open stream whose pointer is set to the first + * byte of input. + * @param array $options Configuration options when parsing the HTML. + * + * @return \DOMDocument A DOM document. These object type is defined by the libxml + * library, and should have been included with your version of PHP. + */ + public function load($file, array $options = array()) + { + // Handle the case where file is a resource. + if (is_resource($file)) { + return $this->parse(stream_get_contents($file), $options); + } + + return $this->parse(file_get_contents($file), $options); + } + + /** + * Parse a HTML Document from a string. + * + * Take a string of HTML 5 (or earlier) and parse it into a + * DOMDocument. + * + * @param string $string A html5 document as a string. + * @param array $options Configuration options when parsing the HTML. + * + * @return \DOMDocument A DOM document. DOM is part of libxml, which is included with + * almost all distribtions of PHP. + */ + public function loadHTML($string, array $options = array()) + { + return $this->parse($string, $options); + } + + /** + * Convenience function to load an HTML file. + * + * This is here to provide backwards compatibility with the + * PHP DOM implementation. It simply calls load(). + * + * @param string $file The path to the file to parse. If this is a resource, it is + * assumed to be an open stream whose pointer is set to the first + * byte of input. + * @param array $options Configuration options when parsing the HTML. + * + * @return \DOMDocument A DOM document. These object type is defined by the libxml + * library, and should have been included with your version of PHP. + */ + public function loadHTMLFile($file, array $options = array()) + { + return $this->load($file, $options); + } + + /** + * Parse a HTML fragment from a string. + * + * @param string $string the HTML5 fragment as a string + * @param array $options Configuration options when parsing the HTML + * + * @return \DOMDocumentFragment A DOM fragment. The DOM is part of libxml, which is included with + * almost all distributions of PHP. + */ + public function loadHTMLFragment($string, array $options = array()) + { + return $this->parseFragment($string, $options); + } + + /** + * Return all errors encountered into parsing phase. + * + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + /** + * Return true it some errors were encountered into parsing phase. + * + * @return bool + */ + public function hasErrors() + { + return count($this->errors) > 0; + } + + /** + * Parse an input string. + * + * @param string $input + * @param array $options + * + * @return \DOMDocument + */ + public function parse($input, array $options = array()) + { + $this->errors = array(); + $options = array_merge($this->defaultOptions, $options); + $events = new DOMTreeBuilder(false, $options); + $scanner = new Scanner($input, !empty($options['encoding']) ? $options['encoding'] : 'UTF-8'); + $parser = new Tokenizer($scanner, $events, !empty($options['xmlNamespaces']) ? Tokenizer::CONFORMANT_XML : Tokenizer::CONFORMANT_HTML); + + $parser->parse(); + $this->errors = $events->getErrors(); + + return $events->document(); + } + + /** + * Parse an input stream where the stream is a fragment. + * + * Lower-level loading function. This requires an input stream instead + * of a string, file, or resource. + * + * @param string $input The input data to parse in the form of a string. + * @param array $options An array of options. + * + * @return \DOMDocumentFragment + */ + public function parseFragment($input, array $options = array()) + { + $options = array_merge($this->defaultOptions, $options); + $events = new DOMTreeBuilder(true, $options); + $scanner = new Scanner($input, !empty($options['encoding']) ? $options['encoding'] : 'UTF-8'); + $parser = new Tokenizer($scanner, $events, !empty($options['xmlNamespaces']) ? Tokenizer::CONFORMANT_XML : Tokenizer::CONFORMANT_HTML); + + $parser->parse(); + $this->errors = $events->getErrors(); + + return $events->fragment(); + } + + /** + * Save a DOM into a given file as HTML5. + * + * @param mixed $dom The DOM to be serialized. + * @param string|resource $file The filename to be written or resource to write to. + * @param array $options Configuration options when serializing the DOM. These include: + * - encode_entities: Text written to the output is escaped by default and not all + * entities are encoded. If this is set to true all entities will be encoded. + * Defaults to false. + */ + public function save($dom, $file, $options = array()) + { + $close = true; + if (is_resource($file)) { + $stream = $file; + $close = false; + } else { + $stream = fopen($file, 'wb'); + } + $options = array_merge($this->defaultOptions, $options); + $rules = new OutputRules($stream, $options); + $trav = new Traverser($dom, $stream, $rules, $options); + + $trav->walk(); + /* + * release the traverser to avoid cyclic references and allow PHP to free memory without waiting for gc_collect_cycles + */ + $rules->unsetTraverser(); + if ($close) { + fclose($stream); + } + } + + /** + * Convert a DOM into an HTML5 string. + * + * @param mixed $dom The DOM to be serialized. + * @param array $options Configuration options when serializing the DOM. These include: + * - encode_entities: Text written to the output is escaped by default and not all + * entities are encoded. If this is set to true all entities will be encoded. + * Defaults to false. + * + * @return string A HTML5 documented generated from the DOM. + */ + public function saveHTML($dom, $options = array()) + { + $stream = fopen('php://temp', 'wb'); + $this->save($dom, $stream, array_merge($this->defaultOptions, $options)); + + $html = stream_get_contents($stream, -1, 0); + + fclose($stream); + + return $html; + } +} diff --git a/masterminds/html5/src/HTML5/Elements.php b/masterminds/html5/src/HTML5/Elements.php new file mode 100644 index 000000000..5d8cfd442 --- /dev/null +++ b/masterminds/html5/src/HTML5/Elements.php @@ -0,0 +1,637 @@ + [PARENT-TAG-NAME-TO-CLOSE1, PARENT-TAG-NAME-TO-CLOSE2, ...]. + * + * Order is important, after auto-closing one parent with might have to close also their parent. + * + * @var array + */ + public static $optionalEndElementsParentsToClose = array( + 'tr' => array('td', 'tr'), + 'td' => array('td', 'th'), + 'th' => array('td', 'th'), + 'tfoot' => array('td', 'th', 'tr', 'tbody', 'thead'), + 'tbody' => array('td', 'th', 'tr', 'thead'), + ); + + /** + * The HTML5 elements as defined in http://dev.w3.org/html5/markup/elements.html. + * + * @var array + */ + public static $html5 = array( + 'a' => 1, + 'abbr' => 1, + 'address' => 65, // NORMAL | BLOCK_TAG + 'area' => 9, // NORMAL | VOID_TAG + 'article' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'aside' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'audio' => 1, // NORMAL + 'b' => 1, + 'base' => 9, // NORMAL | VOID_TAG + 'bdi' => 1, + 'bdo' => 1, + 'blockquote' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'body' => 1, + 'br' => 9, // NORMAL | VOID_TAG + 'button' => 1, + 'canvas' => 65, // NORMAL | BLOCK_TAG + 'caption' => 1, + 'cite' => 1, + 'code' => 1, + 'col' => 9, // NORMAL | VOID_TAG + 'colgroup' => 1, + 'command' => 9, // NORMAL | VOID_TAG + // "data" => 1, // This is highly experimental and only part of the whatwg spec (not w3c). See https://developer.mozilla.org/en-US/docs/HTML/Element/data + 'datalist' => 1, + 'dd' => 65, // NORMAL | BLOCK_TAG + 'del' => 1, + 'details' => 17, // NORMAL | AUTOCLOSE_P, + 'dfn' => 1, + 'dialog' => 17, // NORMAL | AUTOCLOSE_P, + 'div' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'dl' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'dt' => 1, + 'em' => 1, + 'embed' => 9, // NORMAL | VOID_TAG + 'fieldset' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'figcaption' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'figure' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'footer' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'form' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'h1' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'h2' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'h3' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'h4' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'h5' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'h6' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'head' => 1, + 'header' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'hgroup' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'hr' => 73, // NORMAL | VOID_TAG + 'html' => 1, + 'i' => 1, + 'iframe' => 3, // NORMAL | TEXT_RAW + 'img' => 9, // NORMAL | VOID_TAG + 'input' => 9, // NORMAL | VOID_TAG + 'kbd' => 1, + 'ins' => 1, + 'keygen' => 9, // NORMAL | VOID_TAG + 'label' => 1, + 'legend' => 1, + 'li' => 1, + 'link' => 9, // NORMAL | VOID_TAG + 'map' => 1, + 'mark' => 1, + 'menu' => 17, // NORMAL | AUTOCLOSE_P, + 'meta' => 9, // NORMAL | VOID_TAG + 'meter' => 1, + 'nav' => 17, // NORMAL | AUTOCLOSE_P, + 'noscript' => 65, // NORMAL | BLOCK_TAG + 'object' => 1, + 'ol' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'optgroup' => 1, + 'option' => 1, + 'output' => 65, // NORMAL | BLOCK_TAG + 'p' => 209, // NORMAL | AUTOCLOSE_P | BLOCK_TAG | BLOCK_ONLY_INLINE + 'param' => 9, // NORMAL | VOID_TAG + 'pre' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'progress' => 1, + 'q' => 1, + 'rp' => 1, + 'rt' => 1, + 'ruby' => 1, + 's' => 1, + 'samp' => 1, + 'script' => 3, // NORMAL | TEXT_RAW + 'section' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'select' => 1, + 'small' => 1, + 'source' => 9, // NORMAL | VOID_TAG + 'span' => 1, + 'strong' => 1, + 'style' => 3, // NORMAL | TEXT_RAW + 'sub' => 1, + 'summary' => 17, // NORMAL | AUTOCLOSE_P, + 'sup' => 1, + 'table' => 65, // NORMAL | BLOCK_TAG + 'tbody' => 1, + 'td' => 1, + 'textarea' => 5, // NORMAL | TEXT_RCDATA + 'tfoot' => 65, // NORMAL | BLOCK_TAG + 'th' => 1, + 'thead' => 1, + 'time' => 1, + 'title' => 5, // NORMAL | TEXT_RCDATA + 'tr' => 1, + 'track' => 9, // NORMAL | VOID_TAG + 'u' => 1, + 'ul' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG + 'var' => 1, + 'video' => 1, + 'wbr' => 9, // NORMAL | VOID_TAG + + // Legacy? + 'basefont' => 8, // VOID_TAG + 'bgsound' => 8, // VOID_TAG + 'noframes' => 2, // RAW_TEXT + 'frame' => 9, // NORMAL | VOID_TAG + 'frameset' => 1, + 'center' => 16, + 'dir' => 16, + 'listing' => 16, // AUTOCLOSE_P + 'plaintext' => 48, // AUTOCLOSE_P | TEXT_PLAINTEXT + 'applet' => 0, + 'marquee' => 0, + 'isindex' => 8, // VOID_TAG + 'xmp' => 20, // AUTOCLOSE_P | VOID_TAG | RAW_TEXT + 'noembed' => 2, // RAW_TEXT + ); + + /** + * The MathML elements. + * See http://www.w3.org/wiki/MathML/Elements. + * + * In our case we are only concerned with presentation MathML and not content + * MathML. There is a nice list of this subset at https://developer.mozilla.org/en-US/docs/MathML/Element. + * + * @var array + */ + public static $mathml = array( + 'maction' => 1, + 'maligngroup' => 1, + 'malignmark' => 1, + 'math' => 1, + 'menclose' => 1, + 'merror' => 1, + 'mfenced' => 1, + 'mfrac' => 1, + 'mglyph' => 1, + 'mi' => 1, + 'mlabeledtr' => 1, + 'mlongdiv' => 1, + 'mmultiscripts' => 1, + 'mn' => 1, + 'mo' => 1, + 'mover' => 1, + 'mpadded' => 1, + 'mphantom' => 1, + 'mroot' => 1, + 'mrow' => 1, + 'ms' => 1, + 'mscarries' => 1, + 'mscarry' => 1, + 'msgroup' => 1, + 'msline' => 1, + 'mspace' => 1, + 'msqrt' => 1, + 'msrow' => 1, + 'mstack' => 1, + 'mstyle' => 1, + 'msub' => 1, + 'msup' => 1, + 'msubsup' => 1, + 'mtable' => 1, + 'mtd' => 1, + 'mtext' => 1, + 'mtr' => 1, + 'munder' => 1, + 'munderover' => 1, + ); + + /** + * The svg elements. + * + * The Mozilla documentation has a good list at https://developer.mozilla.org/en-US/docs/SVG/Element. + * The w3c list appears to be lacking in some areas like filter effect elements. + * That list can be found at http://www.w3.org/wiki/SVG/Elements. + * + * Note, FireFox appears to do a better job rendering filter effects than chrome. + * While they are in the spec I'm not sure how widely implemented they are. + * + * @var array + */ + public static $svg = array( + 'a' => 1, + 'altGlyph' => 1, + 'altGlyphDef' => 1, + 'altGlyphItem' => 1, + 'animate' => 1, + 'animateColor' => 1, + 'animateMotion' => 1, + 'animateTransform' => 1, + 'circle' => 1, + 'clipPath' => 1, + 'color-profile' => 1, + 'cursor' => 1, + 'defs' => 1, + 'desc' => 1, + 'ellipse' => 1, + 'feBlend' => 1, + 'feColorMatrix' => 1, + 'feComponentTransfer' => 1, + 'feComposite' => 1, + 'feConvolveMatrix' => 1, + 'feDiffuseLighting' => 1, + 'feDisplacementMap' => 1, + 'feDistantLight' => 1, + 'feFlood' => 1, + 'feFuncA' => 1, + 'feFuncB' => 1, + 'feFuncG' => 1, + 'feFuncR' => 1, + 'feGaussianBlur' => 1, + 'feImage' => 1, + 'feMerge' => 1, + 'feMergeNode' => 1, + 'feMorphology' => 1, + 'feOffset' => 1, + 'fePointLight' => 1, + 'feSpecularLighting' => 1, + 'feSpotLight' => 1, + 'feTile' => 1, + 'feTurbulence' => 1, + 'filter' => 1, + 'font' => 1, + 'font-face' => 1, + 'font-face-format' => 1, + 'font-face-name' => 1, + 'font-face-src' => 1, + 'font-face-uri' => 1, + 'foreignObject' => 1, + 'g' => 1, + 'glyph' => 1, + 'glyphRef' => 1, + 'hkern' => 1, + 'image' => 1, + 'line' => 1, + 'linearGradient' => 1, + 'marker' => 1, + 'mask' => 1, + 'metadata' => 1, + 'missing-glyph' => 1, + 'mpath' => 1, + 'path' => 1, + 'pattern' => 1, + 'polygon' => 1, + 'polyline' => 1, + 'radialGradient' => 1, + 'rect' => 1, + 'script' => 3, // NORMAL | RAW_TEXT + 'set' => 1, + 'stop' => 1, + 'style' => 3, // NORMAL | RAW_TEXT + 'svg' => 1, + 'switch' => 1, + 'symbol' => 1, + 'text' => 1, + 'textPath' => 1, + 'title' => 1, + 'tref' => 1, + 'tspan' => 1, + 'use' => 1, + 'view' => 1, + 'vkern' => 1, + ); + + /** + * Some attributes in SVG are case sensitive. + * + * This map contains key/value pairs with the key as the lowercase attribute + * name and the value with the correct casing. + */ + public static $svgCaseSensitiveAttributeMap = array( + 'attributename' => 'attributeName', + 'attributetype' => 'attributeType', + 'basefrequency' => 'baseFrequency', + 'baseprofile' => 'baseProfile', + 'calcmode' => 'calcMode', + 'clippathunits' => 'clipPathUnits', + 'contentscripttype' => 'contentScriptType', + 'contentstyletype' => 'contentStyleType', + 'diffuseconstant' => 'diffuseConstant', + 'edgemode' => 'edgeMode', + 'externalresourcesrequired' => 'externalResourcesRequired', + 'filterres' => 'filterRes', + 'filterunits' => 'filterUnits', + 'glyphref' => 'glyphRef', + 'gradienttransform' => 'gradientTransform', + 'gradientunits' => 'gradientUnits', + 'kernelmatrix' => 'kernelMatrix', + 'kernelunitlength' => 'kernelUnitLength', + 'keypoints' => 'keyPoints', + 'keysplines' => 'keySplines', + 'keytimes' => 'keyTimes', + 'lengthadjust' => 'lengthAdjust', + 'limitingconeangle' => 'limitingConeAngle', + 'markerheight' => 'markerHeight', + 'markerunits' => 'markerUnits', + 'markerwidth' => 'markerWidth', + 'maskcontentunits' => 'maskContentUnits', + 'maskunits' => 'maskUnits', + 'numoctaves' => 'numOctaves', + 'pathlength' => 'pathLength', + 'patterncontentunits' => 'patternContentUnits', + 'patterntransform' => 'patternTransform', + 'patternunits' => 'patternUnits', + 'pointsatx' => 'pointsAtX', + 'pointsaty' => 'pointsAtY', + 'pointsatz' => 'pointsAtZ', + 'preservealpha' => 'preserveAlpha', + 'preserveaspectratio' => 'preserveAspectRatio', + 'primitiveunits' => 'primitiveUnits', + 'refx' => 'refX', + 'refy' => 'refY', + 'repeatcount' => 'repeatCount', + 'repeatdur' => 'repeatDur', + 'requiredextensions' => 'requiredExtensions', + 'requiredfeatures' => 'requiredFeatures', + 'specularconstant' => 'specularConstant', + 'specularexponent' => 'specularExponent', + 'spreadmethod' => 'spreadMethod', + 'startoffset' => 'startOffset', + 'stddeviation' => 'stdDeviation', + 'stitchtiles' => 'stitchTiles', + 'surfacescale' => 'surfaceScale', + 'systemlanguage' => 'systemLanguage', + 'tablevalues' => 'tableValues', + 'targetx' => 'targetX', + 'targety' => 'targetY', + 'textlength' => 'textLength', + 'viewbox' => 'viewBox', + 'viewtarget' => 'viewTarget', + 'xchannelselector' => 'xChannelSelector', + 'ychannelselector' => 'yChannelSelector', + 'zoomandpan' => 'zoomAndPan', + ); + + /** + * Some SVG elements are case sensitive. + * This map contains these. + * + * The map contains key/value store of the name is lowercase as the keys and + * the correct casing as the value. + */ + public static $svgCaseSensitiveElementMap = array( + 'altglyph' => 'altGlyph', + 'altglyphdef' => 'altGlyphDef', + 'altglyphitem' => 'altGlyphItem', + 'animatecolor' => 'animateColor', + 'animatemotion' => 'animateMotion', + 'animatetransform' => 'animateTransform', + 'clippath' => 'clipPath', + 'feblend' => 'feBlend', + 'fecolormatrix' => 'feColorMatrix', + 'fecomponenttransfer' => 'feComponentTransfer', + 'fecomposite' => 'feComposite', + 'feconvolvematrix' => 'feConvolveMatrix', + 'fediffuselighting' => 'feDiffuseLighting', + 'fedisplacementmap' => 'feDisplacementMap', + 'fedistantlight' => 'feDistantLight', + 'feflood' => 'feFlood', + 'fefunca' => 'feFuncA', + 'fefuncb' => 'feFuncB', + 'fefuncg' => 'feFuncG', + 'fefuncr' => 'feFuncR', + 'fegaussianblur' => 'feGaussianBlur', + 'feimage' => 'feImage', + 'femerge' => 'feMerge', + 'femergenode' => 'feMergeNode', + 'femorphology' => 'feMorphology', + 'feoffset' => 'feOffset', + 'fepointlight' => 'fePointLight', + 'fespecularlighting' => 'feSpecularLighting', + 'fespotlight' => 'feSpotLight', + 'fetile' => 'feTile', + 'feturbulence' => 'feTurbulence', + 'foreignobject' => 'foreignObject', + 'glyphref' => 'glyphRef', + 'lineargradient' => 'linearGradient', + 'radialgradient' => 'radialGradient', + 'textpath' => 'textPath', + ); + + /** + * Check whether the given element meets the given criterion. + * + * Example: + * + * Elements::isA('script', Elements::TEXT_RAW); // Returns true. + * + * Elements::isA('script', Elements::TEXT_RCDATA); // Returns false. + * + * @param string $name The element name. + * @param int $mask One of the constants on this class. + * + * @return bool true if the element matches the mask, false otherwise. + */ + public static function isA($name, $mask) + { + return (static::element($name) & $mask) === $mask; + } + + /** + * Test if an element is a valid html5 element. + * + * @param string $name The name of the element. + * + * @return bool true if a html5 element and false otherwise. + */ + public static function isHtml5Element($name) + { + // html5 element names are case insensitive. Forcing lowercase for the check. + // Do we need this check or will all data passed here already be lowercase? + return isset(static::$html5[strtolower($name)]); + } + + /** + * Test if an element name is a valid MathML presentation element. + * + * @param string $name The name of the element. + * + * @return bool true if a MathML name and false otherwise. + */ + public static function isMathMLElement($name) + { + // MathML is case-sensitive unlike html5 elements. + return isset(static::$mathml[$name]); + } + + /** + * Test if an element is a valid SVG element. + * + * @param string $name The name of the element. + * + * @return bool true if a SVG element and false otherise. + */ + public static function isSvgElement($name) + { + // SVG is case-sensitive unlike html5 elements. + return isset(static::$svg[$name]); + } + + /** + * Is an element name valid in an html5 document. + * This includes html5 elements along with other allowed embedded content + * such as svg and mathml. + * + * @param string $name The name of the element. + * + * @return bool true if valid and false otherwise. + */ + public static function isElement($name) + { + return static::isHtml5Element($name) || static::isMathMLElement($name) || static::isSvgElement($name); + } + + /** + * Get the element mask for the given element name. + * + * @param string $name The name of the element. + * + * @return int the element mask. + */ + public static function element($name) + { + if (isset(static::$html5[$name])) { + return static::$html5[$name]; + } + if (isset(static::$svg[$name])) { + return static::$svg[$name]; + } + if (isset(static::$mathml[$name])) { + return static::$mathml[$name]; + } + + return 0; + } + + /** + * Normalize a SVG element name to its proper case and form. + * + * @param string $name The name of the element. + * + * @return string the normalized form of the element name. + */ + public static function normalizeSvgElement($name) + { + $name = strtolower($name); + if (isset(static::$svgCaseSensitiveElementMap[$name])) { + $name = static::$svgCaseSensitiveElementMap[$name]; + } + + return $name; + } + + /** + * Normalize a SVG attribute name to its proper case and form. + * + * @param string $name The name of the attribute. + * + * @return string The normalized form of the attribute name. + */ + public static function normalizeSvgAttribute($name) + { + $name = strtolower($name); + if (isset(static::$svgCaseSensitiveAttributeMap[$name])) { + $name = static::$svgCaseSensitiveAttributeMap[$name]; + } + + return $name; + } + + /** + * Normalize a MathML attribute name to its proper case and form. + * Note, all MathML element names are lowercase. + * + * @param string $name The name of the attribute. + * + * @return string The normalized form of the attribute name. + */ + public static function normalizeMathMlAttribute($name) + { + $name = strtolower($name); + + // Only one attribute has a mixed case form for MathML. + if ('definitionurl' === $name) { + $name = 'definitionURL'; + } + + return $name; + } +} diff --git a/masterminds/html5/src/HTML5/Entities.php b/masterminds/html5/src/HTML5/Entities.php new file mode 100644 index 000000000..0e7227dc1 --- /dev/null +++ b/masterminds/html5/src/HTML5/Entities.php @@ -0,0 +1,2236 @@ + 'Á', + 'Aacut' => 'Á', + 'aacute' => 'á', + 'aacut' => 'á', + 'Abreve' => 'Ă', + 'abreve' => 'ă', + 'ac' => '∾', + 'acd' => '∿', + 'acE' => '∾̳', + 'Acirc' => 'Â', + 'Acir' => 'Â', + 'acirc' => 'â', + 'acir' => 'â', + 'acute' => '´', + 'acut' => '´', + 'Acy' => 'А', + 'acy' => 'а', + 'AElig' => 'Æ', + 'AEli' => 'Æ', + 'aelig' => 'æ', + 'aeli' => 'æ', + 'af' => '⁡', + 'Afr' => '𝔄', + 'afr' => '𝔞', + 'Agrave' => 'À', + 'Agrav' => 'À', + 'agrave' => 'à', + 'agrav' => 'à', + 'alefsym' => 'ℵ', + 'aleph' => 'ℵ', + 'Alpha' => 'Α', + 'alpha' => 'α', + 'Amacr' => 'Ā', + 'amacr' => 'ā', + 'amalg' => '⨿', + 'AMP' => '&', + 'AM' => '&', + 'amp' => '&', + 'am' => '&', + 'And' => '⩓', + 'and' => '∧', + 'andand' => '⩕', + 'andd' => '⩜', + 'andslope' => '⩘', + 'andv' => '⩚', + 'ang' => '∠', + 'ange' => '⦤', + 'angle' => '∠', + 'angmsd' => '∡', + 'angmsdaa' => '⦨', + 'angmsdab' => '⦩', + 'angmsdac' => '⦪', + 'angmsdad' => '⦫', + 'angmsdae' => '⦬', + 'angmsdaf' => '⦭', + 'angmsdag' => '⦮', + 'angmsdah' => '⦯', + 'angrt' => '∟', + 'angrtvb' => '⊾', + 'angrtvbd' => '⦝', + 'angsph' => '∢', + 'angst' => 'Å', + 'angzarr' => '⍼', + 'Aogon' => 'Ą', + 'aogon' => 'ą', + 'Aopf' => '𝔸', + 'aopf' => '𝕒', + 'ap' => '≈', + 'apacir' => '⩯', + 'apE' => '⩰', + 'ape' => '≊', + 'apid' => '≋', + 'apos' => '\'', + 'ApplyFunction' => '⁡', + 'approx' => '≈', + 'approxeq' => '≊', + 'Aring' => 'Å', + 'Arin' => 'Å', + 'aring' => 'å', + 'arin' => 'å', + 'Ascr' => '𝒜', + 'ascr' => '𝒶', + 'Assign' => '≔', + 'ast' => '*', + 'asymp' => '≈', + 'asympeq' => '≍', + 'Atilde' => 'Ã', + 'Atild' => 'Ã', + 'atilde' => 'ã', + 'atild' => 'ã', + 'Auml' => 'Ä', + 'Aum' => 'Ä', + 'auml' => 'ä', + 'aum' => 'ä', + 'awconint' => '∳', + 'awint' => '⨑', + 'backcong' => '≌', + 'backepsilon' => '϶', + 'backprime' => '‵', + 'backsim' => '∽', + 'backsimeq' => '⋍', + 'Backslash' => '∖', + 'Barv' => '⫧', + 'barvee' => '⊽', + 'Barwed' => '⌆', + 'barwed' => '⌅', + 'barwedge' => '⌅', + 'bbrk' => '⎵', + 'bbrktbrk' => '⎶', + 'bcong' => '≌', + 'Bcy' => 'Б', + 'bcy' => 'б', + 'bdquo' => '„', + 'becaus' => '∵', + 'Because' => '∵', + 'because' => '∵', + 'bemptyv' => '⦰', + 'bepsi' => '϶', + 'bernou' => 'ℬ', + 'Bernoullis' => 'ℬ', + 'Beta' => 'Β', + 'beta' => 'β', + 'beth' => 'ℶ', + 'between' => '≬', + 'Bfr' => '𝔅', + 'bfr' => '𝔟', + 'bigcap' => '⋂', + 'bigcirc' => '◯', + 'bigcup' => '⋃', + 'bigodot' => '⨀', + 'bigoplus' => '⨁', + 'bigotimes' => '⨂', + 'bigsqcup' => '⨆', + 'bigstar' => '★', + 'bigtriangledown' => '▽', + 'bigtriangleup' => '△', + 'biguplus' => '⨄', + 'bigvee' => '⋁', + 'bigwedge' => '⋀', + 'bkarow' => '⤍', + 'blacklozenge' => '⧫', + 'blacksquare' => '▪', + 'blacktriangle' => '▴', + 'blacktriangledown' => '▾', + 'blacktriangleleft' => '◂', + 'blacktriangleright' => '▸', + 'blank' => '␣', + 'blk12' => '▒', + 'blk14' => '░', + 'blk34' => '▓', + 'block' => '█', + 'bne' => '=⃥', + 'bnequiv' => '≡⃥', + 'bNot' => '⫭', + 'bnot' => '⌐', + 'Bopf' => '𝔹', + 'bopf' => '𝕓', + 'bot' => '⊥', + 'bottom' => '⊥', + 'bowtie' => '⋈', + 'boxbox' => '⧉', + 'boxDL' => '╗', + 'boxDl' => '╖', + 'boxdL' => '╕', + 'boxdl' => '┐', + 'boxDR' => '╔', + 'boxDr' => '╓', + 'boxdR' => '╒', + 'boxdr' => '┌', + 'boxH' => '═', + 'boxh' => '─', + 'boxHD' => '╦', + 'boxHd' => '╤', + 'boxhD' => '╥', + 'boxhd' => '┬', + 'boxHU' => '╩', + 'boxHu' => '╧', + 'boxhU' => '╨', + 'boxhu' => '┴', + 'boxminus' => '⊟', + 'boxplus' => '⊞', + 'boxtimes' => '⊠', + 'boxUL' => '╝', + 'boxUl' => '╜', + 'boxuL' => '╛', + 'boxul' => '┘', + 'boxUR' => '╚', + 'boxUr' => '╙', + 'boxuR' => '╘', + 'boxur' => '└', + 'boxV' => '║', + 'boxv' => '│', + 'boxVH' => '╬', + 'boxVh' => '╫', + 'boxvH' => '╪', + 'boxvh' => '┼', + 'boxVL' => '╣', + 'boxVl' => '╢', + 'boxvL' => '╡', + 'boxvl' => '┤', + 'boxVR' => '╠', + 'boxVr' => '╟', + 'boxvR' => '╞', + 'boxvr' => '├', + 'bprime' => '‵', + 'Breve' => '˘', + 'breve' => '˘', + 'brvbar' => '¦', + 'brvba' => '¦', + 'Bscr' => 'ℬ', + 'bscr' => '𝒷', + 'bsemi' => '⁏', + 'bsim' => '∽', + 'bsime' => '⋍', + 'bsol' => '\\', + 'bsolb' => '⧅', + 'bsolhsub' => '⟈', + 'bull' => '•', + 'bullet' => '•', + 'bump' => '≎', + 'bumpE' => '⪮', + 'bumpe' => '≏', + 'Bumpeq' => '≎', + 'bumpeq' => '≏', + 'Cacute' => 'Ć', + 'cacute' => 'ć', + 'Cap' => '⋒', + 'cap' => '∩', + 'capand' => '⩄', + 'capbrcup' => '⩉', + 'capcap' => '⩋', + 'capcup' => '⩇', + 'capdot' => '⩀', + 'CapitalDifferentialD' => 'ⅅ', + 'caps' => '∩︀', + 'caret' => '⁁', + 'caron' => 'ˇ', + 'Cayleys' => 'ℭ', + 'ccaps' => '⩍', + 'Ccaron' => 'Č', + 'ccaron' => 'č', + 'Ccedil' => 'Ç', + 'Ccedi' => 'Ç', + 'ccedil' => 'ç', + 'ccedi' => 'ç', + 'Ccirc' => 'Ĉ', + 'ccirc' => 'ĉ', + 'Cconint' => '∰', + 'ccups' => '⩌', + 'ccupssm' => '⩐', + 'Cdot' => 'Ċ', + 'cdot' => 'ċ', + 'cedil' => '¸', + 'cedi' => '¸', + 'Cedilla' => '¸', + 'cemptyv' => '⦲', + 'cent' => '¢', + 'cen' => '¢', + 'CenterDot' => '·', + 'centerdot' => '·', + 'Cfr' => 'ℭ', + 'cfr' => '𝔠', + 'CHcy' => 'Ч', + 'chcy' => 'ч', + 'check' => '✓', + 'checkmark' => '✓', + 'Chi' => 'Χ', + 'chi' => 'χ', + 'cir' => '○', + 'circ' => 'ˆ', + 'circeq' => '≗', + 'circlearrowleft' => '↺', + 'circlearrowright' => '↻', + 'circledast' => '⊛', + 'circledcirc' => '⊚', + 'circleddash' => '⊝', + 'CircleDot' => '⊙', + 'circledR' => '®', + 'circledS' => 'Ⓢ', + 'CircleMinus' => '⊖', + 'CirclePlus' => '⊕', + 'CircleTimes' => '⊗', + 'cirE' => '⧃', + 'cire' => '≗', + 'cirfnint' => '⨐', + 'cirmid' => '⫯', + 'cirscir' => '⧂', + 'ClockwiseContourIntegral' => '∲', + 'CloseCurlyDoubleQuote' => '”', + 'CloseCurlyQuote' => '’', + 'clubs' => '♣', + 'clubsuit' => '♣', + 'Colon' => '∷', + 'colon' => ':', + 'Colone' => '⩴', + 'colone' => '≔', + 'coloneq' => '≔', + 'comma' => ',', + 'commat' => '@', + 'comp' => '∁', + 'compfn' => '∘', + 'complement' => '∁', + 'complexes' => 'ℂ', + 'cong' => '≅', + 'congdot' => '⩭', + 'Congruent' => '≡', + 'Conint' => '∯', + 'conint' => '∮', + 'ContourIntegral' => '∮', + 'Copf' => 'ℂ', + 'copf' => '𝕔', + 'coprod' => '∐', + 'Coproduct' => '∐', + 'COPY' => '©', + 'COP' => '©', + 'copy' => '©', + 'cop' => '©', + 'copysr' => '℗', + 'CounterClockwiseContourIntegral' => '∳', + 'crarr' => '↵', + 'Cross' => '⨯', + 'cross' => '✗', + 'Cscr' => '𝒞', + 'cscr' => '𝒸', + 'csub' => '⫏', + 'csube' => '⫑', + 'csup' => '⫐', + 'csupe' => '⫒', + 'ctdot' => '⋯', + 'cudarrl' => '⤸', + 'cudarrr' => '⤵', + 'cuepr' => '⋞', + 'cuesc' => '⋟', + 'cularr' => '↶', + 'cularrp' => '⤽', + 'Cup' => '⋓', + 'cup' => '∪', + 'cupbrcap' => '⩈', + 'CupCap' => '≍', + 'cupcap' => '⩆', + 'cupcup' => '⩊', + 'cupdot' => '⊍', + 'cupor' => '⩅', + 'cups' => '∪︀', + 'curarr' => '↷', + 'curarrm' => '⤼', + 'curlyeqprec' => '⋞', + 'curlyeqsucc' => '⋟', + 'curlyvee' => '⋎', + 'curlywedge' => '⋏', + 'curren' => '¤', + 'curre' => '¤', + 'curvearrowleft' => '↶', + 'curvearrowright' => '↷', + 'cuvee' => '⋎', + 'cuwed' => '⋏', + 'cwconint' => '∲', + 'cwint' => '∱', + 'cylcty' => '⌭', + 'Dagger' => '‡', + 'dagger' => '†', + 'daleth' => 'ℸ', + 'Darr' => '↡', + 'dArr' => '⇓', + 'darr' => '↓', + 'dash' => '‐', + 'Dashv' => '⫤', + 'dashv' => '⊣', + 'dbkarow' => '⤏', + 'dblac' => '˝', + 'Dcaron' => 'Ď', + 'dcaron' => 'ď', + 'Dcy' => 'Д', + 'dcy' => 'д', + 'DD' => 'ⅅ', + 'dd' => 'ⅆ', + 'ddagger' => '‡', + 'ddarr' => '⇊', + 'DDotrahd' => '⤑', + 'ddotseq' => '⩷', + 'deg' => '°', + 'de' => '°', + 'Del' => '∇', + 'Delta' => 'Δ', + 'delta' => 'δ', + 'demptyv' => '⦱', + 'dfisht' => '⥿', + 'Dfr' => '𝔇', + 'dfr' => '𝔡', + 'dHar' => '⥥', + 'dharl' => '⇃', + 'dharr' => '⇂', + 'DiacriticalAcute' => '´', + 'DiacriticalDot' => '˙', + 'DiacriticalDoubleAcute' => '˝', + 'DiacriticalGrave' => '`', + 'DiacriticalTilde' => '˜', + 'diam' => '⋄', + 'Diamond' => '⋄', + 'diamond' => '⋄', + 'diamondsuit' => '♦', + 'diams' => '♦', + 'die' => '¨', + 'DifferentialD' => 'ⅆ', + 'digamma' => 'ϝ', + 'disin' => '⋲', + 'div' => '÷', + 'divide' => '÷', + 'divid' => '÷', + 'divideontimes' => '⋇', + 'divonx' => '⋇', + 'DJcy' => 'Ђ', + 'djcy' => 'ђ', + 'dlcorn' => '⌞', + 'dlcrop' => '⌍', + 'dollar' => '$', + 'Dopf' => '𝔻', + 'dopf' => '𝕕', + 'Dot' => '¨', + 'dot' => '˙', + 'DotDot' => '⃜', + 'doteq' => '≐', + 'doteqdot' => '≑', + 'DotEqual' => '≐', + 'dotminus' => '∸', + 'dotplus' => '∔', + 'dotsquare' => '⊡', + 'doublebarwedge' => '⌆', + 'DoubleContourIntegral' => '∯', + 'DoubleDot' => '¨', + 'DoubleDownArrow' => '⇓', + 'DoubleLeftArrow' => '⇐', + 'DoubleLeftRightArrow' => '⇔', + 'DoubleLeftTee' => '⫤', + 'DoubleLongLeftArrow' => '⟸', + 'DoubleLongLeftRightArrow' => '⟺', + 'DoubleLongRightArrow' => '⟹', + 'DoubleRightArrow' => '⇒', + 'DoubleRightTee' => '⊨', + 'DoubleUpArrow' => '⇑', + 'DoubleUpDownArrow' => '⇕', + 'DoubleVerticalBar' => '∥', + 'DownArrow' => '↓', + 'Downarrow' => '⇓', + 'downarrow' => '↓', + 'DownArrowBar' => '⤓', + 'DownArrowUpArrow' => '⇵', + 'DownBreve' => '̑', + 'downdownarrows' => '⇊', + 'downharpoonleft' => '⇃', + 'downharpoonright' => '⇂', + 'DownLeftRightVector' => '⥐', + 'DownLeftTeeVector' => '⥞', + 'DownLeftVector' => '↽', + 'DownLeftVectorBar' => '⥖', + 'DownRightTeeVector' => '⥟', + 'DownRightVector' => '⇁', + 'DownRightVectorBar' => '⥗', + 'DownTee' => '⊤', + 'DownTeeArrow' => '↧', + 'drbkarow' => '⤐', + 'drcorn' => '⌟', + 'drcrop' => '⌌', + 'Dscr' => '𝒟', + 'dscr' => '𝒹', + 'DScy' => 'Ѕ', + 'dscy' => 'ѕ', + 'dsol' => '⧶', + 'Dstrok' => 'Đ', + 'dstrok' => 'đ', + 'dtdot' => '⋱', + 'dtri' => '▿', + 'dtrif' => '▾', + 'duarr' => '⇵', + 'duhar' => '⥯', + 'dwangle' => '⦦', + 'DZcy' => 'Џ', + 'dzcy' => 'џ', + 'dzigrarr' => '⟿', + 'Eacute' => 'É', + 'Eacut' => 'É', + 'eacute' => 'é', + 'eacut' => 'é', + 'easter' => '⩮', + 'Ecaron' => 'Ě', + 'ecaron' => 'ě', + 'ecir' => 'ê', + 'Ecirc' => 'Ê', + 'Ecir' => 'Ê', + 'ecirc' => 'ê', + 'ecolon' => '≕', + 'Ecy' => 'Э', + 'ecy' => 'э', + 'eDDot' => '⩷', + 'Edot' => 'Ė', + 'eDot' => '≑', + 'edot' => 'ė', + 'ee' => 'ⅇ', + 'efDot' => '≒', + 'Efr' => '𝔈', + 'efr' => '𝔢', + 'eg' => '⪚', + 'Egrave' => 'È', + 'Egrav' => 'È', + 'egrave' => 'è', + 'egrav' => 'è', + 'egs' => '⪖', + 'egsdot' => '⪘', + 'el' => '⪙', + 'Element' => '∈', + 'elinters' => '⏧', + 'ell' => 'ℓ', + 'els' => '⪕', + 'elsdot' => '⪗', + 'Emacr' => 'Ē', + 'emacr' => 'ē', + 'empty' => '∅', + 'emptyset' => '∅', + 'EmptySmallSquare' => '◻', + 'emptyv' => '∅', + 'EmptyVerySmallSquare' => '▫', + 'emsp' => ' ', + 'emsp13' => ' ', + 'emsp14' => ' ', + 'ENG' => 'Ŋ', + 'eng' => 'ŋ', + 'ensp' => ' ', + 'Eogon' => 'Ę', + 'eogon' => 'ę', + 'Eopf' => '𝔼', + 'eopf' => '𝕖', + 'epar' => '⋕', + 'eparsl' => '⧣', + 'eplus' => '⩱', + 'epsi' => 'ε', + 'Epsilon' => 'Ε', + 'epsilon' => 'ε', + 'epsiv' => 'ϵ', + 'eqcirc' => '≖', + 'eqcolon' => '≕', + 'eqsim' => '≂', + 'eqslantgtr' => '⪖', + 'eqslantless' => '⪕', + 'Equal' => '⩵', + 'equals' => '=', + 'EqualTilde' => '≂', + 'equest' => '≟', + 'Equilibrium' => '⇌', + 'equiv' => '≡', + 'equivDD' => '⩸', + 'eqvparsl' => '⧥', + 'erarr' => '⥱', + 'erDot' => '≓', + 'Escr' => 'ℰ', + 'escr' => 'ℯ', + 'esdot' => '≐', + 'Esim' => '⩳', + 'esim' => '≂', + 'Eta' => 'Η', + 'eta' => 'η', + 'ETH' => 'Ð', + 'ET' => 'Ð', + 'eth' => 'ð', + 'et' => 'ð', + 'Euml' => 'Ë', + 'Eum' => 'Ë', + 'euml' => 'ë', + 'eum' => 'ë', + 'euro' => '€', + 'excl' => '!', + 'exist' => '∃', + 'Exists' => '∃', + 'expectation' => 'ℰ', + 'ExponentialE' => 'ⅇ', + 'exponentiale' => 'ⅇ', + 'fallingdotseq' => '≒', + 'Fcy' => 'Ф', + 'fcy' => 'ф', + 'female' => '♀', + 'ffilig' => 'ffi', + 'fflig' => 'ff', + 'ffllig' => 'ffl', + 'Ffr' => '𝔉', + 'ffr' => '𝔣', + 'filig' => 'fi', + 'FilledSmallSquare' => '◼', + 'FilledVerySmallSquare' => '▪', + 'fjlig' => 'fj', + 'flat' => '♭', + 'fllig' => 'fl', + 'fltns' => '▱', + 'fnof' => 'ƒ', + 'Fopf' => '𝔽', + 'fopf' => '𝕗', + 'ForAll' => '∀', + 'forall' => '∀', + 'fork' => '⋔', + 'forkv' => '⫙', + 'Fouriertrf' => 'ℱ', + 'fpartint' => '⨍', + 'frac12' => '½', + 'frac1' => '¼', + 'frac13' => '⅓', + 'frac14' => '¼', + 'frac15' => '⅕', + 'frac16' => '⅙', + 'frac18' => '⅛', + 'frac23' => '⅔', + 'frac25' => '⅖', + 'frac34' => '¾', + 'frac3' => '¾', + 'frac35' => '⅗', + 'frac38' => '⅜', + 'frac45' => '⅘', + 'frac56' => '⅚', + 'frac58' => '⅝', + 'frac78' => '⅞', + 'frasl' => '⁄', + 'frown' => '⌢', + 'Fscr' => 'ℱ', + 'fscr' => '𝒻', + 'gacute' => 'ǵ', + 'Gamma' => 'Γ', + 'gamma' => 'γ', + 'Gammad' => 'Ϝ', + 'gammad' => 'ϝ', + 'gap' => '⪆', + 'Gbreve' => 'Ğ', + 'gbreve' => 'ğ', + 'Gcedil' => 'Ģ', + 'Gcirc' => 'Ĝ', + 'gcirc' => 'ĝ', + 'Gcy' => 'Г', + 'gcy' => 'г', + 'Gdot' => 'Ġ', + 'gdot' => 'ġ', + 'gE' => '≧', + 'ge' => '≥', + 'gEl' => '⪌', + 'gel' => '⋛', + 'geq' => '≥', + 'geqq' => '≧', + 'geqslant' => '⩾', + 'ges' => '⩾', + 'gescc' => '⪩', + 'gesdot' => '⪀', + 'gesdoto' => '⪂', + 'gesdotol' => '⪄', + 'gesl' => '⋛︀', + 'gesles' => '⪔', + 'Gfr' => '𝔊', + 'gfr' => '𝔤', + 'Gg' => '⋙', + 'gg' => '≫', + 'ggg' => '⋙', + 'gimel' => 'ℷ', + 'GJcy' => 'Ѓ', + 'gjcy' => 'ѓ', + 'gl' => '≷', + 'gla' => '⪥', + 'glE' => '⪒', + 'glj' => '⪤', + 'gnap' => '⪊', + 'gnapprox' => '⪊', + 'gnE' => '≩', + 'gne' => '⪈', + 'gneq' => '⪈', + 'gneqq' => '≩', + 'gnsim' => '⋧', + 'Gopf' => '𝔾', + 'gopf' => '𝕘', + 'grave' => '`', + 'GreaterEqual' => '≥', + 'GreaterEqualLess' => '⋛', + 'GreaterFullEqual' => '≧', + 'GreaterGreater' => '⪢', + 'GreaterLess' => '≷', + 'GreaterSlantEqual' => '⩾', + 'GreaterTilde' => '≳', + 'Gscr' => '𝒢', + 'gscr' => 'ℊ', + 'gsim' => '≳', + 'gsime' => '⪎', + 'gsiml' => '⪐', + 'GT' => '>', + 'G' => '>', + 'Gt' => '≫', + 'gt' => '>', + 'g' => '>', + 'gtcc' => '⪧', + 'gtcir' => '⩺', + 'gtdot' => '⋗', + 'gtlPar' => '⦕', + 'gtquest' => '⩼', + 'gtrapprox' => '⪆', + 'gtrarr' => '⥸', + 'gtrdot' => '⋗', + 'gtreqless' => '⋛', + 'gtreqqless' => '⪌', + 'gtrless' => '≷', + 'gtrsim' => '≳', + 'gvertneqq' => '≩︀', + 'gvnE' => '≩︀', + 'Hacek' => 'ˇ', + 'hairsp' => ' ', + 'half' => '½', + 'hamilt' => 'ℋ', + 'HARDcy' => 'Ъ', + 'hardcy' => 'ъ', + 'hArr' => '⇔', + 'harr' => '↔', + 'harrcir' => '⥈', + 'harrw' => '↭', + 'Hat' => '^', + 'hbar' => 'ℏ', + 'Hcirc' => 'Ĥ', + 'hcirc' => 'ĥ', + 'hearts' => '♥', + 'heartsuit' => '♥', + 'hellip' => '…', + 'hercon' => '⊹', + 'Hfr' => 'ℌ', + 'hfr' => '𝔥', + 'HilbertSpace' => 'ℋ', + 'hksearow' => '⤥', + 'hkswarow' => '⤦', + 'hoarr' => '⇿', + 'homtht' => '∻', + 'hookleftarrow' => '↩', + 'hookrightarrow' => '↪', + 'Hopf' => 'ℍ', + 'hopf' => '𝕙', + 'horbar' => '―', + 'HorizontalLine' => '─', + 'Hscr' => 'ℋ', + 'hscr' => '𝒽', + 'hslash' => 'ℏ', + 'Hstrok' => 'Ħ', + 'hstrok' => 'ħ', + 'HumpDownHump' => '≎', + 'HumpEqual' => '≏', + 'hybull' => '⁃', + 'hyphen' => '‐', + 'Iacute' => 'Í', + 'Iacut' => 'Í', + 'iacute' => 'í', + 'iacut' => 'í', + 'ic' => '⁣', + 'Icirc' => 'Î', + 'Icir' => 'Î', + 'icirc' => 'î', + 'icir' => 'î', + 'Icy' => 'И', + 'icy' => 'и', + 'Idot' => 'İ', + 'IEcy' => 'Е', + 'iecy' => 'е', + 'iexcl' => '¡', + 'iexc' => '¡', + 'iff' => '⇔', + 'Ifr' => 'ℑ', + 'ifr' => '𝔦', + 'Igrave' => 'Ì', + 'Igrav' => 'Ì', + 'igrave' => 'ì', + 'igrav' => 'ì', + 'ii' => 'ⅈ', + 'iiiint' => '⨌', + 'iiint' => '∭', + 'iinfin' => '⧜', + 'iiota' => '℩', + 'IJlig' => 'IJ', + 'ijlig' => 'ij', + 'Im' => 'ℑ', + 'Imacr' => 'Ī', + 'imacr' => 'ī', + 'image' => 'ℑ', + 'ImaginaryI' => 'ⅈ', + 'imagline' => 'ℐ', + 'imagpart' => 'ℑ', + 'imath' => 'ı', + 'imof' => '⊷', + 'imped' => 'Ƶ', + 'Implies' => '⇒', + 'in' => '∈', + 'incare' => '℅', + 'infin' => '∞', + 'infintie' => '⧝', + 'inodot' => 'ı', + 'Int' => '∬', + 'int' => '∫', + 'intcal' => '⊺', + 'integers' => 'ℤ', + 'Integral' => '∫', + 'intercal' => '⊺', + 'Intersection' => '⋂', + 'intlarhk' => '⨗', + 'intprod' => '⨼', + 'InvisibleComma' => '⁣', + 'InvisibleTimes' => '⁢', + 'IOcy' => 'Ё', + 'iocy' => 'ё', + 'Iogon' => 'Į', + 'iogon' => 'į', + 'Iopf' => '𝕀', + 'iopf' => '𝕚', + 'Iota' => 'Ι', + 'iota' => 'ι', + 'iprod' => '⨼', + 'iquest' => '¿', + 'iques' => '¿', + 'Iscr' => 'ℐ', + 'iscr' => '𝒾', + 'isin' => '∈', + 'isindot' => '⋵', + 'isinE' => '⋹', + 'isins' => '⋴', + 'isinsv' => '⋳', + 'isinv' => '∈', + 'it' => '⁢', + 'Itilde' => 'Ĩ', + 'itilde' => 'ĩ', + 'Iukcy' => 'І', + 'iukcy' => 'і', + 'Iuml' => 'Ï', + 'Ium' => 'Ï', + 'iuml' => 'ï', + 'ium' => 'ï', + 'Jcirc' => 'Ĵ', + 'jcirc' => 'ĵ', + 'Jcy' => 'Й', + 'jcy' => 'й', + 'Jfr' => '𝔍', + 'jfr' => '𝔧', + 'jmath' => 'ȷ', + 'Jopf' => '𝕁', + 'jopf' => '𝕛', + 'Jscr' => '𝒥', + 'jscr' => '𝒿', + 'Jsercy' => 'Ј', + 'jsercy' => 'ј', + 'Jukcy' => 'Є', + 'jukcy' => 'є', + 'Kappa' => 'Κ', + 'kappa' => 'κ', + 'kappav' => 'ϰ', + 'Kcedil' => 'Ķ', + 'kcedil' => 'ķ', + 'Kcy' => 'К', + 'kcy' => 'к', + 'Kfr' => '𝔎', + 'kfr' => '𝔨', + 'kgreen' => 'ĸ', + 'KHcy' => 'Х', + 'khcy' => 'х', + 'KJcy' => 'Ќ', + 'kjcy' => 'ќ', + 'Kopf' => '𝕂', + 'kopf' => '𝕜', + 'Kscr' => '𝒦', + 'kscr' => '𝓀', + 'lAarr' => '⇚', + 'Lacute' => 'Ĺ', + 'lacute' => 'ĺ', + 'laemptyv' => '⦴', + 'lagran' => 'ℒ', + 'Lambda' => 'Λ', + 'lambda' => 'λ', + 'Lang' => '⟪', + 'lang' => '⟨', + 'langd' => '⦑', + 'langle' => '⟨', + 'lap' => '⪅', + 'Laplacetrf' => 'ℒ', + 'laquo' => '«', + 'laqu' => '«', + 'Larr' => '↞', + 'lArr' => '⇐', + 'larr' => '←', + 'larrb' => '⇤', + 'larrbfs' => '⤟', + 'larrfs' => '⤝', + 'larrhk' => '↩', + 'larrlp' => '↫', + 'larrpl' => '⤹', + 'larrsim' => '⥳', + 'larrtl' => '↢', + 'lat' => '⪫', + 'lAtail' => '⤛', + 'latail' => '⤙', + 'late' => '⪭', + 'lates' => '⪭︀', + 'lBarr' => '⤎', + 'lbarr' => '⤌', + 'lbbrk' => '❲', + 'lbrace' => '{', + 'lbrack' => '[', + 'lbrke' => '⦋', + 'lbrksld' => '⦏', + 'lbrkslu' => '⦍', + 'Lcaron' => 'Ľ', + 'lcaron' => 'ľ', + 'Lcedil' => 'Ļ', + 'lcedil' => 'ļ', + 'lceil' => '⌈', + 'lcub' => '{', + 'Lcy' => 'Л', + 'lcy' => 'л', + 'ldca' => '⤶', + 'ldquo' => '“', + 'ldquor' => '„', + 'ldrdhar' => '⥧', + 'ldrushar' => '⥋', + 'ldsh' => '↲', + 'lE' => '≦', + 'le' => '≤', + 'LeftAngleBracket' => '⟨', + 'LeftArrow' => '←', + 'Leftarrow' => '⇐', + 'leftarrow' => '←', + 'LeftArrowBar' => '⇤', + 'LeftArrowRightArrow' => '⇆', + 'leftarrowtail' => '↢', + 'LeftCeiling' => '⌈', + 'LeftDoubleBracket' => '⟦', + 'LeftDownTeeVector' => '⥡', + 'LeftDownVector' => '⇃', + 'LeftDownVectorBar' => '⥙', + 'LeftFloor' => '⌊', + 'leftharpoondown' => '↽', + 'leftharpoonup' => '↼', + 'leftleftarrows' => '⇇', + 'LeftRightArrow' => '↔', + 'Leftrightarrow' => '⇔', + 'leftrightarrow' => '↔', + 'leftrightarrows' => '⇆', + 'leftrightharpoons' => '⇋', + 'leftrightsquigarrow' => '↭', + 'LeftRightVector' => '⥎', + 'LeftTee' => '⊣', + 'LeftTeeArrow' => '↤', + 'LeftTeeVector' => '⥚', + 'leftthreetimes' => '⋋', + 'LeftTriangle' => '⊲', + 'LeftTriangleBar' => '⧏', + 'LeftTriangleEqual' => '⊴', + 'LeftUpDownVector' => '⥑', + 'LeftUpTeeVector' => '⥠', + 'LeftUpVector' => '↿', + 'LeftUpVectorBar' => '⥘', + 'LeftVector' => '↼', + 'LeftVectorBar' => '⥒', + 'lEg' => '⪋', + 'leg' => '⋚', + 'leq' => '≤', + 'leqq' => '≦', + 'leqslant' => '⩽', + 'les' => '⩽', + 'lescc' => '⪨', + 'lesdot' => '⩿', + 'lesdoto' => '⪁', + 'lesdotor' => '⪃', + 'lesg' => '⋚︀', + 'lesges' => '⪓', + 'lessapprox' => '⪅', + 'lessdot' => '⋖', + 'lesseqgtr' => '⋚', + 'lesseqqgtr' => '⪋', + 'LessEqualGreater' => '⋚', + 'LessFullEqual' => '≦', + 'LessGreater' => '≶', + 'lessgtr' => '≶', + 'LessLess' => '⪡', + 'lesssim' => '≲', + 'LessSlantEqual' => '⩽', + 'LessTilde' => '≲', + 'lfisht' => '⥼', + 'lfloor' => '⌊', + 'Lfr' => '𝔏', + 'lfr' => '𝔩', + 'lg' => '≶', + 'lgE' => '⪑', + 'lHar' => '⥢', + 'lhard' => '↽', + 'lharu' => '↼', + 'lharul' => '⥪', + 'lhblk' => '▄', + 'LJcy' => 'Љ', + 'ljcy' => 'љ', + 'Ll' => '⋘', + 'll' => '≪', + 'llarr' => '⇇', + 'llcorner' => '⌞', + 'Lleftarrow' => '⇚', + 'llhard' => '⥫', + 'lltri' => '◺', + 'Lmidot' => 'Ŀ', + 'lmidot' => 'ŀ', + 'lmoust' => '⎰', + 'lmoustache' => '⎰', + 'lnap' => '⪉', + 'lnapprox' => '⪉', + 'lnE' => '≨', + 'lne' => '⪇', + 'lneq' => '⪇', + 'lneqq' => '≨', + 'lnsim' => '⋦', + 'loang' => '⟬', + 'loarr' => '⇽', + 'lobrk' => '⟦', + 'LongLeftArrow' => '⟵', + 'Longleftarrow' => '⟸', + 'longleftarrow' => '⟵', + 'LongLeftRightArrow' => '⟷', + 'Longleftrightarrow' => '⟺', + 'longleftrightarrow' => '⟷', + 'longmapsto' => '⟼', + 'LongRightArrow' => '⟶', + 'Longrightarrow' => '⟹', + 'longrightarrow' => '⟶', + 'looparrowleft' => '↫', + 'looparrowright' => '↬', + 'lopar' => '⦅', + 'Lopf' => '𝕃', + 'lopf' => '𝕝', + 'loplus' => '⨭', + 'lotimes' => '⨴', + 'lowast' => '∗', + 'lowbar' => '_', + 'LowerLeftArrow' => '↙', + 'LowerRightArrow' => '↘', + 'loz' => '◊', + 'lozenge' => '◊', + 'lozf' => '⧫', + 'lpar' => '(', + 'lparlt' => '⦓', + 'lrarr' => '⇆', + 'lrcorner' => '⌟', + 'lrhar' => '⇋', + 'lrhard' => '⥭', + 'lrm' => '‎', + 'lrtri' => '⊿', + 'lsaquo' => '‹', + 'Lscr' => 'ℒ', + 'lscr' => '𝓁', + 'Lsh' => '↰', + 'lsh' => '↰', + 'lsim' => '≲', + 'lsime' => '⪍', + 'lsimg' => '⪏', + 'lsqb' => '[', + 'lsquo' => '‘', + 'lsquor' => '‚', + 'Lstrok' => 'Ł', + 'lstrok' => 'ł', + 'LT' => '<', + 'L' => '<', + 'Lt' => '≪', + 'lt' => '<', + 'l' => '<', + 'ltcc' => '⪦', + 'ltcir' => '⩹', + 'ltdot' => '⋖', + 'lthree' => '⋋', + 'ltimes' => '⋉', + 'ltlarr' => '⥶', + 'ltquest' => '⩻', + 'ltri' => '◃', + 'ltrie' => '⊴', + 'ltrif' => '◂', + 'ltrPar' => '⦖', + 'lurdshar' => '⥊', + 'luruhar' => '⥦', + 'lvertneqq' => '≨︀', + 'lvnE' => '≨︀', + 'macr' => '¯', + 'mac' => '¯', + 'male' => '♂', + 'malt' => '✠', + 'maltese' => '✠', + 'Map' => '⤅', + 'map' => '↦', + 'mapsto' => '↦', + 'mapstodown' => '↧', + 'mapstoleft' => '↤', + 'mapstoup' => '↥', + 'marker' => '▮', + 'mcomma' => '⨩', + 'Mcy' => 'М', + 'mcy' => 'м', + 'mdash' => '—', + 'mDDot' => '∺', + 'measuredangle' => '∡', + 'MediumSpace' => ' ', + 'Mellintrf' => 'ℳ', + 'Mfr' => '𝔐', + 'mfr' => '𝔪', + 'mho' => '℧', + 'micro' => 'µ', + 'micr' => 'µ', + 'mid' => '∣', + 'midast' => '*', + 'midcir' => '⫰', + 'middot' => '·', + 'middo' => '·', + 'minus' => '−', + 'minusb' => '⊟', + 'minusd' => '∸', + 'minusdu' => '⨪', + 'MinusPlus' => '∓', + 'mlcp' => '⫛', + 'mldr' => '…', + 'mnplus' => '∓', + 'models' => '⊧', + 'Mopf' => '𝕄', + 'mopf' => '𝕞', + 'mp' => '∓', + 'Mscr' => 'ℳ', + 'mscr' => '𝓂', + 'mstpos' => '∾', + 'Mu' => 'Μ', + 'mu' => 'μ', + 'multimap' => '⊸', + 'mumap' => '⊸', + 'nabla' => '∇', + 'Nacute' => 'Ń', + 'nacute' => 'ń', + 'nang' => '∠⃒', + 'nap' => '≉', + 'napE' => '⩰̸', + 'napid' => '≋̸', + 'napos' => 'ʼn', + 'napprox' => '≉', + 'natur' => '♮', + 'natural' => '♮', + 'naturals' => 'ℕ', + 'nbsp' => ' ', + 'nbs' => ' ', + 'nbump' => '≎̸', + 'nbumpe' => '≏̸', + 'ncap' => '⩃', + 'Ncaron' => 'Ň', + 'ncaron' => 'ň', + 'Ncedil' => 'Ņ', + 'ncedil' => 'ņ', + 'ncong' => '≇', + 'ncongdot' => '⩭̸', + 'ncup' => '⩂', + 'Ncy' => 'Н', + 'ncy' => 'н', + 'ndash' => '–', + 'ne' => '≠', + 'nearhk' => '⤤', + 'neArr' => '⇗', + 'nearr' => '↗', + 'nearrow' => '↗', + 'nedot' => '≐̸', + 'NegativeMediumSpace' => '​', + 'NegativeThickSpace' => '​', + 'NegativeThinSpace' => '​', + 'NegativeVeryThinSpace' => '​', + 'nequiv' => '≢', + 'nesear' => '⤨', + 'nesim' => '≂̸', + 'NestedGreaterGreater' => '≫', + 'NestedLessLess' => '≪', + 'NewLine' => ' +', + 'nexist' => '∄', + 'nexists' => '∄', + 'Nfr' => '𝔑', + 'nfr' => '𝔫', + 'ngE' => '≧̸', + 'nge' => '≱', + 'ngeq' => '≱', + 'ngeqq' => '≧̸', + 'ngeqslant' => '⩾̸', + 'nges' => '⩾̸', + 'nGg' => '⋙̸', + 'ngsim' => '≵', + 'nGt' => '≫⃒', + 'ngt' => '≯', + 'ngtr' => '≯', + 'nGtv' => '≫̸', + 'nhArr' => '⇎', + 'nharr' => '↮', + 'nhpar' => '⫲', + 'ni' => '∋', + 'nis' => '⋼', + 'nisd' => '⋺', + 'niv' => '∋', + 'NJcy' => 'Њ', + 'njcy' => 'њ', + 'nlArr' => '⇍', + 'nlarr' => '↚', + 'nldr' => '‥', + 'nlE' => '≦̸', + 'nle' => '≰', + 'nLeftarrow' => '⇍', + 'nleftarrow' => '↚', + 'nLeftrightarrow' => '⇎', + 'nleftrightarrow' => '↮', + 'nleq' => '≰', + 'nleqq' => '≦̸', + 'nleqslant' => '⩽̸', + 'nles' => '⩽̸', + 'nless' => '≮', + 'nLl' => '⋘̸', + 'nlsim' => '≴', + 'nLt' => '≪⃒', + 'nlt' => '≮', + 'nltri' => '⋪', + 'nltrie' => '⋬', + 'nLtv' => '≪̸', + 'nmid' => '∤', + 'NoBreak' => '⁠', + 'NonBreakingSpace' => ' ', + 'Nopf' => 'ℕ', + 'nopf' => '𝕟', + 'Not' => '⫬', + 'not' => '¬', + 'no' => '¬', + 'NotCongruent' => '≢', + 'NotCupCap' => '≭', + 'NotDoubleVerticalBar' => '∦', + 'NotElement' => '∉', + 'NotEqual' => '≠', + 'NotEqualTilde' => '≂̸', + 'NotExists' => '∄', + 'NotGreater' => '≯', + 'NotGreaterEqual' => '≱', + 'NotGreaterFullEqual' => '≧̸', + 'NotGreaterGreater' => '≫̸', + 'NotGreaterLess' => '≹', + 'NotGreaterSlantEqual' => '⩾̸', + 'NotGreaterTilde' => '≵', + 'NotHumpDownHump' => '≎̸', + 'NotHumpEqual' => '≏̸', + 'notin' => '∉', + 'notindot' => '⋵̸', + 'notinE' => '⋹̸', + 'notinva' => '∉', + 'notinvb' => '⋷', + 'notinvc' => '⋶', + 'NotLeftTriangle' => '⋪', + 'NotLeftTriangleBar' => '⧏̸', + 'NotLeftTriangleEqual' => '⋬', + 'NotLess' => '≮', + 'NotLessEqual' => '≰', + 'NotLessGreater' => '≸', + 'NotLessLess' => '≪̸', + 'NotLessSlantEqual' => '⩽̸', + 'NotLessTilde' => '≴', + 'NotNestedGreaterGreater' => '⪢̸', + 'NotNestedLessLess' => '⪡̸', + 'notni' => '∌', + 'notniva' => '∌', + 'notnivb' => '⋾', + 'notnivc' => '⋽', + 'NotPrecedes' => '⊀', + 'NotPrecedesEqual' => '⪯̸', + 'NotPrecedesSlantEqual' => '⋠', + 'NotReverseElement' => '∌', + 'NotRightTriangle' => '⋫', + 'NotRightTriangleBar' => '⧐̸', + 'NotRightTriangleEqual' => '⋭', + 'NotSquareSubset' => '⊏̸', + 'NotSquareSubsetEqual' => '⋢', + 'NotSquareSuperset' => '⊐̸', + 'NotSquareSupersetEqual' => '⋣', + 'NotSubset' => '⊂⃒', + 'NotSubsetEqual' => '⊈', + 'NotSucceeds' => '⊁', + 'NotSucceedsEqual' => '⪰̸', + 'NotSucceedsSlantEqual' => '⋡', + 'NotSucceedsTilde' => '≿̸', + 'NotSuperset' => '⊃⃒', + 'NotSupersetEqual' => '⊉', + 'NotTilde' => '≁', + 'NotTildeEqual' => '≄', + 'NotTildeFullEqual' => '≇', + 'NotTildeTilde' => '≉', + 'NotVerticalBar' => '∤', + 'npar' => '∦', + 'nparallel' => '∦', + 'nparsl' => '⫽⃥', + 'npart' => '∂̸', + 'npolint' => '⨔', + 'npr' => '⊀', + 'nprcue' => '⋠', + 'npre' => '⪯̸', + 'nprec' => '⊀', + 'npreceq' => '⪯̸', + 'nrArr' => '⇏', + 'nrarr' => '↛', + 'nrarrc' => '⤳̸', + 'nrarrw' => '↝̸', + 'nRightarrow' => '⇏', + 'nrightarrow' => '↛', + 'nrtri' => '⋫', + 'nrtrie' => '⋭', + 'nsc' => '⊁', + 'nsccue' => '⋡', + 'nsce' => '⪰̸', + 'Nscr' => '𝒩', + 'nscr' => '𝓃', + 'nshortmid' => '∤', + 'nshortparallel' => '∦', + 'nsim' => '≁', + 'nsime' => '≄', + 'nsimeq' => '≄', + 'nsmid' => '∤', + 'nspar' => '∦', + 'nsqsube' => '⋢', + 'nsqsupe' => '⋣', + 'nsub' => '⊄', + 'nsubE' => '⫅̸', + 'nsube' => '⊈', + 'nsubset' => '⊂⃒', + 'nsubseteq' => '⊈', + 'nsubseteqq' => '⫅̸', + 'nsucc' => '⊁', + 'nsucceq' => '⪰̸', + 'nsup' => '⊅', + 'nsupE' => '⫆̸', + 'nsupe' => '⊉', + 'nsupset' => '⊃⃒', + 'nsupseteq' => '⊉', + 'nsupseteqq' => '⫆̸', + 'ntgl' => '≹', + 'Ntilde' => 'Ñ', + 'Ntild' => 'Ñ', + 'ntilde' => 'ñ', + 'ntild' => 'ñ', + 'ntlg' => '≸', + 'ntriangleleft' => '⋪', + 'ntrianglelefteq' => '⋬', + 'ntriangleright' => '⋫', + 'ntrianglerighteq' => '⋭', + 'Nu' => 'Ν', + 'nu' => 'ν', + 'num' => '#', + 'numero' => '№', + 'numsp' => ' ', + 'nvap' => '≍⃒', + 'nVDash' => '⊯', + 'nVdash' => '⊮', + 'nvDash' => '⊭', + 'nvdash' => '⊬', + 'nvge' => '≥⃒', + 'nvgt' => '>⃒', + 'nvHarr' => '⤄', + 'nvinfin' => '⧞', + 'nvlArr' => '⤂', + 'nvle' => '≤⃒', + 'nvlt' => '<⃒', + 'nvltrie' => '⊴⃒', + 'nvrArr' => '⤃', + 'nvrtrie' => '⊵⃒', + 'nvsim' => '∼⃒', + 'nwarhk' => '⤣', + 'nwArr' => '⇖', + 'nwarr' => '↖', + 'nwarrow' => '↖', + 'nwnear' => '⤧', + 'Oacute' => 'Ó', + 'Oacut' => 'Ó', + 'oacute' => 'ó', + 'oacut' => 'ó', + 'oast' => '⊛', + 'ocir' => 'ô', + 'Ocirc' => 'Ô', + 'Ocir' => 'Ô', + 'ocirc' => 'ô', + 'Ocy' => 'О', + 'ocy' => 'о', + 'odash' => '⊝', + 'Odblac' => 'Ő', + 'odblac' => 'ő', + 'odiv' => '⨸', + 'odot' => '⊙', + 'odsold' => '⦼', + 'OElig' => 'Œ', + 'oelig' => 'œ', + 'ofcir' => '⦿', + 'Ofr' => '𝔒', + 'ofr' => '𝔬', + 'ogon' => '˛', + 'Ograve' => 'Ò', + 'Ograv' => 'Ò', + 'ograve' => 'ò', + 'ograv' => 'ò', + 'ogt' => '⧁', + 'ohbar' => '⦵', + 'ohm' => 'Ω', + 'oint' => '∮', + 'olarr' => '↺', + 'olcir' => '⦾', + 'olcross' => '⦻', + 'oline' => '‾', + 'olt' => '⧀', + 'Omacr' => 'Ō', + 'omacr' => 'ō', + 'Omega' => 'Ω', + 'omega' => 'ω', + 'Omicron' => 'Ο', + 'omicron' => 'ο', + 'omid' => '⦶', + 'ominus' => '⊖', + 'Oopf' => '𝕆', + 'oopf' => '𝕠', + 'opar' => '⦷', + 'OpenCurlyDoubleQuote' => '“', + 'OpenCurlyQuote' => '‘', + 'operp' => '⦹', + 'oplus' => '⊕', + 'Or' => '⩔', + 'or' => '∨', + 'orarr' => '↻', + 'ord' => 'º', + 'order' => 'ℴ', + 'orderof' => 'ℴ', + 'ordf' => 'ª', + 'ordm' => 'º', + 'origof' => '⊶', + 'oror' => '⩖', + 'orslope' => '⩗', + 'orv' => '⩛', + 'oS' => 'Ⓢ', + 'Oscr' => '𝒪', + 'oscr' => 'ℴ', + 'Oslash' => 'Ø', + 'Oslas' => 'Ø', + 'oslash' => 'ø', + 'oslas' => 'ø', + 'osol' => '⊘', + 'Otilde' => 'Õ', + 'Otild' => 'Õ', + 'otilde' => 'õ', + 'otild' => 'õ', + 'Otimes' => '⨷', + 'otimes' => '⊗', + 'otimesas' => '⨶', + 'Ouml' => 'Ö', + 'Oum' => 'Ö', + 'ouml' => 'ö', + 'oum' => 'ö', + 'ovbar' => '⌽', + 'OverBar' => '‾', + 'OverBrace' => '⏞', + 'OverBracket' => '⎴', + 'OverParenthesis' => '⏜', + 'par' => '¶', + 'para' => '¶', + 'parallel' => '∥', + 'parsim' => '⫳', + 'parsl' => '⫽', + 'part' => '∂', + 'PartialD' => '∂', + 'Pcy' => 'П', + 'pcy' => 'п', + 'percnt' => '%', + 'period' => '.', + 'permil' => '‰', + 'perp' => '⊥', + 'pertenk' => '‱', + 'Pfr' => '𝔓', + 'pfr' => '𝔭', + 'Phi' => 'Φ', + 'phi' => 'φ', + 'phiv' => 'ϕ', + 'phmmat' => 'ℳ', + 'phone' => '☎', + 'Pi' => 'Π', + 'pi' => 'π', + 'pitchfork' => '⋔', + 'piv' => 'ϖ', + 'planck' => 'ℏ', + 'planckh' => 'ℎ', + 'plankv' => 'ℏ', + 'plus' => '+', + 'plusacir' => '⨣', + 'plusb' => '⊞', + 'pluscir' => '⨢', + 'plusdo' => '∔', + 'plusdu' => '⨥', + 'pluse' => '⩲', + 'PlusMinus' => '±', + 'plusmn' => '±', + 'plusm' => '±', + 'plussim' => '⨦', + 'plustwo' => '⨧', + 'pm' => '±', + 'Poincareplane' => 'ℌ', + 'pointint' => '⨕', + 'Popf' => 'ℙ', + 'popf' => '𝕡', + 'pound' => '£', + 'poun' => '£', + 'Pr' => '⪻', + 'pr' => '≺', + 'prap' => '⪷', + 'prcue' => '≼', + 'prE' => '⪳', + 'pre' => '⪯', + 'prec' => '≺', + 'precapprox' => '⪷', + 'preccurlyeq' => '≼', + 'Precedes' => '≺', + 'PrecedesEqual' => '⪯', + 'PrecedesSlantEqual' => '≼', + 'PrecedesTilde' => '≾', + 'preceq' => '⪯', + 'precnapprox' => '⪹', + 'precneqq' => '⪵', + 'precnsim' => '⋨', + 'precsim' => '≾', + 'Prime' => '″', + 'prime' => '′', + 'primes' => 'ℙ', + 'prnap' => '⪹', + 'prnE' => '⪵', + 'prnsim' => '⋨', + 'prod' => '∏', + 'Product' => '∏', + 'profalar' => '⌮', + 'profline' => '⌒', + 'profsurf' => '⌓', + 'prop' => '∝', + 'Proportion' => '∷', + 'Proportional' => '∝', + 'propto' => '∝', + 'prsim' => '≾', + 'prurel' => '⊰', + 'Pscr' => '𝒫', + 'pscr' => '𝓅', + 'Psi' => 'Ψ', + 'psi' => 'ψ', + 'puncsp' => ' ', + 'Qfr' => '𝔔', + 'qfr' => '𝔮', + 'qint' => '⨌', + 'Qopf' => 'ℚ', + 'qopf' => '𝕢', + 'qprime' => '⁗', + 'Qscr' => '𝒬', + 'qscr' => '𝓆', + 'quaternions' => 'ℍ', + 'quatint' => '⨖', + 'quest' => '?', + 'questeq' => '≟', + 'QUOT' => '"', + 'QUO' => '"', + 'quot' => '"', + 'quo' => '"', + 'rAarr' => '⇛', + 'race' => '∽̱', + 'Racute' => 'Ŕ', + 'racute' => 'ŕ', + 'radic' => '√', + 'raemptyv' => '⦳', + 'Rang' => '⟫', + 'rang' => '⟩', + 'rangd' => '⦒', + 'range' => '⦥', + 'rangle' => '⟩', + 'raquo' => '»', + 'raqu' => '»', + 'Rarr' => '↠', + 'rArr' => '⇒', + 'rarr' => '→', + 'rarrap' => '⥵', + 'rarrb' => '⇥', + 'rarrbfs' => '⤠', + 'rarrc' => '⤳', + 'rarrfs' => '⤞', + 'rarrhk' => '↪', + 'rarrlp' => '↬', + 'rarrpl' => '⥅', + 'rarrsim' => '⥴', + 'Rarrtl' => '⤖', + 'rarrtl' => '↣', + 'rarrw' => '↝', + 'rAtail' => '⤜', + 'ratail' => '⤚', + 'ratio' => '∶', + 'rationals' => 'ℚ', + 'RBarr' => '⤐', + 'rBarr' => '⤏', + 'rbarr' => '⤍', + 'rbbrk' => '❳', + 'rbrace' => '}', + 'rbrack' => ']', + 'rbrke' => '⦌', + 'rbrksld' => '⦎', + 'rbrkslu' => '⦐', + 'Rcaron' => 'Ř', + 'rcaron' => 'ř', + 'Rcedil' => 'Ŗ', + 'rcedil' => 'ŗ', + 'rceil' => '⌉', + 'rcub' => '}', + 'Rcy' => 'Р', + 'rcy' => 'р', + 'rdca' => '⤷', + 'rdldhar' => '⥩', + 'rdquo' => '”', + 'rdquor' => '”', + 'rdsh' => '↳', + 'Re' => 'ℜ', + 'real' => 'ℜ', + 'realine' => 'ℛ', + 'realpart' => 'ℜ', + 'reals' => 'ℝ', + 'rect' => '▭', + 'REG' => '®', + 'RE' => '®', + 'reg' => '®', + 're' => '®', + 'ReverseElement' => '∋', + 'ReverseEquilibrium' => '⇋', + 'ReverseUpEquilibrium' => '⥯', + 'rfisht' => '⥽', + 'rfloor' => '⌋', + 'Rfr' => 'ℜ', + 'rfr' => '𝔯', + 'rHar' => '⥤', + 'rhard' => '⇁', + 'rharu' => '⇀', + 'rharul' => '⥬', + 'Rho' => 'Ρ', + 'rho' => 'ρ', + 'rhov' => 'ϱ', + 'RightAngleBracket' => '⟩', + 'RightArrow' => '→', + 'Rightarrow' => '⇒', + 'rightarrow' => '→', + 'RightArrowBar' => '⇥', + 'RightArrowLeftArrow' => '⇄', + 'rightarrowtail' => '↣', + 'RightCeiling' => '⌉', + 'RightDoubleBracket' => '⟧', + 'RightDownTeeVector' => '⥝', + 'RightDownVector' => '⇂', + 'RightDownVectorBar' => '⥕', + 'RightFloor' => '⌋', + 'rightharpoondown' => '⇁', + 'rightharpoonup' => '⇀', + 'rightleftarrows' => '⇄', + 'rightleftharpoons' => '⇌', + 'rightrightarrows' => '⇉', + 'rightsquigarrow' => '↝', + 'RightTee' => '⊢', + 'RightTeeArrow' => '↦', + 'RightTeeVector' => '⥛', + 'rightthreetimes' => '⋌', + 'RightTriangle' => '⊳', + 'RightTriangleBar' => '⧐', + 'RightTriangleEqual' => '⊵', + 'RightUpDownVector' => '⥏', + 'RightUpTeeVector' => '⥜', + 'RightUpVector' => '↾', + 'RightUpVectorBar' => '⥔', + 'RightVector' => '⇀', + 'RightVectorBar' => '⥓', + 'ring' => '˚', + 'risingdotseq' => '≓', + 'rlarr' => '⇄', + 'rlhar' => '⇌', + 'rlm' => '‏', + 'rmoust' => '⎱', + 'rmoustache' => '⎱', + 'rnmid' => '⫮', + 'roang' => '⟭', + 'roarr' => '⇾', + 'robrk' => '⟧', + 'ropar' => '⦆', + 'Ropf' => 'ℝ', + 'ropf' => '𝕣', + 'roplus' => '⨮', + 'rotimes' => '⨵', + 'RoundImplies' => '⥰', + 'rpar' => ')', + 'rpargt' => '⦔', + 'rppolint' => '⨒', + 'rrarr' => '⇉', + 'Rrightarrow' => '⇛', + 'rsaquo' => '›', + 'Rscr' => 'ℛ', + 'rscr' => '𝓇', + 'Rsh' => '↱', + 'rsh' => '↱', + 'rsqb' => ']', + 'rsquo' => '’', + 'rsquor' => '’', + 'rthree' => '⋌', + 'rtimes' => '⋊', + 'rtri' => '▹', + 'rtrie' => '⊵', + 'rtrif' => '▸', + 'rtriltri' => '⧎', + 'RuleDelayed' => '⧴', + 'ruluhar' => '⥨', + 'rx' => '℞', + 'Sacute' => 'Ś', + 'sacute' => 'ś', + 'sbquo' => '‚', + 'Sc' => '⪼', + 'sc' => '≻', + 'scap' => '⪸', + 'Scaron' => 'Š', + 'scaron' => 'š', + 'sccue' => '≽', + 'scE' => '⪴', + 'sce' => '⪰', + 'Scedil' => 'Ş', + 'scedil' => 'ş', + 'Scirc' => 'Ŝ', + 'scirc' => 'ŝ', + 'scnap' => '⪺', + 'scnE' => '⪶', + 'scnsim' => '⋩', + 'scpolint' => '⨓', + 'scsim' => '≿', + 'Scy' => 'С', + 'scy' => 'с', + 'sdot' => '⋅', + 'sdotb' => '⊡', + 'sdote' => '⩦', + 'searhk' => '⤥', + 'seArr' => '⇘', + 'searr' => '↘', + 'searrow' => '↘', + 'sect' => '§', + 'sec' => '§', + 'semi' => ';', + 'seswar' => '⤩', + 'setminus' => '∖', + 'setmn' => '∖', + 'sext' => '✶', + 'Sfr' => '𝔖', + 'sfr' => '𝔰', + 'sfrown' => '⌢', + 'sharp' => '♯', + 'SHCHcy' => 'Щ', + 'shchcy' => 'щ', + 'SHcy' => 'Ш', + 'shcy' => 'ш', + 'ShortDownArrow' => '↓', + 'ShortLeftArrow' => '←', + 'shortmid' => '∣', + 'shortparallel' => '∥', + 'ShortRightArrow' => '→', + 'ShortUpArrow' => '↑', + 'shy' => '­', + 'sh' => '­', + 'Sigma' => 'Σ', + 'sigma' => 'σ', + 'sigmaf' => 'ς', + 'sigmav' => 'ς', + 'sim' => '∼', + 'simdot' => '⩪', + 'sime' => '≃', + 'simeq' => '≃', + 'simg' => '⪞', + 'simgE' => '⪠', + 'siml' => '⪝', + 'simlE' => '⪟', + 'simne' => '≆', + 'simplus' => '⨤', + 'simrarr' => '⥲', + 'slarr' => '←', + 'SmallCircle' => '∘', + 'smallsetminus' => '∖', + 'smashp' => '⨳', + 'smeparsl' => '⧤', + 'smid' => '∣', + 'smile' => '⌣', + 'smt' => '⪪', + 'smte' => '⪬', + 'smtes' => '⪬︀', + 'SOFTcy' => 'Ь', + 'softcy' => 'ь', + 'sol' => '/', + 'solb' => '⧄', + 'solbar' => '⌿', + 'Sopf' => '𝕊', + 'sopf' => '𝕤', + 'spades' => '♠', + 'spadesuit' => '♠', + 'spar' => '∥', + 'sqcap' => '⊓', + 'sqcaps' => '⊓︀', + 'sqcup' => '⊔', + 'sqcups' => '⊔︀', + 'Sqrt' => '√', + 'sqsub' => '⊏', + 'sqsube' => '⊑', + 'sqsubset' => '⊏', + 'sqsubseteq' => '⊑', + 'sqsup' => '⊐', + 'sqsupe' => '⊒', + 'sqsupset' => '⊐', + 'sqsupseteq' => '⊒', + 'squ' => '□', + 'Square' => '□', + 'square' => '□', + 'SquareIntersection' => '⊓', + 'SquareSubset' => '⊏', + 'SquareSubsetEqual' => '⊑', + 'SquareSuperset' => '⊐', + 'SquareSupersetEqual' => '⊒', + 'SquareUnion' => '⊔', + 'squarf' => '▪', + 'squf' => '▪', + 'srarr' => '→', + 'Sscr' => '𝒮', + 'sscr' => '𝓈', + 'ssetmn' => '∖', + 'ssmile' => '⌣', + 'sstarf' => '⋆', + 'Star' => '⋆', + 'star' => '☆', + 'starf' => '★', + 'straightepsilon' => 'ϵ', + 'straightphi' => 'ϕ', + 'strns' => '¯', + 'Sub' => '⋐', + 'sub' => '⊂', + 'subdot' => '⪽', + 'subE' => '⫅', + 'sube' => '⊆', + 'subedot' => '⫃', + 'submult' => '⫁', + 'subnE' => '⫋', + 'subne' => '⊊', + 'subplus' => '⪿', + 'subrarr' => '⥹', + 'Subset' => '⋐', + 'subset' => '⊂', + 'subseteq' => '⊆', + 'subseteqq' => '⫅', + 'SubsetEqual' => '⊆', + 'subsetneq' => '⊊', + 'subsetneqq' => '⫋', + 'subsim' => '⫇', + 'subsub' => '⫕', + 'subsup' => '⫓', + 'succ' => '≻', + 'succapprox' => '⪸', + 'succcurlyeq' => '≽', + 'Succeeds' => '≻', + 'SucceedsEqual' => '⪰', + 'SucceedsSlantEqual' => '≽', + 'SucceedsTilde' => '≿', + 'succeq' => '⪰', + 'succnapprox' => '⪺', + 'succneqq' => '⪶', + 'succnsim' => '⋩', + 'succsim' => '≿', + 'SuchThat' => '∋', + 'Sum' => '∑', + 'sum' => '∑', + 'sung' => '♪', + 'Sup' => '⋑', + 'sup' => '³', + 'sup1' => '¹', + 'sup2' => '²', + 'sup3' => '³', + 'supdot' => '⪾', + 'supdsub' => '⫘', + 'supE' => '⫆', + 'supe' => '⊇', + 'supedot' => '⫄', + 'Superset' => '⊃', + 'SupersetEqual' => '⊇', + 'suphsol' => '⟉', + 'suphsub' => '⫗', + 'suplarr' => '⥻', + 'supmult' => '⫂', + 'supnE' => '⫌', + 'supne' => '⊋', + 'supplus' => '⫀', + 'Supset' => '⋑', + 'supset' => '⊃', + 'supseteq' => '⊇', + 'supseteqq' => '⫆', + 'supsetneq' => '⊋', + 'supsetneqq' => '⫌', + 'supsim' => '⫈', + 'supsub' => '⫔', + 'supsup' => '⫖', + 'swarhk' => '⤦', + 'swArr' => '⇙', + 'swarr' => '↙', + 'swarrow' => '↙', + 'swnwar' => '⤪', + 'szlig' => 'ß', + 'szli' => 'ß', + 'Tab' => ' ', + 'target' => '⌖', + 'Tau' => 'Τ', + 'tau' => 'τ', + 'tbrk' => '⎴', + 'Tcaron' => 'Ť', + 'tcaron' => 'ť', + 'Tcedil' => 'Ţ', + 'tcedil' => 'ţ', + 'Tcy' => 'Т', + 'tcy' => 'т', + 'tdot' => '⃛', + 'telrec' => '⌕', + 'Tfr' => '𝔗', + 'tfr' => '𝔱', + 'there4' => '∴', + 'Therefore' => '∴', + 'therefore' => '∴', + 'Theta' => 'Θ', + 'theta' => 'θ', + 'thetasym' => 'ϑ', + 'thetav' => 'ϑ', + 'thickapprox' => '≈', + 'thicksim' => '∼', + 'ThickSpace' => '  ', + 'thinsp' => ' ', + 'ThinSpace' => ' ', + 'thkap' => '≈', + 'thksim' => '∼', + 'THORN' => 'Þ', + 'THOR' => 'Þ', + 'thorn' => 'þ', + 'thor' => 'þ', + 'Tilde' => '∼', + 'tilde' => '˜', + 'TildeEqual' => '≃', + 'TildeFullEqual' => '≅', + 'TildeTilde' => '≈', + 'times' => '×', + 'time' => '×', + 'timesb' => '⊠', + 'timesbar' => '⨱', + 'timesd' => '⨰', + 'tint' => '∭', + 'toea' => '⤨', + 'top' => '⊤', + 'topbot' => '⌶', + 'topcir' => '⫱', + 'Topf' => '𝕋', + 'topf' => '𝕥', + 'topfork' => '⫚', + 'tosa' => '⤩', + 'tprime' => '‴', + 'TRADE' => '™', + 'trade' => '™', + 'triangle' => '▵', + 'triangledown' => '▿', + 'triangleleft' => '◃', + 'trianglelefteq' => '⊴', + 'triangleq' => '≜', + 'triangleright' => '▹', + 'trianglerighteq' => '⊵', + 'tridot' => '◬', + 'trie' => '≜', + 'triminus' => '⨺', + 'TripleDot' => '⃛', + 'triplus' => '⨹', + 'trisb' => '⧍', + 'tritime' => '⨻', + 'trpezium' => '⏢', + 'Tscr' => '𝒯', + 'tscr' => '𝓉', + 'TScy' => 'Ц', + 'tscy' => 'ц', + 'TSHcy' => 'Ћ', + 'tshcy' => 'ћ', + 'Tstrok' => 'Ŧ', + 'tstrok' => 'ŧ', + 'twixt' => '≬', + 'twoheadleftarrow' => '↞', + 'twoheadrightarrow' => '↠', + 'Uacute' => 'Ú', + 'Uacut' => 'Ú', + 'uacute' => 'ú', + 'uacut' => 'ú', + 'Uarr' => '↟', + 'uArr' => '⇑', + 'uarr' => '↑', + 'Uarrocir' => '⥉', + 'Ubrcy' => 'Ў', + 'ubrcy' => 'ў', + 'Ubreve' => 'Ŭ', + 'ubreve' => 'ŭ', + 'Ucirc' => 'Û', + 'Ucir' => 'Û', + 'ucirc' => 'û', + 'ucir' => 'û', + 'Ucy' => 'У', + 'ucy' => 'у', + 'udarr' => '⇅', + 'Udblac' => 'Ű', + 'udblac' => 'ű', + 'udhar' => '⥮', + 'ufisht' => '⥾', + 'Ufr' => '𝔘', + 'ufr' => '𝔲', + 'Ugrave' => 'Ù', + 'Ugrav' => 'Ù', + 'ugrave' => 'ù', + 'ugrav' => 'ù', + 'uHar' => '⥣', + 'uharl' => '↿', + 'uharr' => '↾', + 'uhblk' => '▀', + 'ulcorn' => '⌜', + 'ulcorner' => '⌜', + 'ulcrop' => '⌏', + 'ultri' => '◸', + 'Umacr' => 'Ū', + 'umacr' => 'ū', + 'uml' => '¨', + 'um' => '¨', + 'UnderBar' => '_', + 'UnderBrace' => '⏟', + 'UnderBracket' => '⎵', + 'UnderParenthesis' => '⏝', + 'Union' => '⋃', + 'UnionPlus' => '⊎', + 'Uogon' => 'Ų', + 'uogon' => 'ų', + 'Uopf' => '𝕌', + 'uopf' => '𝕦', + 'UpArrow' => '↑', + 'Uparrow' => '⇑', + 'uparrow' => '↑', + 'UpArrowBar' => '⤒', + 'UpArrowDownArrow' => '⇅', + 'UpDownArrow' => '↕', + 'Updownarrow' => '⇕', + 'updownarrow' => '↕', + 'UpEquilibrium' => '⥮', + 'upharpoonleft' => '↿', + 'upharpoonright' => '↾', + 'uplus' => '⊎', + 'UpperLeftArrow' => '↖', + 'UpperRightArrow' => '↗', + 'Upsi' => 'ϒ', + 'upsi' => 'υ', + 'upsih' => 'ϒ', + 'Upsilon' => 'Υ', + 'upsilon' => 'υ', + 'UpTee' => '⊥', + 'UpTeeArrow' => '↥', + 'upuparrows' => '⇈', + 'urcorn' => '⌝', + 'urcorner' => '⌝', + 'urcrop' => '⌎', + 'Uring' => 'Ů', + 'uring' => 'ů', + 'urtri' => '◹', + 'Uscr' => '𝒰', + 'uscr' => '𝓊', + 'utdot' => '⋰', + 'Utilde' => 'Ũ', + 'utilde' => 'ũ', + 'utri' => '▵', + 'utrif' => '▴', + 'uuarr' => '⇈', + 'Uuml' => 'Ü', + 'Uum' => 'Ü', + 'uuml' => 'ü', + 'uum' => 'ü', + 'uwangle' => '⦧', + 'vangrt' => '⦜', + 'varepsilon' => 'ϵ', + 'varkappa' => 'ϰ', + 'varnothing' => '∅', + 'varphi' => 'ϕ', + 'varpi' => 'ϖ', + 'varpropto' => '∝', + 'vArr' => '⇕', + 'varr' => '↕', + 'varrho' => 'ϱ', + 'varsigma' => 'ς', + 'varsubsetneq' => '⊊︀', + 'varsubsetneqq' => '⫋︀', + 'varsupsetneq' => '⊋︀', + 'varsupsetneqq' => '⫌︀', + 'vartheta' => 'ϑ', + 'vartriangleleft' => '⊲', + 'vartriangleright' => '⊳', + 'Vbar' => '⫫', + 'vBar' => '⫨', + 'vBarv' => '⫩', + 'Vcy' => 'В', + 'vcy' => 'в', + 'VDash' => '⊫', + 'Vdash' => '⊩', + 'vDash' => '⊨', + 'vdash' => '⊢', + 'Vdashl' => '⫦', + 'Vee' => '⋁', + 'vee' => '∨', + 'veebar' => '⊻', + 'veeeq' => '≚', + 'vellip' => '⋮', + 'Verbar' => '‖', + 'verbar' => '|', + 'Vert' => '‖', + 'vert' => '|', + 'VerticalBar' => '∣', + 'VerticalLine' => '|', + 'VerticalSeparator' => '❘', + 'VerticalTilde' => '≀', + 'VeryThinSpace' => ' ', + 'Vfr' => '𝔙', + 'vfr' => '𝔳', + 'vltri' => '⊲', + 'vnsub' => '⊂⃒', + 'vnsup' => '⊃⃒', + 'Vopf' => '𝕍', + 'vopf' => '𝕧', + 'vprop' => '∝', + 'vrtri' => '⊳', + 'Vscr' => '𝒱', + 'vscr' => '𝓋', + 'vsubnE' => '⫋︀', + 'vsubne' => '⊊︀', + 'vsupnE' => '⫌︀', + 'vsupne' => '⊋︀', + 'Vvdash' => '⊪', + 'vzigzag' => '⦚', + 'Wcirc' => 'Ŵ', + 'wcirc' => 'ŵ', + 'wedbar' => '⩟', + 'Wedge' => '⋀', + 'wedge' => '∧', + 'wedgeq' => '≙', + 'weierp' => '℘', + 'Wfr' => '𝔚', + 'wfr' => '𝔴', + 'Wopf' => '𝕎', + 'wopf' => '𝕨', + 'wp' => '℘', + 'wr' => '≀', + 'wreath' => '≀', + 'Wscr' => '𝒲', + 'wscr' => '𝓌', + 'xcap' => '⋂', + 'xcirc' => '◯', + 'xcup' => '⋃', + 'xdtri' => '▽', + 'Xfr' => '𝔛', + 'xfr' => '𝔵', + 'xhArr' => '⟺', + 'xharr' => '⟷', + 'Xi' => 'Ξ', + 'xi' => 'ξ', + 'xlArr' => '⟸', + 'xlarr' => '⟵', + 'xmap' => '⟼', + 'xnis' => '⋻', + 'xodot' => '⨀', + 'Xopf' => '𝕏', + 'xopf' => '𝕩', + 'xoplus' => '⨁', + 'xotime' => '⨂', + 'xrArr' => '⟹', + 'xrarr' => '⟶', + 'Xscr' => '𝒳', + 'xscr' => '𝓍', + 'xsqcup' => '⨆', + 'xuplus' => '⨄', + 'xutri' => '△', + 'xvee' => '⋁', + 'xwedge' => '⋀', + 'Yacute' => 'Ý', + 'Yacut' => 'Ý', + 'yacute' => 'ý', + 'yacut' => 'ý', + 'YAcy' => 'Я', + 'yacy' => 'я', + 'Ycirc' => 'Ŷ', + 'ycirc' => 'ŷ', + 'Ycy' => 'Ы', + 'ycy' => 'ы', + 'yen' => '¥', + 'ye' => '¥', + 'Yfr' => '𝔜', + 'yfr' => '𝔶', + 'YIcy' => 'Ї', + 'yicy' => 'ї', + 'Yopf' => '𝕐', + 'yopf' => '𝕪', + 'Yscr' => '𝒴', + 'yscr' => '𝓎', + 'YUcy' => 'Ю', + 'yucy' => 'ю', + 'Yuml' => 'Ÿ', + 'yuml' => 'ÿ', + 'yum' => 'ÿ', + 'Zacute' => 'Ź', + 'zacute' => 'ź', + 'Zcaron' => 'Ž', + 'zcaron' => 'ž', + 'Zcy' => 'З', + 'zcy' => 'з', + 'Zdot' => 'Ż', + 'zdot' => 'ż', + 'zeetrf' => 'ℨ', + 'ZeroWidthSpace' => '​', + 'Zeta' => 'Ζ', + 'zeta' => 'ζ', + 'Zfr' => 'ℨ', + 'zfr' => '𝔷', + 'ZHcy' => 'Ж', + 'zhcy' => 'ж', + 'zigrarr' => '⇝', + 'Zopf' => 'ℤ', + 'zopf' => '𝕫', + 'Zscr' => '𝒵', + 'zscr' => '𝓏', + 'zwj' => '‍', + 'zwnj' => '‌', + ); +} diff --git a/masterminds/html5/src/HTML5/Exception.php b/masterminds/html5/src/HTML5/Exception.php new file mode 100644 index 000000000..64e97e6ff --- /dev/null +++ b/masterminds/html5/src/HTML5/Exception.php @@ -0,0 +1,10 @@ + self::NAMESPACE_HTML, + 'svg' => self::NAMESPACE_SVG, + 'math' => self::NAMESPACE_MATHML, + ); + + /** + * Holds the always available namespaces (which does not require the XMLNS declaration). + * + * @var array + */ + protected $implicitNamespaces = array( + 'xml' => self::NAMESPACE_XML, + 'xmlns' => self::NAMESPACE_XMLNS, + 'xlink' => self::NAMESPACE_XLINK, + ); + + /** + * Holds a stack of currently active namespaces. + * + * @var array + */ + protected $nsStack = array(); + + /** + * Holds the number of namespaces declared by a node. + * + * @var array + */ + protected $pushes = array(); + + /** + * Defined in 8.2.5. + */ + const IM_INITIAL = 0; + + const IM_BEFORE_HTML = 1; + + const IM_BEFORE_HEAD = 2; + + const IM_IN_HEAD = 3; + + const IM_IN_HEAD_NOSCRIPT = 4; + + const IM_AFTER_HEAD = 5; + + const IM_IN_BODY = 6; + + const IM_TEXT = 7; + + const IM_IN_TABLE = 8; + + const IM_IN_TABLE_TEXT = 9; + + const IM_IN_CAPTION = 10; + + const IM_IN_COLUMN_GROUP = 11; + + const IM_IN_TABLE_BODY = 12; + + const IM_IN_ROW = 13; + + const IM_IN_CELL = 14; + + const IM_IN_SELECT = 15; + + const IM_IN_SELECT_IN_TABLE = 16; + + const IM_AFTER_BODY = 17; + + const IM_IN_FRAMESET = 18; + + const IM_AFTER_FRAMESET = 19; + + const IM_AFTER_AFTER_BODY = 20; + + const IM_AFTER_AFTER_FRAMESET = 21; + + const IM_IN_SVG = 22; + + const IM_IN_MATHML = 23; + + protected $options = array(); + + protected $stack = array(); + + protected $current; // Pointer in the tag hierarchy. + protected $rules; + protected $doc; + + protected $frag; + + protected $processor; + + protected $insertMode = 0; + + /** + * Track if we are in an element that allows only inline child nodes. + * + * @var string|null + */ + protected $onlyInline; + + /** + * Quirks mode is enabled by default. + * Any document that is missing the DT will be considered to be in quirks mode. + */ + protected $quirks = true; + + protected $errors = array(); + + public function __construct($isFragment = false, array $options = array()) + { + $this->options = $options; + + if (isset($options[self::OPT_TARGET_DOC])) { + $this->doc = $options[self::OPT_TARGET_DOC]; + } else { + $impl = new \DOMImplementation(); + // XXX: + // Create the doctype. For now, we are always creating HTML5 + // documents, and attempting to up-convert any older DTDs to HTML5. + $dt = $impl->createDocumentType('html'); + // $this->doc = \DOMImplementation::createDocument(NULL, 'html', $dt); + $this->doc = $impl->createDocument(null, '', $dt); + $this->doc->encoding = !empty($options['encoding']) ? $options['encoding'] : 'UTF-8'; + } + + $this->errors = array(); + + $this->current = $this->doc; // ->documentElement; + + // Create a rules engine for tags. + $this->rules = new TreeBuildingRules(); + + $implicitNS = array(); + if (isset($this->options[self::OPT_IMPLICIT_NS])) { + $implicitNS = $this->options[self::OPT_IMPLICIT_NS]; + } elseif (isset($this->options['implicitNamespaces'])) { + $implicitNS = $this->options['implicitNamespaces']; + } + + // Fill $nsStack with the defalut HTML5 namespaces, plus the "implicitNamespaces" array taken form $options + array_unshift($this->nsStack, $implicitNS + array('' => self::NAMESPACE_HTML) + $this->implicitNamespaces); + + if ($isFragment) { + $this->insertMode = static::IM_IN_BODY; + $this->frag = $this->doc->createDocumentFragment(); + $this->current = $this->frag; + } + } + + /** + * Get the document. + */ + public function document() + { + return $this->doc; + } + + /** + * Get the DOM fragment for the body. + * + * This returns a DOMNodeList because a fragment may have zero or more + * DOMNodes at its root. + * + * @see http://www.w3.org/TR/2012/CR-html5-20121217/syntax.html#concept-frag-parse-context + * + * @return \DOMDocumentFragment + */ + public function fragment() + { + return $this->frag; + } + + /** + * Provide an instruction processor. + * + * This is used for handling Processor Instructions as they are + * inserted. If omitted, PI's are inserted directly into the DOM tree. + * + * @param InstructionProcessor $proc + */ + public function setInstructionProcessor(InstructionProcessor $proc) + { + $this->processor = $proc; + } + + public function doctype($name, $idType = 0, $id = null, $quirks = false) + { + // This is used solely for setting quirks mode. Currently we don't + // try to preserve the inbound DT. We convert it to HTML5. + $this->quirks = $quirks; + + if ($this->insertMode > static::IM_INITIAL) { + $this->parseError('Illegal placement of DOCTYPE tag. Ignoring: ' . $name); + + return; + } + + $this->insertMode = static::IM_BEFORE_HTML; + } + + /** + * Process the start tag. + * + * @todo - XMLNS namespace handling (we need to parse, even if it's not valid) + * - XLink, MathML and SVG namespace handling + * - Omission rules: 8.1.2.4 Optional tags + * + * @param string $name + * @param array $attributes + * @param bool $selfClosing + * + * @return int + */ + public function startTag($name, $attributes = array(), $selfClosing = false) + { + $lname = $this->normalizeTagName($name); + + // Make sure we have an html element. + if (!$this->doc->documentElement && 'html' !== $name && !$this->frag) { + $this->startTag('html'); + } + + // Set quirks mode if we're at IM_INITIAL with no doctype. + if ($this->insertMode === static::IM_INITIAL) { + $this->quirks = true; + $this->parseError('No DOCTYPE specified.'); + } + + // SPECIAL TAG HANDLING: + // Spec says do this, and "don't ask." + // find the spec where this is defined... looks problematic + if ('image' === $name && !($this->insertMode === static::IM_IN_SVG || $this->insertMode === static::IM_IN_MATHML)) { + $name = 'img'; + } + + // Autoclose p tags where appropriate. + if ($this->insertMode >= static::IM_IN_BODY && Elements::isA($name, Elements::AUTOCLOSE_P)) { + $this->autoclose('p'); + } + + // Set insert mode: + switch ($name) { + case 'html': + $this->insertMode = static::IM_BEFORE_HEAD; + break; + case 'head': + if ($this->insertMode > static::IM_BEFORE_HEAD) { + $this->parseError('Unexpected head tag outside of head context.'); + } else { + $this->insertMode = static::IM_IN_HEAD; + } + break; + case 'body': + $this->insertMode = static::IM_IN_BODY; + break; + case 'svg': + $this->insertMode = static::IM_IN_SVG; + break; + case 'math': + $this->insertMode = static::IM_IN_MATHML; + break; + case 'noscript': + if ($this->insertMode === static::IM_IN_HEAD) { + $this->insertMode = static::IM_IN_HEAD_NOSCRIPT; + } + break; + } + + // Special case handling for SVG. + if ($this->insertMode === static::IM_IN_SVG) { + $lname = Elements::normalizeSvgElement($lname); + } + + $pushes = 0; + // when we found a tag thats appears inside $nsRoots, we have to switch the defalut namespace + if (isset($this->nsRoots[$lname]) && $this->nsStack[0][''] !== $this->nsRoots[$lname]) { + array_unshift($this->nsStack, array( + '' => $this->nsRoots[$lname], + ) + $this->nsStack[0]); + ++$pushes; + } + $needsWorkaround = false; + if (isset($this->options['xmlNamespaces']) && $this->options['xmlNamespaces']) { + // when xmlNamespaces is true a and we found a 'xmlns' or 'xmlns:*' attribute, we should add a new item to the $nsStack + foreach ($attributes as $aName => $aVal) { + if ('xmlns' === $aName) { + $needsWorkaround = $aVal; + array_unshift($this->nsStack, array( + '' => $aVal, + ) + $this->nsStack[0]); + ++$pushes; + } elseif ('xmlns' === (($pos = strpos($aName, ':')) ? substr($aName, 0, $pos) : '')) { + array_unshift($this->nsStack, array( + substr($aName, $pos + 1) => $aVal, + ) + $this->nsStack[0]); + ++$pushes; + } + } + } + + if ($this->onlyInline && Elements::isA($lname, Elements::BLOCK_TAG)) { + $this->autoclose($this->onlyInline); + $this->onlyInline = null; + } + + // some elements as table related tags might have optional end tags that force us to auto close multiple tags + // https://www.w3.org/TR/html401/struct/tables.html + if ($this->current instanceof \DOMElement && isset(Elements::$optionalEndElementsParentsToClose[$lname])) { + foreach (Elements::$optionalEndElementsParentsToClose[$lname] as $parentElName) { + if ($this->current instanceof \DOMElement && $this->current->tagName === $parentElName) { + $this->autoclose($parentElName); + } + } + } + + try { + $prefix = ($pos = strpos($lname, ':')) ? substr($lname, 0, $pos) : ''; + + if (false !== $needsWorkaround) { + $xml = "<$lname xmlns=\"$needsWorkaround\" " . (strlen($prefix) && isset($this->nsStack[0][$prefix]) ? ("xmlns:$prefix=\"" . $this->nsStack[0][$prefix] . '"') : '') . '/>'; + + $frag = new \DOMDocument('1.0', 'UTF-8'); + $frag->loadXML($xml); + + $ele = $this->doc->importNode($frag->documentElement, true); + } else { + if (!isset($this->nsStack[0][$prefix]) || ('' === $prefix && isset($this->options[self::OPT_DISABLE_HTML_NS]) && $this->options[self::OPT_DISABLE_HTML_NS])) { + $ele = $this->doc->createElement($lname); + } else { + $ele = $this->doc->createElementNS($this->nsStack[0][$prefix], $lname); + } + } + } catch (\DOMException $e) { + $this->parseError("Illegal tag name: <$lname>. Replaced with ."); + $ele = $this->doc->createElement('invalid'); + } + + if (Elements::isA($lname, Elements::BLOCK_ONLY_INLINE)) { + $this->onlyInline = $lname; + } + + // When we add some namespacess, we have to track them. Later, when "endElement" is invoked, we have to remove them. + // When we are on a void tag, we do not need to care about namesapce nesting. + if ($pushes > 0 && !Elements::isA($name, Elements::VOID_TAG)) { + // PHP tends to free the memory used by DOM, + // to avoid spl_object_hash collisions whe have to avoid garbage collection of $ele storing it into $pushes + // see https://bugs.php.net/bug.php?id=67459 + $this->pushes[spl_object_hash($ele)] = array($pushes, $ele); + } + + foreach ($attributes as $aName => $aVal) { + // xmlns attributes can't be set + if ('xmlns' === $aName) { + continue; + } + + if ($this->insertMode === static::IM_IN_SVG) { + $aName = Elements::normalizeSvgAttribute($aName); + } elseif ($this->insertMode === static::IM_IN_MATHML) { + $aName = Elements::normalizeMathMlAttribute($aName); + } + + $aVal = (string) $aVal; + + try { + $prefix = ($pos = strpos($aName, ':')) ? substr($aName, 0, $pos) : false; + + if ('xmlns' === $prefix) { + $ele->setAttributeNS(self::NAMESPACE_XMLNS, $aName, $aVal); + } elseif (false !== $prefix && isset($this->nsStack[0][$prefix])) { + $ele->setAttributeNS($this->nsStack[0][$prefix], $aName, $aVal); + } else { + $ele->setAttribute($aName, $aVal); + } + } catch (\DOMException $e) { + $this->parseError("Illegal attribute name for tag $name. Ignoring: $aName"); + continue; + } + + // This is necessary on a non-DTD schema, like HTML5. + if ('id' === $aName) { + $ele->setIdAttribute('id', true); + } + } + + if ($this->frag !== $this->current && $this->rules->hasRules($name)) { + // Some elements have special processing rules. Handle those separately. + $this->current = $this->rules->evaluate($ele, $this->current); + } else { + // Otherwise, it's a standard element. + $this->current->appendChild($ele); + + if (!Elements::isA($name, Elements::VOID_TAG)) { + $this->current = $ele; + } + + // Self-closing tags should only be respected on foreign elements + // (and are implied on void elements) + // See: https://www.w3.org/TR/html5/syntax.html#start-tags + if (Elements::isHtml5Element($name)) { + $selfClosing = false; + } + } + + // This is sort of a last-ditch attempt to correct for cases where no head/body + // elements are provided. + if ($this->insertMode <= static::IM_BEFORE_HEAD && 'head' !== $name && 'html' !== $name) { + $this->insertMode = static::IM_IN_BODY; + } + + // When we are on a void tag, we do not need to care about namesapce nesting, + // but we have to remove the namespaces pushed to $nsStack. + if ($pushes > 0 && Elements::isA($name, Elements::VOID_TAG)) { + // remove the namespaced definded by current node + for ($i = 0; $i < $pushes; ++$i) { + array_shift($this->nsStack); + } + } + + if ($selfClosing) { + $this->endTag($name); + } + + // Return the element mask, which the tokenizer can then use to set + // various processing rules. + return Elements::element($name); + } + + public function endTag($name) + { + $lname = $this->normalizeTagName($name); + + // Special case within 12.2.6.4.7: An end tag whose tag name is "br" should be treated as an opening tag + if ('br' === $name) { + $this->parseError('Closing tag encountered for void element br.'); + + $this->startTag('br'); + } + // Ignore closing tags for other unary elements. + elseif (Elements::isA($name, Elements::VOID_TAG)) { + return; + } + + if ($this->insertMode <= static::IM_BEFORE_HTML) { + // 8.2.5.4.2 + if (in_array($name, array( + 'html', + 'br', + 'head', + 'title', + ))) { + $this->startTag('html'); + $this->endTag($name); + $this->insertMode = static::IM_BEFORE_HEAD; + + return; + } + + // Ignore the tag. + $this->parseError('Illegal closing tag at global scope.'); + + return; + } + + // Special case handling for SVG. + if ($this->insertMode === static::IM_IN_SVG) { + $lname = Elements::normalizeSvgElement($lname); + } + + $cid = spl_object_hash($this->current); + + // XXX: HTML has no parent. What do we do, though, + // if this element appears in the wrong place? + if ('html' === $lname) { + return; + } + + // remove the namespaced definded by current node + if (isset($this->pushes[$cid])) { + for ($i = 0; $i < $this->pushes[$cid][0]; ++$i) { + array_shift($this->nsStack); + } + unset($this->pushes[$cid]); + } + + if (!$this->autoclose($lname)) { + $this->parseError('Could not find closing tag for ' . $lname); + } + + switch ($lname) { + case 'head': + $this->insertMode = static::IM_AFTER_HEAD; + break; + case 'body': + $this->insertMode = static::IM_AFTER_BODY; + break; + case 'svg': + case 'mathml': + $this->insertMode = static::IM_IN_BODY; + break; + } + } + + public function comment($cdata) + { + // TODO: Need to handle case where comment appears outside of the HTML tag. + $node = $this->doc->createComment($cdata); + $this->current->appendChild($node); + } + + public function text($data) + { + // XXX: Hmmm.... should we really be this strict? + if ($this->insertMode < static::IM_IN_HEAD) { + // Per '8.2.5.4.3 The "before head" insertion mode' the characters + // " \t\n\r\f" should be ignored but no mention of a parse error. This is + // practical as most documents contain these characters. Other text is not + // expected here so recording a parse error is necessary. + $dataTmp = trim($data, " \t\n\r\f"); + if (!empty($dataTmp)) { + // fprintf(STDOUT, "Unexpected insert mode: %d", $this->insertMode); + $this->parseError('Unexpected text. Ignoring: ' . $dataTmp); + } + + return; + } + // fprintf(STDOUT, "Appending text %s.", $data); + $node = $this->doc->createTextNode($data); + $this->current->appendChild($node); + } + + public function eof() + { + // If the $current isn't the $root, do we need to do anything? + } + + public function parseError($msg, $line = 0, $col = 0) + { + $this->errors[] = sprintf('Line %d, Col %d: %s', $line, $col, $msg); + } + + public function getErrors() + { + return $this->errors; + } + + public function cdata($data) + { + $node = $this->doc->createCDATASection($data); + $this->current->appendChild($node); + } + + public function processingInstruction($name, $data = null) + { + // XXX: Ignore initial XML declaration, per the spec. + if ($this->insertMode === static::IM_INITIAL && 'xml' === strtolower($name)) { + return; + } + + // Important: The processor may modify the current DOM tree however it sees fit. + if ($this->processor instanceof InstructionProcessor) { + $res = $this->processor->process($this->current, $name, $data); + if (!empty($res)) { + $this->current = $res; + } + + return; + } + + // Otherwise, this is just a dumb PI element. + $node = $this->doc->createProcessingInstruction($name, $data); + + $this->current->appendChild($node); + } + + // ========================================================================== + // UTILITIES + // ========================================================================== + + /** + * Apply normalization rules to a tag name. + * See sections 2.9 and 8.1.2. + * + * @param string $tagName + * + * @return string The normalized tag name. + */ + protected function normalizeTagName($tagName) + { + /* + * Section 2.9 suggests that we should not do this. if (strpos($name, ':') !== false) { // We know from the grammar that there must be at least one other // char besides :, since : is not a legal tag start. $parts = explode(':', $name); return array_pop($parts); } + */ + return $tagName; + } + + protected function quirksTreeResolver($name) + { + throw new \Exception('Not implemented.'); + } + + /** + * Automatically climb the tree and close the closest node with the matching $tag. + * + * @param string $tagName + * + * @return bool + */ + protected function autoclose($tagName) + { + $working = $this->current; + do { + if (XML_ELEMENT_NODE !== $working->nodeType) { + return false; + } + if ($working->tagName === $tagName) { + $this->current = $working->parentNode; + + return true; + } + } while ($working = $working->parentNode); + + return false; + } + + /** + * Checks if the given tagname is an ancestor of the present candidate. + * + * If $this->current or anything above $this->current matches the given tag + * name, this returns true. + * + * @param string $tagName + * + * @return bool + */ + protected function isAncestor($tagName) + { + $candidate = $this->current; + while (XML_ELEMENT_NODE === $candidate->nodeType) { + if ($candidate->tagName === $tagName) { + return true; + } + $candidate = $candidate->parentNode; + } + + return false; + } + + /** + * Returns true if the immediate parent element is of the given tagname. + * + * @param string $tagName + * + * @return bool + */ + protected function isParent($tagName) + { + return $this->current->tagName === $tagName; + } +} diff --git a/masterminds/html5/src/HTML5/Parser/EventHandler.php b/masterminds/html5/src/HTML5/Parser/EventHandler.php new file mode 100644 index 000000000..9893a718b --- /dev/null +++ b/masterminds/html5/src/HTML5/Parser/EventHandler.php @@ -0,0 +1,114 @@ +). + * + * @return int one of the Tokenizer::TEXTMODE_* constants + */ + public function startTag($name, $attributes = array(), $selfClosing = false); + + /** + * An end-tag. + */ + public function endTag($name); + + /** + * A comment section (unparsed character data). + */ + public function comment($cdata); + + /** + * A unit of parsed character data. + * + * Entities in this text are *already decoded*. + */ + public function text($cdata); + + /** + * Indicates that the document has been entirely processed. + */ + public function eof(); + + /** + * Emitted when the parser encounters an error condition. + */ + public function parseError($msg, $line, $col); + + /** + * A CDATA section. + * + * @param string $data + * The unparsed character data + */ + public function cdata($data); + + /** + * This is a holdover from the XML spec. + * + * While user agents don't get PIs, server-side does. + * + * @param string $name The name of the processor (e.g. 'php'). + * @param string $data The unparsed data. + */ + public function processingInstruction($name, $data = null); +} diff --git a/masterminds/html5/src/HTML5/Parser/FileInputStream.php b/masterminds/html5/src/HTML5/Parser/FileInputStream.php new file mode 100644 index 000000000..b081ed96b --- /dev/null +++ b/masterminds/html5/src/HTML5/Parser/FileInputStream.php @@ -0,0 +1,33 @@ +errors = UTF8Utils::checkForIllegalCodepoints($data); + + $data = $this->replaceLinefeeds($data); + + $this->data = $data; + $this->char = 0; + $this->EOF = strlen($data); + } + + /** + * Check if upcomming chars match the given sequence. + * + * This will read the stream for the $sequence. If it's + * found, this will return true. If not, return false. + * Since this unconsumes any chars it reads, the caller + * will still need to read the next sequence, even if + * this returns true. + * + * Example: $this->scanner->sequenceMatches('') will + * see if the input stream is at the start of a + * '' string. + * + * @param string $sequence + * @param bool $caseSensitive + * + * @return bool + */ + public function sequenceMatches($sequence, $caseSensitive = true) + { + $portion = substr($this->data, $this->char, strlen($sequence)); + + return $caseSensitive ? $portion === $sequence : 0 === strcasecmp($portion, $sequence); + } + + /** + * Get the current position. + * + * @return int The current intiger byte position. + */ + public function position() + { + return $this->char; + } + + /** + * Take a peek at the next character in the data. + * + * @return string The next character. + */ + public function peek() + { + if (($this->char + 1) < $this->EOF) { + return $this->data[$this->char + 1]; + } + + return false; + } + + /** + * Get the next character. + * Note: This advances the pointer. + * + * @return string The next character. + */ + public function next() + { + ++$this->char; + + if ($this->char < $this->EOF) { + return $this->data[$this->char]; + } + + return false; + } + + /** + * Get the current character. + * Note, this does not advance the pointer. + * + * @return string The current character. + */ + public function current() + { + if ($this->char < $this->EOF) { + return $this->data[$this->char]; + } + + return false; + } + + /** + * Silently consume N chars. + * + * @param int $count + */ + public function consume($count = 1) + { + $this->char += $count; + } + + /** + * Unconsume some of the data. + * This moves the data pointer backwards. + * + * @param int $howMany The number of characters to move the pointer back. + */ + public function unconsume($howMany = 1) + { + if (($this->char - $howMany) >= 0) { + $this->char -= $howMany; + } + } + + /** + * Get the next group of that contains hex characters. + * Note, along with getting the characters the pointer in the data will be + * moved as well. + * + * @return string The next group that is hex characters. + */ + public function getHex() + { + return $this->doCharsWhile(static::CHARS_HEX); + } + + /** + * Get the next group of characters that are ASCII Alpha characters. + * Note, along with getting the characters the pointer in the data will be + * moved as well. + * + * @return string The next group of ASCII alpha characters. + */ + public function getAsciiAlpha() + { + return $this->doCharsWhile(static::CHARS_ALPHA); + } + + /** + * Get the next group of characters that are ASCII Alpha characters and numbers. + * Note, along with getting the characters the pointer in the data will be + * moved as well. + * + * @return string The next group of ASCII alpha characters and numbers. + */ + public function getAsciiAlphaNum() + { + return $this->doCharsWhile(static::CHARS_ALNUM); + } + + /** + * Get the next group of numbers. + * Note, along with getting the characters the pointer in the data will be + * moved as well. + * + * @return string The next group of numbers. + */ + public function getNumeric() + { + return $this->doCharsWhile('0123456789'); + } + + /** + * Consume whitespace. + * Whitespace in HTML5 is: formfeed, tab, newline, space. + * + * @return int The length of the matched whitespaces. + */ + public function whitespace() + { + if ($this->char >= $this->EOF) { + return false; + } + + $len = strspn($this->data, "\n\t\f ", $this->char); + + $this->char += $len; + + return $len; + } + + /** + * Returns the current line that is being consumed. + * + * @return int The current line number. + */ + public function currentLine() + { + if (empty($this->EOF) || 0 === $this->char) { + return 1; + } + + // Add one to $this->char because we want the number for the next + // byte to be processed. + return substr_count($this->data, "\n", 0, min($this->char, $this->EOF)) + 1; + } + + /** + * Read chars until something in the mask is encountered. + * + * @param string $mask + * + * @return mixed + */ + public function charsUntil($mask) + { + return $this->doCharsUntil($mask); + } + + /** + * Read chars as long as the mask matches. + * + * @param string $mask + * + * @return int + */ + public function charsWhile($mask) + { + return $this->doCharsWhile($mask); + } + + /** + * Returns the current column of the current line that the tokenizer is at. + * + * Newlines are column 0. The first char after a newline is column 1. + * + * @return int The column number. + */ + public function columnOffset() + { + // Short circuit for the first char. + if (0 === $this->char) { + return 0; + } + + // strrpos is weird, and the offset needs to be negative for what we + // want (i.e., the last \n before $this->char). This needs to not have + // one (to make it point to the next character, the one we want the + // position of) added to it because strrpos's behaviour includes the + // final offset byte. + $backwardFrom = $this->char - 1 - strlen($this->data); + $lastLine = strrpos($this->data, "\n", $backwardFrom); + + // However, for here we want the length up until the next byte to be + // processed, so add one to the current byte ($this->char). + if (false !== $lastLine) { + $findLengthOf = substr($this->data, $lastLine + 1, $this->char - 1 - $lastLine); + } else { + // After a newline. + $findLengthOf = substr($this->data, 0, $this->char); + } + + return UTF8Utils::countChars($findLengthOf); + } + + /** + * Get all characters until EOF. + * + * This consumes characters until the EOF. + * + * @return int The number of characters remaining. + */ + public function remainingChars() + { + if ($this->char < $this->EOF) { + $data = substr($this->data, $this->char); + $this->char = $this->EOF; + + return $data; + } + + return ''; // false; + } + + /** + * Replace linefeed characters according to the spec. + * + * @param $data + * + * @return string + */ + private function replaceLinefeeds($data) + { + /* + * U+000D CARRIAGE RETURN (CR) characters and U+000A LINE FEED (LF) characters are treated specially. + * Any CR characters that are followed by LF characters must be removed, and any CR characters not + * followed by LF characters must be converted to LF characters. Thus, newlines in HTML DOMs are + * represented by LF characters, and there are never any CR characters in the input to the tokenization + * stage. + */ + $crlfTable = array( + "\0" => "\xEF\xBF\xBD", + "\r\n" => "\n", + "\r" => "\n", + ); + + return strtr($data, $crlfTable); + } + + /** + * Read to a particular match (or until $max bytes are consumed). + * + * This operates on byte sequences, not characters. + * + * Matches as far as possible until we reach a certain set of bytes + * and returns the matched substring. + * + * @param string $bytes Bytes to match. + * @param int $max Maximum number of bytes to scan. + * + * @return mixed Index or false if no match is found. You should use strong + * equality when checking the result, since index could be 0. + */ + private function doCharsUntil($bytes, $max = null) + { + if ($this->char >= $this->EOF) { + return false; + } + + if (0 === $max || $max) { + $len = strcspn($this->data, $bytes, $this->char, $max); + } else { + $len = strcspn($this->data, $bytes, $this->char); + } + + $string = (string) substr($this->data, $this->char, $len); + $this->char += $len; + + return $string; + } + + /** + * Returns the string so long as $bytes matches. + * + * Matches as far as possible with a certain set of bytes + * and returns the matched substring. + * + * @param string $bytes A mask of bytes to match. If ANY byte in this mask matches the + * current char, the pointer advances and the char is part of the + * substring. + * @param int $max The max number of chars to read. + * + * @return string + */ + private function doCharsWhile($bytes, $max = null) + { + if ($this->char >= $this->EOF) { + return false; + } + + if (0 === $max || $max) { + $len = strspn($this->data, $bytes, $this->char, $max); + } else { + $len = strspn($this->data, $bytes, $this->char); + } + + $string = (string) substr($this->data, $this->char, $len); + $this->char += $len; + + return $string; + } +} diff --git a/masterminds/html5/src/HTML5/Parser/StringInputStream.php b/masterminds/html5/src/HTML5/Parser/StringInputStream.php new file mode 100644 index 000000000..75b088610 --- /dev/null +++ b/masterminds/html5/src/HTML5/Parser/StringInputStream.php @@ -0,0 +1,336 @@ + + +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. + +*/ + +// Some conventions: +// - /* */ indicates verbatim text from the HTML 5 specification +// MPB: Not sure which version of the spec. Moving from HTML5lib to +// HTML5-PHP, I have been using this version: +// http://www.w3.org/TR/2012/CR-html5-20121217/Overview.html#contents +// +// - // indicates regular comments + +/** + * @deprecated since 2.4, to remove in 3.0. Use a string in the scanner instead. + */ +class StringInputStream implements InputStream +{ + /** + * The string data we're parsing. + */ + private $data; + + /** + * The current integer byte position we are in $data. + */ + private $char; + + /** + * Length of $data; when $char === $data, we are at the end-of-file. + */ + private $EOF; + + /** + * Parse errors. + */ + public $errors = array(); + + /** + * Create a new InputStream wrapper. + * + * @param string $data Data to parse. + * @param string $encoding The encoding to use for the data. + * @param string $debug A fprintf format to use to echo the data on stdout. + */ + public function __construct($data, $encoding = 'UTF-8', $debug = '') + { + $data = UTF8Utils::convertToUTF8($data, $encoding); + if ($debug) { + fprintf(STDOUT, $debug, $data, strlen($data)); + } + + // There is good reason to question whether it makes sense to + // do this here, since most of these checks are done during + // parsing, and since this check doesn't actually *do* anything. + $this->errors = UTF8Utils::checkForIllegalCodepoints($data); + + $data = $this->replaceLinefeeds($data); + + $this->data = $data; + $this->char = 0; + $this->EOF = strlen($data); + } + + public function __toString() + { + return $this->data; + } + + /** + * Replace linefeed characters according to the spec. + */ + protected function replaceLinefeeds($data) + { + /* + * U+000D CARRIAGE RETURN (CR) characters and U+000A LINE FEED (LF) characters are treated specially. + * Any CR characters that are followed by LF characters must be removed, and any CR characters not + * followed by LF characters must be converted to LF characters. Thus, newlines in HTML DOMs are + * represented by LF characters, and there are never any CR characters in the input to the tokenization + * stage. + */ + $crlfTable = array( + "\0" => "\xEF\xBF\xBD", + "\r\n" => "\n", + "\r" => "\n", + ); + + return strtr($data, $crlfTable); + } + + /** + * Returns the current line that the tokenizer is at. + */ + public function currentLine() + { + if (empty($this->EOF) || 0 === $this->char) { + return 1; + } + // Add one to $this->char because we want the number for the next + // byte to be processed. + return substr_count($this->data, "\n", 0, min($this->char, $this->EOF)) + 1; + } + + /** + * @deprecated + */ + public function getCurrentLine() + { + return $this->currentLine(); + } + + /** + * Returns the current column of the current line that the tokenizer is at. + * Newlines are column 0. The first char after a newline is column 1. + * + * @return int The column number. + */ + public function columnOffset() + { + // Short circuit for the first char. + if (0 === $this->char) { + return 0; + } + // strrpos is weird, and the offset needs to be negative for what we + // want (i.e., the last \n before $this->char). This needs to not have + // one (to make it point to the next character, the one we want the + // position of) added to it because strrpos's behaviour includes the + // final offset byte. + $backwardFrom = $this->char - 1 - strlen($this->data); + $lastLine = strrpos($this->data, "\n", $backwardFrom); + + // However, for here we want the length up until the next byte to be + // processed, so add one to the current byte ($this->char). + if (false !== $lastLine) { + $findLengthOf = substr($this->data, $lastLine + 1, $this->char - 1 - $lastLine); + } else { + // After a newline. + $findLengthOf = substr($this->data, 0, $this->char); + } + + return UTF8Utils::countChars($findLengthOf); + } + + /** + * @deprecated + */ + public function getColumnOffset() + { + return $this->columnOffset(); + } + + /** + * Get the current character. + * + * @return string The current character. + */ + #[\ReturnTypeWillChange] + public function current() + { + return $this->data[$this->char]; + } + + /** + * Advance the pointer. + * This is part of the Iterator interface. + */ + #[\ReturnTypeWillChange] + public function next() + { + ++$this->char; + } + + /** + * Rewind to the start of the string. + */ + #[\ReturnTypeWillChange] + public function rewind() + { + $this->char = 0; + } + + /** + * Is the current pointer location valid. + * + * @return bool Whether the current pointer location is valid. + */ + #[\ReturnTypeWillChange] + public function valid() + { + return $this->char < $this->EOF; + } + + /** + * Get all characters until EOF. + * + * This reads to the end of the file, and sets the read marker at the + * end of the file. + * + * Note this performs bounds checking. + * + * @return string Returns the remaining text. If called when the InputStream is + * already exhausted, it returns an empty string. + */ + public function remainingChars() + { + if ($this->char < $this->EOF) { + $data = substr($this->data, $this->char); + $this->char = $this->EOF; + + return $data; + } + + return ''; // false; + } + + /** + * Read to a particular match (or until $max bytes are consumed). + * + * This operates on byte sequences, not characters. + * + * Matches as far as possible until we reach a certain set of bytes + * and returns the matched substring. + * + * @param string $bytes Bytes to match. + * @param int $max Maximum number of bytes to scan. + * + * @return mixed Index or false if no match is found. You should use strong + * equality when checking the result, since index could be 0. + */ + public function charsUntil($bytes, $max = null) + { + if ($this->char >= $this->EOF) { + return false; + } + + if (0 === $max || $max) { + $len = strcspn($this->data, $bytes, $this->char, $max); + } else { + $len = strcspn($this->data, $bytes, $this->char); + } + + $string = (string) substr($this->data, $this->char, $len); + $this->char += $len; + + return $string; + } + + /** + * Returns the string so long as $bytes matches. + * + * Matches as far as possible with a certain set of bytes + * and returns the matched substring. + * + * @param string $bytes A mask of bytes to match. If ANY byte in this mask matches the + * current char, the pointer advances and the char is part of the + * substring. + * @param int $max The max number of chars to read. + * + * @return string + */ + public function charsWhile($bytes, $max = null) + { + if ($this->char >= $this->EOF) { + return false; + } + + if (0 === $max || $max) { + $len = strspn($this->data, $bytes, $this->char, $max); + } else { + $len = strspn($this->data, $bytes, $this->char); + } + $string = (string) substr($this->data, $this->char, $len); + $this->char += $len; + + return $string; + } + + /** + * Unconsume characters. + * + * @param int $howMany The number of characters to unconsume. + */ + public function unconsume($howMany = 1) + { + if (($this->char - $howMany) >= 0) { + $this->char -= $howMany; + } + } + + /** + * Look ahead without moving cursor. + */ + public function peek() + { + if (($this->char + 1) <= $this->EOF) { + return $this->data[$this->char + 1]; + } + + return false; + } + + #[\ReturnTypeWillChange] + public function key() + { + return $this->char; + } +} diff --git a/masterminds/html5/src/HTML5/Parser/Tokenizer.php b/masterminds/html5/src/HTML5/Parser/Tokenizer.php new file mode 100644 index 000000000..e8b4aa098 --- /dev/null +++ b/masterminds/html5/src/HTML5/Parser/Tokenizer.php @@ -0,0 +1,1214 @@ +scanner = $scanner; + $this->events = $eventHandler; + $this->mode = $mode; + } + + /** + * Begin parsing. + * + * This will begin scanning the document, tokenizing as it goes. + * Tokens are emitted into the event handler. + * + * Tokenizing will continue until the document is completely + * read. Errors are emitted into the event handler, but + * the parser will attempt to continue parsing until the + * entire input stream is read. + */ + public function parse() + { + do { + $this->consumeData(); + // FIXME: Add infinite loop protection. + } while ($this->carryOn); + } + + /** + * Set the text mode for the character data reader. + * + * HTML5 defines three different modes for reading text: + * - Normal: Read until a tag is encountered. + * - RCDATA: Read until a tag is encountered, but skip a few otherwise- + * special characters. + * - Raw: Read until a special closing tag is encountered (viz. pre, script) + * + * This allows those modes to be set. + * + * Normally, setting is done by the event handler via a special return code on + * startTag(), but it can also be set manually using this function. + * + * @param int $textmode One of Elements::TEXT_*. + * @param string $untilTag The tag that should stop RAW or RCDATA mode. Normal mode does not + * use this indicator. + */ + public function setTextMode($textmode, $untilTag = null) + { + $this->textMode = $textmode & (Elements::TEXT_RAW | Elements::TEXT_RCDATA); + $this->untilTag = $untilTag; + } + + /** + * Consume a character and make a move. + * HTML5 8.2.4.1. + */ + protected function consumeData() + { + $tok = $this->scanner->current(); + + if ('&' === $tok) { + // Character reference + $ref = $this->decodeCharacterReference(); + $this->buffer($ref); + + $tok = $this->scanner->current(); + } + + // Parse tag + if ('<' === $tok) { + // Any buffered text data can go out now. + $this->flushBuffer(); + + $tok = $this->scanner->next(); + + if (false === $tok) { + // end of string + $this->parseError('Illegal tag opening'); + } elseif ('!' === $tok) { + $this->markupDeclaration(); + } elseif ('/' === $tok) { + $this->endTag(); + } elseif ('?' === $tok) { + $this->processingInstruction(); + } elseif ($this->is_alpha($tok)) { + $this->tagName(); + } else { + $this->parseError('Illegal tag opening'); + // TODO is this necessary ? + $this->characterData(); + } + + $tok = $this->scanner->current(); + } + + if (false === $tok) { + // Handle end of document + $this->eof(); + } else { + // Parse character + switch ($this->textMode) { + case Elements::TEXT_RAW: + $this->rawText($tok); + break; + + case Elements::TEXT_RCDATA: + $this->rcdata($tok); + break; + + default: + if ('<' === $tok || '&' === $tok) { + break; + } + + // NULL character + if ("\00" === $tok) { + $this->parseError('Received null character.'); + + $this->text .= $tok; + $this->scanner->consume(); + + break; + } + + $this->text .= $this->scanner->charsUntil("<&\0"); + } + } + + return $this->carryOn; + } + + /** + * Parse anything that looks like character data. + * + * Different rules apply based on the current text mode. + * + * @see Elements::TEXT_RAW Elements::TEXT_RCDATA. + */ + protected function characterData() + { + $tok = $this->scanner->current(); + if (false === $tok) { + return false; + } + switch ($this->textMode) { + case Elements::TEXT_RAW: + return $this->rawText($tok); + case Elements::TEXT_RCDATA: + return $this->rcdata($tok); + default: + if ('<' === $tok || '&' === $tok) { + return false; + } + + return $this->text($tok); + } + } + + /** + * This buffers the current token as character data. + * + * @param string $tok The current token. + * + * @return bool + */ + protected function text($tok) + { + // This should never happen... + if (false === $tok) { + return false; + } + + // NULL character + if ("\00" === $tok) { + $this->parseError('Received null character.'); + } + + $this->buffer($tok); + $this->scanner->consume(); + + return true; + } + + /** + * Read text in RAW mode. + * + * @param string $tok The current token. + * + * @return bool + */ + protected function rawText($tok) + { + if (is_null($this->untilTag)) { + return $this->text($tok); + } + + $sequence = 'untilTag . '>'; + $txt = $this->readUntilSequence($sequence); + $this->events->text($txt); + $this->setTextMode(0); + + return $this->endTag(); + } + + /** + * Read text in RCDATA mode. + * + * @param string $tok The current token. + * + * @return bool + */ + protected function rcdata($tok) + { + if (is_null($this->untilTag)) { + return $this->text($tok); + } + + $sequence = 'untilTag; + $txt = ''; + + $caseSensitive = !Elements::isHtml5Element($this->untilTag); + while (false !== $tok && !('<' == $tok && ($this->scanner->sequenceMatches($sequence, $caseSensitive)))) { + if ('&' == $tok) { + $txt .= $this->decodeCharacterReference(); + $tok = $this->scanner->current(); + } else { + $txt .= $tok; + $tok = $this->scanner->next(); + } + } + $len = strlen($sequence); + $this->scanner->consume($len); + $len += $this->scanner->whitespace(); + if ('>' !== $this->scanner->current()) { + $this->parseError('Unclosed RCDATA end tag'); + } + + $this->scanner->unconsume($len); + $this->events->text($txt); + $this->setTextMode(0); + + return $this->endTag(); + } + + /** + * If the document is read, emit an EOF event. + */ + protected function eof() + { + // fprintf(STDOUT, "EOF"); + $this->flushBuffer(); + $this->events->eof(); + $this->carryOn = false; + } + + /** + * Look for markup. + */ + protected function markupDeclaration() + { + $tok = $this->scanner->next(); + + // Comment: + if ('-' == $tok && '-' == $this->scanner->peek()) { + $this->scanner->consume(2); + + return $this->comment(); + } elseif ('D' == $tok || 'd' == $tok) { // Doctype + return $this->doctype(); + } elseif ('[' == $tok) { // CDATA section + return $this->cdataSection(); + } + + // FINISH + $this->parseError('Expected . Emit an empty comment because 8.2.4.46 says to. + if ('>' == $tok) { + // Parse error. Emit the comment token. + $this->parseError("Expected comment data, got '>'"); + $this->events->comment(''); + $this->scanner->consume(); + + return true; + } + + // Replace NULL with the replacement char. + if ("\0" == $tok) { + $tok = UTF8Utils::FFFD; + } + while (!$this->isCommentEnd()) { + $comment .= $tok; + $tok = $this->scanner->next(); + } + + $this->events->comment($comment); + $this->scanner->consume(); + + return true; + } + + /** + * Check if the scanner has reached the end of a comment. + * + * @return bool + */ + protected function isCommentEnd() + { + $tok = $this->scanner->current(); + + // EOF + if (false === $tok) { + // Hit the end. + $this->parseError('Unexpected EOF in a comment.'); + + return true; + } + + // If next two tokens are not '--', not the end. + if ('-' != $tok || '-' != $this->scanner->peek()) { + return false; + } + + $this->scanner->consume(2); // Consume '-' and one of '!' or '>' + + // Test for '>' + if ('>' == $this->scanner->current()) { + return true; + } + // Test for '!>' + if ('!' == $this->scanner->current() && '>' == $this->scanner->peek()) { + $this->scanner->consume(); // Consume the last '>' + return true; + } + // Unread '-' and one of '!' or '>'; + $this->scanner->unconsume(2); + + return false; + } + + /** + * Parse a DOCTYPE. + * + * Parse a DOCTYPE declaration. This method has strong bearing on whether or + * not Quirksmode is enabled on the event handler. + * + * @todo This method is a little long. Should probably refactor. + * + * @return bool + */ + protected function doctype() + { + // Check that string is DOCTYPE. + if ($this->scanner->sequenceMatches('DOCTYPE', false)) { + $this->scanner->consume(7); + } else { + $chars = $this->scanner->charsWhile('DOCTYPEdoctype'); + $this->parseError('Expected DOCTYPE, got %s', $chars); + + return $this->bogusComment('scanner->whitespace(); + $tok = $this->scanner->current(); + + // EOF: die. + if (false === $tok) { + $this->events->doctype('html5', EventHandler::DOCTYPE_NONE, '', true); + $this->eof(); + + return true; + } + + // NULL char: convert. + if ("\0" === $tok) { + $this->parseError('Unexpected null character in DOCTYPE.'); + } + + $stop = " \n\f>"; + $doctypeName = $this->scanner->charsUntil($stop); + // Lowercase ASCII, replace \0 with FFFD + $doctypeName = strtolower(strtr($doctypeName, "\0", UTF8Utils::FFFD)); + + $tok = $this->scanner->current(); + + // If false, emit a parse error, DOCTYPE, and return. + if (false === $tok) { + $this->parseError('Unexpected EOF in DOCTYPE declaration.'); + $this->events->doctype($doctypeName, EventHandler::DOCTYPE_NONE, null, true); + + return true; + } + + // Short DOCTYPE, like + if ('>' == $tok) { + // DOCTYPE without a name. + if (0 == strlen($doctypeName)) { + $this->parseError('Expected a DOCTYPE name. Got nothing.'); + $this->events->doctype($doctypeName, 0, null, true); + $this->scanner->consume(); + + return true; + } + $this->events->doctype($doctypeName); + $this->scanner->consume(); + + return true; + } + $this->scanner->whitespace(); + + $pub = strtoupper($this->scanner->getAsciiAlpha()); + $white = $this->scanner->whitespace(); + + // Get ID, and flag it as pub or system. + if (('PUBLIC' == $pub || 'SYSTEM' == $pub) && $white > 0) { + // Get the sys ID. + $type = 'PUBLIC' == $pub ? EventHandler::DOCTYPE_PUBLIC : EventHandler::DOCTYPE_SYSTEM; + $id = $this->quotedString("\0>"); + if (false === $id) { + $this->events->doctype($doctypeName, $type, $pub, false); + + return true; + } + + // Premature EOF. + if (false === $this->scanner->current()) { + $this->parseError('Unexpected EOF in DOCTYPE'); + $this->events->doctype($doctypeName, $type, $id, true); + + return true; + } + + // Well-formed complete DOCTYPE. + $this->scanner->whitespace(); + if ('>' == $this->scanner->current()) { + $this->events->doctype($doctypeName, $type, $id, false); + $this->scanner->consume(); + + return true; + } + + // If we get here, we have scanner->charsUntil('>'); + $this->parseError('Malformed DOCTYPE.'); + $this->events->doctype($doctypeName, $type, $id, true); + $this->scanner->consume(); + + return true; + } + + // Else it's a bogus DOCTYPE. + // Consume to > and trash. + $this->scanner->charsUntil('>'); + + $this->parseError('Expected PUBLIC or SYSTEM. Got %s.', $pub); + $this->events->doctype($doctypeName, 0, null, true); + $this->scanner->consume(); + + return true; + } + + /** + * Utility for reading a quoted string. + * + * @param string $stopchars Characters (in addition to a close-quote) that should stop the string. + * E.g. sometimes '>' is higher precedence than '"' or "'". + * + * @return mixed String if one is found (quotations omitted). + */ + protected function quotedString($stopchars) + { + $tok = $this->scanner->current(); + if ('"' == $tok || "'" == $tok) { + $this->scanner->consume(); + $ret = $this->scanner->charsUntil($tok . $stopchars); + if ($this->scanner->current() == $tok) { + $this->scanner->consume(); + } else { + // Parse error because no close quote. + $this->parseError('Expected %s, got %s', $tok, $this->scanner->current()); + } + + return $ret; + } + + return false; + } + + /** + * Handle a CDATA section. + * + * @return bool + */ + protected function cdataSection() + { + $cdata = ''; + $this->scanner->consume(); + + $chars = $this->scanner->charsWhile('CDAT'); + if ('CDATA' != $chars || '[' != $this->scanner->current()) { + $this->parseError('Expected [CDATA[, got %s', $chars); + + return $this->bogusComment('scanner->next(); + do { + if (false === $tok) { + $this->parseError('Unexpected EOF inside CDATA.'); + $this->bogusComment('scanner->next(); + } while (!$this->scanner->sequenceMatches(']]>')); + + // Consume ]]> + $this->scanner->consume(3); + + $this->events->cdata($cdata); + + return true; + } + + // ================================================================ + // Non-HTML5 + // ================================================================ + + /** + * Handle a processing instruction. + * + * XML processing instructions are supposed to be ignored in HTML5, + * treated as "bogus comments". However, since we're not a user + * agent, we allow them. We consume until ?> and then issue a + * EventListener::processingInstruction() event. + * + * @return bool + */ + protected function processingInstruction() + { + if ('?' != $this->scanner->current()) { + return false; + } + + $tok = $this->scanner->next(); + $procName = $this->scanner->getAsciiAlpha(); + $white = $this->scanner->whitespace(); + + // If not a PI, send to bogusComment. + if (0 == strlen($procName) || 0 == $white || false == $this->scanner->current()) { + $this->parseError("Expected processing instruction name, got $tok"); + $this->bogusComment('. + while (!('?' == $this->scanner->current() && '>' == $this->scanner->peek())) { + $data .= $this->scanner->current(); + + $tok = $this->scanner->next(); + if (false === $tok) { + $this->parseError('Unexpected EOF in processing instruction.'); + $this->events->processingInstruction($procName, $data); + + return true; + } + } + + $this->scanner->consume(2); // Consume the closing tag + $this->events->processingInstruction($procName, $data); + + return true; + } + + // ================================================================ + // UTILITY FUNCTIONS + // ================================================================ + + /** + * Read from the input stream until we get to the desired sequene + * or hit the end of the input stream. + * + * @param string $sequence + * + * @return string + */ + protected function readUntilSequence($sequence) + { + $buffer = ''; + + // Optimization for reading larger blocks faster. + $first = substr($sequence, 0, 1); + while (false !== $this->scanner->current()) { + $buffer .= $this->scanner->charsUntil($first); + + // Stop as soon as we hit the stopping condition. + if ($this->scanner->sequenceMatches($sequence, false)) { + return $buffer; + } + $buffer .= $this->scanner->current(); + $this->scanner->consume(); + } + + // If we get here, we hit the EOF. + $this->parseError('Unexpected EOF during text read.'); + + return $buffer; + } + + /** + * Check if upcomming chars match the given sequence. + * + * This will read the stream for the $sequence. If it's + * found, this will return true. If not, return false. + * Since this unconsumes any chars it reads, the caller + * will still need to read the next sequence, even if + * this returns true. + * + * Example: $this->scanner->sequenceMatches('') will + * see if the input stream is at the start of a + * '' string. + * + * @param string $sequence + * @param bool $caseSensitive + * + * @return bool + */ + protected function sequenceMatches($sequence, $caseSensitive = true) + { + @trigger_error(__METHOD__ . ' method is deprecated since version 2.4 and will be removed in 3.0. Use Scanner::sequenceMatches() instead.', E_USER_DEPRECATED); + + return $this->scanner->sequenceMatches($sequence, $caseSensitive); + } + + /** + * Send a TEXT event with the contents of the text buffer. + * + * This emits an EventHandler::text() event with the current contents of the + * temporary text buffer. (The buffer is used to group as much PCDATA + * as we can instead of emitting lots and lots of TEXT events.) + */ + protected function flushBuffer() + { + if ('' === $this->text) { + return; + } + $this->events->text($this->text); + $this->text = ''; + } + + /** + * Add text to the temporary buffer. + * + * @see flushBuffer() + * + * @param string $str + */ + protected function buffer($str) + { + $this->text .= $str; + } + + /** + * Emit a parse error. + * + * A parse error always returns false because it never consumes any + * characters. + * + * @param string $msg + * + * @return string + */ + protected function parseError($msg) + { + $args = func_get_args(); + + if (count($args) > 1) { + array_shift($args); + $msg = vsprintf($msg, $args); + } + + $line = $this->scanner->currentLine(); + $col = $this->scanner->columnOffset(); + $this->events->parseError($msg, $line, $col); + + return false; + } + + /** + * Decode a character reference and return the string. + * + * If $inAttribute is set to true, a bare & will be returned as-is. + * + * @param bool $inAttribute Set to true if the text is inside of an attribute value. + * false otherwise. + * + * @return string + */ + protected function decodeCharacterReference($inAttribute = false) + { + // Next char after &. + $tok = $this->scanner->next(); + $start = $this->scanner->position(); + + if (false === $tok) { + return '&'; + } + + // These indicate not an entity. We return just + // the &. + if ("\t" === $tok || "\n" === $tok || "\f" === $tok || ' ' === $tok || '&' === $tok || '<' === $tok) { + // $this->scanner->next(); + return '&'; + } + + // Numeric entity + if ('#' === $tok) { + $tok = $this->scanner->next(); + + if (false === $tok) { + $this->parseError('Expected &#DEC; &#HEX;, got EOF'); + $this->scanner->unconsume(1); + + return '&'; + } + + // Hexidecimal encoding. + // X[0-9a-fA-F]+; + // x[0-9a-fA-F]+; + if ('x' === $tok || 'X' === $tok) { + $tok = $this->scanner->next(); // Consume x + + // Convert from hex code to char. + $hex = $this->scanner->getHex(); + if (empty($hex)) { + $this->parseError('Expected &#xHEX;, got &#x%s', $tok); + // We unconsume because we don't know what parser rules might + // be in effect for the remaining chars. For example. '&#>' + // might result in a specific parsing rule inside of tag + // contexts, while not inside of pcdata context. + $this->scanner->unconsume(2); + + return '&'; + } + $entity = CharacterReference::lookupHex($hex); + } // Decimal encoding. + // [0-9]+; + else { + // Convert from decimal to char. + $numeric = $this->scanner->getNumeric(); + if (false === $numeric) { + $this->parseError('Expected &#DIGITS;, got &#%s', $tok); + $this->scanner->unconsume(2); + + return '&'; + } + $entity = CharacterReference::lookupDecimal($numeric); + } + } elseif ('=' === $tok && $inAttribute) { + return '&'; + } else { // String entity. + // Attempt to consume a string up to a ';'. + // [a-zA-Z0-9]+; + $cname = $this->scanner->getAsciiAlphaNum(); + $entity = CharacterReference::lookupName($cname); + + // When no entity is found provide the name of the unmatched string + // and continue on as the & is not part of an entity. The & will + // be converted to & elsewhere. + if (null === $entity) { + if (!$inAttribute || '' === $cname) { + $this->parseError("No match in entity table for '%s'", $cname); + } + $this->scanner->unconsume($this->scanner->position() - $start); + + return '&'; + } + } + + // The scanner has advanced the cursor for us. + $tok = $this->scanner->current(); + + // We have an entity. We're done here. + if (';' === $tok) { + $this->scanner->consume(); + + return $entity; + } + + // Failing to match ; means unconsume the entire string. + $this->scanner->unconsume($this->scanner->position() - $start); + + $this->parseError('Expected &ENTITY;, got &ENTITY%s (no trailing ;) ', $tok); + + return '&'; + } + + /** + * Checks whether a (single-byte) character is an ASCII letter or not. + * + * @param string $input A single-byte string + * + * @return bool True if it is a letter, False otherwise + */ + protected function is_alpha($input) + { + $code = ord($input); + + return ($code >= 97 && $code <= 122) || ($code >= 65 && $code <= 90); + } +} diff --git a/masterminds/html5/src/HTML5/Parser/TreeBuildingRules.php b/masterminds/html5/src/HTML5/Parser/TreeBuildingRules.php new file mode 100644 index 000000000..00d3951fd --- /dev/null +++ b/masterminds/html5/src/HTML5/Parser/TreeBuildingRules.php @@ -0,0 +1,127 @@ + 1, + 'dd' => 1, + 'dt' => 1, + 'rt' => 1, + 'rp' => 1, + 'tr' => 1, + 'th' => 1, + 'td' => 1, + 'thead' => 1, + 'tfoot' => 1, + 'tbody' => 1, + 'table' => 1, + 'optgroup' => 1, + 'option' => 1, + ); + + /** + * Returns true if the given tagname has special processing rules. + */ + public function hasRules($tagname) + { + return isset(static::$tags[$tagname]); + } + + /** + * Evaluate the rule for the current tag name. + * + * This may modify the existing DOM. + * + * @return \DOMElement The new Current DOM element. + */ + public function evaluate($new, $current) + { + switch ($new->tagName) { + case 'li': + return $this->handleLI($new, $current); + case 'dt': + case 'dd': + return $this->handleDT($new, $current); + case 'rt': + case 'rp': + return $this->handleRT($new, $current); + case 'optgroup': + return $this->closeIfCurrentMatches($new, $current, array( + 'optgroup', + )); + case 'option': + return $this->closeIfCurrentMatches($new, $current, array( + 'option', + )); + case 'tr': + return $this->closeIfCurrentMatches($new, $current, array( + 'tr', + )); + case 'td': + case 'th': + return $this->closeIfCurrentMatches($new, $current, array( + 'th', + 'td', + )); + case 'tbody': + case 'thead': + case 'tfoot': + case 'table': // Spec isn't explicit about this, but it's necessary. + + return $this->closeIfCurrentMatches($new, $current, array( + 'thead', + 'tfoot', + 'tbody', + )); + } + + return $current; + } + + protected function handleLI($ele, $current) + { + return $this->closeIfCurrentMatches($ele, $current, array( + 'li', + )); + } + + protected function handleDT($ele, $current) + { + return $this->closeIfCurrentMatches($ele, $current, array( + 'dt', + 'dd', + )); + } + + protected function handleRT($ele, $current) + { + return $this->closeIfCurrentMatches($ele, $current, array( + 'rt', + 'rp', + )); + } + + protected function closeIfCurrentMatches($ele, $current, $match) + { + if (in_array($current->tagName, $match, true)) { + $current->parentNode->appendChild($ele); + } else { + $current->appendChild($ele); + } + + return $ele; + } +} diff --git a/masterminds/html5/src/HTML5/Parser/UTF8Utils.php b/masterminds/html5/src/HTML5/Parser/UTF8Utils.php new file mode 100644 index 000000000..4405e4cc0 --- /dev/null +++ b/masterminds/html5/src/HTML5/Parser/UTF8Utils.php @@ -0,0 +1,177 @@ + + +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. +*/ + +use Masterminds\HTML5\Exception; + +class UTF8Utils +{ + /** + * The Unicode replacement character. + */ + const FFFD = "\xEF\xBF\xBD"; + + /** + * Count the number of characters in a string. + * UTF-8 aware. This will try (in order) iconv, MB, and finally a custom counter. + * + * @param string $string + * + * @return int + */ + public static function countChars($string) + { + // Get the length for the string we need. + if (function_exists('mb_strlen')) { + return mb_strlen($string, 'utf-8'); + } + + if (function_exists('iconv_strlen')) { + return iconv_strlen($string, 'utf-8'); + } + + $count = count_chars($string); + + // 0x80 = 0x7F - 0 + 1 (one added to get inclusive range) + // 0x33 = 0xF4 - 0x2C + 1 (one added to get inclusive range) + return array_sum(array_slice($count, 0, 0x80)) + array_sum(array_slice($count, 0xC2, 0x33)); + } + + /** + * Convert data from the given encoding to UTF-8. + * + * This has not yet been tested with charactersets other than UTF-8. + * It should work with ISO-8859-1/-13 and standard Latin Win charsets. + * + * @param string $data The data to convert + * @param string $encoding A valid encoding. Examples: http://www.php.net/manual/en/mbstring.supported-encodings.php + * + * @return string + */ + public static function convertToUTF8($data, $encoding = 'UTF-8') + { + /* + * From the HTML5 spec: Given an encoding, the bytes in the input stream must be converted + * to Unicode characters for the tokeniser, as described by the rules for that encoding, + * except that the leading U+FEFF BYTE ORDER MARK character, if any, must not be stripped + * by the encoding layer (it is stripped by the rule below). Bytes or sequences of bytes + * in the original byte stream that could not be converted to Unicode characters must be + * converted to U+FFFD REPLACEMENT CHARACTER code points. + */ + + // mb_convert_encoding is chosen over iconv because of a bug. The best + // details for the bug are on http://us1.php.net/manual/en/function.iconv.php#108643 + // which contains links to the actual but reports as well as work around + // details. + if (function_exists('mb_convert_encoding')) { + // mb library has the following behaviors: + // - UTF-16 surrogates result in false. + // - Overlongs and outside Plane 16 result in empty strings. + + // Before we run mb_convert_encoding we need to tell it what to do with + // characters it does not know. This could be different than the parent + // application executing this library so we store the value, change it + // to our needs, and then change it back when we are done. This feels + // a little excessive and it would be great if there was a better way. + $save = mb_substitute_character(); + mb_substitute_character('none'); + $data = mb_convert_encoding($data, 'UTF-8', $encoding); + mb_substitute_character($save); + } + // @todo Get iconv running in at least some environments if that is possible. + elseif (function_exists('iconv') && 'auto' !== $encoding) { + // fprintf(STDOUT, "iconv found\n"); + // iconv has the following behaviors: + // - Overlong representations are ignored. + // - Beyond Plane 16 is replaced with a lower char. + // - Incomplete sequences generate a warning. + $data = @iconv($encoding, 'UTF-8//IGNORE', $data); + } else { + throw new Exception('Not implemented, please install mbstring or iconv'); + } + + /* + * One leading U+FEFF BYTE ORDER MARK character must be ignored if any are present. + */ + if ("\xEF\xBB\xBF" === substr($data, 0, 3)) { + $data = substr($data, 3); + } + + return $data; + } + + /** + * Checks for Unicode code points that are not valid in a document. + * + * @param string $data A string to analyze + * + * @return array An array of (string) error messages produced by the scanning + */ + public static function checkForIllegalCodepoints($data) + { + // Vestigal error handling. + $errors = array(); + + /* + * All U+0000 null characters in the input must be replaced by U+FFFD REPLACEMENT CHARACTERs. + * Any occurrences of such characters is a parse error. + */ + for ($i = 0, $count = substr_count($data, "\0"); $i < $count; ++$i) { + $errors[] = 'null-character'; + } + + /* + * Any occurrences of any characters in the ranges U+0001 to U+0008, U+000B, U+000E to U+001F, U+007F + * to U+009F, U+D800 to U+DFFF , U+FDD0 to U+FDEF, and characters U+FFFE, U+FFFF, U+1FFFE, U+1FFFF, + * U+2FFFE, U+2FFFF, U+3FFFE, U+3FFFF, U+4FFFE, U+4FFFF, U+5FFFE, U+5FFFF, U+6FFFE, U+6FFFF, U+7FFFE, + * U+7FFFF, U+8FFFE, U+8FFFF, U+9FFFE, U+9FFFF, U+AFFFE, U+AFFFF, U+BFFFE, U+BFFFF, U+CFFFE, U+CFFFF, + * U+DFFFE, U+DFFFF, U+EFFFE, U+EFFFF, U+FFFFE, U+FFFFF, U+10FFFE, and U+10FFFF are parse errors. + * (These are all control characters or permanently undefined Unicode characters.) + */ + // Check PCRE is loaded. + $count = preg_match_all( + '/(?: + [\x01-\x08\x0B\x0E-\x1F\x7F] # U+0001 to U+0008, U+000B, U+000E to U+001F and U+007F + | + \xC2[\x80-\x9F] # U+0080 to U+009F + | + \xED(?:\xA0[\x80-\xFF]|[\xA1-\xBE][\x00-\xFF]|\xBF[\x00-\xBF]) # U+D800 to U+DFFFF + | + \xEF\xB7[\x90-\xAF] # U+FDD0 to U+FDEF + | + \xEF\xBF[\xBE\xBF] # U+FFFE and U+FFFF + | + [\xF0-\xF4][\x8F-\xBF]\xBF[\xBE\xBF] # U+nFFFE and U+nFFFF (1 <= n <= 10_{16}) + )/x', $data, $matches); + for ($i = 0; $i < $count; ++$i) { + $errors[] = 'invalid-codepoint'; + } + + return $errors; + } +} diff --git a/masterminds/html5/src/HTML5/Serializer/HTML5Entities.php b/masterminds/html5/src/HTML5/Serializer/HTML5Entities.php new file mode 100644 index 000000000..e9421a12d --- /dev/null +++ b/masterminds/html5/src/HTML5/Serializer/HTML5Entities.php @@ -0,0 +1,1533 @@ + ' ', + "\n" => ' ', + '!' => '!', + '"' => '"', + '#' => '#', + '$' => '$', + '%' => '%', + '&' => '&', + '\'' => ''', + '(' => '(', + ')' => ')', + '*' => '*', + '+' => '+', + ',' => ',', + '.' => '.', + '/' => '/', + ':' => ':', + ';' => ';', + '<' => '<', + '<⃒' => '&nvlt', + '=' => '=', + '=⃥' => '&bne', + '>' => '>', + '>⃒' => '&nvgt', + '?' => '?', + '@' => '@', + '[' => '[', + '\\' => '\', + ']' => ']', + '^' => '^', + '_' => '_', + '`' => '`', + 'fj' => '&fjlig', + '{' => '{', + '|' => '|', + '}' => '}', + ' ' => ' ', + '¡' => '¡', + '¢' => '¢', + '£' => '£', + '¤' => '¤', + '¥' => '¥', + '¦' => '¦', + '§' => '§', + '¨' => '¨', + '©' => '©', + 'ª' => 'ª', + '«' => '«', + '¬' => '¬', + '­' => '­', + '®' => '®', + '¯' => '¯', + '°' => '°', + '±' => '±', + '²' => '²', + '³' => '³', + '´' => '´', + 'µ' => 'µ', + '¶' => '¶', + '·' => '·', + '¸' => '¸', + '¹' => '¹', + 'º' => 'º', + '»' => '»', + '¼' => '¼', + '½' => '½', + '¾' => '¾', + '¿' => '¿', + 'À' => 'À', + 'Á' => 'Á', + 'Â' => 'Â', + 'Ã' => 'Ã', + 'Ä' => 'Ä', + 'Å' => 'Å', + 'Æ' => 'Æ', + 'Ç' => 'Ç', + 'È' => 'È', + 'É' => 'É', + 'Ê' => 'Ê', + 'Ë' => 'Ë', + 'Ì' => 'Ì', + 'Í' => 'Í', + 'Î' => 'Î', + 'Ï' => 'Ï', + 'Ð' => 'Ð', + 'Ñ' => 'Ñ', + 'Ò' => 'Ò', + 'Ó' => 'Ó', + 'Ô' => 'Ô', + 'Õ' => 'Õ', + 'Ö' => 'Ö', + '×' => '×', + 'Ø' => 'Ø', + 'Ù' => 'Ù', + 'Ú' => 'Ú', + 'Û' => 'Û', + 'Ü' => 'Ü', + 'Ý' => 'Ý', + 'Þ' => 'Þ', + 'ß' => 'ß', + 'à' => 'à', + 'á' => 'á', + 'â' => 'â', + 'ã' => 'ã', + 'ä' => 'ä', + 'å' => 'å', + 'æ' => 'æ', + 'ç' => 'ç', + 'è' => 'è', + 'é' => 'é', + 'ê' => 'ê', + 'ë' => 'ë', + 'ì' => 'ì', + 'í' => 'í', + 'î' => 'î', + 'ï' => 'ï', + 'ð' => 'ð', + 'ñ' => 'ñ', + 'ò' => 'ò', + 'ó' => 'ó', + 'ô' => 'ô', + 'õ' => 'õ', + 'ö' => 'ö', + '÷' => '÷', + 'ø' => 'ø', + 'ù' => 'ù', + 'ú' => 'ú', + 'û' => 'û', + 'ü' => 'ü', + 'ý' => 'ý', + 'þ' => 'þ', + 'ÿ' => 'ÿ', + 'Ā' => 'Ā', + 'ā' => 'ā', + 'Ă' => 'Ă', + 'ă' => 'ă', + 'Ą' => 'Ą', + 'ą' => 'ą', + 'Ć' => 'Ć', + 'ć' => 'ć', + 'Ĉ' => 'Ĉ', + 'ĉ' => 'ĉ', + 'Ċ' => 'Ċ', + 'ċ' => 'ċ', + 'Č' => 'Č', + 'č' => 'č', + 'Ď' => 'Ď', + 'ď' => 'ď', + 'Đ' => 'Đ', + 'đ' => 'đ', + 'Ē' => 'Ē', + 'ē' => 'ē', + 'Ė' => 'Ė', + 'ė' => 'ė', + 'Ę' => 'Ę', + 'ę' => 'ę', + 'Ě' => 'Ě', + 'ě' => 'ě', + 'Ĝ' => 'Ĝ', + 'ĝ' => 'ĝ', + 'Ğ' => 'Ğ', + 'ğ' => 'ğ', + 'Ġ' => 'Ġ', + 'ġ' => 'ġ', + 'Ģ' => 'Ģ', + 'Ĥ' => 'Ĥ', + 'ĥ' => 'ĥ', + 'Ħ' => 'Ħ', + 'ħ' => 'ħ', + 'Ĩ' => 'Ĩ', + 'ĩ' => 'ĩ', + 'Ī' => 'Ī', + 'ī' => 'ī', + 'Į' => 'Į', + 'į' => 'į', + 'İ' => 'İ', + 'ı' => 'ı', + 'IJ' => 'IJ', + 'ij' => 'ij', + 'Ĵ' => 'Ĵ', + 'ĵ' => 'ĵ', + 'Ķ' => 'Ķ', + 'ķ' => 'ķ', + 'ĸ' => 'ĸ', + 'Ĺ' => 'Ĺ', + 'ĺ' => 'ĺ', + 'Ļ' => 'Ļ', + 'ļ' => 'ļ', + 'Ľ' => 'Ľ', + 'ľ' => 'ľ', + 'Ŀ' => 'Ŀ', + 'ŀ' => 'ŀ', + 'Ł' => 'Ł', + 'ł' => 'ł', + 'Ń' => 'Ń', + 'ń' => 'ń', + 'Ņ' => 'Ņ', + 'ņ' => 'ņ', + 'Ň' => 'Ň', + 'ň' => 'ň', + 'ʼn' => 'ʼn', + 'Ŋ' => 'Ŋ', + 'ŋ' => 'ŋ', + 'Ō' => 'Ō', + 'ō' => 'ō', + 'Ő' => 'Ő', + 'ő' => 'ő', + 'Œ' => 'Œ', + 'œ' => 'œ', + 'Ŕ' => 'Ŕ', + 'ŕ' => 'ŕ', + 'Ŗ' => 'Ŗ', + 'ŗ' => 'ŗ', + 'Ř' => 'Ř', + 'ř' => 'ř', + 'Ś' => 'Ś', + 'ś' => 'ś', + 'Ŝ' => 'Ŝ', + 'ŝ' => 'ŝ', + 'Ş' => 'Ş', + 'ş' => 'ş', + 'Š' => 'Š', + 'š' => 'š', + 'Ţ' => 'Ţ', + 'ţ' => 'ţ', + 'Ť' => 'Ť', + 'ť' => 'ť', + 'Ŧ' => 'Ŧ', + 'ŧ' => 'ŧ', + 'Ũ' => 'Ũ', + 'ũ' => 'ũ', + 'Ū' => 'Ū', + 'ū' => 'ū', + 'Ŭ' => 'Ŭ', + 'ŭ' => 'ŭ', + 'Ů' => 'Ů', + 'ů' => 'ů', + 'Ű' => 'Ű', + 'ű' => 'ű', + 'Ų' => 'Ų', + 'ų' => 'ų', + 'Ŵ' => 'Ŵ', + 'ŵ' => 'ŵ', + 'Ŷ' => 'Ŷ', + 'ŷ' => 'ŷ', + 'Ÿ' => 'Ÿ', + 'Ź' => 'Ź', + 'ź' => 'ź', + 'Ż' => 'Ż', + 'ż' => 'ż', + 'Ž' => 'Ž', + 'ž' => 'ž', + 'ƒ' => 'ƒ', + 'Ƶ' => 'Ƶ', + 'ǵ' => 'ǵ', + 'ȷ' => 'ȷ', + 'ˆ' => 'ˆ', + 'ˇ' => 'ˇ', + '˘' => '˘', + '˙' => '˙', + '˚' => '˚', + '˛' => '˛', + '˜' => '˜', + '˝' => '˝', + '̑' => '̑', + 'Α' => 'Α', + 'Β' => 'Β', + 'Γ' => 'Γ', + 'Δ' => 'Δ', + 'Ε' => 'Ε', + 'Ζ' => 'Ζ', + 'Η' => 'Η', + 'Θ' => 'Θ', + 'Ι' => 'Ι', + 'Κ' => 'Κ', + 'Λ' => 'Λ', + 'Μ' => 'Μ', + 'Ν' => 'Ν', + 'Ξ' => 'Ξ', + 'Ο' => 'Ο', + 'Π' => 'Π', + 'Ρ' => 'Ρ', + 'Σ' => 'Σ', + 'Τ' => 'Τ', + 'Υ' => 'Υ', + 'Φ' => 'Φ', + 'Χ' => 'Χ', + 'Ψ' => 'Ψ', + 'Ω' => 'Ω', + 'α' => 'α', + 'β' => 'β', + 'γ' => 'γ', + 'δ' => 'δ', + 'ε' => 'ε', + 'ζ' => 'ζ', + 'η' => 'η', + 'θ' => 'θ', + 'ι' => 'ι', + 'κ' => 'κ', + 'λ' => 'λ', + 'μ' => 'μ', + 'ν' => 'ν', + 'ξ' => 'ξ', + 'ο' => 'ο', + 'π' => 'π', + 'ρ' => 'ρ', + 'ς' => 'ς', + 'σ' => 'σ', + 'τ' => 'τ', + 'υ' => 'υ', + 'φ' => 'φ', + 'χ' => 'χ', + 'ψ' => 'ψ', + 'ω' => 'ω', + 'ϑ' => 'ϑ', + 'ϒ' => 'ϒ', + 'ϕ' => 'ϕ', + 'ϖ' => 'ϖ', + 'Ϝ' => 'Ϝ', + 'ϝ' => 'ϝ', + 'ϰ' => 'ϰ', + 'ϱ' => 'ϱ', + 'ϵ' => 'ϵ', + '϶' => '϶', + 'Ё' => 'Ё', + 'Ђ' => 'Ђ', + 'Ѓ' => 'Ѓ', + 'Є' => 'Є', + 'Ѕ' => 'Ѕ', + 'І' => 'І', + 'Ї' => 'Ї', + 'Ј' => 'Ј', + 'Љ' => 'Љ', + 'Њ' => 'Њ', + 'Ћ' => 'Ћ', + 'Ќ' => 'Ќ', + 'Ў' => 'Ў', + 'Џ' => 'Џ', + 'А' => 'А', + 'Б' => 'Б', + 'В' => 'В', + 'Г' => 'Г', + 'Д' => 'Д', + 'Е' => 'Е', + 'Ж' => 'Ж', + 'З' => 'З', + 'И' => 'И', + 'Й' => 'Й', + 'К' => 'К', + 'Л' => 'Л', + 'М' => 'М', + 'Н' => 'Н', + 'О' => 'О', + 'П' => 'П', + 'Р' => 'Р', + 'С' => 'С', + 'Т' => 'Т', + 'У' => 'У', + 'Ф' => 'Ф', + 'Х' => 'Х', + 'Ц' => 'Ц', + 'Ч' => 'Ч', + 'Ш' => 'Ш', + 'Щ' => 'Щ', + 'Ъ' => 'Ъ', + 'Ы' => 'Ы', + 'Ь' => 'Ь', + 'Э' => 'Э', + 'Ю' => 'Ю', + 'Я' => 'Я', + 'а' => 'а', + 'б' => 'б', + 'в' => 'в', + 'г' => 'г', + 'д' => 'д', + 'е' => 'е', + 'ж' => 'ж', + 'з' => 'з', + 'и' => 'и', + 'й' => 'й', + 'к' => 'к', + 'л' => 'л', + 'м' => 'м', + 'н' => 'н', + 'о' => 'о', + 'п' => 'п', + 'р' => 'р', + 'с' => 'с', + 'т' => 'т', + 'у' => 'у', + 'ф' => 'ф', + 'х' => 'х', + 'ц' => 'ц', + 'ч' => 'ч', + 'ш' => 'ш', + 'щ' => 'щ', + 'ъ' => 'ъ', + 'ы' => 'ы', + 'ь' => 'ь', + 'э' => 'э', + 'ю' => 'ю', + 'я' => 'я', + 'ё' => 'ё', + 'ђ' => 'ђ', + 'ѓ' => 'ѓ', + 'є' => 'є', + 'ѕ' => 'ѕ', + 'і' => 'і', + 'ї' => 'ї', + 'ј' => 'ј', + 'љ' => 'љ', + 'њ' => 'њ', + 'ћ' => 'ћ', + 'ќ' => 'ќ', + 'ў' => 'ў', + 'џ' => 'џ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + '​' => '​', + '‌' => '‌', + '‍' => '‍', + '‎' => '‎', + '‏' => '‏', + '‐' => '‐', + '–' => '–', + '—' => '—', + '―' => '―', + '‖' => '‖', + '‘' => '‘', + '’' => '’', + '‚' => '‚', + '“' => '“', + '”' => '”', + '„' => '„', + '†' => '†', + '‡' => '‡', + '•' => '•', + '‥' => '‥', + '…' => '…', + '‰' => '‰', + '‱' => '‱', + '′' => '′', + '″' => '″', + '‴' => '‴', + '‵' => '‵', + '‹' => '‹', + '›' => '›', + '‾' => '‾', + '⁁' => '⁁', + '⁃' => '⁃', + '⁄' => '⁄', + '⁏' => '⁏', + '⁗' => '⁗', + ' ' => ' ', + '  ' => '&ThickSpace', + '⁠' => '⁠', + '⁡' => '⁡', + '⁢' => '⁢', + '⁣' => '⁣', + '€' => '€', + '⃛' => '⃛', + '⃜' => '⃜', + 'ℂ' => 'ℂ', + '℅' => '℅', + 'ℊ' => 'ℊ', + 'ℋ' => 'ℋ', + 'ℌ' => 'ℌ', + 'ℍ' => 'ℍ', + 'ℎ' => 'ℎ', + 'ℏ' => 'ℏ', + 'ℐ' => 'ℐ', + 'ℑ' => 'ℑ', + 'ℒ' => 'ℒ', + 'ℓ' => 'ℓ', + 'ℕ' => 'ℕ', + '№' => '№', + '℗' => '℗', + '℘' => '℘', + 'ℙ' => 'ℙ', + 'ℚ' => 'ℚ', + 'ℛ' => 'ℛ', + 'ℜ' => 'ℜ', + 'ℝ' => 'ℝ', + '℞' => '℞', + '™' => '™', + 'ℤ' => 'ℤ', + '℧' => '℧', + 'ℨ' => 'ℨ', + '℩' => '℩', + 'ℬ' => 'ℬ', + 'ℭ' => 'ℭ', + 'ℯ' => 'ℯ', + 'ℰ' => 'ℰ', + 'ℱ' => 'ℱ', + 'ℳ' => 'ℳ', + 'ℴ' => 'ℴ', + 'ℵ' => 'ℵ', + 'ℶ' => 'ℶ', + 'ℷ' => 'ℷ', + 'ℸ' => 'ℸ', + 'ⅅ' => 'ⅅ', + 'ⅆ' => 'ⅆ', + 'ⅇ' => 'ⅇ', + 'ⅈ' => 'ⅈ', + '⅓' => '⅓', + '⅔' => '⅔', + '⅕' => '⅕', + '⅖' => '⅖', + '⅗' => '⅗', + '⅘' => '⅘', + '⅙' => '⅙', + '⅚' => '⅚', + '⅛' => '⅛', + '⅜' => '⅜', + '⅝' => '⅝', + '⅞' => '⅞', + '←' => '←', + '↑' => '↑', + '→' => '→', + '↓' => '↓', + '↔' => '↔', + '↕' => '↕', + '↖' => '↖', + '↗' => '↗', + '↘' => '↘', + '↙' => '↙', + '↚' => '↚', + '↛' => '↛', + '↝' => '↝', + '↝̸' => '&nrarrw', + '↞' => '↞', + '↟' => '↟', + '↠' => '↠', + '↡' => '↡', + '↢' => '↢', + '↣' => '↣', + '↤' => '↤', + '↥' => '↥', + '↦' => '↦', + '↧' => '↧', + '↩' => '↩', + '↪' => '↪', + '↫' => '↫', + '↬' => '↬', + '↭' => '↭', + '↮' => '↮', + '↰' => '↰', + '↱' => '↱', + '↲' => '↲', + '↳' => '↳', + '↵' => '↵', + '↶' => '↶', + '↷' => '↷', + '↺' => '↺', + '↻' => '↻', + '↼' => '↼', + '↽' => '↽', + '↾' => '↾', + '↿' => '↿', + '⇀' => '⇀', + '⇁' => '⇁', + '⇂' => '⇂', + '⇃' => '⇃', + '⇄' => '⇄', + '⇅' => '⇅', + '⇆' => '⇆', + '⇇' => '⇇', + '⇈' => '⇈', + '⇉' => '⇉', + '⇊' => '⇊', + '⇋' => '⇋', + '⇌' => '⇌', + '⇍' => '⇍', + '⇎' => '⇎', + '⇏' => '⇏', + '⇐' => '⇐', + '⇑' => '⇑', + '⇒' => '⇒', + '⇓' => '⇓', + '⇔' => '⇔', + '⇕' => '⇕', + '⇖' => '⇖', + '⇗' => '⇗', + '⇘' => '⇘', + '⇙' => '⇙', + '⇚' => '⇚', + '⇛' => '⇛', + '⇝' => '⇝', + '⇤' => '⇤', + '⇥' => '⇥', + '⇵' => '⇵', + '⇽' => '⇽', + '⇾' => '⇾', + '⇿' => '⇿', + '∀' => '∀', + '∁' => '∁', + '∂' => '∂', + '∂̸' => '&npart', + '∃' => '∃', + '∄' => '∄', + '∅' => '∅', + '∇' => '∇', + '∈' => '∈', + '∉' => '∉', + '∋' => '∋', + '∌' => '∌', + '∏' => '∏', + '∐' => '∐', + '∑' => '∑', + '−' => '−', + '∓' => '∓', + '∔' => '∔', + '∖' => '∖', + '∗' => '∗', + '∘' => '∘', + '√' => '√', + '∝' => '∝', + '∞' => '∞', + '∟' => '∟', + '∠' => '∠', + '∠⃒' => '&nang', + '∡' => '∡', + '∢' => '∢', + '∣' => '∣', + '∤' => '∤', + '∥' => '∥', + '∦' => '∦', + '∧' => '∧', + '∨' => '∨', + '∩' => '∩', + '∩︀' => '&caps', + '∪' => '∪', + '∪︀' => '&cups', + '∫' => '∫', + '∬' => '∬', + '∭' => '∭', + '∮' => '∮', + '∯' => '∯', + '∰' => '∰', + '∱' => '∱', + '∲' => '∲', + '∳' => '∳', + '∴' => '∴', + '∵' => '∵', + '∶' => '∶', + '∷' => '∷', + '∸' => '∸', + '∺' => '∺', + '∻' => '∻', + '∼' => '∼', + '∼⃒' => '&nvsim', + '∽' => '∽', + '∽̱' => '&race', + '∾' => '∾', + '∾̳' => '&acE', + '∿' => '∿', + '≀' => '≀', + '≁' => '≁', + '≂' => '≂', + '≂̸' => '&nesim', + '≃' => '≃', + '≄' => '≄', + '≅' => '≅', + '≆' => '≆', + '≇' => '≇', + '≈' => '≈', + '≉' => '≉', + '≊' => '≊', + '≋' => '≋', + '≋̸' => '&napid', + '≌' => '≌', + '≍' => '≍', + '≍⃒' => '&nvap', + '≎' => '≎', + '≎̸' => '&nbump', + '≏' => '≏', + '≏̸' => '&nbumpe', + '≐' => '≐', + '≐̸' => '&nedot', + '≑' => '≑', + '≒' => '≒', + '≓' => '≓', + '≔' => '≔', + '≕' => '≕', + '≖' => '≖', + '≗' => '≗', + '≙' => '≙', + '≚' => '≚', + '≜' => '≜', + '≟' => '≟', + '≠' => '≠', + '≡' => '≡', + '≡⃥' => '&bnequiv', + '≢' => '≢', + '≤' => '≤', + '≤⃒' => '&nvle', + '≥' => '≥', + '≥⃒' => '&nvge', + '≦' => '≦', + '≦̸' => '&nlE', + '≧' => '≧', + '≧̸' => '&NotGreaterFullEqual', + '≨' => '≨', + '≨︀' => '&lvertneqq', + '≩' => '≩', + '≩︀' => '&gvertneqq', + '≪' => '≪', + '≪̸' => '&nLtv', + '≪⃒' => '&nLt', + '≫' => '≫', + '≫̸' => '&NotGreaterGreater', + '≫⃒' => '&nGt', + '≬' => '≬', + '≭' => '≭', + '≮' => '≮', + '≯' => '≯', + '≰' => '≰', + '≱' => '≱', + '≲' => '≲', + '≳' => '≳', + '≴' => '≴', + '≵' => '≵', + '≶' => '≶', + '≷' => '≷', + '≸' => '≸', + '≹' => '≹', + '≺' => '≺', + '≻' => '≻', + '≼' => '≼', + '≽' => '≽', + '≾' => '≾', + '≿' => '≿', + '≿̸' => '&NotSucceedsTilde', + '⊀' => '⊀', + '⊁' => '⊁', + '⊂' => '⊂', + '⊂⃒' => '&vnsub', + '⊃' => '⊃', + '⊃⃒' => '&nsupset', + '⊄' => '⊄', + '⊅' => '⊅', + '⊆' => '⊆', + '⊇' => '⊇', + '⊈' => '⊈', + '⊉' => '⊉', + '⊊' => '⊊', + '⊊︀' => '&vsubne', + '⊋' => '⊋', + '⊋︀' => '&vsupne', + '⊍' => '⊍', + '⊎' => '⊎', + '⊏' => '⊏', + '⊏̸' => '&NotSquareSubset', + '⊐' => '⊐', + '⊐̸' => '&NotSquareSuperset', + '⊑' => '⊑', + '⊒' => '⊒', + '⊓' => '⊓', + '⊓︀' => '&sqcaps', + '⊔' => '⊔', + '⊔︀' => '&sqcups', + '⊕' => '⊕', + '⊖' => '⊖', + '⊗' => '⊗', + '⊘' => '⊘', + '⊙' => '⊙', + '⊚' => '⊚', + '⊛' => '⊛', + '⊝' => '⊝', + '⊞' => '⊞', + '⊟' => '⊟', + '⊠' => '⊠', + '⊡' => '⊡', + '⊢' => '⊢', + '⊣' => '⊣', + '⊤' => '⊤', + '⊥' => '⊥', + '⊧' => '⊧', + '⊨' => '⊨', + '⊩' => '⊩', + '⊪' => '⊪', + '⊫' => '⊫', + '⊬' => '⊬', + '⊭' => '⊭', + '⊮' => '⊮', + '⊯' => '⊯', + '⊰' => '⊰', + '⊲' => '⊲', + '⊳' => '⊳', + '⊴' => '⊴', + '⊴⃒' => '&nvltrie', + '⊵' => '⊵', + '⊵⃒' => '&nvrtrie', + '⊶' => '⊶', + '⊷' => '⊷', + '⊸' => '⊸', + '⊹' => '⊹', + '⊺' => '⊺', + '⊻' => '⊻', + '⊽' => '⊽', + '⊾' => '⊾', + '⊿' => '⊿', + '⋀' => '⋀', + '⋁' => '⋁', + '⋂' => '⋂', + '⋃' => '⋃', + '⋄' => '⋄', + '⋅' => '⋅', + '⋆' => '⋆', + '⋇' => '⋇', + '⋈' => '⋈', + '⋉' => '⋉', + '⋊' => '⋊', + '⋋' => '⋋', + '⋌' => '⋌', + '⋍' => '⋍', + '⋎' => '⋎', + '⋏' => '⋏', + '⋐' => '⋐', + '⋑' => '⋑', + '⋒' => '⋒', + '⋓' => '⋓', + '⋔' => '⋔', + '⋕' => '⋕', + '⋖' => '⋖', + '⋗' => '⋗', + '⋘' => '⋘', + '⋘̸' => '&nLl', + '⋙' => '⋙', + '⋙̸' => '&nGg', + '⋚' => '⋚', + '⋚︀' => '&lesg', + '⋛' => '⋛', + '⋛︀' => '&gesl', + '⋞' => '⋞', + '⋟' => '⋟', + '⋠' => '⋠', + '⋡' => '⋡', + '⋢' => '⋢', + '⋣' => '⋣', + '⋦' => '⋦', + '⋧' => '⋧', + '⋨' => '⋨', + '⋩' => '⋩', + '⋪' => '⋪', + '⋫' => '⋫', + '⋬' => '⋬', + '⋭' => '⋭', + '⋮' => '⋮', + '⋯' => '⋯', + '⋰' => '⋰', + '⋱' => '⋱', + '⋲' => '⋲', + '⋳' => '⋳', + '⋴' => '⋴', + '⋵' => '⋵', + '⋵̸' => '¬indot', + '⋶' => '⋶', + '⋷' => '⋷', + '⋹' => '⋹', + '⋹̸' => '¬inE', + '⋺' => '⋺', + '⋻' => '⋻', + '⋼' => '⋼', + '⋽' => '⋽', + '⋾' => '⋾', + '⌅' => '⌅', + '⌆' => '⌆', + '⌈' => '⌈', + '⌉' => '⌉', + '⌊' => '⌊', + '⌋' => '⌋', + '⌌' => '⌌', + '⌍' => '⌍', + '⌎' => '⌎', + '⌏' => '⌏', + '⌐' => '⌐', + '⌒' => '⌒', + '⌓' => '⌓', + '⌕' => '⌕', + '⌖' => '⌖', + '⌜' => '⌜', + '⌝' => '⌝', + '⌞' => '⌞', + '⌟' => '⌟', + '⌢' => '⌢', + '⌣' => '⌣', + '⌭' => '⌭', + '⌮' => '⌮', + '⌶' => '⌶', + '⌽' => '⌽', + '⌿' => '⌿', + '⍼' => '⍼', + '⎰' => '⎰', + '⎱' => '⎱', + '⎴' => '⎴', + '⎵' => '⎵', + '⎶' => '⎶', + '⏜' => '⏜', + '⏝' => '⏝', + '⏞' => '⏞', + '⏟' => '⏟', + '⏢' => '⏢', + '⏧' => '⏧', + '␣' => '␣', + 'Ⓢ' => 'Ⓢ', + '─' => '─', + '│' => '│', + '┌' => '┌', + '┐' => '┐', + '└' => '└', + '┘' => '┘', + '├' => '├', + '┤' => '┤', + '┬' => '┬', + '┴' => '┴', + '┼' => '┼', + '═' => '═', + '║' => '║', + '╒' => '╒', + '╓' => '╓', + '╔' => '╔', + '╕' => '╕', + '╖' => '╖', + '╗' => '╗', + '╘' => '╘', + '╙' => '╙', + '╚' => '╚', + '╛' => '╛', + '╜' => '╜', + '╝' => '╝', + '╞' => '╞', + '╟' => '╟', + '╠' => '╠', + '╡' => '╡', + '╢' => '╢', + '╣' => '╣', + '╤' => '╤', + '╥' => '╥', + '╦' => '╦', + '╧' => '╧', + '╨' => '╨', + '╩' => '╩', + '╪' => '╪', + '╫' => '╫', + '╬' => '╬', + '▀' => '▀', + '▄' => '▄', + '█' => '█', + '░' => '░', + '▒' => '▒', + '▓' => '▓', + '□' => '□', + '▪' => '▪', + '▫' => '▫', + '▭' => '▭', + '▮' => '▮', + '▱' => '▱', + '△' => '△', + '▴' => '▴', + '▵' => '▵', + '▸' => '▸', + '▹' => '▹', + '▽' => '▽', + '▾' => '▾', + '▿' => '▿', + '◂' => '◂', + '◃' => '◃', + '◊' => '◊', + '○' => '○', + '◬' => '◬', + '◯' => '◯', + '◸' => '◸', + '◹' => '◹', + '◺' => '◺', + '◻' => '◻', + '◼' => '◼', + '★' => '★', + '☆' => '☆', + '☎' => '☎', + '♀' => '♀', + '♂' => '♂', + '♠' => '♠', + '♣' => '♣', + '♥' => '♥', + '♦' => '♦', + '♪' => '♪', + '♭' => '♭', + '♮' => '♮', + '♯' => '♯', + '✓' => '✓', + '✗' => '✗', + '✠' => '✠', + '✶' => '✶', + '❘' => '❘', + '❲' => '❲', + '❳' => '❳', + '⟈' => '⟈', + '⟉' => '⟉', + '⟦' => '⟦', + '⟧' => '⟧', + '⟨' => '⟨', + '⟩' => '⟩', + '⟪' => '⟪', + '⟫' => '⟫', + '⟬' => '⟬', + '⟭' => '⟭', + '⟵' => '⟵', + '⟶' => '⟶', + '⟷' => '⟷', + '⟸' => '⟸', + '⟹' => '⟹', + '⟺' => '⟺', + '⟼' => '⟼', + '⟿' => '⟿', + '⤂' => '⤂', + '⤃' => '⤃', + '⤄' => '⤄', + '⤅' => '⤅', + '⤌' => '⤌', + '⤍' => '⤍', + '⤎' => '⤎', + '⤏' => '⤏', + '⤐' => '⤐', + '⤑' => '⤑', + '⤒' => '⤒', + '⤓' => '⤓', + '⤖' => '⤖', + '⤙' => '⤙', + '⤚' => '⤚', + '⤛' => '⤛', + '⤜' => '⤜', + '⤝' => '⤝', + '⤞' => '⤞', + '⤟' => '⤟', + '⤠' => '⤠', + '⤣' => '⤣', + '⤤' => '⤤', + '⤥' => '⤥', + '⤦' => '⤦', + '⤧' => '⤧', + '⤨' => '⤨', + '⤩' => '⤩', + '⤪' => '⤪', + '⤳' => '⤳', + '⤳̸' => '&nrarrc', + '⤵' => '⤵', + '⤶' => '⤶', + '⤷' => '⤷', + '⤸' => '⤸', + '⤹' => '⤹', + '⤼' => '⤼', + '⤽' => '⤽', + '⥅' => '⥅', + '⥈' => '⥈', + '⥉' => '⥉', + '⥊' => '⥊', + '⥋' => '⥋', + '⥎' => '⥎', + '⥏' => '⥏', + '⥐' => '⥐', + '⥑' => '⥑', + '⥒' => '⥒', + '⥓' => '⥓', + '⥔' => '⥔', + '⥕' => '⥕', + '⥖' => '⥖', + '⥗' => '⥗', + '⥘' => '⥘', + '⥙' => '⥙', + '⥚' => '⥚', + '⥛' => '⥛', + '⥜' => '⥜', + '⥝' => '⥝', + '⥞' => '⥞', + '⥟' => '⥟', + '⥠' => '⥠', + '⥡' => '⥡', + '⥢' => '⥢', + '⥣' => '⥣', + '⥤' => '⥤', + '⥥' => '⥥', + '⥦' => '⥦', + '⥧' => '⥧', + '⥨' => '⥨', + '⥩' => '⥩', + '⥪' => '⥪', + '⥫' => '⥫', + '⥬' => '⥬', + '⥭' => '⥭', + '⥮' => '⥮', + '⥯' => '⥯', + '⥰' => '⥰', + '⥱' => '⥱', + '⥲' => '⥲', + '⥳' => '⥳', + '⥴' => '⥴', + '⥵' => '⥵', + '⥶' => '⥶', + '⥸' => '⥸', + '⥹' => '⥹', + '⥻' => '⥻', + '⥼' => '⥼', + '⥽' => '⥽', + '⥾' => '⥾', + '⥿' => '⥿', + '⦅' => '⦅', + '⦆' => '⦆', + '⦋' => '⦋', + '⦌' => '⦌', + '⦍' => '⦍', + '⦎' => '⦎', + '⦏' => '⦏', + '⦐' => '⦐', + '⦑' => '⦑', + '⦒' => '⦒', + '⦓' => '⦓', + '⦔' => '⦔', + '⦕' => '⦕', + '⦖' => '⦖', + '⦚' => '⦚', + '⦜' => '⦜', + '⦝' => '⦝', + '⦤' => '⦤', + '⦥' => '⦥', + '⦦' => '⦦', + '⦧' => '⦧', + '⦨' => '⦨', + '⦩' => '⦩', + '⦪' => '⦪', + '⦫' => '⦫', + '⦬' => '⦬', + '⦭' => '⦭', + '⦮' => '⦮', + '⦯' => '⦯', + '⦰' => '⦰', + '⦱' => '⦱', + '⦲' => '⦲', + '⦳' => '⦳', + '⦴' => '⦴', + '⦵' => '⦵', + '⦶' => '⦶', + '⦷' => '⦷', + '⦹' => '⦹', + '⦻' => '⦻', + '⦼' => '⦼', + '⦾' => '⦾', + '⦿' => '⦿', + '⧀' => '⧀', + '⧁' => '⧁', + '⧂' => '⧂', + '⧃' => '⧃', + '⧄' => '⧄', + '⧅' => '⧅', + '⧉' => '⧉', + '⧍' => '⧍', + '⧎' => '⧎', + '⧏' => '⧏', + '⧏̸' => '&NotLeftTriangleBar', + '⧐' => '⧐', + '⧐̸' => '&NotRightTriangleBar', + '⧜' => '⧜', + '⧝' => '⧝', + '⧞' => '⧞', + '⧣' => '⧣', + '⧤' => '⧤', + '⧥' => '⧥', + '⧫' => '⧫', + '⧴' => '⧴', + '⧶' => '⧶', + '⨀' => '⨀', + '⨁' => '⨁', + '⨂' => '⨂', + '⨄' => '⨄', + '⨆' => '⨆', + '⨌' => '⨌', + '⨍' => '⨍', + '⨐' => '⨐', + '⨑' => '⨑', + '⨒' => '⨒', + '⨓' => '⨓', + '⨔' => '⨔', + '⨕' => '⨕', + '⨖' => '⨖', + '⨗' => '⨗', + '⨢' => '⨢', + '⨣' => '⨣', + '⨤' => '⨤', + '⨥' => '⨥', + '⨦' => '⨦', + '⨧' => '⨧', + '⨩' => '⨩', + '⨪' => '⨪', + '⨭' => '⨭', + '⨮' => '⨮', + '⨯' => '⨯', + '⨰' => '⨰', + '⨱' => '⨱', + '⨳' => '⨳', + '⨴' => '⨴', + '⨵' => '⨵', + '⨶' => '⨶', + '⨷' => '⨷', + '⨸' => '⨸', + '⨹' => '⨹', + '⨺' => '⨺', + '⨻' => '⨻', + '⨼' => '⨼', + '⨿' => '⨿', + '⩀' => '⩀', + '⩂' => '⩂', + '⩃' => '⩃', + '⩄' => '⩄', + '⩅' => '⩅', + '⩆' => '⩆', + '⩇' => '⩇', + '⩈' => '⩈', + '⩉' => '⩉', + '⩊' => '⩊', + '⩋' => '⩋', + '⩌' => '⩌', + '⩍' => '⩍', + '⩐' => '⩐', + '⩓' => '⩓', + '⩔' => '⩔', + '⩕' => '⩕', + '⩖' => '⩖', + '⩗' => '⩗', + '⩘' => '⩘', + '⩚' => '⩚', + '⩛' => '⩛', + '⩜' => '⩜', + '⩝' => '⩝', + '⩟' => '⩟', + '⩦' => '⩦', + '⩪' => '⩪', + '⩭' => '⩭', + '⩭̸' => '&ncongdot', + '⩮' => '⩮', + '⩯' => '⩯', + '⩰' => '⩰', + '⩰̸' => '&napE', + '⩱' => '⩱', + '⩲' => '⩲', + '⩳' => '⩳', + '⩴' => '⩴', + '⩵' => '⩵', + '⩷' => '⩷', + '⩸' => '⩸', + '⩹' => '⩹', + '⩺' => '⩺', + '⩻' => '⩻', + '⩼' => '⩼', + '⩽' => '⩽', + '⩽̸' => '&nles', + '⩾' => '⩾', + '⩾̸' => '&nges', + '⩿' => '⩿', + '⪀' => '⪀', + '⪁' => '⪁', + '⪂' => '⪂', + '⪃' => '⪃', + '⪄' => '⪄', + '⪅' => '⪅', + '⪆' => '⪆', + '⪇' => '⪇', + '⪈' => '⪈', + '⪉' => '⪉', + '⪊' => '⪊', + '⪋' => '⪋', + '⪌' => '⪌', + '⪍' => '⪍', + '⪎' => '⪎', + '⪏' => '⪏', + '⪐' => '⪐', + '⪑' => '⪑', + '⪒' => '⪒', + '⪓' => '⪓', + '⪔' => '⪔', + '⪕' => '⪕', + '⪖' => '⪖', + '⪗' => '⪗', + '⪘' => '⪘', + '⪙' => '⪙', + '⪚' => '⪚', + '⪝' => '⪝', + '⪞' => '⪞', + '⪟' => '⪟', + '⪠' => '⪠', + '⪡' => '⪡', + '⪡̸' => '&NotNestedLessLess', + '⪢' => '⪢', + '⪢̸' => '&NotNestedGreaterGreater', + '⪤' => '⪤', + '⪥' => '⪥', + '⪦' => '⪦', + '⪧' => '⪧', + '⪨' => '⪨', + '⪩' => '⪩', + '⪪' => '⪪', + '⪫' => '⪫', + '⪬' => '⪬', + '⪬︀' => '&smtes', + '⪭' => '⪭', + '⪭︀' => '&lates', + '⪮' => '⪮', + '⪯' => '⪯', + '⪯̸' => '&NotPrecedesEqual', + '⪰' => '⪰', + '⪰̸' => '&NotSucceedsEqual', + '⪳' => '⪳', + '⪴' => '⪴', + '⪵' => '⪵', + '⪶' => '⪶', + '⪷' => '⪷', + '⪸' => '⪸', + '⪹' => '⪹', + '⪺' => '⪺', + '⪻' => '⪻', + '⪼' => '⪼', + '⪽' => '⪽', + '⪾' => '⪾', + '⪿' => '⪿', + '⫀' => '⫀', + '⫁' => '⫁', + '⫂' => '⫂', + '⫃' => '⫃', + '⫄' => '⫄', + '⫅' => '⫅', + '⫅̸' => '&nsubE', + '⫆' => '⫆', + '⫆̸' => '&nsupseteqq', + '⫇' => '⫇', + '⫈' => '⫈', + '⫋' => '⫋', + '⫋︀' => '&vsubnE', + '⫌' => '⫌', + '⫌︀' => '&varsupsetneqq', + '⫏' => '⫏', + '⫐' => '⫐', + '⫑' => '⫑', + '⫒' => '⫒', + '⫓' => '⫓', + '⫔' => '⫔', + '⫕' => '⫕', + '⫖' => '⫖', + '⫗' => '⫗', + '⫘' => '⫘', + '⫙' => '⫙', + '⫚' => '⫚', + '⫛' => '⫛', + '⫤' => '⫤', + '⫦' => '⫦', + '⫧' => '⫧', + '⫨' => '⫨', + '⫩' => '⫩', + '⫫' => '⫫', + '⫬' => '⫬', + '⫭' => '⫭', + '⫮' => '⫮', + '⫯' => '⫯', + '⫰' => '⫰', + '⫱' => '⫱', + '⫲' => '⫲', + '⫳' => '⫳', + '⫽︀' => '&varsupsetneqq', + 'ff' => 'ff', + 'fi' => 'fi', + 'fl' => 'fl', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + '𝒜' => '𝒜', + '𝒞' => '𝒞', + '𝒟' => '𝒟', + '𝒢' => '𝒢', + '𝒥' => '𝒥', + '𝒦' => '𝒦', + '𝒩' => '𝒩', + '𝒪' => '𝒪', + '𝒫' => '𝒫', + '𝒬' => '𝒬', + '𝒮' => '𝒮', + '𝒯' => '𝒯', + '𝒰' => '𝒰', + '𝒱' => '𝒱', + '𝒲' => '𝒲', + '𝒳' => '𝒳', + '𝒴' => '𝒴', + '𝒵' => '𝒵', + '𝒶' => '𝒶', + '𝒷' => '𝒷', + '𝒸' => '𝒸', + '𝒹' => '𝒹', + '𝒻' => '𝒻', + '𝒽' => '𝒽', + '𝒾' => '𝒾', + '𝒿' => '𝒿', + '𝓀' => '𝓀', + '𝓁' => '𝓁', + '𝓂' => '𝓂', + '𝓃' => '𝓃', + '𝓅' => '𝓅', + '𝓆' => '𝓆', + '𝓇' => '𝓇', + '𝓈' => '𝓈', + '𝓉' => '𝓉', + '𝓊' => '𝓊', + '𝓋' => '𝓋', + '𝓌' => '𝓌', + '𝓍' => '𝓍', + '𝓎' => '𝓎', + '𝓏' => '𝓏', + '𝔄' => '𝔄', + '𝔅' => '𝔅', + '𝔇' => '𝔇', + '𝔈' => '𝔈', + '𝔉' => '𝔉', + '𝔊' => '𝔊', + '𝔍' => '𝔍', + '𝔎' => '𝔎', + '𝔏' => '𝔏', + '𝔐' => '𝔐', + '𝔑' => '𝔑', + '𝔒' => '𝔒', + '𝔓' => '𝔓', + '𝔔' => '𝔔', + '𝔖' => '𝔖', + '𝔗' => '𝔗', + '𝔘' => '𝔘', + '𝔙' => '𝔙', + '𝔚' => '𝔚', + '𝔛' => '𝔛', + '𝔜' => '𝔜', + '𝔞' => '𝔞', + '𝔟' => '𝔟', + '𝔠' => '𝔠', + '𝔡' => '𝔡', + '𝔢' => '𝔢', + '𝔣' => '𝔣', + '𝔤' => '𝔤', + '𝔥' => '𝔥', + '𝔦' => '𝔦', + '𝔧' => '𝔧', + '𝔨' => '𝔨', + '𝔩' => '𝔩', + '𝔪' => '𝔪', + '𝔫' => '𝔫', + '𝔬' => '𝔬', + '𝔭' => '𝔭', + '𝔮' => '𝔮', + '𝔯' => '𝔯', + '𝔰' => '𝔰', + '𝔱' => '𝔱', + '𝔲' => '𝔲', + '𝔳' => '𝔳', + '𝔴' => '𝔴', + '𝔵' => '𝔵', + '𝔶' => '𝔶', + '𝔷' => '𝔷', + '𝔸' => '𝔸', + '𝔹' => '𝔹', + '𝔻' => '𝔻', + '𝔼' => '𝔼', + '𝔽' => '𝔽', + '𝔾' => '𝔾', + '𝕀' => '𝕀', + '𝕁' => '𝕁', + '𝕂' => '𝕂', + '𝕃' => '𝕃', + '𝕄' => '𝕄', + '𝕆' => '𝕆', + '𝕊' => '𝕊', + '𝕋' => '𝕋', + '𝕌' => '𝕌', + '𝕍' => '𝕍', + '𝕎' => '𝕎', + '𝕏' => '𝕏', + '𝕐' => '𝕐', + '𝕒' => '𝕒', + '𝕓' => '𝕓', + '𝕔' => '𝕔', + '𝕕' => '𝕕', + '𝕖' => '𝕖', + '𝕗' => '𝕗', + '𝕘' => '𝕘', + '𝕙' => '𝕙', + '𝕚' => '𝕚', + '𝕛' => '𝕛', + '𝕜' => '𝕜', + '𝕝' => '𝕝', + '𝕞' => '𝕞', + '𝕟' => '𝕟', + '𝕠' => '𝕠', + '𝕡' => '𝕡', + '𝕢' => '𝕢', + '𝕣' => '𝕣', + '𝕤' => '𝕤', + '𝕥' => '𝕥', + '𝕦' => '𝕦', + '𝕧' => '𝕧', + '𝕨' => '𝕨', + '𝕩' => '𝕩', + '𝕪' => '𝕪', + '𝕫' => '𝕫', + ); +} diff --git a/masterminds/html5/src/HTML5/Serializer/OutputRules.php b/masterminds/html5/src/HTML5/Serializer/OutputRules.php new file mode 100644 index 000000000..ec467f22c --- /dev/null +++ b/masterminds/html5/src/HTML5/Serializer/OutputRules.php @@ -0,0 +1,553 @@ +'http://www.w3.org/1999/xhtml', + 'attrNamespace'=>'http://www.w3.org/1999/xhtml', + + 'nodeName'=>'img', 'nodeName'=>array('img', 'a'), + 'attrName'=>'alt', 'attrName'=>array('title', 'alt'), + ), + */ + array( + 'nodeNamespace' => 'http://www.w3.org/1999/xhtml', + 'attrName' => array('href', + 'hreflang', + 'http-equiv', + 'icon', + 'id', + 'keytype', + 'kind', + 'label', + 'lang', + 'language', + 'list', + 'maxlength', + 'media', + 'method', + 'name', + 'placeholder', + 'rel', + 'rows', + 'rowspan', + 'sandbox', + 'spellcheck', + 'scope', + 'seamless', + 'shape', + 'size', + 'sizes', + 'span', + 'src', + 'srcdoc', + 'srclang', + 'srcset', + 'start', + 'step', + 'style', + 'summary', + 'tabindex', + 'target', + 'title', + 'type', + 'value', + 'width', + 'border', + 'charset', + 'cite', + 'class', + 'code', + 'codebase', + 'color', + 'cols', + 'colspan', + 'content', + 'coords', + 'data', + 'datetime', + 'default', + 'dir', + 'dirname', + 'enctype', + 'for', + 'form', + 'formaction', + 'headers', + 'height', + 'accept', + 'accept-charset', + 'accesskey', + 'action', + 'align', + 'alt', + 'bgcolor', + ), + ), + array( + 'nodeNamespace' => 'http://www.w3.org/1999/xhtml', + 'xpath' => 'starts-with(local-name(), \'data-\')', + ), + ); + + const DOCTYPE = ''; + + public function __construct($output, $options = array()) + { + if (isset($options['encode_entities'])) { + $this->encode = $options['encode_entities']; + } + + $this->outputMode = static::IM_IN_HTML; + $this->out = $output; + $this->hasHTML5 = defined('ENT_HTML5'); + } + + public function addRule(array $rule) + { + $this->nonBooleanAttributes[] = $rule; + } + + public function setTraverser(Traverser $traverser) + { + $this->traverser = $traverser; + + return $this; + } + + public function unsetTraverser() + { + $this->traverser = null; + + return $this; + } + + public function document($dom) + { + $this->doctype(); + if ($dom->documentElement) { + foreach ($dom->childNodes as $node) { + $this->traverser->node($node); + } + $this->nl(); + } + } + + protected function doctype() + { + $this->wr(static::DOCTYPE); + $this->nl(); + } + + public function element($ele) + { + $name = $ele->tagName; + + // Per spec: + // If the element has a declared namespace in the HTML, MathML or + // SVG namespaces, we use the lname instead of the tagName. + if ($this->traverser->isLocalElement($ele)) { + $name = $ele->localName; + } + + // If we are in SVG or MathML there is special handling. + // Using if/elseif instead of switch because it's faster in PHP. + if ('svg' == $name) { + $this->outputMode = static::IM_IN_SVG; + $name = Elements::normalizeSvgElement($name); + } elseif ('math' == $name) { + $this->outputMode = static::IM_IN_MATHML; + } + + $this->openTag($ele); + if (Elements::isA($name, Elements::TEXT_RAW)) { + foreach ($ele->childNodes as $child) { + if ($child instanceof \DOMCharacterData) { + $this->wr($child->data); + } elseif ($child instanceof \DOMElement) { + $this->element($child); + } + } + } else { + // Handle children. + if ($ele->hasChildNodes()) { + $this->traverser->children($ele->childNodes); + } + + // Close out the SVG or MathML special handling. + if ('svg' == $name || 'math' == $name) { + $this->outputMode = static::IM_IN_HTML; + } + } + + // If not unary, add a closing tag. + if (!Elements::isA($name, Elements::VOID_TAG)) { + $this->closeTag($ele); + } + } + + /** + * Write a text node. + * + * @param \DOMText $ele The text node to write. + */ + public function text($ele) + { + if (isset($ele->parentNode) && isset($ele->parentNode->tagName) && Elements::isA($ele->parentNode->localName, Elements::TEXT_RAW)) { + $this->wr($ele->data); + + return; + } + + // FIXME: This probably needs some flags set. + $this->wr($this->enc($ele->data)); + } + + public function cdata($ele) + { + // This encodes CDATA. + $this->wr($ele->ownerDocument->saveXML($ele)); + } + + public function comment($ele) + { + // These produce identical output. + // $this->wr(''); + $this->wr($ele->ownerDocument->saveXML($ele)); + } + + public function processorInstruction($ele) + { + $this->wr('wr($ele->target) + ->wr(' ') + ->wr($ele->data) + ->wr('?>'); + } + + /** + * Write the namespace attributes. + * + * @param \DOMNode $ele The element being written. + */ + protected function namespaceAttrs($ele) + { + if (!$this->xpath || $this->xpath->document !== $ele->ownerDocument) { + $this->xpath = new \DOMXPath($ele->ownerDocument); + } + + foreach ($this->xpath->query('namespace::*[not(.=../../namespace::*)]', $ele) as $nsNode) { + if (!in_array($nsNode->nodeValue, $this->implicitNamespaces)) { + $this->wr(' ')->wr($nsNode->nodeName)->wr('="')->wr($nsNode->nodeValue)->wr('"'); + } + } + } + + /** + * Write the opening tag. + * + * Tags for HTML, MathML, and SVG are in the local name. Otherwise, use the + * qualified name (8.3). + * + * @param \DOMNode $ele The element being written. + */ + protected function openTag($ele) + { + $this->wr('<')->wr($this->traverser->isLocalElement($ele) ? $ele->localName : $ele->tagName); + + $this->attrs($ele); + $this->namespaceAttrs($ele); + + if ($this->outputMode == static::IM_IN_HTML) { + $this->wr('>'); + } // If we are not in html mode we are in SVG, MathML, or XML embedded content. + else { + if ($ele->hasChildNodes()) { + $this->wr('>'); + } // If there are no children this is self closing. + else { + $this->wr(' />'); + } + } + } + + protected function attrs($ele) + { + // FIXME: Needs support for xml, xmlns, xlink, and namespaced elements. + if (!$ele->hasAttributes()) { + return $this; + } + + // TODO: Currently, this always writes name="value", and does not do + // value-less attributes. + $map = $ele->attributes; + $len = $map->length; + for ($i = 0; $i < $len; ++$i) { + $node = $map->item($i); + $val = $this->enc($node->value, true); + + // XXX: The spec says that we need to ensure that anything in + // the XML, XMLNS, or XLink NS's should use the canonical + // prefix. It seems that DOM does this for us already, but there + // may be exceptions. + $name = $node->nodeName; + + // Special handling for attributes in SVG and MathML. + // Using if/elseif instead of switch because it's faster in PHP. + if ($this->outputMode == static::IM_IN_SVG) { + $name = Elements::normalizeSvgAttribute($name); + } elseif ($this->outputMode == static::IM_IN_MATHML) { + $name = Elements::normalizeMathMlAttribute($name); + } + + $this->wr(' ')->wr($name); + + if ((isset($val) && '' !== $val) || $this->nonBooleanAttribute($node)) { + $this->wr('="')->wr($val)->wr('"'); + } + } + } + + protected function nonBooleanAttribute(\DOMAttr $attr) + { + $ele = $attr->ownerElement; + foreach ($this->nonBooleanAttributes as $rule) { + if (isset($rule['nodeNamespace']) && $rule['nodeNamespace'] !== $ele->namespaceURI) { + continue; + } + if (isset($rule['attNamespace']) && $rule['attNamespace'] !== $attr->namespaceURI) { + continue; + } + if (isset($rule['nodeName']) && !is_array($rule['nodeName']) && $rule['nodeName'] !== $ele->localName) { + continue; + } + if (isset($rule['nodeName']) && is_array($rule['nodeName']) && !in_array($ele->localName, $rule['nodeName'], true)) { + continue; + } + if (isset($rule['attrName']) && !is_array($rule['attrName']) && $rule['attrName'] !== $attr->localName) { + continue; + } + if (isset($rule['attrName']) && is_array($rule['attrName']) && !in_array($attr->localName, $rule['attrName'], true)) { + continue; + } + if (isset($rule['xpath'])) { + $xp = $this->getXPath($attr); + if (isset($rule['prefixes'])) { + foreach ($rule['prefixes'] as $nsPrefix => $ns) { + $xp->registerNamespace($nsPrefix, $ns); + } + } + if (!$xp->evaluate($rule['xpath'], $attr)) { + continue; + } + } + + return true; + } + + return false; + } + + private function getXPath(\DOMNode $node) + { + if (!$this->xpath) { + $this->xpath = new \DOMXPath($node->ownerDocument); + } + + return $this->xpath; + } + + /** + * Write the closing tag. + * + * Tags for HTML, MathML, and SVG are in the local name. Otherwise, use the + * qualified name (8.3). + * + * @param \DOMNode $ele The element being written. + */ + protected function closeTag($ele) + { + if ($this->outputMode == static::IM_IN_HTML || $ele->hasChildNodes()) { + $this->wr('wr($this->traverser->isLocalElement($ele) ? $ele->localName : $ele->tagName)->wr('>'); + } + } + + /** + * Write to the output. + * + * @param string $text The string to put into the output + * + * @return $this + */ + protected function wr($text) + { + fwrite($this->out, $text); + + return $this; + } + + /** + * Write a new line character. + * + * @return $this + */ + protected function nl() + { + fwrite($this->out, PHP_EOL); + + return $this; + } + + /** + * Encode text. + * + * When encode is set to false, the default value, the text passed in is + * escaped per section 8.3 of the html5 spec. For details on how text is + * escaped see the escape() method. + * + * When encoding is set to true the text is converted to named character + * references where appropriate. Section 8.1.4 Character references of the + * html5 spec refers to using named character references. This is useful for + * characters that can't otherwise legally be used in the text. + * + * The named character references are listed in section 8.5. + * + * @see http://www.w3.org/TR/2013/CR-html5-20130806/syntax.html#named-character-references True encoding will turn all named character references into their entities. + * This includes such characters as +.# and many other common ones. By default + * encoding here will just escape &'<>". + * + * Note, PHP 5.4+ has better html5 encoding. + * + * @todo Use the Entities class in php 5.3 to have html5 entities. + * + * @param string $text Text to encode. + * @param bool $attribute True if we are encoding an attrubute, false otherwise. + * + * @return string The encoded text. + */ + protected function enc($text, $attribute = false) + { + // Escape the text rather than convert to named character references. + if (!$this->encode) { + return $this->escape($text, $attribute); + } + + // If we are in PHP 5.4+ we can use the native html5 entity functionality to + // convert the named character references. + + if ($this->hasHTML5) { + return htmlentities($text, ENT_HTML5 | ENT_SUBSTITUTE | ENT_QUOTES, 'UTF-8', false); + } // If a version earlier than 5.4 html5 entities are not entirely handled. + // This manually handles them. + else { + return strtr($text, HTML5Entities::$map); + } + } + + /** + * Escape test. + * + * According to the html5 spec section 8.3 Serializing HTML fragments, text + * within tags that are not style, script, xmp, iframe, noembed, and noframes + * need to be properly escaped. + * + * The & should be converted to &, no breaking space unicode characters + * converted to  , when in attribute mode the " should be converted to + * ", and when not in attribute mode the < and > should be converted to + * < and >. + * + * @see http://www.w3.org/TR/2013/CR-html5-20130806/syntax.html#escapingString + * + * @param string $text Text to escape. + * @param bool $attribute True if we are escaping an attrubute, false otherwise. + */ + protected function escape($text, $attribute = false) + { + // Not using htmlspecialchars because, while it does escaping, it doesn't + // match the requirements of section 8.5. For example, it doesn't handle + // non-breaking spaces. + if ($attribute) { + $replace = array( + '"' => '"', + '&' => '&', + "\xc2\xa0" => ' ', + ); + } else { + $replace = array( + '<' => '<', + '>' => '>', + '&' => '&', + "\xc2\xa0" => ' ', + ); + } + + return strtr($text, $replace); + } +} diff --git a/masterminds/html5/src/HTML5/Serializer/RulesInterface.php b/masterminds/html5/src/HTML5/Serializer/RulesInterface.php new file mode 100644 index 000000000..69a6ecdad --- /dev/null +++ b/masterminds/html5/src/HTML5/Serializer/RulesInterface.php @@ -0,0 +1,99 @@ + 'html', + 'http://www.w3.org/1998/Math/MathML' => 'math', + 'http://www.w3.org/2000/svg' => 'svg', + ); + + protected $dom; + + protected $options; + + protected $encode = false; + + protected $rules; + + protected $out; + + /** + * Create a traverser. + * + * @param \DOMNode|\DOMNodeList $dom The document or node to traverse. + * @param resource $out A stream that allows writing. The traverser will output into this + * stream. + * @param array $options An array of options for the traverser as key/value pairs. These include: + * - encode_entities: A bool to specify if full encding should happen for all named + * charachter references. Defaults to false which escapes &'<>". + * - output_rules: The path to the class handling the output rules. + */ + public function __construct($dom, $out, RulesInterface $rules, $options = array()) + { + $this->dom = $dom; + $this->out = $out; + $this->rules = $rules; + $this->options = $options; + + $this->rules->setTraverser($this); + } + + /** + * Tell the traverser to walk the DOM. + * + * @return resource $out Returns the output stream. + */ + public function walk() + { + if ($this->dom instanceof \DOMDocument) { + $this->rules->document($this->dom); + } elseif ($this->dom instanceof \DOMDocumentFragment) { + // Document fragments are a special case. Only the children need to + // be serialized. + if ($this->dom->hasChildNodes()) { + $this->children($this->dom->childNodes); + } + } // If NodeList, loop + elseif ($this->dom instanceof \DOMNodeList) { + // If this is a NodeList of DOMDocuments this will not work. + $this->children($this->dom); + } // Else assume this is a DOMNode-like datastructure. + else { + $this->node($this->dom); + } + + return $this->out; + } + + /** + * Process a node in the DOM. + * + * @param mixed $node A node implementing \DOMNode. + */ + public function node($node) + { + // A listing of types is at http://php.net/manual/en/dom.constants.php + switch ($node->nodeType) { + case XML_ELEMENT_NODE: + $this->rules->element($node); + break; + case XML_TEXT_NODE: + $this->rules->text($node); + break; + case XML_CDATA_SECTION_NODE: + $this->rules->cdata($node); + break; + case XML_PI_NODE: + $this->rules->processorInstruction($node); + break; + case XML_COMMENT_NODE: + $this->rules->comment($node); + break; + // Currently we don't support embedding DTDs. + default: + //print ''; + break; + } + } + + /** + * Walk through all the nodes on a node list. + * + * @param \DOMNodeList $nl A list of child elements to walk through. + */ + public function children($nl) + { + foreach ($nl as $node) { + $this->node($node); + } + } + + /** + * Is an element local? + * + * @param mixed $ele An element that implement \DOMNode. + * + * @return bool true if local and false otherwise. + */ + public function isLocalElement($ele) + { + $uri = $ele->namespaceURI; + if (empty($uri)) { + return false; + } + + return isset(static::$local_ns[$uri]); + } +} diff --git a/mtdowling/jmespath.php/README.rst b/mtdowling/jmespath.php/README.rst deleted file mode 100644 index b65ee4665..000000000 --- a/mtdowling/jmespath.php/README.rst +++ /dev/null @@ -1,123 +0,0 @@ -============ -jmespath.php -============ - -JMESPath (pronounced "jaymz path") allows you to declaratively specify how to -extract elements from a JSON document. *jmespath.php* allows you to use -JMESPath in PHP applications with PHP data structures. It requires PHP 5.4 or -greater and can be installed through `Composer `_ -using the ``mtdowling/jmespath.php`` package. - -.. code-block:: php - - require 'vendor/autoload.php'; - - $expression = 'foo.*.baz'; - - $data = [ - 'foo' => [ - 'bar' => ['baz' => 1], - 'bam' => ['baz' => 2], - 'boo' => ['baz' => 3] - ] - ]; - - JmesPath\search($expression, $data); - // Returns: [1, 2, 3] - -- `JMESPath Tutorial `_ -- `JMESPath Grammar `_ -- `JMESPath Python library `_ - -PHP Usage -========= - -The ``JmesPath\search`` function can be used in most cases when using the -library. This function utilizes a JMESPath runtime based on your environment. -The runtime utilized can be configured using environment variables and may at -some point in the future automatically utilize a C extension if available. - -.. code-block:: php - - $result = JmesPath\search($expression, $data); - - // or, if you require PSR-4 compliance. - $result = JmesPath\Env::search($expression, $data); - -Runtimes --------- - -jmespath.php utilizes *runtimes*. There are currently two runtimes: -AstRuntime and CompilerRuntime. - -AstRuntime is utilized by ``JmesPath\search()`` and ``JmesPath\Env::search()`` -by default. - -AstRuntime -~~~~~~~~~~ - -The AstRuntime will parse an expression, cache the resulting AST in memory, -and interpret the AST using an external tree visitor. AstRuntime provides a -good general approach for interpreting JMESPath expressions that have a low to -moderate level of reuse. - -.. code-block:: php - - $runtime = new JmesPath\AstRuntime(); - $runtime('foo.bar', ['foo' => ['bar' => 'baz']]); - // > 'baz' - -CompilerRuntime -~~~~~~~~~~~~~~~ - -``JmesPath\CompilerRuntime`` provides the most performance for -applications that have a moderate to high level of reuse of JMESPath -expressions. The CompilerRuntime will walk a JMESPath AST and emit PHP source -code, resulting in anywhere from 7x to 60x speed improvements. - -Compiling JMESPath expressions to source code is a slower process than just -walking and interpreting a JMESPath AST (via the AstRuntime). However, -running the compiled JMESPath code results in much better performance than -walking an AST. This essentially means that there is a warm-up period when -using the ``CompilerRuntime``, but after the warm-up period, it will provide -much better performance. - -Use the CompilerRuntime if you know that you will be executing JMESPath -expressions more than once or if you can pre-compile JMESPath expressions -before executing them (for example, server-side applications). - -.. code-block:: php - - // Note: The cache directory argument is optional. - $runtime = new JmesPath\CompilerRuntime('/path/to/compile/folder'); - $runtime('foo.bar', ['foo' => ['bar' => 'baz']]); - // > 'baz' - -Environment Variables -^^^^^^^^^^^^^^^^^^^^^ - -You can utilize the CompilerRuntime in ``JmesPath\search()`` by setting -the ``JP_PHP_COMPILE`` environment variable to "on" or to a directory -on disk used to store cached expressions. - -Testing -======= - -A comprehensive list of test cases can be found at -https://github.com/jmespath/jmespath.php/tree/master/tests/compliance. -These compliance tests are utilized by jmespath.php to ensure consistency with -other implementations, and can serve as examples of the language. - -jmespath.php is tested using PHPUnit. In order to run the tests, you need to -first install the dependencies using Composer as described in the *Installation* -section. Next you just need to run the tests via make: - -.. code-block:: bash - - make test - -You can run a suite of performance tests as well: - -.. code-block:: bash - - make perf diff --git a/symfony/css-selector/CssSelectorConverter.php b/symfony/css-selector/CssSelectorConverter.php index bbb6afe21..7120a2950 100644 --- a/symfony/css-selector/CssSelectorConverter.php +++ b/symfony/css-selector/CssSelectorConverter.php @@ -26,11 +26,11 @@ */ class CssSelectorConverter { - private $translator; - private $cache; + private Translator $translator; + private array $cache; - private static $xmlCache = []; - private static $htmlCache = []; + private static array $xmlCache = []; + private static array $htmlCache = []; /** * @param bool $html Whether HTML support should be enabled. Disable it for XML documents @@ -59,11 +59,9 @@ public function __construct(bool $html = true) * * Optionally, a prefix can be added to the resulting XPath * expression with the $prefix parameter. - * - * @return string */ - public function toXPath(string $cssExpr, string $prefix = 'descendant-or-self::') + public function toXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string { - return $this->cache[$prefix][$cssExpr] ?? $this->cache[$prefix][$cssExpr] = $this->translator->cssToXPath($cssExpr, $prefix); + return $this->cache[$prefix][$cssExpr] ??= $this->translator->cssToXPath($cssExpr, $prefix); } } diff --git a/symfony/css-selector/Exception/SyntaxErrorException.php b/symfony/css-selector/Exception/SyntaxErrorException.php index 7deacf9c5..5a9d8078d 100644 --- a/symfony/css-selector/Exception/SyntaxErrorException.php +++ b/symfony/css-selector/Exception/SyntaxErrorException.php @@ -23,42 +23,32 @@ */ class SyntaxErrorException extends ParseException { - /** - * @return self - */ - public static function unexpectedToken(string $expectedValue, Token $foundToken) + public static function unexpectedToken(string $expectedValue, Token $foundToken): self { return new self(sprintf('Expected %s, but %s found.', $expectedValue, $foundToken)); } - /** - * @return self - */ - public static function pseudoElementFound(string $pseudoElement, string $unexpectedLocation) + public static function pseudoElementFound(string $pseudoElement, string $unexpectedLocation): self { return new self(sprintf('Unexpected pseudo-element "::%s" found %s.', $pseudoElement, $unexpectedLocation)); } - /** - * @return self - */ - public static function unclosedString(int $position) + public static function unclosedString(int $position): self { return new self(sprintf('Unclosed/invalid string at %s.', $position)); } - /** - * @return self - */ - public static function nestedNot() + public static function nestedNot(): self { return new self('Got nested ::not().'); } - /** - * @return self - */ - public static function stringAsFunctionArgument() + public static function notAtTheStartOfASelector(string $pseudoElement): self + { + return new self(sprintf('Got immediate child pseudo-element ":%s" not at the start of a selector', $pseudoElement)); + } + + public static function stringAsFunctionArgument(): self { return new self('String not allowed as function argument.'); } diff --git a/symfony/css-selector/LICENSE b/symfony/css-selector/LICENSE index 88bf75bb4..0138f8f07 100644 --- a/symfony/css-selector/LICENSE +++ b/symfony/css-selector/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2022 Fabien Potencier +Copyright (c) 2004-present 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 diff --git a/symfony/css-selector/Node/AbstractNode.php b/symfony/css-selector/Node/AbstractNode.php index 1306aeacb..d99e80a87 100644 --- a/symfony/css-selector/Node/AbstractNode.php +++ b/symfony/css-selector/Node/AbstractNode.php @@ -23,17 +23,10 @@ */ abstract class AbstractNode implements NodeInterface { - /** - * @var string - */ - private $nodeName; + private string $nodeName; public function getNodeName(): string { - if (null === $this->nodeName) { - $this->nodeName = preg_replace('~.*\\\\([^\\\\]+)Node$~', '$1', static::class); - } - - return $this->nodeName; + return $this->nodeName ??= preg_replace('~.*\\\\([^\\\\]+)Node$~', '$1', static::class); } } diff --git a/symfony/css-selector/Node/AttributeNode.php b/symfony/css-selector/Node/AttributeNode.php index 0b6e0ee0a..ba4df3100 100644 --- a/symfony/css-selector/Node/AttributeNode.php +++ b/symfony/css-selector/Node/AttributeNode.php @@ -23,11 +23,11 @@ */ class AttributeNode extends AbstractNode { - private $selector; - private $namespace; - private $attribute; - private $operator; - private $value; + private NodeInterface $selector; + private ?string $namespace; + private string $attribute; + private string $operator; + private ?string $value; public function __construct(NodeInterface $selector, ?string $namespace, string $attribute, string $operator, ?string $value) { @@ -63,9 +63,6 @@ public function getValue(): ?string return $this->value; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); diff --git a/symfony/css-selector/Node/ClassNode.php b/symfony/css-selector/Node/ClassNode.php index 1efca808d..717445870 100644 --- a/symfony/css-selector/Node/ClassNode.php +++ b/symfony/css-selector/Node/ClassNode.php @@ -23,8 +23,8 @@ */ class ClassNode extends AbstractNode { - private $selector; - private $name; + private NodeInterface $selector; + private string $name; public function __construct(NodeInterface $selector, string $name) { @@ -42,9 +42,6 @@ public function getName(): string return $this->name; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); diff --git a/symfony/css-selector/Node/CombinedSelectorNode.php b/symfony/css-selector/Node/CombinedSelectorNode.php index a217a45ed..19fecb700 100644 --- a/symfony/css-selector/Node/CombinedSelectorNode.php +++ b/symfony/css-selector/Node/CombinedSelectorNode.php @@ -23,9 +23,9 @@ */ class CombinedSelectorNode extends AbstractNode { - private $selector; - private $combinator; - private $subSelector; + private NodeInterface $selector; + private string $combinator; + private NodeInterface $subSelector; public function __construct(NodeInterface $selector, string $combinator, NodeInterface $subSelector) { @@ -49,9 +49,6 @@ public function getSubSelector(): NodeInterface return $this->subSelector; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); diff --git a/symfony/css-selector/Node/ElementNode.php b/symfony/css-selector/Node/ElementNode.php index fbf8ea0f9..76d2078ea 100644 --- a/symfony/css-selector/Node/ElementNode.php +++ b/symfony/css-selector/Node/ElementNode.php @@ -23,10 +23,10 @@ */ class ElementNode extends AbstractNode { - private $namespace; - private $element; + private ?string $namespace; + private ?string $element; - public function __construct(string $namespace = null, string $element = null) + public function __construct(?string $namespace = null, ?string $element = null) { $this->namespace = $namespace; $this->element = $element; @@ -42,9 +42,6 @@ public function getElement(): ?string return $this->element; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return new Specificity(0, 0, $this->element ? 1 : 0); diff --git a/symfony/css-selector/Node/FunctionNode.php b/symfony/css-selector/Node/FunctionNode.php index c464cf7c0..938a82b1a 100644 --- a/symfony/css-selector/Node/FunctionNode.php +++ b/symfony/css-selector/Node/FunctionNode.php @@ -25,9 +25,9 @@ */ class FunctionNode extends AbstractNode { - private $selector; - private $name; - private $arguments; + private NodeInterface $selector; + private string $name; + private array $arguments; /** * @param Token[] $arguments @@ -57,9 +57,6 @@ public function getArguments(): array return $this->arguments; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); @@ -67,9 +64,7 @@ public function getSpecificity(): Specificity public function __toString(): string { - $arguments = implode(', ', array_map(function (Token $token) { - return "'".$token->getValue()."'"; - }, $this->arguments)); + $arguments = implode(', ', array_map(fn (Token $token) => "'".$token->getValue()."'", $this->arguments)); return sprintf('%s[%s:%s(%s)]', $this->getNodeName(), $this->selector, $this->name, $arguments ? '['.$arguments.']' : ''); } diff --git a/symfony/css-selector/Node/HashNode.php b/symfony/css-selector/Node/HashNode.php index 94114c095..3af8e8474 100644 --- a/symfony/css-selector/Node/HashNode.php +++ b/symfony/css-selector/Node/HashNode.php @@ -23,8 +23,8 @@ */ class HashNode extends AbstractNode { - private $selector; - private $id; + private NodeInterface $selector; + private string $id; public function __construct(NodeInterface $selector, string $id) { @@ -42,9 +42,6 @@ public function getId(): string return $this->id; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(1, 0, 0)); diff --git a/symfony/css-selector/Node/NegationNode.php b/symfony/css-selector/Node/NegationNode.php index f00522fb9..f80675838 100644 --- a/symfony/css-selector/Node/NegationNode.php +++ b/symfony/css-selector/Node/NegationNode.php @@ -23,8 +23,8 @@ */ class NegationNode extends AbstractNode { - private $selector; - private $subSelector; + private NodeInterface $selector; + private NodeInterface $subSelector; public function __construct(NodeInterface $selector, NodeInterface $subSelector) { @@ -42,9 +42,6 @@ public function getSubSelector(): NodeInterface return $this->subSelector; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); diff --git a/symfony/css-selector/Node/NodeInterface.php b/symfony/css-selector/Node/NodeInterface.php index b078d26d4..7d541f9c8 100644 --- a/symfony/css-selector/Node/NodeInterface.php +++ b/symfony/css-selector/Node/NodeInterface.php @@ -21,11 +21,9 @@ * * @internal */ -interface NodeInterface +interface NodeInterface extends \Stringable { public function getNodeName(): string; public function getSpecificity(): Specificity; - - public function __toString(): string; } diff --git a/symfony/css-selector/Node/PseudoNode.php b/symfony/css-selector/Node/PseudoNode.php index 12b7bd266..c21cd6e92 100644 --- a/symfony/css-selector/Node/PseudoNode.php +++ b/symfony/css-selector/Node/PseudoNode.php @@ -23,8 +23,8 @@ */ class PseudoNode extends AbstractNode { - private $selector; - private $identifier; + private NodeInterface $selector; + private string $identifier; public function __construct(NodeInterface $selector, string $identifier) { @@ -42,9 +42,6 @@ public function getIdentifier(): string return $this->identifier; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); diff --git a/symfony/css-selector/Node/SelectorNode.php b/symfony/css-selector/Node/SelectorNode.php index 6e52b2fa7..2318b2bf2 100644 --- a/symfony/css-selector/Node/SelectorNode.php +++ b/symfony/css-selector/Node/SelectorNode.php @@ -23,10 +23,10 @@ */ class SelectorNode extends AbstractNode { - private $tree; - private $pseudoElement; + private NodeInterface $tree; + private ?string $pseudoElement; - public function __construct(NodeInterface $tree, string $pseudoElement = null) + public function __construct(NodeInterface $tree, ?string $pseudoElement = null) { $this->tree = $tree; $this->pseudoElement = $pseudoElement ? strtolower($pseudoElement) : null; @@ -42,9 +42,6 @@ public function getPseudoElement(): ?string return $this->pseudoElement; } - /** - * {@inheritdoc} - */ public function getSpecificity(): Specificity { return $this->tree->getSpecificity()->plus(new Specificity(0, 0, $this->pseudoElement ? 1 : 0)); diff --git a/symfony/css-selector/Node/Specificity.php b/symfony/css-selector/Node/Specificity.php index b00f6d281..bb8e5e346 100644 --- a/symfony/css-selector/Node/Specificity.php +++ b/symfony/css-selector/Node/Specificity.php @@ -29,9 +29,9 @@ class Specificity public const B_FACTOR = 10; public const C_FACTOR = 1; - private $a; - private $b; - private $c; + private int $a; + private int $b; + private int $c; public function __construct(int $a, int $b, int $c) { diff --git a/symfony/css-selector/Parser/Handler/CommentHandler.php b/symfony/css-selector/Parser/Handler/CommentHandler.php index 93f318844..cc01d1e6e 100644 --- a/symfony/css-selector/Parser/Handler/CommentHandler.php +++ b/symfony/css-selector/Parser/Handler/CommentHandler.php @@ -26,9 +26,6 @@ */ class CommentHandler implements HandlerInterface { - /** - * {@inheritdoc} - */ public function handle(Reader $reader, TokenStream $stream): bool { if ('/*' !== $reader->getSubstring(2)) { diff --git a/symfony/css-selector/Parser/Handler/HashHandler.php b/symfony/css-selector/Parser/Handler/HashHandler.php index 7ae9b438c..b29042f56 100644 --- a/symfony/css-selector/Parser/Handler/HashHandler.php +++ b/symfony/css-selector/Parser/Handler/HashHandler.php @@ -29,8 +29,8 @@ */ class HashHandler implements HandlerInterface { - private $patterns; - private $escaping; + private TokenizerPatterns $patterns; + private TokenizerEscaping $escaping; public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) { @@ -38,9 +38,6 @@ public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $esca $this->escaping = $escaping; } - /** - * {@inheritdoc} - */ public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getHashPattern()); diff --git a/symfony/css-selector/Parser/Handler/IdentifierHandler.php b/symfony/css-selector/Parser/Handler/IdentifierHandler.php index 7b2a14e2c..25c0761e9 100644 --- a/symfony/css-selector/Parser/Handler/IdentifierHandler.php +++ b/symfony/css-selector/Parser/Handler/IdentifierHandler.php @@ -29,8 +29,8 @@ */ class IdentifierHandler implements HandlerInterface { - private $patterns; - private $escaping; + private TokenizerPatterns $patterns; + private TokenizerEscaping $escaping; public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) { @@ -38,9 +38,6 @@ public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $esca $this->escaping = $escaping; } - /** - * {@inheritdoc} - */ public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getIdentifierPattern()); diff --git a/symfony/css-selector/Parser/Handler/NumberHandler.php b/symfony/css-selector/Parser/Handler/NumberHandler.php index 8291a68d1..e3eb7afe3 100644 --- a/symfony/css-selector/Parser/Handler/NumberHandler.php +++ b/symfony/css-selector/Parser/Handler/NumberHandler.php @@ -28,16 +28,13 @@ */ class NumberHandler implements HandlerInterface { - private $patterns; + private TokenizerPatterns $patterns; public function __construct(TokenizerPatterns $patterns) { $this->patterns = $patterns; } - /** - * {@inheritdoc} - */ public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getNumberPattern()); diff --git a/symfony/css-selector/Parser/Handler/StringHandler.php b/symfony/css-selector/Parser/Handler/StringHandler.php index 6ce83cdc9..5fd44df26 100644 --- a/symfony/css-selector/Parser/Handler/StringHandler.php +++ b/symfony/css-selector/Parser/Handler/StringHandler.php @@ -31,8 +31,8 @@ */ class StringHandler implements HandlerInterface { - private $patterns; - private $escaping; + private TokenizerPatterns $patterns; + private TokenizerEscaping $escaping; public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) { @@ -40,9 +40,6 @@ public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $esca $this->escaping = $escaping; } - /** - * {@inheritdoc} - */ public function handle(Reader $reader, TokenStream $stream): bool { $quote = $reader->getSubstring(1); diff --git a/symfony/css-selector/Parser/Handler/WhitespaceHandler.php b/symfony/css-selector/Parser/Handler/WhitespaceHandler.php index 21345e32c..eb41c3f7f 100644 --- a/symfony/css-selector/Parser/Handler/WhitespaceHandler.php +++ b/symfony/css-selector/Parser/Handler/WhitespaceHandler.php @@ -27,9 +27,6 @@ */ class WhitespaceHandler implements HandlerInterface { - /** - * {@inheritdoc} - */ public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern('~^[ \t\r\n\f]+~'); diff --git a/symfony/css-selector/Parser/Parser.php b/symfony/css-selector/Parser/Parser.php index d73489edf..309c9b521 100644 --- a/symfony/css-selector/Parser/Parser.php +++ b/symfony/css-selector/Parser/Parser.php @@ -19,7 +19,7 @@ * CSS selector parser. * * This component is a port of the Python cssselect library, - * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * which is copyright Ian Bicking, @see https://github.com/scrapy/cssselect. * * @author Jean-François Simon * @@ -27,16 +27,13 @@ */ class Parser implements ParserInterface { - private $tokenizer; + private Tokenizer $tokenizer; - public function __construct(Tokenizer $tokenizer = null) + public function __construct(?Tokenizer $tokenizer = null) { $this->tokenizer = $tokenizer ?? new Tokenizer(); } - /** - * {@inheritdoc} - */ public function parse(string $source): array { $reader = new Reader($source); @@ -60,9 +57,7 @@ public static function parseSeries(array $tokens): array } } - $joined = trim(implode('', array_map(function (Token $token) { - return $token->getValue(); - }, $tokens))); + $joined = trim(implode('', array_map(fn (Token $token) => $token->getValue(), $tokens))); $int = function ($string) { if (!is_numeric($string)) { @@ -197,7 +192,18 @@ private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = if (!$stream->getPeek()->isDelimiter(['('])) { $result = new Node\PseudoNode($result, $identifier); - + if ('Pseudo[Element[*]:scope]' === $result->__toString()) { + $used = \count($stream->getUsed()); + if (!(2 === $used + || 3 === $used && $stream->getUsed()[0]->isWhiteSpace() + || $used >= 3 && $stream->getUsed()[$used - 3]->isDelimiter([',']) + || $used >= 4 + && $stream->getUsed()[$used - 3]->isWhiteSpace() + && $stream->getUsed()[$used - 4]->isDelimiter([',']) + )) { + throw SyntaxErrorException::notAtTheStartOfASelector('scope'); + } + } continue; } @@ -242,7 +248,7 @@ private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = } } - if (empty($arguments)) { + if (!$arguments) { throw SyntaxErrorException::unexpectedToken('at least one argument', $next); } diff --git a/symfony/css-selector/Parser/Reader.php b/symfony/css-selector/Parser/Reader.php index 4b43effed..7f6ae7a60 100644 --- a/symfony/css-selector/Parser/Reader.php +++ b/symfony/css-selector/Parser/Reader.php @@ -23,9 +23,9 @@ */ class Reader { - private $source; - private $length; - private $position = 0; + private string $source; + private int $length; + private int $position = 0; public function __construct(string $source) { @@ -53,17 +53,17 @@ public function getSubstring(int $length, int $offset = 0): string return substr($this->source, $this->position + $offset, $length); } - public function getOffset(string $string) + /** + * @return int|false + */ + public function getOffset(string $string): int|bool { $position = strpos($this->source, $string, $this->position); return false === $position ? false : $position - $this->position; } - /** - * @return array|false - */ - public function findPattern(string $pattern) + public function findPattern(string $pattern): array|false { $source = substr($this->source, $this->position); @@ -74,12 +74,12 @@ public function findPattern(string $pattern) return false; } - public function moveForward(int $length) + public function moveForward(int $length): void { $this->position += $length; } - public function moveToEnd() + public function moveToEnd(): void { $this->position = $this->length; } diff --git a/symfony/css-selector/Parser/Shortcut/ClassParser.php b/symfony/css-selector/Parser/Shortcut/ClassParser.php index 17fa8c27e..f0ce61184 100644 --- a/symfony/css-selector/Parser/Shortcut/ClassParser.php +++ b/symfony/css-selector/Parser/Shortcut/ClassParser.php @@ -28,9 +28,6 @@ */ class ClassParser implements ParserInterface { - /** - * {@inheritdoc} - */ public function parse(string $source): array { // Matches an optional namespace, optional element, and required class diff --git a/symfony/css-selector/Parser/Shortcut/ElementParser.php b/symfony/css-selector/Parser/Shortcut/ElementParser.php index 8b9a86386..a448e4a87 100644 --- a/symfony/css-selector/Parser/Shortcut/ElementParser.php +++ b/symfony/css-selector/Parser/Shortcut/ElementParser.php @@ -27,9 +27,6 @@ */ class ElementParser implements ParserInterface { - /** - * {@inheritdoc} - */ public function parse(string $source): array { // Matches an optional namespace, required element or `*` diff --git a/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php b/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php index 222df5cd2..a63919120 100644 --- a/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php +++ b/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php @@ -31,9 +31,6 @@ */ class EmptyStringParser implements ParserInterface { - /** - * {@inheritdoc} - */ public function parse(string $source): array { // Matches an empty string diff --git a/symfony/css-selector/Parser/Shortcut/HashParser.php b/symfony/css-selector/Parser/Shortcut/HashParser.php index fb07ee6cf..6683126a3 100644 --- a/symfony/css-selector/Parser/Shortcut/HashParser.php +++ b/symfony/css-selector/Parser/Shortcut/HashParser.php @@ -28,9 +28,6 @@ */ class HashParser implements ParserInterface { - /** - * {@inheritdoc} - */ public function parse(string $source): array { // Matches an optional namespace, optional element, and required id diff --git a/symfony/css-selector/Parser/Token.php b/symfony/css-selector/Parser/Token.php index a053203c0..b50441a8e 100644 --- a/symfony/css-selector/Parser/Token.php +++ b/symfony/css-selector/Parser/Token.php @@ -31,9 +31,9 @@ class Token public const TYPE_NUMBER = 'number'; public const TYPE_STRING = 'string'; - private $type; - private $value; - private $position; + private ?string $type; + private ?string $value; + private ?int $position; public function __construct(?string $type, ?string $value, ?int $position) { @@ -68,7 +68,7 @@ public function isDelimiter(array $values = []): bool return false; } - if (empty($values)) { + if (!$values) { return true; } diff --git a/symfony/css-selector/Parser/TokenStream.php b/symfony/css-selector/Parser/TokenStream.php index 2085f2dd7..8b72d5dbc 100644 --- a/symfony/css-selector/Parser/TokenStream.php +++ b/symfony/css-selector/Parser/TokenStream.php @@ -29,34 +29,23 @@ class TokenStream /** * @var Token[] */ - private $tokens = []; + private array $tokens = []; /** * @var Token[] */ - private $used = []; + private array $used = []; - /** - * @var int - */ - private $cursor = 0; - - /** - * @var Token|null - */ - private $peeked; - - /** - * @var bool - */ - private $peeking = false; + private int $cursor = 0; + private ?Token $peeked; + private bool $peeking = false; /** * Pushes a token. * * @return $this */ - public function push(Token $token): self + public function push(Token $token): static { $this->tokens[] = $token; @@ -68,7 +57,7 @@ public function push(Token $token): self * * @return $this */ - public function freeze(): self + public function freeze(): static { return $this; } @@ -156,7 +145,7 @@ public function getNextIdentifierOrStar(): ?string /** * Skips next whitespace if any. */ - public function skipWhitespace() + public function skipWhitespace(): void { $peek = $this->getPeek(); diff --git a/symfony/css-selector/Parser/Tokenizer/Tokenizer.php b/symfony/css-selector/Parser/Tokenizer/Tokenizer.php index e0dcc5bf1..35c96a484 100644 --- a/symfony/css-selector/Parser/Tokenizer/Tokenizer.php +++ b/symfony/css-selector/Parser/Tokenizer/Tokenizer.php @@ -31,7 +31,7 @@ class Tokenizer /** * @var Handler\HandlerInterface[] */ - private $handlers; + private array $handlers; public function __construct() { diff --git a/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php b/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php index 013e827d2..8c4b9f742 100644 --- a/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php +++ b/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php @@ -23,7 +23,7 @@ */ class TokenizerEscaping { - private $patterns; + private TokenizerPatterns $patterns; public function __construct(TokenizerPatterns $patterns) { diff --git a/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php b/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php index 5f16ac48f..3c77cf091 100644 --- a/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php +++ b/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php @@ -23,18 +23,18 @@ */ 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; + private string $unicodeEscapePattern; + private string $simpleEscapePattern; + private string $newLineEscapePattern; + private string $escapePattern; + private string $stringEscapePattern; + private string $nonAsciiPattern; + private string $nmCharPattern; + private string $nmStartPattern; + private string $identifierPattern; + private string $hashPattern; + private string $numberPattern; + private string $quotedStringPattern; public function __construct() { @@ -49,22 +49,22 @@ public function __construct() $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.')*'; + $this->quotedStringPattern = '([^\n\r\f\\\\%s]|'.$this->stringEscapePattern.')*'; } public function getNewLineEscapePattern(): string { - return '~^'.$this->newLineEscapePattern.'~'; + return '~'.$this->newLineEscapePattern.'~'; } public function getSimpleEscapePattern(): string { - return '~^'.$this->simpleEscapePattern.'~'; + return '~'.$this->simpleEscapePattern.'~'; } public function getUnicodeEscapePattern(): string { - return '~^'.$this->unicodeEscapePattern.'~i'; + return '~'.$this->unicodeEscapePattern.'~i'; } public function getIdentifierPattern(): string diff --git a/symfony/css-selector/XPath/Extension/AbstractExtension.php b/symfony/css-selector/XPath/Extension/AbstractExtension.php index 44e0035a7..495f88291 100644 --- a/symfony/css-selector/XPath/Extension/AbstractExtension.php +++ b/symfony/css-selector/XPath/Extension/AbstractExtension.php @@ -23,41 +23,26 @@ */ abstract class AbstractExtension implements ExtensionInterface { - /** - * {@inheritdoc} - */ public function getNodeTranslators(): array { return []; } - /** - * {@inheritdoc} - */ public function getCombinationTranslators(): array { return []; } - /** - * {@inheritdoc} - */ public function getFunctionTranslators(): array { return []; } - /** - * {@inheritdoc} - */ public function getPseudoClassTranslators(): array { return []; } - /** - * {@inheritdoc} - */ public function getAttributeMatchingTranslators(): array { return []; diff --git a/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php b/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php index a9879f1be..3c785e97a 100644 --- a/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php +++ b/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php @@ -26,20 +26,17 @@ */ class AttributeMatchingExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getAttributeMatchingTranslators(): array { return [ - 'exists' => [$this, 'translateExists'], - '=' => [$this, 'translateEquals'], - '~=' => [$this, 'translateIncludes'], - '|=' => [$this, 'translateDashMatch'], - '^=' => [$this, 'translatePrefixMatch'], - '$=' => [$this, 'translateSuffixMatch'], - '*=' => [$this, 'translateSubstringMatch'], - '!=' => [$this, 'translateDifferent'], + 'exists' => $this->translateExists(...), + '=' => $this->translateEquals(...), + '~=' => $this->translateIncludes(...), + '|=' => $this->translateDashMatch(...), + '^=' => $this->translatePrefixMatch(...), + '$=' => $this->translateSuffixMatch(...), + '*=' => $this->translateSubstringMatch(...), + '!=' => $this->translateDifferent(...), ]; } @@ -109,9 +106,6 @@ public function translateDifferent(XPathExpr $xpath, string $attribute, ?string )); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'attribute-matching'; diff --git a/symfony/css-selector/XPath/Extension/CombinationExtension.php b/symfony/css-selector/XPath/Extension/CombinationExtension.php index aee976e94..f78d48883 100644 --- a/symfony/css-selector/XPath/Extension/CombinationExtension.php +++ b/symfony/css-selector/XPath/Extension/CombinationExtension.php @@ -25,16 +25,13 @@ */ class CombinationExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getCombinationTranslators(): array { return [ - ' ' => [$this, 'translateDescendant'], - '>' => [$this, 'translateChild'], - '+' => [$this, 'translateDirectAdjacent'], - '~' => [$this, 'translateIndirectAdjacent'], + ' ' => $this->translateDescendant(...), + '>' => $this->translateChild(...), + '+' => $this->translateDirectAdjacent(...), + '~' => $this->translateIndirectAdjacent(...), ]; } @@ -61,9 +58,6 @@ public function translateIndirectAdjacent(XPathExpr $xpath, XPathExpr $combinedX return $xpath->join('/following-sibling::', $combinedXpath); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'combination'; diff --git a/symfony/css-selector/XPath/Extension/FunctionExtension.php b/symfony/css-selector/XPath/Extension/FunctionExtension.php index d3f7222a4..4b9d7bc26 100644 --- a/symfony/css-selector/XPath/Extension/FunctionExtension.php +++ b/symfony/css-selector/XPath/Extension/FunctionExtension.php @@ -30,18 +30,15 @@ */ class FunctionExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getFunctionTranslators(): array { return [ - 'nth-child' => [$this, 'translateNthChild'], - 'nth-last-child' => [$this, 'translateNthLastChild'], - 'nth-of-type' => [$this, 'translateNthOfType'], - 'nth-last-of-type' => [$this, 'translateNthLastOfType'], - 'contains' => [$this, 'translateContains'], - 'lang' => [$this, 'translateLang'], + 'nth-child' => $this->translateNthChild(...), + 'nth-last-child' => $this->translateNthLastChild(...), + 'nth-of-type' => $this->translateNthOfType(...), + 'nth-last-of-type' => $this->translateNthLastOfType(...), + 'contains' => $this->translateContains(...), + 'lang' => $this->translateLang(...), ]; } @@ -161,9 +158,6 @@ public function translateLang(XPathExpr $xpath, FunctionNode $function): XPathEx )); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'function'; diff --git a/symfony/css-selector/XPath/Extension/HtmlExtension.php b/symfony/css-selector/XPath/Extension/HtmlExtension.php index 6edc08581..4653add5d 100644 --- a/symfony/css-selector/XPath/Extension/HtmlExtension.php +++ b/symfony/css-selector/XPath/Extension/HtmlExtension.php @@ -36,30 +36,24 @@ public function __construct(Translator $translator) ->setFlag(NodeExtension::ATTRIBUTE_NAME_IN_LOWER_CASE, true); } - /** - * {@inheritdoc} - */ public function getPseudoClassTranslators(): array { return [ - 'checked' => [$this, 'translateChecked'], - 'link' => [$this, 'translateLink'], - 'disabled' => [$this, 'translateDisabled'], - 'enabled' => [$this, 'translateEnabled'], - 'selected' => [$this, 'translateSelected'], - 'invalid' => [$this, 'translateInvalid'], - 'hover' => [$this, 'translateHover'], - 'visited' => [$this, 'translateVisited'], + 'checked' => $this->translateChecked(...), + 'link' => $this->translateLink(...), + 'disabled' => $this->translateDisabled(...), + 'enabled' => $this->translateEnabled(...), + 'selected' => $this->translateSelected(...), + 'invalid' => $this->translateInvalid(...), + 'hover' => $this->translateHover(...), + 'visited' => $this->translateVisited(...), ]; } - /** - * {@inheritdoc} - */ public function getFunctionTranslators(): array { return [ - 'lang' => [$this, 'translateLang'], + 'lang' => $this->translateLang(...), ]; } @@ -177,9 +171,6 @@ public function translateVisited(XPathExpr $xpath): XPathExpr return $xpath->addCondition('0'); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'html'; diff --git a/symfony/css-selector/XPath/Extension/NodeExtension.php b/symfony/css-selector/XPath/Extension/NodeExtension.php index aa6f3f704..49e894ade 100644 --- a/symfony/css-selector/XPath/Extension/NodeExtension.php +++ b/symfony/css-selector/XPath/Extension/NodeExtension.php @@ -31,7 +31,7 @@ class NodeExtension extends AbstractExtension public const ATTRIBUTE_NAME_IN_LOWER_CASE = 2; public const ATTRIBUTE_VALUE_IN_LOWER_CASE = 4; - private $flags; + private int $flags; public function __construct(int $flags = 0) { @@ -41,7 +41,7 @@ public function __construct(int $flags = 0) /** * @return $this */ - public function setFlag(int $flag, bool $on): self + public function setFlag(int $flag, bool $on): static { if ($on && !$this->hasFlag($flag)) { $this->flags += $flag; @@ -59,21 +59,18 @@ public function hasFlag(int $flag): bool return (bool) ($this->flags & $flag); } - /** - * {@inheritdoc} - */ public function getNodeTranslators(): array { return [ - 'Selector' => [$this, 'translateSelector'], - 'CombinedSelector' => [$this, 'translateCombinedSelector'], - 'Negation' => [$this, 'translateNegation'], - 'Function' => [$this, 'translateFunction'], - 'Pseudo' => [$this, 'translatePseudo'], - 'Attribute' => [$this, 'translateAttribute'], - 'Class' => [$this, 'translateClass'], - 'Hash' => [$this, 'translateHash'], - 'Element' => [$this, 'translateElement'], + 'Selector' => $this->translateSelector(...), + 'CombinedSelector' => $this->translateCombinedSelector(...), + 'Negation' => $this->translateNegation(...), + 'Function' => $this->translateFunction(...), + 'Pseudo' => $this->translatePseudo(...), + 'Attribute' => $this->translateAttribute(...), + 'Class' => $this->translateClass(...), + 'Hash' => $this->translateHash(...), + 'Element' => $this->translateElement(...), ]; } @@ -182,9 +179,6 @@ public function translateElement(Node\ElementNode $node): XPathExpr return $xpath; } - /** - * {@inheritdoc} - */ public function getName(): string { return 'node'; diff --git a/symfony/css-selector/XPath/Extension/PseudoClassExtension.php b/symfony/css-selector/XPath/Extension/PseudoClassExtension.php index a50b0486a..aada83291 100644 --- a/symfony/css-selector/XPath/Extension/PseudoClassExtension.php +++ b/symfony/css-selector/XPath/Extension/PseudoClassExtension.php @@ -26,20 +26,18 @@ */ class PseudoClassExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getPseudoClassTranslators(): array { return [ - 'root' => [$this, 'translateRoot'], - 'first-child' => [$this, 'translateFirstChild'], - 'last-child' => [$this, 'translateLastChild'], - 'first-of-type' => [$this, 'translateFirstOfType'], - 'last-of-type' => [$this, 'translateLastOfType'], - 'only-child' => [$this, 'translateOnlyChild'], - 'only-of-type' => [$this, 'translateOnlyOfType'], - 'empty' => [$this, 'translateEmpty'], + 'root' => $this->translateRoot(...), + 'scope' => $this->translateScopePseudo(...), + 'first-child' => $this->translateFirstChild(...), + 'last-child' => $this->translateLastChild(...), + 'first-of-type' => $this->translateFirstOfType(...), + 'last-of-type' => $this->translateLastOfType(...), + 'only-child' => $this->translateOnlyChild(...), + 'only-of-type' => $this->translateOnlyOfType(...), + 'empty' => $this->translateEmpty(...), ]; } @@ -48,6 +46,11 @@ public function translateRoot(XPathExpr $xpath): XPathExpr return $xpath->addCondition('not(parent::*)'); } + public function translateScopePseudo(XPathExpr $xpath): XPathExpr + { + return $xpath->addCondition('1'); + } + public function translateFirstChild(XPathExpr $xpath): XPathExpr { return $xpath @@ -112,9 +115,6 @@ public function translateEmpty(XPathExpr $xpath): XPathExpr return $xpath->addCondition('not(*) and not(string-length())'); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'pseudo-class'; diff --git a/symfony/css-selector/XPath/Translator.php b/symfony/css-selector/XPath/Translator.php index 8ce473036..9e66ce7dd 100644 --- a/symfony/css-selector/XPath/Translator.php +++ b/symfony/css-selector/XPath/Translator.php @@ -30,25 +30,25 @@ */ class Translator implements TranslatorInterface { - private $mainParser; + private ParserInterface $mainParser; /** * @var ParserInterface[] */ - private $shortcutParsers = []; + private array $shortcutParsers = []; /** * @var Extension\ExtensionInterface[] */ - private $extensions = []; + private array $extensions = []; - private $nodeTranslators = []; - private $combinationTranslators = []; - private $functionTranslators = []; - private $pseudoClassTranslators = []; - private $attributeMatchingTranslators = []; + private array $nodeTranslators = []; + private array $combinationTranslators = []; + private array $functionTranslators = []; + private array $pseudoClassTranslators = []; + private array $attributeMatchingTranslators = []; - public function __construct(ParserInterface $parser = null) + public function __construct(?ParserInterface $parser = null) { $this->mainParser = $parser ?? new Parser(); @@ -87,9 +87,6 @@ public static function getXpathLiteral(string $element): string return sprintf('concat(%s)', implode(', ', $parts)); } - /** - * {@inheritdoc} - */ public function cssToXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string { $selectors = $this->parseSelectors($cssExpr); @@ -106,9 +103,6 @@ public function cssToXPath(string $cssExpr, string $prefix = 'descendant-or-self return implode(' | ', $selectors); } - /** - * {@inheritdoc} - */ public function selectorToXPath(SelectorNode $selector, string $prefix = 'descendant-or-self::'): string { return ($prefix ?: '').$this->nodeToXPath($selector); @@ -117,7 +111,7 @@ public function selectorToXPath(SelectorNode $selector, string $prefix = 'descen /** * @return $this */ - public function registerExtension(Extension\ExtensionInterface $extension): self + public function registerExtension(Extension\ExtensionInterface $extension): static { $this->extensions[$extension->getName()] = $extension; @@ -145,7 +139,7 @@ public function getExtension(string $name): Extension\ExtensionInterface /** * @return $this */ - public function registerParserShortcut(ParserInterface $shortcut): self + public function registerParserShortcut(ParserInterface $shortcut): static { $this->shortcutParsers[] = $shortcut; @@ -220,7 +214,7 @@ private function parseSelectors(string $css): array foreach ($this->shortcutParsers as $shortcut) { $tokens = $shortcut->parse($css); - if (!empty($tokens)) { + if ($tokens) { return $tokens; } } diff --git a/symfony/css-selector/XPath/XPathExpr.php b/symfony/css-selector/XPath/XPathExpr.php index e45ce7d8c..a76e30bec 100644 --- a/symfony/css-selector/XPath/XPathExpr.php +++ b/symfony/css-selector/XPath/XPathExpr.php @@ -23,9 +23,9 @@ */ class XPathExpr { - private $path; - private $element; - private $condition; + private string $path; + private string $element; + private string $condition; public function __construct(string $path = '', string $element = '*', string $condition = '', bool $starPrefix = false) { @@ -46,7 +46,7 @@ public function getElement(): string /** * @return $this */ - public function addCondition(string $condition): self + public function addCondition(string $condition): static { $this->condition = $this->condition ? sprintf('(%s) and (%s)', $this->condition, $condition) : $condition; @@ -61,7 +61,7 @@ public function getCondition(): string /** * @return $this */ - public function addNameTest(): self + public function addNameTest(): static { if ('*' !== $this->element) { $this->addCondition('name() = '.Translator::getXpathLiteral($this->element)); @@ -74,7 +74,7 @@ public function addNameTest(): self /** * @return $this */ - public function addStarPrefix(): self + public function addStarPrefix(): static { $this->path .= '*/'; @@ -86,7 +86,7 @@ public function addStarPrefix(): self * * @return $this */ - public function join(string $combiner, self $expr): self + public function join(string $combiner, self $expr): static { $path = $this->__toString().$combiner; diff --git a/symfony/dom-crawler/AbstractUriElement.php b/symfony/dom-crawler/AbstractUriElement.php index 8ff0b992a..e67716f64 100644 --- a/symfony/dom-crawler/AbstractUriElement.php +++ b/symfony/dom-crawler/AbstractUriElement.php @@ -40,7 +40,7 @@ abstract class AbstractUriElement * * @throws \InvalidArgumentException if the node is not a link */ - public function __construct(\DOMElement $node, string $currentUri = null, ?string $method = 'GET') + public function __construct(\DOMElement $node, ?string $currentUri = null, ?string $method = 'GET') { $this->setNode($node); $this->method = $method ? strtoupper($method) : null; @@ -55,49 +55,39 @@ public function __construct(\DOMElement $node, string $currentUri = null, ?strin /** * Gets the node associated with this link. - * - * @return \DOMElement */ - public function getNode() + public function getNode(): \DOMElement { return $this->node; } /** * Gets the method associated with this link. - * - * @return string */ - public function getMethod() + public function getMethod(): string { return $this->method ?? 'GET'; } /** * Gets the URI associated with this link. - * - * @return string */ - public function getUri() + public function getUri(): string { return UriResolver::resolve($this->getRawUri(), $this->currentUri); } /** * Returns raw URI data. - * - * @return string */ - abstract protected function getRawUri(); + abstract protected function getRawUri(): string; /** * Returns the canonicalized URI path (see RFC 3986, section 5.2.4). * * @param string $path URI path - * - * @return string */ - protected function canonicalizePath(string $path) + protected function canonicalizePath(string $path): string { if ('' === $path || '/' === $path) { return $path; @@ -125,6 +115,8 @@ protected function canonicalizePath(string $path) * * @param \DOMElement $node A \DOMElement instance * + * @return void + * * @throws \LogicException If given node is not an anchor */ abstract protected function setNode(\DOMElement $node); diff --git a/symfony/dom-crawler/Crawler.php b/symfony/dom-crawler/Crawler.php index a32bc1dd8..8c5e26930 100644 --- a/symfony/dom-crawler/Crawler.php +++ b/symfony/dom-crawler/Crawler.php @@ -30,62 +30,44 @@ class Crawler implements \Countable, \IteratorAggregate /** * The default namespace prefix to be used with XPath and CSS expressions. - * - * @var string */ - private $defaultNamespacePrefix = 'default'; + private string $defaultNamespacePrefix = 'default'; /** * A map of manually registered namespaces. * * @var array */ - private $namespaces = []; + private array $namespaces = []; /** * A map of cached namespaces. - * - * @var \ArrayObject - */ - private $cachedNamespaces; - - /** - * The base href value. - * - * @var string|null */ - private $baseHref; + private \ArrayObject $cachedNamespaces; - /** - * @var \DOMDocument|null - */ - private $document; + private ?string $baseHref; + private ?\DOMDocument $document = null; /** * @var list<\DOMNode> */ - private $nodes = []; + private array $nodes = []; /** * Whether the Crawler contains HTML or XML content (used when converting CSS to XPath). - * - * @var bool */ - private $isHtml = true; + private bool $isHtml = true; - /** - * @var HTML5|null - */ - private $html5Parser; + private ?HTML5 $html5Parser = null; /** * @param \DOMNodeList|\DOMNode|\DOMNode[]|string|null $node A Node to use as the base for the crawling */ - public function __construct($node = null, string $uri = null, string $baseHref = null) + public function __construct(\DOMNodeList|\DOMNode|array|string|null $node = null, ?string $uri = null, ?string $baseHref = null, bool $useHtml5Parser = true) { $this->uri = $uri; $this->baseHref = $baseHref ?: $uri; - $this->html5Parser = class_exists(HTML5::class) ? new HTML5(['disable_html_ns' => true]) : null; + $this->html5Parser = $useHtml5Parser ? new HTML5(['disable_html_ns' => true]) : null; $this->cachedNamespaces = new \ArrayObject(); $this->add($node); @@ -93,26 +75,24 @@ public function __construct($node = null, string $uri = null, string $baseHref = /** * Returns the current URI. - * - * @return string|null */ - public function getUri() + public function getUri(): ?string { return $this->uri; } /** * Returns base href. - * - * @return string|null */ - public function getBaseHref() + public function getBaseHref(): ?string { return $this->baseHref; } /** * Removes all the nodes. + * + * @return void */ public function clear() { @@ -129,9 +109,11 @@ public function clear() * * @param \DOMNodeList|\DOMNode|\DOMNode[]|string|null $node A node * + * @return void + * * @throws \InvalidArgumentException when node is not the expected type */ - public function add($node) + public function add(\DOMNodeList|\DOMNode|array|string|null $node) { if ($node instanceof \DOMNodeList) { $this->addNodeList($node); @@ -152,8 +134,10 @@ public function add($node) * If the charset is not set via the content type, it is assumed to be UTF-8, * or ISO-8859-1 as a fallback, which is the default charset defined by the * HTTP 1.1 specification. + * + * @return void */ - public function addContent(string $content, string $type = null) + public function addContent(string $content, ?string $type = null) { if (empty($type)) { $type = str_starts_with($content, 'validateOnParse = true; @@ -247,9 +232,6 @@ public function addXmlContent(string $content, string $charset = 'UTF-8', int $o } libxml_use_internal_errors($internalErrors); - if (\LIBXML_VERSION < 20900) { - libxml_disable_entity_loader($disableEntities); - } $this->addDocument($dom); @@ -260,6 +242,8 @@ public function addXmlContent(string $content, string $charset = 'UTF-8', int $o * Adds a \DOMDocument to the list of nodes. * * @param \DOMDocument $dom A \DOMDocument instance + * + * @return void */ public function addDocument(\DOMDocument $dom) { @@ -272,6 +256,8 @@ public function addDocument(\DOMDocument $dom) * Adds a \DOMNodeList to the list of nodes. * * @param \DOMNodeList $nodes A \DOMNodeList instance + * + * @return void */ public function addNodeList(\DOMNodeList $nodes) { @@ -286,6 +272,8 @@ public function addNodeList(\DOMNodeList $nodes) * Adds an array of \DOMNode instances to the list of nodes. * * @param \DOMNode[] $nodes An array of \DOMNode instances + * + * @return void */ public function addNodes(array $nodes) { @@ -298,6 +286,8 @@ public function addNodes(array $nodes) * Adds a \DOMNode instance to the list of nodes. * * @param \DOMNode $node A \DOMNode instance + * + * @return void */ public function addNode(\DOMNode $node) { @@ -309,9 +299,7 @@ public function addNode(\DOMNode $node) throw new \InvalidArgumentException('Attaching DOM nodes from multiple documents in the same crawler is forbidden.'); } - if (null === $this->document) { - $this->document = $node->ownerDocument; - } + $this->document ??= $node->ownerDocument; // Don't add duplicate nodes in the Crawler if (\in_array($node, $this->nodes, true)) { @@ -323,10 +311,8 @@ public function addNode(\DOMNode $node) /** * Returns a node given its position in the node list. - * - * @return static */ - public function eq(int $position) + public function eq(int $position): static { if (isset($this->nodes[$position])) { return $this->createSubCrawler($this->nodes[$position]); @@ -351,7 +337,7 @@ public function eq(int $position) * * @return array An array of values returned by the anonymous function */ - public function each(\Closure $closure) + public function each(\Closure $closure): array { $data = []; foreach ($this->nodes as $i => $node) { @@ -363,10 +349,8 @@ public function each(\Closure $closure) /** * Slices the list of nodes by $offset and $length. - * - * @return static */ - public function slice(int $offset = 0, int $length = null) + public function slice(int $offset = 0, ?int $length = null): static { return $this->createSubCrawler(\array_slice($this->nodes, $offset, $length)); } @@ -377,10 +361,8 @@ public function slice(int $offset = 0, int $length = null) * To remove a node from the list, the anonymous function must return false. * * @param \Closure $closure An anonymous function - * - * @return static */ - public function reduce(\Closure $closure) + public function reduce(\Closure $closure): static { $nodes = []; foreach ($this->nodes as $i => $node) { @@ -394,20 +376,16 @@ public function reduce(\Closure $closure) /** * Returns the first node of the current selection. - * - * @return static */ - public function first() + public function first(): static { return $this->eq(0); } /** * Returns the last node of the current selection. - * - * @return static */ - public function last() + public function last(): static { return $this->eq(\count($this->nodes) - 1); } @@ -415,11 +393,9 @@ public function last() /** * Returns the siblings nodes of the current selection. * - * @return static - * * @throws \InvalidArgumentException When current node is empty */ - public function siblings() + public function siblings(): static { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); @@ -470,11 +446,9 @@ public function closest(string $selector): ?self /** * Returns the next siblings nodes of the current selection. * - * @return static - * * @throws \InvalidArgumentException When current node is empty */ - public function nextAll() + public function nextAll(): static { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); @@ -486,11 +460,9 @@ public function nextAll() /** * Returns the previous sibling nodes of the current selection. * - * @return static - * * @throws \InvalidArgumentException */ - public function previousAll() + public function previousAll(): static { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); @@ -499,28 +471,12 @@ public function previousAll() return $this->createSubCrawler($this->sibling($this->getNode(0), 'previousSibling')); } - /** - * Returns the parent nodes of the current selection. - * - * @return static - * - * @throws \InvalidArgumentException When current node is empty - */ - public function parents() - { - trigger_deprecation('symfony/dom-crawler', '5.3', 'The %s() method is deprecated, use ancestors() instead.', __METHOD__); - - return $this->ancestors(); - } - /** * Returns the ancestors of the current selection. * - * @return static - * * @throws \InvalidArgumentException When the current node is empty */ - public function ancestors() + public function ancestors(): static { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); @@ -541,12 +497,10 @@ public function ancestors() /** * Returns the children nodes of the current selection. * - * @return static - * * @throws \InvalidArgumentException When current node is empty * @throws \RuntimeException If the CssSelector Component is not available and $selector is provided */ - public function children(string $selector = null) + public function children(?string $selector = null): static { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); @@ -567,29 +521,32 @@ public function children(string $selector = null) /** * Returns the attribute value of the first node of the list. * - * @return string|null + * @param string|null $default When not null: the value to return when the node or attribute is empty * * @throws \InvalidArgumentException When current node is empty */ - public function attr(string $attribute) + public function attr(string $attribute/* , string $default = null */): ?string { + $default = \func_num_args() > 1 ? func_get_arg(1) : null; if (!$this->nodes) { + if (null !== $default) { + return $default; + } + throw new \InvalidArgumentException('The current node list is empty.'); } $node = $this->getNode(0); - return $node->hasAttribute($attribute) ? $node->getAttribute($attribute) : null; + return $node->hasAttribute($attribute) ? $node->getAttribute($attribute) : $default; } /** * Returns the node name of the first node of the list. * - * @return string - * * @throws \InvalidArgumentException When current node is empty */ - public function nodeName() + public function nodeName(): string { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); @@ -606,11 +563,9 @@ public function nodeName() * @param string|null $default When not null: the value to return when the current node is empty * @param bool $normalizeWhitespace Whether whitespaces should be trimmed and normalized to single spaces * - * @return string - * * @throws \InvalidArgumentException When current node is empty */ - public function text(string $default = null, bool $normalizeWhitespace = true) + public function text(?string $default = null, bool $normalizeWhitespace = true): string { if (!$this->nodes) { if (null !== $default) { @@ -623,7 +578,7 @@ public function text(string $default = null, bool $normalizeWhitespace = true) $text = $this->getNode(0)->nodeValue; if ($normalizeWhitespace) { - return trim(preg_replace('/(?:\s{2,}+|[^\S ])/', ' ', $text)); + return $this->normalizeWhitespace($text); } return $text; @@ -631,10 +586,26 @@ public function text(string $default = null, bool $normalizeWhitespace = true) /** * Returns only the inner text that is the direct descendent of the current node, excluding any child nodes. + * + * @param bool $normalizeWhitespace Whether whitespaces should be trimmed and normalized to single spaces */ - public function innerText(): string + public function innerText(/* bool $normalizeWhitespace = true */): string { - return $this->filterXPath('.//text()')->text(); + $normalizeWhitespace = 1 <= \func_num_args() ? func_get_arg(0) : true; + + foreach ($this->getNode(0)->childNodes as $childNode) { + if (\XML_TEXT_NODE !== $childNode->nodeType && \XML_CDATA_SECTION_NODE !== $childNode->nodeType) { + continue; + } + if (!$normalizeWhitespace) { + return $childNode->nodeValue; + } + if ('' !== trim($childNode->nodeValue)) { + return $this->normalizeWhitespace($childNode->nodeValue); + } + } + + return ''; } /** @@ -642,11 +613,9 @@ public function innerText(): string * * @param string|null $default When not null: the value to return when the current node is empty * - * @return string - * * @throws \InvalidArgumentException When current node is empty */ - public function html(string $default = null) + public function html(?string $default = null): string { if (!$this->nodes) { if (null !== $default) { @@ -659,7 +628,7 @@ public function html(string $default = null) $node = $this->getNode(0); $owner = $node->ownerDocument; - if (null !== $this->html5Parser && '' === $owner->saveXML($owner->childNodes[0])) { + if ($this->html5Parser && '' === $owner->saveXML($owner->childNodes[0])) { $owner = $this->html5Parser; } @@ -680,7 +649,7 @@ public function outerHtml(): string $node = $this->getNode(0); $owner = $node->ownerDocument; - if (null !== $this->html5Parser && '' === $owner->saveXML($owner->childNodes[0])) { + if ($this->html5Parser && '' === $owner->saveXML($owner->childNodes[0])) { $owner = $this->html5Parser; } @@ -692,10 +661,8 @@ public function outerHtml(): string * * Since an XPath expression might evaluate to either a simple type or a \DOMNodeList, * this method will return either an array of simple types or a new Crawler instance. - * - * @return array|Crawler */ - public function evaluate(string $xpath) + public function evaluate(string $xpath): array|self { if (null === $this->document) { throw new \LogicException('Cannot evaluate the expression on an uninitialized crawler.'); @@ -723,10 +690,8 @@ public function evaluate(string $xpath) * Example: * * $crawler->filter('h1 a')->extract(['_text', 'href']); - * - * @return array */ - public function extract(array $attributes) + public function extract(array $attributes): array { $count = \count($attributes); @@ -756,10 +721,8 @@ public function extract(array $attributes) * is considered as a fake parent of the elements inside it. * This means that a child selector "div" or "./div" will match only * the div elements of the current crawler, not their children. - * - * @return static */ - public function filterXPath(string $xpath) + public function filterXPath(string $xpath): static { $xpath = $this->relativize($xpath); @@ -776,11 +739,9 @@ public function filterXPath(string $xpath) * * This method only works if you have installed the CssSelector Symfony Component. * - * @return static - * - * @throws \RuntimeException if the CssSelector Component is not available + * @throws \LogicException if the CssSelector Component is not available */ - public function filter(string $selector) + public function filter(string $selector): static { $converter = $this->createCssSelectorConverter(); @@ -790,10 +751,8 @@ public function filter(string $selector) /** * Selects links by name or alt value for clickable images. - * - * @return static */ - public function selectLink(string $value) + public function selectLink(string $value): static { return $this->filterRelativeXPath( sprintf('descendant-or-self::a[contains(concat(\' \', normalize-space(string(.)), \' \'), %1$s) or ./img[contains(concat(\' \', normalize-space(string(@alt)), \' \'), %1$s)]]', static::xpathLiteral(' '.$value.' ')) @@ -802,10 +761,8 @@ public function selectLink(string $value) /** * Selects images by alt value. - * - * @return static */ - public function selectImage(string $value) + public function selectImage(string $value): static { $xpath = sprintf('descendant-or-self::img[contains(normalize-space(string(@alt)), %s)]', static::xpathLiteral($value)); @@ -814,10 +771,8 @@ public function selectImage(string $value) /** * Selects a button by name or alt value for images. - * - * @return static */ - public function selectButton(string $value) + public function selectButton(string $value): static { return $this->filterRelativeXPath( sprintf('descendant-or-self::input[((contains(%1$s, "submit") or contains(%1$s, "button")) and contains(concat(\' \', normalize-space(string(@value)), \' \'), %2$s)) or (contains(%1$s, "image") and contains(concat(\' \', normalize-space(string(@alt)), \' \'), %2$s)) or @id=%3$s or @name=%3$s] | descendant-or-self::button[contains(concat(\' \', normalize-space(string(.)), \' \'), %2$s) or @id=%3$s or @name=%3$s]', 'translate(@type, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")', static::xpathLiteral(' '.$value.' '), static::xpathLiteral($value)) @@ -827,11 +782,9 @@ public function selectButton(string $value) /** * Returns a Link object for the first node in the list. * - * @return Link - * * @throws \InvalidArgumentException If the current node list is empty or the selected node is not instance of DOMElement */ - public function link(string $method = 'get') + public function link(string $method = 'get'): Link { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); @@ -853,7 +806,7 @@ public function link(string $method = 'get') * * @throws \InvalidArgumentException If the current node list contains non-DOMElement instances */ - public function links() + public function links(): array { $links = []; foreach ($this->nodes as $node) { @@ -870,11 +823,9 @@ public function links() /** * Returns an Image object for the first node in the list. * - * @return Image - * * @throws \InvalidArgumentException If the current node list is empty */ - public function image() + public function image(): Image { if (!\count($this)) { throw new \InvalidArgumentException('The current node list is empty.'); @@ -894,7 +845,7 @@ public function image() * * @return Image[] */ - public function images() + public function images(): array { $images = []; foreach ($this as $node) { @@ -911,11 +862,9 @@ public function images() /** * Returns a Form object for the first node in the list. * - * @return Form - * * @throws \InvalidArgumentException If the current node list is empty or the selected node is not instance of DOMElement */ - public function form(array $values = null, string $method = null) + public function form(?array $values = null, ?string $method = null): Form { if (!$this->nodes) { throw new \InvalidArgumentException('The current node list is empty.'); @@ -938,12 +887,17 @@ public function form(array $values = null, string $method = null) /** * Overloads a default namespace prefix to be used with XPath and CSS expressions. + * + * @return void */ public function setDefaultNamespacePrefix(string $prefix) { $this->defaultNamespacePrefix = $prefix; } + /** + * @return void + */ public function registerNamespace(string $prefix, string $namespace) { $this->namespaces[$prefix] = $namespace; @@ -964,10 +918,8 @@ public function registerNamespace(string $prefix, string $namespace) * * echo Crawler::xpathLiteral('a\'b"c'); * //prints concat('a', "'", 'b"c') - * - * @return string */ - public static function xpathLiteral(string $s) + public static function xpathLiteral(string $s): string { if (!str_contains($s, "'")) { return sprintf("'%s'", $s); @@ -997,10 +949,8 @@ public static function xpathLiteral(string $s) * Filters the list of nodes with an XPath expression. * * The XPath expression should already be processed to apply it in the context of each node. - * - * @return static */ - private function filterRelativeXPath(string $xpath): object + private function filterRelativeXPath(string $xpath): static { $crawler = $this->createSubCrawler(null); if (null === $this->document) { @@ -1106,19 +1056,12 @@ private function relativize(string $xpath): string return $xpath; // The XPath expression is invalid } - /** - * @return \DOMNode|null - */ - public function getNode(int $position) + public function getNode(int $position): ?\DOMNode { return $this->nodes[$position] ?? null; } - /** - * @return int - */ - #[\ReturnTypeWillChange] - public function count() + public function count(): int { return \count($this->nodes); } @@ -1126,16 +1069,12 @@ public function count() /** * @return \ArrayIterator */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): \ArrayIterator { return new \ArrayIterator($this->nodes); } - /** - * @return array - */ - protected function sibling(\DOMNode $node, string $siblingDir = 'nextSibling') + protected function sibling(\DOMNode $node, string $siblingDir = 'nextSibling'): array { $nodes = []; @@ -1159,9 +1098,6 @@ private function parseXhtml(string $htmlContent, string $charset = 'UTF-8'): \DO $htmlContent = $this->convertToHtmlEntities($htmlContent, $charset); $internalErrors = libxml_use_internal_errors(true); - if (\LIBXML_VERSION < 20900) { - $disableEntities = libxml_disable_entity_loader(true); - } $dom = new \DOMDocument('1.0', $charset); $dom->validateOnParse = true; @@ -1171,9 +1107,6 @@ private function parseXhtml(string $htmlContent, string $charset = 'UTF-8'): \DO } libxml_use_internal_errors($internalErrors); - if (\LIBXML_VERSION < 20900) { - libxml_disable_entity_loader($disableEntities); - } return $dom; } @@ -1183,15 +1116,15 @@ private function parseXhtml(string $htmlContent, string $charset = 'UTF-8'): \DO */ private function convertToHtmlEntities(string $htmlContent, string $charset = 'UTF-8'): string { - set_error_handler(function () { throw new \Exception(); }); + set_error_handler(static fn () => throw new \Exception()); try { return mb_encode_numericentity($htmlContent, [0x80, 0x10FFFF, 0, 0x1FFFFF], $charset); - } catch (\Exception|\ValueError $e) { + } catch (\Exception|\ValueError) { try { $htmlContent = iconv($charset, 'UTF-8', $htmlContent); $htmlContent = mb_encode_numericentity($htmlContent, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8'); - } catch (\Exception|\ValueError $e) { + } catch (\Exception|\ValueError) { } return $htmlContent; @@ -1249,10 +1182,8 @@ private function findNamespacePrefixes(string $xpath): array * Creates a crawler for some subnodes. * * @param \DOMNodeList|\DOMNode|\DOMNode[]|string|null $nodes - * - * @return static */ - private function createSubCrawler($nodes): object + private function createSubCrawler(\DOMNodeList|\DOMNode|array|string|null $nodes): static { $crawler = new static($nodes, $this->uri, $this->baseHref); $crawler->isHtml = $this->isHtml; @@ -1291,12 +1222,14 @@ private function parseHtmlString(string $content, string $charset): \DOMDocument private function canParseHtml5String(string $content): bool { - if (null === $this->html5Parser) { + if (!$this->html5Parser) { return false; } + if (false === ($pos = stripos($content, ''))) { return false; } + $header = substr($content, 0, $pos); return '' === $header || $this->isValidHtml5Heading($header); @@ -1306,4 +1239,9 @@ private function isValidHtml5Heading(string $heading): bool { return 1 === preg_match('/^\x{FEFF}?\s*(\s*)*$/u', $heading); } + + private function normalizeWhitespace(string $string): string + { + return trim(preg_replace("/(?:[ \n\r\t\x0C]{2,}+|[\n\r\t\x0C])/", ' ', $string), " \n\r\t\x0C"); + } } diff --git a/symfony/dom-crawler/Field/ChoiceFormField.php b/symfony/dom-crawler/Field/ChoiceFormField.php index 9eed27ba4..dcae5490a 100644 --- a/symfony/dom-crawler/Field/ChoiceFormField.php +++ b/symfony/dom-crawler/Field/ChoiceFormField.php @@ -20,29 +20,17 @@ */ class ChoiceFormField extends FormField { - /** - * @var string - */ - private $type; - /** - * @var bool - */ - private $multiple; - /** - * @var array - */ - private $options; - /** - * @var bool - */ - private $validationDisabled = false; + private string $type; + private bool $multiple; + private array $options; + private bool $validationDisabled = false; /** * Returns true if the field should be included in the submitted values. * * @return bool true if the field should be included in the submitted values, false otherwise */ - public function hasValue() + public function hasValue(): bool { // don't send a value for unchecked checkboxes if (\in_array($this->type, ['checkbox', 'radio']) && null === $this->value) { @@ -54,10 +42,8 @@ public function hasValue() /** * Check if the current selected option is disabled. - * - * @return bool */ - public function isDisabled() + public function isDisabled(): bool { if (parent::isDisabled() && 'select' === $this->type) { return true; @@ -75,9 +61,9 @@ public function isDisabled() /** * Sets the value of the field. * - * @param string|array $value The value of the field + * @return void */ - public function select($value) + public function select(string|array|bool $value) { $this->setValue($value); } @@ -85,6 +71,8 @@ public function select($value) /** * Ticks a checkbox. * + * @return void + * * @throws \LogicException When the type provided is not correct */ public function tick() @@ -99,6 +87,8 @@ public function tick() /** * Unticks a checkbox. * + * @return void + * * @throws \LogicException When the type provided is not correct */ public function untick() @@ -113,11 +103,11 @@ public function untick() /** * Sets the value of the field. * - * @param string|array|bool|null $value The value of the field + * @return void * * @throws \InvalidArgumentException When value type provided is not correct */ - public function setValue($value) + public function setValue(string|array|bool|null $value) { if ('checkbox' === $this->type && false === $value) { // uncheck @@ -159,7 +149,7 @@ public function setValue($value) * * @internal */ - public function addChoice(\DOMElement $node) + public function addChoice(\DOMElement $node): void { if (!$this->multiple && 'radio' !== $this->type) { throw new \LogicException(sprintf('Unable to add a choice for "%s" as it is not multiple or is not a radio button.', $this->name)); @@ -175,20 +165,16 @@ public function addChoice(\DOMElement $node) /** * Returns the type of the choice field (radio, select, or checkbox). - * - * @return string */ - public function getType() + public function getType(): string { return $this->type; } /** * Returns true if the field accepts multiple values. - * - * @return bool */ - public function isMultiple() + public function isMultiple(): bool { return $this->multiple; } @@ -196,6 +182,8 @@ public function isMultiple() /** * Initializes the form field. * + * @return void + * * @throws \LogicException When node type is incorrect */ protected function initialize() @@ -244,7 +232,7 @@ protected function initialize() } // if no option is selected and if it is a simple select box, take the first option as the value - if (!$found && !$this->multiple && !empty($this->options)) { + if (!$found && !$this->multiple && $this->options) { $this->value = $this->options[0]['value']; } } @@ -268,11 +256,9 @@ private function buildOptionValue(\DOMElement $node): array /** * Checks whether given value is in the existing options. * - * @internal since Symfony 5.3 - * - * @return bool + * @internal */ - public function containsOption(string $optionValue, array $options) + public function containsOption(string $optionValue, array $options): bool { if ($this->validationDisabled) { return true; @@ -290,11 +276,9 @@ public function containsOption(string $optionValue, array $options) /** * Returns list of available field options. * - * @internal since Symfony 5.3 - * - * @return array + * @internal */ - public function availableOptionValues() + public function availableOptionValues(): array { $values = []; @@ -308,11 +292,11 @@ public function availableOptionValues() /** * Disables the internal validation of the field. * - * @internal since Symfony 5.3 + * @internal * * @return $this */ - public function disableValidation() + public function disableValidation(): static { $this->validationDisabled = true; diff --git a/symfony/dom-crawler/Field/FileFormField.php b/symfony/dom-crawler/Field/FileFormField.php index bd97e7688..4ebe766f0 100644 --- a/symfony/dom-crawler/Field/FileFormField.php +++ b/symfony/dom-crawler/Field/FileFormField.php @@ -23,6 +23,8 @@ class FileFormField extends FormField * * @param int $error The error code (one of UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, or UPLOAD_ERR_EXTENSION) * + * @return void + * * @throws \InvalidArgumentException When error code doesn't exist */ public function setErrorCode(int $error) @@ -37,6 +39,8 @@ public function setErrorCode(int $error) /** * Sets the value of the field. + * + * @return void */ public function upload(?string $value) { @@ -45,6 +49,8 @@ public function upload(?string $value) /** * Sets the value of the field. + * + * @return void */ public function setValue(?string $value) { @@ -76,6 +82,8 @@ public function setValue(?string $value) /** * Sets path to the file as string for simulating HTTP request. + * + * @return void */ public function setFilePath(string $path) { @@ -85,6 +93,8 @@ public function setFilePath(string $path) /** * Initializes the form field. * + * @return void + * * @throws \LogicException When node type is incorrect */ protected function initialize() diff --git a/symfony/dom-crawler/Field/FormField.php b/symfony/dom-crawler/Field/FormField.php index 066af4a16..b97d54dda 100644 --- a/symfony/dom-crawler/Field/FormField.php +++ b/symfony/dom-crawler/Field/FormField.php @@ -57,10 +57,8 @@ public function __construct(\DOMElement $node) /** * Returns the label tag associated to the field or null if none. - * - * @return \DOMElement|null */ - public function getLabel() + public function getLabel(): ?\DOMElement { $xpath = new \DOMXPath($this->node->ownerDocument); @@ -78,26 +76,24 @@ public function getLabel() /** * Returns the name of the field. - * - * @return string */ - public function getName() + public function getName(): string { return $this->name; } /** * Gets the value of the field. - * - * @return string|array|null */ - public function getValue() + public function getValue(): string|array|null { return $this->value; } /** * Sets the value of the field. + * + * @return void */ public function setValue(?string $value) { @@ -106,26 +102,24 @@ public function setValue(?string $value) /** * Returns true if the field should be included in the submitted values. - * - * @return bool */ - public function hasValue() + public function hasValue(): bool { return true; } /** * Check if the current field is disabled. - * - * @return bool */ - public function isDisabled() + public function isDisabled(): bool { return $this->node->hasAttribute('disabled'); } /** * Initializes the form field. + * + * @return void */ abstract protected function initialize(); } diff --git a/symfony/dom-crawler/Field/InputFormField.php b/symfony/dom-crawler/Field/InputFormField.php index 1c3c84d72..19d77352f 100644 --- a/symfony/dom-crawler/Field/InputFormField.php +++ b/symfony/dom-crawler/Field/InputFormField.php @@ -24,6 +24,8 @@ class InputFormField extends FormField /** * Initializes the form field. * + * @return void + * * @throws \LogicException When node type is incorrect */ protected function initialize() diff --git a/symfony/dom-crawler/Field/TextareaFormField.php b/symfony/dom-crawler/Field/TextareaFormField.php index 15526e1c2..5168c5225 100644 --- a/symfony/dom-crawler/Field/TextareaFormField.php +++ b/symfony/dom-crawler/Field/TextareaFormField.php @@ -21,6 +21,8 @@ class TextareaFormField extends FormField /** * Initializes the form field. * + * @return void + * * @throws \LogicException When node type is incorrect */ protected function initialize() diff --git a/symfony/dom-crawler/Form.php b/symfony/dom-crawler/Form.php index ebad35b38..fd55546fe 100644 --- a/symfony/dom-crawler/Form.php +++ b/symfony/dom-crawler/Form.php @@ -21,20 +21,9 @@ */ class Form extends Link implements \ArrayAccess { - /** - * @var \DOMElement - */ - private $button; - - /** - * @var FormFieldRegistry - */ - private $fields; - - /** - * @var string - */ - private $baseHref; + private \DOMElement $button; + private FormFieldRegistry $fields; + private ?string $baseHref; /** * @param \DOMElement $node A \DOMElement instance @@ -44,7 +33,7 @@ class Form extends Link implements \ArrayAccess * * @throws \LogicException if the node is not a button inside a form tag */ - public function __construct(\DOMElement $node, string $currentUri = null, string $method = null, string $baseHref = null) + public function __construct(\DOMElement $node, ?string $currentUri = null, ?string $method = null, ?string $baseHref = null) { parent::__construct($node, $currentUri, $method); $this->baseHref = $baseHref; @@ -54,10 +43,8 @@ public function __construct(\DOMElement $node, string $currentUri = null, string /** * Gets the form node associated with this form. - * - * @return \DOMElement */ - public function getFormNode() + public function getFormNode(): \DOMElement { return $this->node; } @@ -69,7 +56,7 @@ public function getFormNode() * * @return $this */ - public function setValues(array $values) + public function setValues(array $values): static { foreach ($values as $name => $value) { $this->fields->set($name, $value); @@ -82,10 +69,8 @@ public function setValues(array $values) * Gets the field values. * * The returned array does not include file fields (@see getFiles). - * - * @return array */ - public function getValues() + public function getValues(): array { $values = []; foreach ($this->fields->all() as $name => $field) { @@ -103,10 +88,8 @@ public function getValues() /** * Gets the file field values. - * - * @return array */ - public function getFiles() + public function getFiles(): array { if (!\in_array($this->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'])) { return []; @@ -132,10 +115,8 @@ public function getFiles() * * This method converts fields with the array notation * (like foo[bar] to arrays) like PHP does. - * - * @return array */ - public function getPhpValues() + public function getPhpValues(): array { $values = []; foreach ($this->getValues() as $name => $value) { @@ -159,10 +140,8 @@ public function getPhpValues() * (@see getPhpValues), rather than uploaded files found in $_FILES. * For a compound file field foo[bar] it will create foo[bar][name], * instead of foo[name][bar] which would be found in $_FILES. - * - * @return array */ - public function getPhpFiles() + public function getPhpFiles(): array { $values = []; foreach ($this->getFiles() as $name => $value) { @@ -195,10 +174,8 @@ function (&$value, $key) { * The returned URI is not the same as the form "action" attribute. * This method merges the value if the method is GET to mimics * browser behavior. - * - * @return string */ - public function getUri() + public function getUri(): string { $uri = parent::getUri(); @@ -219,7 +196,7 @@ public function getUri() return $uri; } - protected function getRawUri() + protected function getRawUri(): string { // If the form was created from a button rather than the form node, check for HTML5 action overrides if ($this->button !== $this->node && $this->button->getAttribute('formaction')) { @@ -233,10 +210,8 @@ protected function getRawUri() * Gets the form method. * * If no method is defined in the form, GET is returned. - * - * @return string */ - public function getMethod() + public function getMethod(): string { if (null !== $this->method) { return $this->method; @@ -262,16 +237,16 @@ public function getName(): string /** * Returns true if the named field exists. - * - * @return bool */ - public function has(string $name) + public function has(string $name): bool { return $this->fields->has($name); } /** * Removes a field from the form. + * + * @return void */ public function remove(string $name) { @@ -285,13 +260,15 @@ public function remove(string $name) * * @throws \InvalidArgumentException When field is not present in this form */ - public function get(string $name) + public function get(string $name): FormField|array { return $this->fields->get($name); } /** * Sets a named field. + * + * @return void */ public function set(FormField $field) { @@ -303,7 +280,7 @@ public function set(FormField $field) * * @return FormField[] */ - public function all() + public function all(): array { return $this->fields->all(); } @@ -312,11 +289,8 @@ public function all() * Returns true if the named field exists. * * @param string $name The field name - * - * @return bool */ - #[\ReturnTypeWillChange] - public function offsetExists($name) + public function offsetExists(mixed $name): bool { return $this->has($name); } @@ -330,8 +304,7 @@ public function offsetExists($name) * * @throws \InvalidArgumentException if the field does not exist */ - #[\ReturnTypeWillChange] - public function offsetGet($name) + public function offsetGet(mixed $name): FormField|array { return $this->fields->get($name); } @@ -342,12 +315,9 @@ public function offsetGet($name) * @param string $name The field name * @param string|array $value The value of the field * - * @return void - * * @throws \InvalidArgumentException if the field does not exist */ - #[\ReturnTypeWillChange] - public function offsetSet($name, $value) + public function offsetSet(mixed $name, mixed $value): void { $this->fields->set($name, $value); } @@ -356,11 +326,8 @@ public function offsetSet($name, $value) * Removes a field from the form. * * @param string $name The field name - * - * @return void */ - #[\ReturnTypeWillChange] - public function offsetUnset($name) + public function offsetUnset(mixed $name): void { $this->fields->remove($name); } @@ -370,7 +337,7 @@ public function offsetUnset($name) * * @return $this */ - public function disableValidation() + public function disableValidation(): static { foreach ($this->fields->all() as $field) { if ($field instanceof Field\ChoiceFormField) { @@ -386,6 +353,8 @@ public function disableValidation() * * Expects a 'submit' button \DOMElement and finds the corresponding form element, or the form element itself. * + * @return void + * * @throws \LogicException If given node is not a button or input or does not have a form ancestor */ protected function setNode(\DOMElement $node) @@ -423,7 +392,7 @@ protected function setNode(\DOMElement $node) * the form node or the entire document depending on whether we need * to find non-descendant elements through HTML5 'form' attribute. */ - private function initialize() + private function initialize(): void { $this->fields = new FormFieldRegistry(); @@ -455,14 +424,14 @@ private function initialize() // corresponding elements are either descendants or have a matching HTML5 form attribute $formId = Crawler::xpathLiteral($this->node->getAttribute('id')); - $fieldNodes = $xpath->query(sprintf('( descendant::input[@form=%s] | descendant::button[@form=%1$s] | descendant::textarea[@form=%1$s] | descendant::select[@form=%1$s] | //form[@id=%1$s]//input[not(@form)] | //form[@id=%1$s]//button[not(@form)] | //form[@id=%1$s]//textarea[not(@form)] | //form[@id=%1$s]//select[not(@form)] )[not(ancestor::template)]', $formId)); + $fieldNodes = $xpath->query(sprintf('( descendant::input[@form=%s] | descendant::button[@form=%1$s] | descendant::textarea[@form=%1$s] | descendant::select[@form=%1$s] | //form[@id=%1$s]//input[not(@form)] | //form[@id=%1$s]//button[not(@form)] | //form[@id=%1$s]//textarea[not(@form)] | //form[@id=%1$s]//select[not(@form)] )[( not(ancestor::template) or ancestor::turbo-stream )]', $formId)); foreach ($fieldNodes as $node) { $this->addField($node); } } else { // do the xpath query with $this->node as the context node, to only find descendant elements // however, descendant elements with form attribute are not part of this form - $fieldNodes = $xpath->query('( descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)] )[not(ancestor::template)]', $this->node); + $fieldNodes = $xpath->query('( descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)] )[( not(ancestor::template) or ancestor::turbo-stream )]', $this->node); foreach ($fieldNodes as $node) { $this->addField($node); } @@ -473,7 +442,7 @@ private function initialize() } } - private function addField(\DOMElement $node) + private function addField(\DOMElement $node): void { if (!$node->hasAttribute('name') || !$node->getAttribute('name')) { return; diff --git a/symfony/dom-crawler/FormFieldRegistry.php b/symfony/dom-crawler/FormFieldRegistry.php index 93522adcb..9e1571656 100644 --- a/symfony/dom-crawler/FormFieldRegistry.php +++ b/symfony/dom-crawler/FormFieldRegistry.php @@ -20,14 +20,13 @@ */ class FormFieldRegistry { - private $fields = []; - - private $base = ''; + private array $fields = []; + private string $base = ''; /** * Adds a field to the registry. */ - public function add(FormField $field) + public function add(FormField $field): void { $segments = $this->getSegments($field->getName()); @@ -47,9 +46,9 @@ public function add(FormField $field) } /** - * Removes a field based on the fully qualifed name and its children from the registry. + * Removes a field based on the fully qualified name and its children from the registry. */ - public function remove(string $name) + public function remove(string $name): void { $segments = $this->getSegments($name); $target = &$this->fields; @@ -64,13 +63,13 @@ public function remove(string $name) } /** - * Returns the value of the field based on the fully qualifed name and its children. + * Returns the value of the field based on the fully qualified name and its children. * * @return FormField|FormField[]|FormField[][] * * @throws \InvalidArgumentException if the field does not exist */ - public function &get(string $name) + public function &get(string $name): FormField|array { $segments = $this->getSegments($name); $target = &$this->fields; @@ -94,7 +93,7 @@ public function has(string $name): bool $this->get($name); return true; - } catch (\InvalidArgumentException $e) { + } catch (\InvalidArgumentException) { return false; } } @@ -102,11 +101,9 @@ public function has(string $name): bool /** * Set the value of a field based on the fully qualified name and its children. * - * @param mixed $value The value - * * @throws \InvalidArgumentException if the field does not exist */ - public function set(string $name, $value) + public function set(string $name, mixed $value): void { $target = &$this->get($name); if ((!\is_array($value) && $target instanceof Field\FormField) || $target instanceof Field\ChoiceFormField) { diff --git a/symfony/dom-crawler/Image.php b/symfony/dom-crawler/Image.php index b1ac5ca2c..34c8fda6c 100644 --- a/symfony/dom-crawler/Image.php +++ b/symfony/dom-crawler/Image.php @@ -16,16 +16,19 @@ */ class Image extends AbstractUriElement { - public function __construct(\DOMElement $node, string $currentUri = null) + public function __construct(\DOMElement $node, ?string $currentUri = null) { parent::__construct($node, $currentUri, 'GET'); } - protected function getRawUri() + protected function getRawUri(): string { return $this->node->getAttribute('src'); } + /** + * @return void + */ protected function setNode(\DOMElement $node) { if ('img' !== $node->nodeName) { diff --git a/symfony/dom-crawler/LICENSE b/symfony/dom-crawler/LICENSE index 88bf75bb4..0138f8f07 100644 --- a/symfony/dom-crawler/LICENSE +++ b/symfony/dom-crawler/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2022 Fabien Potencier +Copyright (c) 2004-present 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 diff --git a/symfony/dom-crawler/Link.php b/symfony/dom-crawler/Link.php index 80a356e46..681a2f7a2 100644 --- a/symfony/dom-crawler/Link.php +++ b/symfony/dom-crawler/Link.php @@ -18,11 +18,14 @@ */ class Link extends AbstractUriElement { - protected function getRawUri() + protected function getRawUri(): string { return $this->node->getAttribute('href'); } + /** + * @return void + */ protected function setNode(\DOMElement $node) { if ('a' !== $node->nodeName && 'area' !== $node->nodeName && 'link' !== $node->nodeName) { diff --git a/symfony/dom-crawler/UriResolver.php b/symfony/dom-crawler/UriResolver.php index be64f5257..d3b0c8396 100644 --- a/symfony/dom-crawler/UriResolver.php +++ b/symfony/dom-crawler/UriResolver.php @@ -58,7 +58,7 @@ public static function resolve(string $uri, ?string $baseUri): string } // absolute URL with relative schema - if (0 === strpos($uri, '//')) { + if (str_starts_with($uri, '//')) { return preg_replace('#^([^/]*)//.*$#', '$1', $baseUriCleaned).$uri; } @@ -70,7 +70,7 @@ public static function resolve(string $uri, ?string $baseUri): string } // relative path - $path = parse_url(substr($baseUri, \strlen($baseUriCleaned)), \PHP_URL_PATH); + $path = parse_url(substr($baseUri, \strlen($baseUriCleaned)), \PHP_URL_PATH) ?? ''; $path = self::canonicalizePath(substr($path, 0, strrpos($path, '/')).'/'.$uri); return $baseUriCleaned.('' === $path || '/' !== $path[0] ? '/' : '').$path; @@ -85,7 +85,7 @@ private static function canonicalizePath(string $path): string return $path; } - if ('.' === substr($path, -1)) { + if (str_ends_with($path, '.')) { $path .= '/'; }