diff --git a/lib/php-css-parser/CHANGELOG.md b/lib/php-css-parser/CHANGELOG.md new file mode 100644 index 00000000000..c90927cc282 --- /dev/null +++ b/lib/php-css-parser/CHANGELOG.md @@ -0,0 +1,365 @@ +# Changelog + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](https://semver.org/). + +## x.y.z + +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed + +## 8.7.0: Add support for PHP 8.4 + +### Added + +- Add support for PHP 8.4 (#675, #701, #746, #751) + +### Changed + +- Mark parsing-internal classes and methods as `@internal` (#711) +- Block installations on unsupported higher PHP versions (#691) + +### Deprecated + +- Deprecate the expansion of shorthand properties (#719) +- Deprecate `Parser::setCharset()` and `Parser::getCharset()` (#703) + +### Fixed + +- Fix type errors in PHP strict mode (#695) + +## 8.6.0 + +### Added + +- Support arithmetic operators in CSS function arguments (#607) +- Add support for inserting an item in a CSS list (#545) +- Add support for the `dvh`, `lvh` and `svh` length units (#415) + +### Changed + +- Improve performance of Value::parseValue with many delimiters by refactoring + to remove `array_search()` (#413) + +## 8.5.2 + +### Changed + +- Mark all class constants as `@internal` (#500) + +### Fixed + +- Fix undefined local variable in `CalcFunction::parse()` (#593) + +## 8.5.1 + +### Fixed + +- Fix PHP notice caused by parsing invalid color values having less than + 6 characters (#485) +- Fix (regression) failure to parse at-rules with strict parsing (#456) + +## 8.5.0 + +### Added + +- Add a method to get an import's media queries (#384) +- Add more unit tests (#381, #382) + +### Fixed + +- Retain CSSList and Rule comments when rendering CSS (#351) +- Replace invalid `turns` unit with `turn` (#350) +- Also allow string values for rules (#348) +- Fix invalid calc parsing (#169) +- Handle scientific notation when parsing sizes (#179) +- Fix PHP 8.1 compatibility in `ParserState::strsplit()` (#344) + +## 8.4.0 + +### Features + +* Support for PHP 8.x +* PHPDoc annotations +* Allow usage of CSS variables inside color functions (by parsing them as + regular functions) +* Use PSR-12 code style +* *No deprecations* + +### Bugfixes + +* Improved handling of whitespace in `calc()` +* Fix parsing units whose prefix is also a valid unit, like `vmin` +* Allow passing an object to `CSSList#replace` +* Fix PHP 7.3 warnings +* Correctly parse keyframes with `%` +* Don’t convert large numbers to scientific notation +* Allow a file to end after an `@import` +* Preserve case of CSS variables as specced +* Allow identifiers to use escapes the same way as strings +* No longer use `eval` for the comparison in `getSelectorsBySpecificity`, in + case it gets passed untrusted input (CVE-2020-13756). Also fixed in 8.3.1, + 8.2.1, 8.1.1, 8.0.1, 7.0.4, 6.0.2, 5.2.1, 5.1.3, 5.0.9, 4.0.1, 3.0.1, 2.0.1, + 1.0.1. +* Prevent an infinite loop when parsing invalid grid line names +* Remove invalid unit `vm` +* Retain rule order after expanding shorthands + +### Backwards-incompatible changes + +* PHP ≥ 5.6 is now required +* HHVM compatibility target dropped + +## 8.3.0 (2019-02-22) + +* Refactor parsing logic to mostly reside in the class files whose data + structure is to be parsed (this should eventually allow us to unit-test + specific parts of the parsing logic individually). +* Fix error in parsing `calc` expessions when the first operand is a negative + number, thanks to @raxbg. +* Support parsing CSS4 colors in hex notation with alpha values, thanks to + @raxbg. +* Swallow more errors in lenient mode, thanks to @raxbg. +* Allow specifying arbitrary strings to output before and after declaration + blocks, thanks to @westonruter. +* *No backwards-incompatible changes* +* *No deprecations* + +## 8.2.0 (2018-07-13) + +* Support parsing `calc()`, thanks to @raxbg. +* Support parsing grid-lines, again thanks to @raxbg. +* Support parsing legacy IE filters (`progid:`) in lenient mode, thanks to + @FMCorz +* Performance improvements parsing large files, again thanks to @FMCorz +* *No backwards-incompatible changes* +* *No deprecations* + +## 8.1.0 (2016-07-19) + +* Comments are no longer silently ignored but stored with the object with which + they appear (no render support, though). Thanks to @FMCorz. +* The IE hacks using `\0` and `\9` can now be parsed (and rendered) in lenient + mode. Thanks (again) to @FMCorz. +* Media queries with or without spaces before the query are parsed. Still no + *real* parsing support, though. Sorry… +* PHPUnit is now listed as a dev-dependency in composer.json. +* *No backwards-incompatible changes* +* *No deprecations* + +## 8.0.0 (2016-06-30) + +* Store source CSS line numbers in tokens and parsing exceptions. +* *No deprecations* + +### Backwards-incompatible changes + +* Unrecoverable parser errors throw an exception of type + `Sabberworm\CSS\Parsing\SourceException` instead of `\Exception`. + +## 7.0.3 (2016-04-27) + +* Fixed parsing empty CSS when multibyte is off +* *No backwards-incompatible changes* +* *No deprecations* + +## 7.0.2 (2016-02-11) + +* 150 time performance boost thanks + to @[ossinkine](https://github.com/ossinkine) +* *No backwards-incompatible changes* +* *No deprecations* + +## 7.0.1 (2015-12-25) + +* No more suppressed `E_NOTICE` +* *No backwards-incompatible changes* +* *No deprecations* + +## 7.0.0 (2015-08-24) + +* Compatibility with PHP 7. Well timed, eh? +* *No deprecations* + +### Backwards-incompatible changes + +* The `Sabberworm\CSS\Value\String` class has been renamed to + `Sabberworm\CSS\Value\CSSString`. + +## 6.0.1 (2015-08-24) + +* Remove some declarations in interfaces incompatible with PHP 5.3 (< 5.3.9) +* *No deprecations* + +## 6.0.0 (2014-07-03) + +* Format output using Sabberworm\CSS\OutputFormat +* *No backwards-incompatible changes* + +### Deprecations + +* The parse() method replaces __toString with an optional argument (instance of + the OutputFormat class) + +## 5.2.0 (2014-06-30) + +* Support removing a selector from a declaration block using + `$oBlock->removeSelector($mSelector)` +* Introduce a specialized exception (Sabberworm\CSS\Parsing\OuputException) for + exceptions during output rendering + +* *No deprecations* + +#### Backwards-incompatible changes + +* Outputting a declaration block that has no selectors throws an OuputException + instead of outputting an invalid ` {…}` into the CSS document. + +## 5.1.2 (2013-10-30) + +* Remove the use of consumeUntil in comment parsing. This makes it possible to + parse comments such as `/** Perfectly valid **/` +* Add fr relative size unit +* Fix some issues with HHVM +* *No backwards-incompatible changes* +* *No deprecations* + +## 5.1.1 (2013-10-28) + +* Updated CHANGELOG.md to reflect changes since 5.0.4 +* *No backwards-incompatible changes* +* *No deprecations* + +## 5.1.0 (2013-10-24) + +* Performance enhancements by Michael M Slusarz +* More rescue entry points for lenient parsing (unexpected tokens between + declaration blocks and unclosed comments) +* *No backwards-incompatible changes* +* *No deprecations* + +## 5.0.8 (2013-08-15) + +* Make default settings’ multibyte parsing option dependent on whether or not + the mbstring extension is actually installed. +* *No backwards-incompatible changes* +* *No deprecations* + +## 5.0.7 (2013-08-04) + +* Fix broken decimal point output optimization +* *No backwards-incompatible changes* +* *No deprecations* + +## 5.0.6 (2013-05-31) + +* Fix broken unit test +* *No backwards-incompatible changes* +* *No deprecations* + +## 5.0.5 (2013-04-17) + +* Initial support for lenient parsing (setting this parser option will catch + some exceptions internally and recover the parser’s state as neatly as + possible). +* *No backwards-incompatible changes* +* *No deprecations* + +## 5.0.4 (2013-03-21) + +* Don’t output floats with locale-aware separator chars +* *No backwards-incompatible changes* +* *No deprecations* + +## 5.0.3 (2013-03-21) + +* More size units recognized +* *No backwards-incompatible changes* +* *No deprecations* + +## 5.0.2 (2013-03-21) + +* CHANGELOG.md file added to distribution +* *No backwards-incompatible changes* +* *No deprecations* + +## 5.0.1 (2013-03-20) + +* Internal cleanup +* *No backwards-incompatible changes* +* *No deprecations* + +## 5.0.0 (2013-03-20) + +* Correctly parse all known CSS 3 units (including Hz and kHz). +* Output RGB colors in short (#aaa or #ababab) notation +* Be case-insensitive when parsing identifiers. +* *No deprecations* + +### Backwards-incompatible changes + +* `Sabberworm\CSS\Value\Color`’s `__toString` method overrides `CSSList`’s to + maybe return something other than `type(value, …)` (see above). + +## 4.0.0 (2013-03-19) + +* Support for more @-rules +* Generic interface `Sabberworm\CSS\Property\AtRule`, implemented by all @-rule + classes +* *No deprecations* + +### Backwards-incompatible changes + +* `Sabberworm\CSS\RuleSet\AtRule` renamed to `Sabberworm\CSS\RuleSet\AtRuleSet` +* `Sabberworm\CSS\CSSList\MediaQuery` renamed to + `Sabberworm\CSS\RuleSet\CSSList\AtRuleBlockList` with differing semantics and + API (which also works for other block-list-based @-rules like `@supports`). + +## 3.0.0 (2013-03-06) + +* Support for lenient parsing (on by default) +* *No deprecations* + +### Backwards-incompatible changes + +* All properties (like whether or not to use `mb_`-functions, which default + charset to use and – new – whether or not to be forgiving when parsing) are + now encapsulated in an instance of `Sabberworm\CSS\Settings` which can be + passed as the second argument to `Sabberworm\CSS\Parser->__construct()`. +* Specifying a charset as the second argument to + `Sabberworm\CSS\Parser->__construct()` is no longer supported. Use + `Sabberworm\CSS\Settings::create()->withDefaultCharset('some-charset')` + instead. +* Setting `Sabberworm\CSS\Parser->bUseMbFunctions` has no effect. Use + `Sabberworm\CSS\Settings::create()->withMultibyteSupport(true/false)` instead. +* `Sabberworm\CSS\Parser->parse()` may throw a + `Sabberworm\CSS\Parsing\UnexpectedTokenException` when in strict parsing mode. + +## 2.0.0 (2013-01-29) + +* Allow multiple rules of the same type per rule set + +### Backwards-incompatible changes + +* `Sabberworm\CSS\RuleSet->getRules()` returns an index-based array instead of + an associative array. Use `Sabberworm\CSS\RuleSet->getRulesAssoc()` (which + eliminates duplicate rules and lets the later rule of the same name win). +* `Sabberworm\CSS\RuleSet->removeRule()` works as it did before except when + passed an instance of `Sabberworm\CSS\Rule\Rule`, in which case it would only + remove the exact rule given instead of all the rules of the same type. To get + the old behaviour, use `Sabberworm\CSS\RuleSet->removeRule($oRule->getRule()`; + +## 1.0 + +Initial release of a stable public API. + +## 0.9 + +Last version not to use PSR-0 project organization semantics. diff --git a/lib/php-css-parser/LICENSE b/lib/php-css-parser/LICENSE new file mode 100644 index 00000000000..686a4e311f9 --- /dev/null +++ b/lib/php-css-parser/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2011 Raphael Schweikert, https://www.sabberworm.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/lib/php-css-parser/README.md b/lib/php-css-parser/README.md new file mode 100644 index 00000000000..90428cbf8a6 --- /dev/null +++ b/lib/php-css-parser/README.md @@ -0,0 +1,637 @@ +# PHP CSS Parser + +[![Build Status](https://github.com/sabberworm/PHP-CSS-Parser/workflows/CI/badge.svg?branch=master)](https://github.com/sabberworm/PHP-CSS-Parser/actions/) + +A Parser for CSS Files written in PHP. Allows extraction of CSS files into a data structure, manipulation of said structure and output as (optimized) CSS. + +## Usage + +### Installation using Composer + +```bash +composer require sabberworm/php-css-parser +``` + +### Extraction + +To use the CSS Parser, create a new instance. The constructor takes the following form: + +```php +new \Sabberworm\CSS\Parser($css); +``` + +To read a file, for example, you’d do the following: + +```php +$parser = new \Sabberworm\CSS\Parser(file_get_contents('somefile.css')); +$cssDocument = $parser->parse(); +``` + +The resulting CSS document structure can be manipulated prior to being output. + +### Options + +#### Charset + +The charset option will only be used if the CSS file does not contain an `@charset` declaration. UTF-8 is the default, so you won’t have to create a settings object at all if you don’t intend to change that. + +```php +$settings = \Sabberworm\CSS\Settings::create() + ->withDefaultCharset('windows-1252'); +$parser = new \Sabberworm\CSS\Parser($css, $settings); +``` + +#### Strict parsing + +To have the parser throw an exception when encountering invalid/unknown constructs (as opposed to trying to ignore them and carry on parsing), supply a thusly configured `\Sabberworm\CSS\Settings` object: + +```php +$parser = new \Sabberworm\CSS\Parser( + file_get_contents('somefile.css'), + \Sabberworm\CSS\Settings::create()->beStrict() +); +``` + +Note that this will also disable a workaround for parsing the unquoted variant of the legacy IE-specific `filter` rule. + +#### Disable multibyte functions + +To achieve faster parsing, you can choose to have PHP-CSS-Parser use regular string functions instead of `mb_*` functions. This should work fine in most cases, even for UTF-8 files, as all the multibyte characters are in string literals. Still it’s not recommended using this with input you have no control over as it’s not thoroughly covered by test cases. + +```php +$settings = \Sabberworm\CSS\Settings::create()->withMultibyteSupport(false); +$parser = new \Sabberworm\CSS\Parser($css, $settings); +``` + +### Manipulation + +The resulting data structure consists mainly of five basic types: `CSSList`, `RuleSet`, `Rule`, `Selector` and `Value`. There are two additional types used: `Import` and `Charset`, which you won’t use often. + +#### CSSList + +`CSSList` represents a generic CSS container, most likely containing declaration blocks (rule sets with a selector), but it may also contain at-rules, charset declarations, etc. + +To access the items stored in a `CSSList` – like the document you got back when calling `$parser->parse()` –, use `getContents()`, then iterate over that collection and use `instanceof` to check whether you’re dealing with another `CSSList`, a `RuleSet`, a `Import` or a `Charset`. + +To append a new item (selector, media query, etc.) to an existing `CSSList`, construct it using the constructor for this class and use the `append($oItem)` method. + +#### RuleSet + +`RuleSet` is a container for individual rules. The most common form of a rule set is one constrained by a selector. The following concrete subtypes exist: + +* `AtRuleSet` – for generic at-rules for generic at-rules which are not covered by specific classes, i.e., not `@import`, `@charset` or `@media`. A common example for this is `@font-face`. +* `DeclarationBlock` – a `RuleSet` constrained by a `Selector`; contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the matching elements. + +Note: A `CSSList` can contain other `CSSList`s (and `Import`s as well as a `Charset`), while a `RuleSet` can only contain `Rule`s. + +If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)` (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules). + +#### Rule + +`Rule`s just have a string key (the rule) and a `Value`. + +#### Value + +`Value` is an abstract class that only defines the `render` method. The concrete subclasses for atomic value types are: + +* `Size` – consists of a numeric `size` value and a unit. +* `Color` – colors can be input in the form #rrggbb, #rgb or schema(val1, val2, …) but are always stored as an array of ('s' => val1, 'c' => val2, 'h' => val3, …) and output in the second form. +* `CSSString` – this is just a wrapper for quoted strings to distinguish them from keywords; always output with double quotes. +* `URL` – URLs in CSS; always output in `URL("")` notation. + +There is another abstract subclass of `Value`, `ValueList`: A `ValueList` represents a lists of `Value`s, separated by some separation character (mostly `,`, whitespace, or `/`). + +There are two types of `ValueList`s: + +* `RuleValueList` – The default type, used to represent all multivalued rules like `font: bold 12px/3 Helvetica, Verdana, sans-serif;` (where the value would be a whitespace-separated list of the primitive value `bold`, a slash-separated list and a comma-separated list). +* `CSSFunction` – A special kind of value that also contains a function name and where the values are the function’s arguments. Also handles equals-sign-separated argument lists like `filter: alpha(opacity=90);`. + +#### Convenience methods + +There are a few convenience methods on `Document` to ease finding, manipulating and deleting rules: + +* `getAllDeclarationBlocks()` – does what it says; no matter how deeply nested the selectors are. Aliased as `getAllSelectors()`. +* `getAllRuleSets()` – does what it says; no matter how deeply nested the rule sets are. +* `getAllValues()` – finds all `Value` objects inside `Rule`s. + +## To-Do + +* More convenience methods (like `selectorsWithElement($sId/Class/TagName)`, `attributesOfType($type)`, `removeAttributesOfType($type)`) +* Real multibyte support. Currently, only multibyte charsets whose first 255 code points take up only one byte and are identical with ASCII are supported (yes, UTF-8 fits this description). +* Named color support (using `Color` instead of an anonymous string literal) + +## Use cases + +### Use `Parser` to prepend an ID to all selectors + +```php +$myId = "#my_id"; +$parser = new \Sabberworm\CSS\Parser($css); +$cssDocument = $parser->parse(); +foreach ($cssDocument->getAllDeclarationBlocks() as $block) { + foreach ($block->getSelectors() as $selector) { + // Loop over all selector parts (the comma-separated strings in a + // selector) and prepend the ID. + $selector->setSelector($myId.' '.$selector->getSelector()); + } +} +``` + +### Shrink all absolute sizes to half + +```php +$parser = new \Sabberworm\CSS\Parser($css); +$cssDocument = $parser->parse(); +foreach ($cssDocument->getAllValues() as $value) { + if ($value instanceof CSSSize && !$value->isRelative()) { + $value->setSize($value->getSize() / 2); + } +} +``` + +### Remove unwanted rules + +```php +$parser = new \Sabberworm\CSS\Parser($css); +$cssDocument = $parser->parse(); +foreach($cssDocument->getAllRuleSets() as $oRuleSet) { + // Note that the added dash will make this remove all rules starting with + // `font-` (like `font-size`, `font-weight`, etc.) as well as a potential + // `font` rule. + $oRuleSet->removeRule('font-'); + $oRuleSet->removeRule('cursor'); +} +``` + +### Output + +To output the entire CSS document into a variable, just use `->render()`: + +```php +$parser = new \Sabberworm\CSS\Parser(file_get_contents('somefile.css')); +$cssDocument = $parser->parse(); +print $cssDocument->render(); +``` + +If you want to format the output, pass an instance of type `\Sabberworm\CSS\OutputFormat`: + +```php +$format = \Sabberworm\CSS\OutputFormat::create() + ->indentWithSpaces(4)->setSpaceBetweenRules("\n"); +print $cssDocument->render($format); +``` + +Or use one of the predefined formats: + +```php +print $cssDocument->render(Sabberworm\CSS\OutputFormat::createPretty()); +print $cssDocument->render(Sabberworm\CSS\OutputFormat::createCompact()); +``` + +To see what you can do with output formatting, look at the tests in `tests/OutputFormatTest.php`. + +## Examples + +### Example 1 (At-Rules) + +#### Input + +```css +@charset "utf-8"; + +@font-face { + font-family: "CrassRoots"; + src: url("../media/cr.ttf"); +} + +html, body { + font-size: 1.6em; +} + +@keyframes mymove { + from { top: 0px; } + to { top: 200px; } +} + +``` + +
+ Structure (var_dump()) + +```php +class Sabberworm\CSS\CSSList\Document#4 (2) { + protected $aContents => + array(4) { + [0] => + class Sabberworm\CSS\Property\Charset#6 (2) { + private $sCharset => + class Sabberworm\CSS\Value\CSSString#5 (2) { + private $sString => + string(5) "utf-8" + protected $iLineNo => + int(1) + } + protected $iLineNo => + int(1) + } + [1] => + class Sabberworm\CSS\RuleSet\AtRuleSet#7 (4) { + private $sType => + string(9) "font-face" + private $sArgs => + string(0) "" + private $aRules => + array(2) { + 'font-family' => + array(1) { + [0] => + class Sabberworm\CSS\Rule\Rule#8 (4) { + private $sRule => + string(11) "font-family" + private $mValue => + class Sabberworm\CSS\Value\CSSString#9 (2) { + private $sString => + string(10) "CrassRoots" + protected $iLineNo => + int(4) + } + private $bIsImportant => + bool(false) + protected $iLineNo => + int(4) + } + } + 'src' => + array(1) { + [0] => + class Sabberworm\CSS\Rule\Rule#10 (4) { + private $sRule => + string(3) "src" + private $mValue => + class Sabberworm\CSS\Value\URL#11 (2) { + private $oURL => + class Sabberworm\CSS\Value\CSSString#12 (2) { + private $sString => + string(15) "../media/cr.ttf" + protected $iLineNo => + int(5) + } + protected $iLineNo => + int(5) + } + private $bIsImportant => + bool(false) + protected $iLineNo => + int(5) + } + } + } + protected $iLineNo => + int(3) + } + [2] => + class Sabberworm\CSS\RuleSet\DeclarationBlock#13 (3) { + private $aSelectors => + array(2) { + [0] => + class Sabberworm\CSS\Property\Selector#14 (2) { + private $sSelector => + string(4) "html" + private $iSpecificity => + NULL + } + [1] => + class Sabberworm\CSS\Property\Selector#15 (2) { + private $sSelector => + string(4) "body" + private $iSpecificity => + NULL + } + } + private $aRules => + array(1) { + 'font-size' => + array(1) { + [0] => + class Sabberworm\CSS\Rule\Rule#16 (4) { + private $sRule => + string(9) "font-size" + private $mValue => + class Sabberworm\CSS\Value\Size#17 (4) { + private $fSize => + double(1.6) + private $sUnit => + string(2) "em" + private $bIsColorComponent => + bool(false) + protected $iLineNo => + int(9) + } + private $bIsImportant => + bool(false) + protected $iLineNo => + int(9) + } + } + } + protected $iLineNo => + int(8) + } + [3] => + class Sabberworm\CSS\CSSList\KeyFrame#18 (4) { + private $vendorKeyFrame => + string(9) "keyframes" + private $animationName => + string(6) "mymove" + protected $aContents => + array(2) { + [0] => + class Sabberworm\CSS\RuleSet\DeclarationBlock#19 (3) { + private $aSelectors => + array(1) { + [0] => + class Sabberworm\CSS\Property\Selector#20 (2) { + private $sSelector => + string(4) "from" + private $iSpecificity => + NULL + } + } + private $aRules => + array(1) { + 'top' => + array(1) { + [0] => + class Sabberworm\CSS\Rule\Rule#21 (4) { + private $sRule => + string(3) "top" + private $mValue => + class Sabberworm\CSS\Value\Size#22 (4) { + private $fSize => + double(0) + private $sUnit => + string(2) "px" + private $bIsColorComponent => + bool(false) + protected $iLineNo => + int(13) + } + private $bIsImportant => + bool(false) + protected $iLineNo => + int(13) + } + } + } + protected $iLineNo => + int(13) + } + [1] => + class Sabberworm\CSS\RuleSet\DeclarationBlock#23 (3) { + private $aSelectors => + array(1) { + [0] => + class Sabberworm\CSS\Property\Selector#24 (2) { + private $sSelector => + string(2) "to" + private $iSpecificity => + NULL + } + } + private $aRules => + array(1) { + 'top' => + array(1) { + [0] => + class Sabberworm\CSS\Rule\Rule#25 (4) { + private $sRule => + string(3) "top" + private $mValue => + class Sabberworm\CSS\Value\Size#26 (4) { + private $fSize => + double(200) + private $sUnit => + string(2) "px" + private $bIsColorComponent => + bool(false) + protected $iLineNo => + int(14) + } + private $bIsImportant => + bool(false) + protected $iLineNo => + int(14) + } + } + } + protected $iLineNo => + int(14) + } + } + protected $iLineNo => + int(12) + } + } + protected $iLineNo => + int(1) +} + +``` +
+ +#### Output (`render()`) + +```css +@charset "utf-8"; +@font-face {font-family: "CrassRoots";src: url("../media/cr.ttf");} +html, body {font-size: 1.6em;} +@keyframes mymove {from {top: 0px;} to {top: 200px;}} +``` + +### Example 2 (Values) + +#### Input + +```css +#header { + margin: 10px 2em 1cm 2%; + font-family: Verdana, Helvetica, "Gill Sans", sans-serif; + color: red !important; +} + +``` + +
+ Structure (var_dump()) + +```php +class Sabberworm\CSS\CSSList\Document#4 (2) { + protected $aContents => + array(1) { + [0] => + class Sabberworm\CSS\RuleSet\DeclarationBlock#5 (3) { + private $aSelectors => + array(1) { + [0] => + class Sabberworm\CSS\Property\Selector#6 (2) { + private $sSelector => + string(7) "#header" + private $iSpecificity => + NULL + } + } + private $aRules => + array(3) { + 'margin' => + array(1) { + [0] => + class Sabberworm\CSS\Rule\Rule#7 (4) { + private $sRule => + string(6) "margin" + private $mValue => + class Sabberworm\CSS\Value\RuleValueList#12 (3) { + protected $aComponents => + array(4) { + [0] => + class Sabberworm\CSS\Value\Size#8 (4) { + private $fSize => + double(10) + private $sUnit => + string(2) "px" + private $bIsColorComponent => + bool(false) + protected $iLineNo => + int(2) + } + [1] => + class Sabberworm\CSS\Value\Size#9 (4) { + private $fSize => + double(2) + private $sUnit => + string(2) "em" + private $bIsColorComponent => + bool(false) + protected $iLineNo => + int(2) + } + [2] => + class Sabberworm\CSS\Value\Size#10 (4) { + private $fSize => + double(1) + private $sUnit => + string(2) "cm" + private $bIsColorComponent => + bool(false) + protected $iLineNo => + int(2) + } + [3] => + class Sabberworm\CSS\Value\Size#11 (4) { + private $fSize => + double(2) + private $sUnit => + string(1) "%" + private $bIsColorComponent => + bool(false) + protected $iLineNo => + int(2) + } + } + protected $sSeparator => + string(1) " " + protected $iLineNo => + int(2) + } + private $bIsImportant => + bool(false) + protected $iLineNo => + int(2) + } + } + 'font-family' => + array(1) { + [0] => + class Sabberworm\CSS\Rule\Rule#13 (4) { + private $sRule => + string(11) "font-family" + private $mValue => + class Sabberworm\CSS\Value\RuleValueList#15 (3) { + protected $aComponents => + array(4) { + [0] => + string(7) "Verdana" + [1] => + string(9) "Helvetica" + [2] => + class Sabberworm\CSS\Value\CSSString#14 (2) { + private $sString => + string(9) "Gill Sans" + protected $iLineNo => + int(3) + } + [3] => + string(10) "sans-serif" + } + protected $sSeparator => + string(1) "," + protected $iLineNo => + int(3) + } + private $bIsImportant => + bool(false) + protected $iLineNo => + int(3) + } + } + 'color' => + array(1) { + [0] => + class Sabberworm\CSS\Rule\Rule#16 (4) { + private $sRule => + string(5) "color" + private $mValue => + string(3) "red" + private $bIsImportant => + bool(true) + protected $iLineNo => + int(4) + } + } + } + protected $iLineNo => + int(1) + } + } + protected $iLineNo => + int(1) +} + +``` +
+ +#### Output (`render()`) + +```css +#header {margin: 10px 2em 1cm 2%;font-family: Verdana,Helvetica,"Gill Sans",sans-serif;color: red !important;} +``` + +## Contributors/Thanks to + +* [oliverklee](https://github.com/oliverklee) for lots of refactorings, code modernizations and CI integrations +* [raxbg](https://github.com/raxbg) for contributions to parse `calc`, grid lines, and various bugfixes. +* [westonruter](https://github.com/westonruter) for bugfixes and improvements. +* [FMCorz](https://github.com/FMCorz) for many patches and suggestions, for being able to parse comments and IE hacks (in lenient mode). +* [Lullabot](https://github.com/Lullabot) for a patch that allows to know the line number for each parsed token. +* [ju1ius](https://github.com/ju1ius) for the specificity parsing code and the ability to expand/compact shorthand properties. +* [ossinkine](https://github.com/ossinkine) for a 150 time performance boost. +* [GaryJones](https://github.com/GaryJones) for lots of input and [https://css-specificity.info/](https://css-specificity.info/). +* [docteurklein](https://github.com/docteurklein) for output formatting and `CSSList->remove()` inspiration. +* [nicolopignatelli](https://github.com/nicolopignatelli) for PSR-0 compatibility. +* [diegoembarcadero](https://github.com/diegoembarcadero) for keyframe at-rule parsing. +* [goetas](https://github.com/goetas) for @namespace at-rule support. +* [View full list](https://github.com/sabberworm/PHP-CSS-Parser/contributors) + +## Misc + +* Legacy Support: The latest pre-PSR-0 version of this project can be checked with the `0.9.0` tag. +* Running Tests: To run all unit tests for this project, run `composer install` to install phpunit and use `./vendor/bin/phpunit`. diff --git a/lib/php-css-parser/composer.json b/lib/php-css-parser/composer.json index e192dd56a18..685db65ec79 100644 --- a/lib/php-css-parser/composer.json +++ b/lib/php-css-parser/composer.json @@ -12,15 +12,22 @@ "authors": [ { "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" } ], "require": { - "php": ">=5.6.20", + "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", "ext-iconv": "*" }, "require-dev": { - "phpunit/phpunit": "^4.8.36", - "codacy/coverage": "^1.4" + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.40" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" @@ -35,6 +42,11 @@ "Sabberworm\\CSS\\Tests\\": "tests/" } }, + "extra": { + "branch-alias": { + "dev-main": "9.0.x-dev" + } + }, "scripts": { "ci": [ "@ci:static" diff --git a/lib/php-css-parser/readme_moodle.txt b/lib/php-css-parser/readme_moodle.txt index 4040005a941..d8cd1bcf72e 100644 --- a/lib/php-css-parser/readme_moodle.txt +++ b/lib/php-css-parser/readme_moodle.txt @@ -2,8 +2,16 @@ PHP CSS Parser -------------- Import procedure: -1. Download the latest release from https://github.com/sabberworm/PHP-CSS-Parser/releases -2. Copy all the files from the folder 'lib/Sabberworm/CSS/' in this directory. -3. Apply the following patches from the following pull requests if they have not yet been merged upstream: - a. https://github.com/sabberworm/PHP-CSS-Parser/pull/115 - b. https://github.com/sabberworm/PHP-CSS-Parser/pull/173 +``` +tempdir=`mktemp -d` +cd $tempdir +composer require sabberworm/php-css-parser +cd - +cp lib/php-css-parser/readme_moodle* $tempdir +rm -rf lib/php-css-parser +cp -rf $tempdir/vendor/sabberworm/php-css-parser lib/php-css-parser +cp -rf $tempdir/readme* lib/php-css-parser +``` + +Apply the following patches: +- None at this time diff --git a/lib/php-css-parser/src/CSSList/AtRuleBlockList.php b/lib/php-css-parser/src/CSSList/AtRuleBlockList.php index 218adb9a196..5930b932b1b 100644 --- a/lib/php-css-parser/src/CSSList/AtRuleBlockList.php +++ b/lib/php-css-parser/src/CSSList/AtRuleBlockList.php @@ -57,17 +57,20 @@ class AtRuleBlockList extends CSSBlockList implements AtRule } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { + $sResult = $oOutputFormat->comments($this); + $sResult .= $oOutputFormat->sBeforeAtRuleBlock; $sArgs = $this->sArgs; if ($sArgs) { $sArgs = ' ' . $sArgs; } - $sResult = $oOutputFormat->sBeforeAtRuleBlock; $sResult .= "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{"; - $sResult .= parent::render($oOutputFormat); + $sResult .= $this->renderListContents($oOutputFormat); $sResult .= '}'; $sResult .= $oOutputFormat->sAfterAtRuleBlock; return $sResult; diff --git a/lib/php-css-parser/src/CSSList/CSSList.php b/lib/php-css-parser/src/CSSList/CSSList.php index e24caeeb6f3..4bb37589149 100644 --- a/lib/php-css-parser/src/CSSList/CSSList.php +++ b/lib/php-css-parser/src/CSSList/CSSList.php @@ -24,10 +24,10 @@ use Sabberworm\CSS\Value\URL; use Sabberworm\CSS\Value\Value; /** - * A `CSSList` is the most generic container available. Its contents include `RuleSet` as well as other `CSSList` - * objects. + * This is the most generic container available. It can contain `DeclarationBlock`s (rule sets with a selector), + * `RuleSet`s as well as other `CSSList` objects. * - * Also, it may contain `Import` and `Charset` objects stemming from at-rules. + * It can also contain `Import` and `Charset` objects stemming from at-rules. */ abstract class CSSList implements Renderable, Commentable { @@ -69,8 +69,9 @@ abstract class CSSList implements Renderable, Commentable $oParserState = new ParserState($oParserState, Settings::create()); } $bLenientParsing = $oParserState->getSettings()->bLenientParsing; + $aComments = []; while (!$oParserState->isEnd()) { - $comments = $oParserState->consumeWhiteSpace(); + $aComments = array_merge($aComments, $oParserState->consumeWhiteSpace()); $oListItem = null; if ($bLenientParsing) { try { @@ -86,10 +87,12 @@ abstract class CSSList implements Renderable, Commentable return; } if ($oListItem) { - $oListItem->setComments($comments); + $oListItem->addComments($aComments); $oList->append($oListItem); } + $aComments = $oParserState->consumeWhiteSpace(); } + $oList->addComments($aComments); if (!$bIsRoot && !$bLenientParsing) { throw new SourceException("Unexpected end of document", $oParserState->currentLine()); } @@ -124,22 +127,19 @@ abstract class CSSList implements Renderable, Commentable $oParserState->currentLine() ); } - $oParserState->setCharset($oAtRule->getCharset()->getString()); + $oParserState->setCharset($oAtRule->getCharset()); } return $oAtRule; } elseif ($oParserState->comes('}')) { - if (!$oParserState->getSettings()->bLenientParsing) { - throw new UnexpectedTokenException('CSS selector', '}', 'identifier', $oParserState->currentLine()); - } else { - if ($bIsRoot) { - if ($oParserState->getSettings()->bLenientParsing) { - return DeclarationBlock::parse($oParserState); - } else { - throw new SourceException("Unopened {", $oParserState->currentLine()); - } + if ($bIsRoot) { + if ($oParserState->getSettings()->bLenientParsing) { + return DeclarationBlock::parse($oParserState); } else { - return null; + throw new SourceException("Unopened {", $oParserState->currentLine()); } + } else { + // End of list + return null; } } else { return DeclarationBlock::parse($oParserState, $oList); @@ -171,10 +171,10 @@ abstract class CSSList implements Renderable, Commentable $oParserState->consumeUntil([';', ParserState::EOF], true, true); return new Import($oLocation, $sMediaQuery ?: null, $iIdentifierLineNum); } elseif ($sIdentifier === 'charset') { - $sCharset = CSSString::parse($oParserState); + $oCharsetString = CSSString::parse($oParserState); $oParserState->consumeWhiteSpace(); $oParserState->consumeUntil([';', ParserState::EOF], true, true); - return new Charset($sCharset, $iIdentifierLineNum); + return new Charset($oCharsetString, $iIdentifierLineNum); } elseif (self::identifierIs($sIdentifier, 'keyframes')) { $oResult = new KeyFrame($iIdentifierLineNum); $oResult->setVendorKeyFrame($sIdentifier); @@ -271,7 +271,7 @@ abstract class CSSList implements Renderable, Commentable } /** - * Appends an item to tje list of contents. + * Appends an item to the list of contents. * * @param RuleSet|CSSList|Import|Charset $oItem * @@ -282,20 +282,6 @@ abstract class CSSList implements Renderable, Commentable $this->aContents[] = $oItem; } - /** - * Insert an item before its sibling. - * - * @param mixed $oItem The item. - * @param mixed $oSibling The sibling. - */ - public function insert($oItem, $oSibling) { - $iIndex = array_search($oSibling, $this->aContents); - if ($iIndex === false) { - return $this->append($oItem); - } - array_splice($this->aContents, $iIndex, 0, array($oItem)); - } - /** * Splices the list of contents. * @@ -310,6 +296,22 @@ abstract class CSSList implements Renderable, Commentable array_splice($this->aContents, $iOffset, $iLength, $mReplacement); } + /** + * Inserts an item in the CSS list before its sibling. If the desired sibling cannot be found, + * the item is appended at the end. + * + * @param RuleSet|CSSList|Import|Charset $item + * @param RuleSet|CSSList|Import|Charset $sibling + */ + public function insertBefore($item, $sibling) + { + if (in_array($sibling, $this->aContents, true)) { + $this->replace($sibling, [$item, $sibling]); + } else { + $this->append($item); + } + } + /** * Removes an item from the CSS list. * @@ -415,7 +417,7 @@ abstract class CSSList implements Renderable, Commentable /** * @return string */ - public function render(OutputFormat $oOutputFormat) + protected function renderListContents(OutputFormat $oOutputFormat) { $sResult = ''; $bIsFirst = true; @@ -455,6 +457,8 @@ abstract class CSSList implements Renderable, Commentable abstract public function isRootList(); /** + * Returns the stored items. + * * @return array */ public function getContents() diff --git a/lib/php-css-parser/src/CSSList/Document.php b/lib/php-css-parser/src/CSSList/Document.php index 91ab2c6b2e7..7d9c6ec0a0c 100644 --- a/lib/php-css-parser/src/CSSList/Document.php +++ b/lib/php-css-parser/src/CSSList/Document.php @@ -11,8 +11,8 @@ use Sabberworm\CSS\RuleSet\RuleSet; use Sabberworm\CSS\Value\Value; /** - * The root `CSSList` of a parsed file. Contains all top-level CSS contents, mostly declaration blocks, - * but also any at-rules encountered. + * This class represents the root of a parsed CSS file. It contains all top-level CSS contents: mostly declaration + * blocks, but also any at-rules encountered (`Import` and `Charset`). */ class Document extends CSSBlockList { @@ -37,7 +37,8 @@ class Document extends CSSBlockList } /** - * Gets all `DeclarationBlock` objects recursively. + * Gets all `DeclarationBlock` objects recursively, no matter how deeply nested the selectors are. + * Aliased as `getAllSelectors()`. * * @return array */ @@ -62,7 +63,7 @@ class Document extends CSSBlockList } /** - * Returns all `RuleSet` objects found recursively in the tree. + * Returns all `RuleSet` objects recursively found in the tree, no matter how deeply nested the rule sets are. * * @return array */ @@ -75,7 +76,7 @@ class Document extends CSSBlockList } /** - * Returns all `Value` objects found recursively in the tree. + * Returns all `Value` objects found recursively in `Rule`s in the tree. * * @param CSSList|RuleSet|string $mElement * the `CSSList` or `RuleSet` to start the search from (defaults to the whole document). @@ -102,7 +103,7 @@ class Document extends CSSBlockList } /** - * Returns all `Selector` objects found recursively in the tree. + * Returns all `Selector` objects with the requested specificity found recursively in the tree. * * Note that this does not yield the full `DeclarationBlock` that the selector belongs to * (and, currently, there is no way to get to that). @@ -127,6 +128,8 @@ class Document extends CSSBlockList * Expands all shorthand properties to their long value. * * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function expandShorthands() { @@ -139,6 +142,8 @@ class Document extends CSSBlockList * Create shorthands properties whenever possible. * * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createShorthands() { @@ -154,12 +159,12 @@ class Document extends CSSBlockList * * @return string */ - public function render(OutputFormat $oOutputFormat = null) + public function render($oOutputFormat = null) { if ($oOutputFormat === null) { $oOutputFormat = new OutputFormat(); } - return parent::render($oOutputFormat); + return $oOutputFormat->comments($this) . $this->renderListContents($oOutputFormat); } /** diff --git a/lib/php-css-parser/src/CSSList/KeyFrame.php b/lib/php-css-parser/src/CSSList/KeyFrame.php index d9420e9c0d4..69e2e4d94d1 100644 --- a/lib/php-css-parser/src/CSSList/KeyFrame.php +++ b/lib/php-css-parser/src/CSSList/KeyFrame.php @@ -68,12 +68,15 @@ class KeyFrame extends CSSList implements AtRule } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { - $sResult = "@{$this->vendorKeyFrame} {$this->animationName}{$oOutputFormat->spaceBeforeOpeningBrace()}{"; - $sResult .= parent::render($oOutputFormat); + $sResult = $oOutputFormat->comments($this); + $sResult .= "@{$this->vendorKeyFrame} {$this->animationName}{$oOutputFormat->spaceBeforeOpeningBrace()}{"; + $sResult .= $this->renderListContents($oOutputFormat); $sResult .= '}'; return $sResult; } diff --git a/lib/php-css-parser/src/Comment/Comment.php b/lib/php-css-parser/src/Comment/Comment.php index 6128d749878..e6ffaaf736e 100644 --- a/lib/php-css-parser/src/Comment/Comment.php +++ b/lib/php-css-parser/src/Comment/Comment.php @@ -62,9 +62,11 @@ class Comment implements Renderable } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { return '/*' . $this->sComment . '*/'; } diff --git a/lib/php-css-parser/src/OutputFormat.php b/lib/php-css-parser/src/OutputFormat.php index 595d306431c..96f26e147e6 100644 --- a/lib/php-css-parser/src/OutputFormat.php +++ b/lib/php-css-parser/src/OutputFormat.php @@ -143,6 +143,13 @@ class OutputFormat */ public $bIgnoreExceptions = false; + /** + * Render comments for lists and RuleSets + * + * @var bool + */ + public $bRenderComments = false; + /** * @var OutputFormatter|null */ @@ -314,8 +321,12 @@ class OutputFormat public static function createCompact() { $format = self::create(); - $format->set('Space*Rules', "")->set('Space*Blocks', "")->setSpaceAfterRuleName('') - ->setSpaceBeforeOpeningBrace('')->setSpaceAfterSelectorSeparator(''); + $format->set('Space*Rules', "") + ->set('Space*Blocks', "") + ->setSpaceAfterRuleName('') + ->setSpaceBeforeOpeningBrace('') + ->setSpaceAfterSelectorSeparator('') + ->setRenderComments(false); return $format; } @@ -327,8 +338,11 @@ class OutputFormat public static function createPretty() { $format = self::create(); - $format->set('Space*Rules', "\n")->set('Space*Blocks', "\n") - ->setSpaceBetweenBlocks("\n\n")->set('SpaceAfterListArgumentSeparator', ['default' => '', ',' => ' ']); + $format->set('Space*Rules', "\n") + ->set('Space*Blocks', "\n") + ->setSpaceBetweenBlocks("\n\n") + ->set('SpaceAfterListArgumentSeparator', ['default' => '', ',' => ' ']) + ->setRenderComments(true); return $format; } } diff --git a/lib/php-css-parser/src/OutputFormatter.php b/lib/php-css-parser/src/OutputFormatter.php index 535feca7ff9..501d15da309 100644 --- a/lib/php-css-parser/src/OutputFormatter.php +++ b/lib/php-css-parser/src/OutputFormatter.php @@ -2,6 +2,7 @@ namespace Sabberworm\CSS; +use Sabberworm\CSS\Comment\Commentable; use Sabberworm\CSS\Parsing\OutputException; class OutputFormatter @@ -211,6 +212,29 @@ class OutputFormatter return implode(';', $sString); } + /** + * + * @param array $aComments + * + * @return string + */ + public function comments(Commentable $oCommentable) + { + if (!$this->oFormat->bRenderComments) { + return ''; + } + + $sResult = ''; + $aComments = $oCommentable->getComments(); + $iLastCommentIndex = count($aComments) - 1; + + foreach ($aComments as $i => $oComment) { + $sResult .= $oComment->render($this->oFormat); + $sResult .= $i === $iLastCommentIndex ? $this->spaceAfterBlocks() : $this->spaceBetweenBlocks(); + } + return $sResult; + } + /** * @param string $sSpaceString * diff --git a/lib/php-css-parser/src/Parser.php b/lib/php-css-parser/src/Parser.php index f3b0493a5fd..f60fc086b2f 100644 --- a/lib/php-css-parser/src/Parser.php +++ b/lib/php-css-parser/src/Parser.php @@ -17,11 +17,11 @@ class Parser private $oParserState; /** - * @param string $sText + * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file) * @param Settings|null $oParserSettings * @param int $iLineNo the line number (starting from 1, not from 0) */ - public function __construct($sText, Settings $oParserSettings = null, $iLineNo = 1) + public function __construct($sText, $oParserSettings = null, $iLineNo = 1) { if ($oParserSettings === null) { $oParserSettings = Settings::create(); @@ -30,9 +30,13 @@ class Parser } /** + * Sets the charset to be used if the CSS does not contain an `@charset` declaration. + * * @param string $sCharset * * @return void + * + * @deprecated since 8.7.0, will be removed in version 9.0.0 with #687 */ public function setCharset($sCharset) { @@ -40,7 +44,11 @@ class Parser } /** + * Returns the charset that is used if the CSS does not contain an `@charset` declaration. + * * @return void + * + * @deprecated since 8.7.0, will be removed in version 9.0.0 with #687 */ public function getCharset() { @@ -49,6 +57,8 @@ class Parser } /** + * Parses the CSS provided to the constructor and creates a `Document` from it. + * * @return Document * * @throws SourceException diff --git a/lib/php-css-parser/src/Parsing/Anchor.php b/lib/php-css-parser/src/Parsing/Anchor.php new file mode 100644 index 00000000000..a42893da66c --- /dev/null +++ b/lib/php-css-parser/src/Parsing/Anchor.php @@ -0,0 +1,37 @@ +iPosition = $iPosition; + $this->oParserState = $oParserState; + } + + /** + * @return void + */ + public function backtrack() + { + $this->oParserState->setPosition($this->iPosition); + } +} diff --git a/lib/php-css-parser/src/Parsing/ParserState.php b/lib/php-css-parser/src/Parsing/ParserState.php index e7d85ee0f1e..2427c5e1f49 100644 --- a/lib/php-css-parser/src/Parsing/ParserState.php +++ b/lib/php-css-parser/src/Parsing/ParserState.php @@ -5,10 +5,15 @@ namespace Sabberworm\CSS\Parsing; use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\Settings; +/** + * @internal since 8.7.0 + */ class ParserState { /** * @var null + * + * @internal since 8.5.2 */ const EOF = null; @@ -33,6 +38,8 @@ class ParserState private $iCurrentPosition; /** + * will only be used if the CSS does not contain an `@charset` declaration + * * @var string */ private $sCharset; @@ -48,7 +55,7 @@ class ParserState private $iLineNo; /** - * @param string $sText + * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file) * @param int $iLineNo */ public function __construct($sText, Settings $oParserSettings, $iLineNo = 1) @@ -61,6 +68,8 @@ class ParserState } /** + * Sets the charset to be used if the CSS does not contain an `@charset` declaration. + * * @param string $sCharset * * @return void @@ -75,6 +84,8 @@ class ParserState } /** + * Returns the charset that is used if the CSS does not contain an `@charset` declaration. + * * @return string */ public function getCharset() @@ -106,6 +117,24 @@ class ParserState return $this->oParserSettings; } + /** + * @return \Sabberworm\CSS\Parsing\Anchor + */ + public function anchor() + { + return new Anchor($this->iCurrentPosition, $this); + } + + /** + * @param int $iPosition + * + * @return void + */ + public function setPosition($iPosition) + { + $this->iCurrentPosition = $iPosition; + } + /** * @param bool $bIgnoreCase * @@ -115,12 +144,15 @@ class ParserState */ public function parseIdentifier($bIgnoreCase = true) { + if ($this->isEnd()) { + throw new UnexpectedEOFException('', '', 'identifier', $this->iLineNo); + } $sResult = $this->parseCharacter(true); if ($sResult === null) { throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier', $this->iLineNo); } $sCharacter = null; - while (($sCharacter = $this->parseCharacter(true)) !== null) { + while (!$this->isEnd() && ($sCharacter = $this->parseCharacter(true)) !== null) { if (preg_match('/[a-zA-Z0-9\x{00A0}-\x{FFFF}_-]/Sux', $sCharacter)) { $sResult .= $sCharacter; } else { @@ -204,7 +236,7 @@ class ParserState */ public function consumeWhiteSpace() { - $comments = []; + $aComments = []; do { while (preg_match('/\\s/isSu', $this->peek()) === 1) { $this->consume(1); @@ -214,16 +246,16 @@ class ParserState $oComment = $this->consumeComment(); } catch (UnexpectedEOFException $e) { $this->iCurrentPosition = $this->iLength; - return; + return $aComments; } } else { $oComment = $this->consumeComment(); } if ($oComment !== false) { - $comments[] = $oComment; + $aComments[] = $oComment; } } while ($oComment !== false); - return $comments; + return $aComments; } /** diff --git a/lib/php-css-parser/src/Property/AtRule.php b/lib/php-css-parser/src/Property/AtRule.php index 9536ff5e973..d946a9046e6 100644 --- a/lib/php-css-parser/src/Property/AtRule.php +++ b/lib/php-css-parser/src/Property/AtRule.php @@ -12,6 +12,8 @@ interface AtRule extends Renderable, Commentable * we’re whitelisting the block rules and have anything else be treated as a set rule. * * @var string + * + * @internal since 8.5.2 */ const BLOCK_RULES = 'media/document/supports/region-style/font-feature-values'; @@ -19,6 +21,8 @@ interface AtRule extends Renderable, Commentable * … and more font-specific ones (to be used inside font-feature-values) * * @var string + * + * @internal since 8.5.2 */ const SET_RULES = 'font-face/counter-style/page/swash/styleset/annotation'; diff --git a/lib/php-css-parser/src/Property/CSSNamespace.php b/lib/php-css-parser/src/Property/CSSNamespace.php index 0d7eb496967..d1bac4f7131 100644 --- a/lib/php-css-parser/src/Property/CSSNamespace.php +++ b/lib/php-css-parser/src/Property/CSSNamespace.php @@ -60,9 +60,11 @@ class CSSNamespace implements AtRule } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { return '@namespace ' . ($this->sPrefix === null ? '' : $this->sPrefix . ' ') . $this->mUrl->render($oOutputFormat) . ';'; diff --git a/lib/php-css-parser/src/Property/Charset.php b/lib/php-css-parser/src/Property/Charset.php index 3ee0c3d042e..870380e34a1 100644 --- a/lib/php-css-parser/src/Property/Charset.php +++ b/lib/php-css-parser/src/Property/Charset.php @@ -4,6 +4,7 @@ namespace Sabberworm\CSS\Property; use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Value\CSSString; /** * Class representing an `@charset` rule. @@ -16,9 +17,9 @@ use Sabberworm\CSS\OutputFormat; class Charset implements AtRule { /** - * @var string + * @var CSSString */ - private $sCharset; + private $oCharset; /** * @var int @@ -31,12 +32,12 @@ class Charset implements AtRule protected $aComments; /** - * @param string $sCharset + * @param CSSString $oCharset * @param int $iLineNo */ - public function __construct($sCharset, $iLineNo = 0) + public function __construct(CSSString $oCharset, $iLineNo = 0) { - $this->sCharset = $sCharset; + $this->oCharset = $oCharset; $this->iLineNo = $iLineNo; $this->aComments = []; } @@ -50,13 +51,14 @@ class Charset implements AtRule } /** - * @param string $sCharset + * @param string|CSSString $oCharset * * @return void */ public function setCharset($sCharset) { - $this->sCharset = $sCharset; + $sCharset = $sCharset instanceof CSSString ? $sCharset : new CSSString($sCharset); + $this->oCharset = $sCharset; } /** @@ -64,7 +66,7 @@ class Charset implements AtRule */ public function getCharset() { - return $this->sCharset; + return $this->oCharset->getString(); } /** @@ -76,11 +78,13 @@ class Charset implements AtRule } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { - return "@charset {$this->sCharset->render($oOutputFormat)};"; + return "{$oOutputFormat->comments($this)}@charset {$this->oCharset->render($oOutputFormat)};"; } /** @@ -96,7 +100,7 @@ class Charset implements AtRule */ public function atRuleArgs() { - return $this->sCharset; + return $this->oCharset; } /** diff --git a/lib/php-css-parser/src/Property/Import.php b/lib/php-css-parser/src/Property/Import.php index a2253016b0c..2a7cad54c53 100644 --- a/lib/php-css-parser/src/Property/Import.php +++ b/lib/php-css-parser/src/Property/Import.php @@ -79,11 +79,13 @@ class Import implements AtRule } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { - return "@import " . $this->oLocation->render($oOutputFormat) + return $oOutputFormat->comments($this) . "@import " . $this->oLocation->render($oOutputFormat) . ($this->sMediaQuery === null ? '' : ' ' . $this->sMediaQuery) . ';'; } @@ -134,4 +136,12 @@ class Import implements AtRule { $this->aComments = $aComments; } + + /** + * @return string + */ + public function getMediaQuery() + { + return $this->sMediaQuery; + } } diff --git a/lib/php-css-parser/src/Property/KeyframeSelector.php b/lib/php-css-parser/src/Property/KeyframeSelector.php index 14ea5ebb77a..2aff8d247c3 100644 --- a/lib/php-css-parser/src/Property/KeyframeSelector.php +++ b/lib/php-css-parser/src/Property/KeyframeSelector.php @@ -8,6 +8,8 @@ class KeyframeSelector extends Selector * regexp for specificity calculations * * @var string + * + * @internal since 8.5.2 */ const SELECTOR_VALIDATION_RX = '/ ^( diff --git a/lib/php-css-parser/src/Property/Selector.php b/lib/php-css-parser/src/Property/Selector.php index 70c9b2fd5c2..44355e6353a 100644 --- a/lib/php-css-parser/src/Property/Selector.php +++ b/lib/php-css-parser/src/Property/Selector.php @@ -12,6 +12,8 @@ class Selector * regexp for specificity calculations * * @var string + * + * @internal */ const NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = '/ (\.[\w]+) # classes @@ -36,6 +38,8 @@ class Selector * regexp for specificity calculations * * @var string + * + * @internal */ const ELEMENTS_AND_PSEUDO_ELEMENTS_RX = '/ ((^|[\s\+\>\~]+)[\w]+ # elements @@ -49,6 +53,8 @@ class Selector * regexp for specificity calculations * * @var string + * + * @internal since 8.5.2 */ const SELECTOR_VALIDATION_RX = '/ ^( diff --git a/lib/php-css-parser/src/Renderable.php b/lib/php-css-parser/src/Renderable.php index dc1bff3c1e9..d7c6aba667a 100644 --- a/lib/php-css-parser/src/Renderable.php +++ b/lib/php-css-parser/src/Renderable.php @@ -10,9 +10,11 @@ interface Renderable public function __toString(); /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat); + public function render($oOutputFormat); /** * @return int diff --git a/lib/php-css-parser/src/Rule/Rule.php b/lib/php-css-parser/src/Rule/Rule.php index 52b97f38e15..c1bad65ebe3 100644 --- a/lib/php-css-parser/src/Rule/Rule.php +++ b/lib/php-css-parser/src/Rule/Rule.php @@ -13,8 +13,9 @@ use Sabberworm\CSS\Value\RuleValueList; use Sabberworm\CSS\Value\Value; /** - * RuleSets contains Rule objects which always have a key and a value. - * In CSS, Rules are expressed as follows: “key: value[0][0] value[0][1], value[1][0] value[1][1];” + * `Rule`s just have a string key (the rule) and a 'Value'. + * + * In CSS, `Rule`s are expressed as follows: “key: value[0][0] value[0][1], value[1][0] value[1][1];” */ class Rule implements Renderable, Commentable { @@ -24,7 +25,7 @@ class Rule implements Renderable, Commentable private $sRule; /** - * @var RuleValueList|null + * @var RuleValueList|string|null */ private $mValue; @@ -107,6 +108,8 @@ class Rule implements Renderable, Commentable $oParserState->consume(';'); } + $oParserState->consumeWhiteSpace(); + return $oRule; } @@ -170,7 +173,7 @@ class Rule implements Renderable, Commentable } /** - * @return RuleValueList|null + * @return RuleValueList|string|null */ public function getValue() { @@ -178,7 +181,7 @@ class Rule implements Renderable, Commentable } /** - * @param RuleValueList|null $mValue + * @param RuleValueList|string|null $mValue * * @return void */ @@ -341,12 +344,14 @@ class Rule implements Renderable, Commentable } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { - $sResult = "{$this->sRule}:{$oOutputFormat->spaceAfterRuleName()}"; - if ($this->mValue instanceof Value) { //Can also be a ValueList + $sResult = "{$oOutputFormat->comments($this)}{$this->sRule}:{$oOutputFormat->spaceAfterRuleName()}"; + if ($this->mValue instanceof Value) { // Can also be a ValueList $sResult .= $this->mValue->render($oOutputFormat); } else { $sResult .= $this->mValue; diff --git a/lib/php-css-parser/src/RuleSet/AtRuleSet.php b/lib/php-css-parser/src/RuleSet/AtRuleSet.php index 88bc5bd3139..93fd07af8bd 100644 --- a/lib/php-css-parser/src/RuleSet/AtRuleSet.php +++ b/lib/php-css-parser/src/RuleSet/AtRuleSet.php @@ -6,7 +6,10 @@ use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Property\AtRule; /** - * A RuleSet constructed by an unknown at-rule. `@font-face` rules are rendered into AtRuleSet objects. + * This class represents rule sets for generic at-rules which are not covered by specific classes, i.e., not + * `@import`, `@charset` or `@media`. + * + * A common example for this is `@font-face`. */ class AtRuleSet extends RuleSet implements AtRule { @@ -57,16 +60,19 @@ class AtRuleSet extends RuleSet implements AtRule } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { + $sResult = $oOutputFormat->comments($this); $sArgs = $this->sArgs; if ($sArgs) { $sArgs = ' ' . $sArgs; } - $sResult = "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{"; - $sResult .= parent::render($oOutputFormat); + $sResult .= "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{"; + $sResult .= $this->renderRules($oOutputFormat); $sResult .= '}'; return $sResult; } diff --git a/lib/php-css-parser/src/RuleSet/DeclarationBlock.php b/lib/php-css-parser/src/RuleSet/DeclarationBlock.php index c27cdd4c81b..8a1da14e3a6 100644 --- a/lib/php-css-parser/src/RuleSet/DeclarationBlock.php +++ b/lib/php-css-parser/src/RuleSet/DeclarationBlock.php @@ -19,7 +19,10 @@ use Sabberworm\CSS\Value\URL; use Sabberworm\CSS\Value\Value; /** - * Declaration blocks are the parts of a CSS file which denote the rules belonging to a selector. + * This class represents a `RuleSet` constrained by a `Selector`. + * + * It contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the + * matching elements. * * Declaration blocks usually appear directly inside a `Document` or another `CSSList` (mostly a `MediaQuery`). */ @@ -178,6 +181,8 @@ class DeclarationBlock extends RuleSet * Splits shorthand declarations (e.g. `margin` or `font`) into their constituent parts. * * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function expandShorthands() { @@ -193,6 +198,8 @@ class DeclarationBlock extends RuleSet * Creates shorthand declarations (e.g. `margin` or `font`) whenever possible. * * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createShorthands() { @@ -212,6 +219,8 @@ class DeclarationBlock extends RuleSet * Multiple borders are not yet supported as of 3. * * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function expandBorderShorthand() { @@ -273,6 +282,8 @@ class DeclarationBlock extends RuleSet * Handles `margin`, `padding`, `border-color`, `border-style` and `border-width`. * * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function expandDimensionsShorthand() { @@ -333,6 +344,8 @@ class DeclarationBlock extends RuleSet * into their constituent parts. * * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function expandFontShorthand() { @@ -403,6 +416,8 @@ class DeclarationBlock extends RuleSet * @see http://www.w3.org/TR/21/colors.html#propdef-background * * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function expandBackgroundShorthand() { @@ -475,6 +490,8 @@ class DeclarationBlock extends RuleSet /** * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function expandListStyleShorthand() { @@ -558,10 +575,13 @@ class DeclarationBlock extends RuleSet * @param string $sShorthand * * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createShorthandProperties(array $aProperties, $sShorthand) { $aRules = $this->getRulesAssoc(); + $oRule = null; $aNewValues = []; foreach ($aProperties as $sProperty) { if (!isset($aRules[$sProperty])) { @@ -582,7 +602,7 @@ class DeclarationBlock extends RuleSet $this->removeRule($sProperty); } } - if (count($aNewValues)) { + if ($aNewValues !== [] && $oRule instanceof Rule) { $oNewRule = new Rule($sShorthand, $oRule->getLineNo(), $oRule->getColNo()); foreach ($aNewValues as $mValue) { $oNewRule->addValue($mValue); @@ -593,6 +613,8 @@ class DeclarationBlock extends RuleSet /** * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createBackgroundShorthand() { @@ -608,6 +630,8 @@ class DeclarationBlock extends RuleSet /** * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createListStyleShorthand() { @@ -625,6 +649,8 @@ class DeclarationBlock extends RuleSet * Should be run after `create_dimensions_shorthand`! * * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createBorderShorthand() { @@ -642,6 +668,8 @@ class DeclarationBlock extends RuleSet * and converts them into shorthand CSS properties. * * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createDimensionsShorthand() { @@ -716,6 +744,8 @@ class DeclarationBlock extends RuleSet * At least `font-size` AND `font-family` must be present in order to create a shorthand declaration. * * @return void + * + * @deprecated since 8.7.0, will be removed without substitution in version 9.0 in #511 */ public function createFontShorthand() { @@ -806,24 +836,27 @@ class DeclarationBlock extends RuleSet } /** + * @param OutputFormat|null $oOutputFormat + * * @return string * * @throws OutputException */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { + $sResult = $oOutputFormat->comments($this); if (count($this->aSelectors) === 0) { // If all the selectors have been removed, this declaration block becomes invalid throw new OutputException("Attempt to print declaration block with missing selector", $this->iLineNo); } - $sResult = $oOutputFormat->sBeforeDeclarationBlock; + $sResult .= $oOutputFormat->sBeforeDeclarationBlock; $sResult .= $oOutputFormat->implode( $oOutputFormat->spaceBeforeSelectorSeparator() . ',' . $oOutputFormat->spaceAfterSelectorSeparator(), $this->aSelectors ); $sResult .= $oOutputFormat->sAfterDeclarationBlockSelectors; $sResult .= $oOutputFormat->spaceBeforeOpeningBrace() . '{'; - $sResult .= parent::render($oOutputFormat); + $sResult .= $this->renderRules($oOutputFormat); $sResult .= '}'; $sResult .= $oOutputFormat->sAfterDeclarationBlock; return $sResult; diff --git a/lib/php-css-parser/src/RuleSet/RuleSet.php b/lib/php-css-parser/src/RuleSet/RuleSet.php index 9404bb0bdb9..8cbbd34f826 100644 --- a/lib/php-css-parser/src/RuleSet/RuleSet.php +++ b/lib/php-css-parser/src/RuleSet/RuleSet.php @@ -12,8 +12,13 @@ use Sabberworm\CSS\Renderable; use Sabberworm\CSS\Rule\Rule; /** - * RuleSet is a generic superclass denoting rules. The typical example for rule sets are declaration block. - * However, unknown At-Rules (like `@font-face`) are also rule sets. + * This class is a container for individual 'Rule's. + * + * The most common form of a rule set is one constrained by a selector, i.e., a `DeclarationBlock`. + * However, unknown `AtRule`s (like `@font-face`) are rule sets as well. + * + * If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)` + * (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules). */ abstract class RuleSet implements Renderable, Commentable { @@ -97,7 +102,7 @@ abstract class RuleSet implements Renderable, Commentable * * @return void */ - public function addRule(Rule $oRule, Rule $oSibling = null) + public function addRule(Rule $oRule, $oSibling = null) { $sRule = $oRule->getRule(); if (!isset($this->aRules[$sRule])) { @@ -266,23 +271,24 @@ abstract class RuleSet implements Renderable, Commentable /** * @return string */ - public function render(OutputFormat $oOutputFormat) + protected function renderRules(OutputFormat $oOutputFormat) { $sResult = ''; $bIsFirst = true; + $oNextLevel = $oOutputFormat->nextLevel(); foreach ($this->aRules as $aRules) { foreach ($aRules as $oRule) { - $sRendered = $oOutputFormat->safely(function () use ($oRule, $oOutputFormat) { - return $oRule->render($oOutputFormat->nextLevel()); + $sRendered = $oNextLevel->safely(function () use ($oRule, $oNextLevel) { + return $oRule->render($oNextLevel); }); if ($sRendered === null) { continue; } if ($bIsFirst) { $bIsFirst = false; - $sResult .= $oOutputFormat->nextLevel()->spaceBeforeRules(); + $sResult .= $oNextLevel->spaceBeforeRules(); } else { - $sResult .= $oOutputFormat->nextLevel()->spaceBetweenRules(); + $sResult .= $oNextLevel->spaceBetweenRules(); } $sResult .= $sRendered; } diff --git a/lib/php-css-parser/src/Settings.php b/lib/php-css-parser/src/Settings.php index 7b8580962c9..79d99803cc2 100644 --- a/lib/php-css-parser/src/Settings.php +++ b/lib/php-css-parser/src/Settings.php @@ -11,7 +11,8 @@ class Settings { /** * Multi-byte string support. - * If true (mbstring extension must be enabled), will use (slower) `mb_strlen`, `mb_convert_case`, `mb_substr` + * + * If `true` (`mbstring` extension must be enabled), will use (slower) `mb_strlen`, `mb_convert_case`, `mb_substr` * and `mb_strpos` functions. Otherwise, the normal (ASCII-Only) functions will be used. * * @var bool @@ -19,15 +20,14 @@ class Settings public $bMultibyteSupport; /** - * The default charset for the CSS if no `@charset` rule is found. Defaults to utf-8. + * The default charset for the CSS if no `@charset` declaration is found. Defaults to utf-8. * * @var string */ public $sDefaultCharset = 'utf-8'; /** - * Lenient parsing. When used (which is true by default), the parser will not choke - * on unexpected tokens but simply ignore them. + * Whether the parser silently ignore invalid rules instead of choking on them. * * @var bool */ @@ -47,6 +47,11 @@ class Settings } /** + * Enables/disables multi-byte string support. + * + * If `true` (`mbstring` extension must be enabled), will use (slower) `mb_strlen`, `mb_convert_case`, `mb_substr` + * and `mb_strpos` functions. Otherwise, the normal (ASCII-Only) functions will be used. + * * @param bool $bMultibyteSupport * * @return self fluent interface @@ -58,6 +63,8 @@ class Settings } /** + * Sets the charset to be used if the CSS does not contain an `@charset` declaration. + * * @param string $sDefaultCharset * * @return self fluent interface @@ -69,6 +76,8 @@ class Settings } /** + * Configures whether the parser should silently ignore invalid rules. + * * @param bool $bLenientParsing * * @return self fluent interface @@ -80,6 +89,8 @@ class Settings } /** + * Configures the parser to choke on invalid rules. + * * @return self fluent interface */ public function beStrict() diff --git a/lib/php-css-parser/src/Value/CSSFunction.php b/lib/php-css-parser/src/Value/CSSFunction.php index e6b8c1189f4..82ffc48c81f 100644 --- a/lib/php-css-parser/src/Value/CSSFunction.php +++ b/lib/php-css-parser/src/Value/CSSFunction.php @@ -3,7 +3,12 @@ namespace Sabberworm\CSS\Value; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Parsing\ParserState; +/** + * A `CSSFunction` represents a special kind of value that also contains a function name and where the values are the + * function’s arguments. It also handles equals-sign-separated argument lists like `filter: alpha(opacity=90);`. + */ class CSSFunction extends ValueList { /** @@ -28,6 +33,26 @@ class CSSFunction extends ValueList parent::__construct($aArguments, $sSeparator, $iLineNo); } + /** + * @param ParserState $oParserState + * @param bool $bIgnoreCase + * + * @return CSSFunction + * + * @throws SourceException + * @throws UnexpectedEOFException + * @throws UnexpectedTokenException + */ + public static function parse(ParserState $oParserState, $bIgnoreCase = false) + { + $mResult = $oParserState->parseIdentifier($bIgnoreCase); + $oParserState->consume('('); + $aArguments = Value::parseValue($oParserState, ['=', ' ', ',']); + $mResult = new CSSFunction($mResult, $aArguments, ',', $oParserState->currentLine()); + $oParserState->consume(')'); + return $mResult; + } + /** * @return string */ @@ -63,9 +88,11 @@ class CSSFunction extends ValueList } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { $aArguments = parent::render($oOutputFormat); return "{$this->sName}({$aArguments})"; diff --git a/lib/php-css-parser/src/Value/CSSString.php b/lib/php-css-parser/src/Value/CSSString.php index 9fafedd7a46..c19b238f568 100644 --- a/lib/php-css-parser/src/Value/CSSString.php +++ b/lib/php-css-parser/src/Value/CSSString.php @@ -8,6 +8,11 @@ use Sabberworm\CSS\Parsing\SourceException; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +/** + * This class is a wrapper for quoted strings to distinguish them from keywords. + * + * `CSSString`s always output with double quotes. + */ class CSSString extends PrimitiveValue { /** @@ -94,9 +99,11 @@ class CSSString extends PrimitiveValue } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { $sString = addslashes($this->sString); $sString = str_replace("\n", '\A', $sString); diff --git a/lib/php-css-parser/src/Value/CalcFunction.php b/lib/php-css-parser/src/Value/CalcFunction.php index 5c92e0c089b..a3db27101c4 100644 --- a/lib/php-css-parser/src/Value/CalcFunction.php +++ b/lib/php-css-parser/src/Value/CalcFunction.php @@ -10,29 +10,48 @@ class CalcFunction extends CSSFunction { /** * @var int + * + * @internal */ const T_OPERAND = 1; /** * @var int + * + * @internal */ const T_OPERATOR = 2; /** + * @param ParserState $oParserState + * @param bool $bIgnoreCase + * * @return CalcFunction * * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ - public static function parse(ParserState $oParserState) + public static function parse(ParserState $oParserState, $bIgnoreCase = false) { $aOperators = ['+', '-', '*', '/']; - $sFunction = trim($oParserState->consumeUntil('(', false, true)); + $sFunction = $oParserState->parseIdentifier(); + if ($oParserState->peek() != '(') { + // Found ; or end of line before an opening bracket + throw new UnexpectedTokenException('(', $oParserState->peek(), 'literal', $oParserState->currentLine()); + } elseif (!in_array($sFunction, ['calc', '-moz-calc', '-webkit-calc'])) { + // Found invalid calc definition. Example calc (... + throw new UnexpectedTokenException('calc', $sFunction, 'literal', $oParserState->currentLine()); + } + $oParserState->consume('('); $oCalcList = new CalcRuleValueList($oParserState->currentLine()); $oList = new RuleValueList(',', $oParserState->currentLine()); $iNestingLevel = 0; $iLastComponentType = null; while (!$oParserState->comes(')') || $iNestingLevel > 0) { + if ($oParserState->isEnd() && $iNestingLevel === 0) { + break; + } + $oParserState->consumeWhiteSpace(); if ($oParserState->comes('(')) { $iNestingLevel++; @@ -72,7 +91,7 @@ class CalcFunction extends CSSFunction sprintf( 'Next token was expected to be an operand of type %s. Instead "%s" was found.', implode(', ', $aOperators), - $oVal + $oParserState->peek() ), '', 'custom', @@ -83,7 +102,9 @@ class CalcFunction extends CSSFunction $oParserState->consumeWhiteSpace(); } $oList->addListComponent($oCalcList); - $oParserState->consume(')'); + if (!$oParserState->isEnd()) { + $oParserState->consume(')'); + } return new CalcFunction($sFunction, $oList, ',', $oParserState->currentLine()); } } diff --git a/lib/php-css-parser/src/Value/CalcRuleValueList.php b/lib/php-css-parser/src/Value/CalcRuleValueList.php index 7dbd26a1b4d..17fbe7cfaaa 100644 --- a/lib/php-css-parser/src/Value/CalcRuleValueList.php +++ b/lib/php-css-parser/src/Value/CalcRuleValueList.php @@ -15,9 +15,11 @@ class CalcRuleValueList extends RuleValueList } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { return $oOutputFormat->implode(' ', $this->aComponents); } diff --git a/lib/php-css-parser/src/Value/Color.php b/lib/php-css-parser/src/Value/Color.php index 8dc52960cd6..a002760b39b 100644 --- a/lib/php-css-parser/src/Value/Color.php +++ b/lib/php-css-parser/src/Value/Color.php @@ -7,6 +7,10 @@ use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +/** + * `Color's can be input in the form #rrggbb, #rgb or schema(val1, val2, …) but are always stored as an array of + * ('s' => val1, 'c' => val2, 'h' => val3, …) and output in the second form. + */ class Color extends CSSFunction { /** @@ -19,12 +23,15 @@ class Color extends CSSFunction } /** + * @param ParserState $oParserState + * @param bool $bIgnoreCase + * * @return Color|CSSFunction * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ - public static function parse(ParserState $oParserState) + public static function parse(ParserState $oParserState, $bIgnoreCase = false) { $aColor = []; if ($oParserState->comes('#')) { @@ -49,12 +56,19 @@ class Color extends CSSFunction $oParserState->currentLine() ), ]; - } else { + } elseif ($oParserState->strlen($sValue) === 6) { $aColor = [ 'r' => new Size(intval($sValue[0] . $sValue[1], 16), null, true, $oParserState->currentLine()), 'g' => new Size(intval($sValue[2] . $sValue[3], 16), null, true, $oParserState->currentLine()), 'b' => new Size(intval($sValue[4] . $sValue[5], 16), null, true, $oParserState->currentLine()), ]; + } else { + throw new UnexpectedTokenException( + 'Invalid hex color value', + $sValue, + 'custom', + $oParserState->currentLine() + ); } } else { $sColorMode = $oParserState->parseIdentifier(true); @@ -146,9 +160,11 @@ class Color extends CSSFunction } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { // Shorthand RGB color values if ($oOutputFormat->getRGBHashNotation() && implode('', array_keys($this->aComponents)) === 'rgb') { diff --git a/lib/php-css-parser/src/Value/LineName.php b/lib/php-css-parser/src/Value/LineName.php index e231ce38ff7..588cb4cbe18 100644 --- a/lib/php-css-parser/src/Value/LineName.php +++ b/lib/php-css-parser/src/Value/LineName.php @@ -56,9 +56,11 @@ class LineName extends ValueList } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { return '[' . parent::render(OutputFormat::createCompact()) . ']'; } diff --git a/lib/php-css-parser/src/Value/RuleValueList.php b/lib/php-css-parser/src/Value/RuleValueList.php index 5d533a7cbe0..8fe88b61065 100644 --- a/lib/php-css-parser/src/Value/RuleValueList.php +++ b/lib/php-css-parser/src/Value/RuleValueList.php @@ -2,6 +2,11 @@ namespace Sabberworm\CSS\Value; +/** + * This class is used to represent all multivalued rules like `font: bold 12px/3 Helvetica, Verdana, sans-serif;` + * (where the value would be a whitespace-separated list of the primitive value `bold`, a slash-separated list + * and a comma-separated list). + */ class RuleValueList extends ValueList { /** diff --git a/lib/php-css-parser/src/Value/Size.php b/lib/php-css-parser/src/Value/Size.php index b3801dc17dc..af8b3a3b264 100644 --- a/lib/php-css-parser/src/Value/Size.php +++ b/lib/php-css-parser/src/Value/Size.php @@ -7,24 +7,38 @@ use Sabberworm\CSS\Parsing\ParserState; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +/** + * A `Size` consists of a numeric `size` value and a unit. + */ class Size extends PrimitiveValue { /** * vh/vw/vm(ax)/vmin/rem are absolute insofar as they don’t scale to the immediate parent (only the viewport) * * @var array + * + * @internal */ - const ABSOLUTE_SIZE_UNITS = ['px', 'cm', 'mm', 'mozmm', 'in', 'pt', 'pc', 'vh', 'vw', 'vmin', 'vmax', 'rem']; + const ABSOLUTE_SIZE_UNITS = [ + 'px', 'pt', 'pc', + 'cm', 'mm', 'mozmm', 'in', + 'vh', 'dvh', 'svh', 'lvh', + 'vw', 'vmin', 'vmax', 'rem', + ]; /** * @var array + * + * @internal */ const RELATIVE_SIZE_UNITS = ['%', 'em', 'ex', 'ch', 'fr']; /** * @var array + * + * @internal */ - const NON_SIZE_UNITS = ['deg', 'grad', 'rad', 's', 'ms', 'turns', 'Hz', 'kHz']; + const NON_SIZE_UNITS = ['deg', 'grad', 'rad', 's', 'ms', 'turn', 'Hz', 'kHz']; /** * @var array>|null @@ -74,9 +88,16 @@ class Size extends PrimitiveValue if ($oParserState->comes('-')) { $sSize .= $oParserState->consume('-'); } - while (is_numeric($oParserState->peek()) || $oParserState->comes('.')) { + while (is_numeric($oParserState->peek()) || $oParserState->comes('.') || $oParserState->comes('e', true)) { if ($oParserState->comes('.')) { $sSize .= $oParserState->consume('.'); + } elseif ($oParserState->comes('e', true)) { + $sLookahead = $oParserState->peek(1, 1); + if (is_numeric($sLookahead) || $sLookahead === '+' || $sLookahead === '-') { + $sSize .= $oParserState->consume(2); + } else { + break; // Reached the unit part of the number like "em" or "ex" + } } else { $sSize .= $oParserState->consume(1); } @@ -195,14 +216,16 @@ class Size extends PrimitiveValue } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { $l = localeconv(); $sPoint = preg_quote($l['decimal_point'], '/'); $sSize = preg_match("/[\d\.]+e[+-]?\d+/i", (string)$this->fSize) - ? preg_replace("/$sPoint?0+$/", "", sprintf("%f", $this->fSize)) : $this->fSize; + ? preg_replace("/$sPoint?0+$/", "", sprintf("%f", $this->fSize)) : (string)$this->fSize; return preg_replace(["/$sPoint/", "/^(-?)0\./"], ['.', '$1.'], $sSize) . ($this->sUnit === null ? '' : $this->sUnit); } diff --git a/lib/php-css-parser/src/Value/URL.php b/lib/php-css-parser/src/Value/URL.php index 1467d505c5c..92da972620c 100644 --- a/lib/php-css-parser/src/Value/URL.php +++ b/lib/php-css-parser/src/Value/URL.php @@ -8,6 +8,9 @@ use Sabberworm\CSS\Parsing\SourceException; use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; +/** + * This class represents URLs in CSS. `URL`s always output in `URL("")` notation. + */ class URL extends PrimitiveValue { /** @@ -33,11 +36,21 @@ class URL extends PrimitiveValue */ public static function parse(ParserState $oParserState) { - $bUseUrl = $oParserState->comes('url', true); + $oAnchor = $oParserState->anchor(); + $sIdentifier = ''; + for ($i = 0; $i < 3; $i++) { + $sChar = $oParserState->parseCharacter(true); + if ($sChar === null) { + break; + } + $sIdentifier .= $sChar; + } + $bUseUrl = $oParserState->streql($sIdentifier, 'url'); if ($bUseUrl) { - $oParserState->consume('url'); $oParserState->consumeWhiteSpace(); $oParserState->consume('('); + } else { + $oAnchor->backtrack(); } $oParserState->consumeWhiteSpace(); $oResult = new URL(CSSString::parse($oParserState), $oParserState->currentLine()); @@ -73,9 +86,11 @@ class URL extends PrimitiveValue } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { return "url({$this->oURL->render($oOutputFormat)})"; } diff --git a/lib/php-css-parser/src/Value/Value.php b/lib/php-css-parser/src/Value/Value.php index 66cb9fd47ee..4b174db0b58 100644 --- a/lib/php-css-parser/src/Value/Value.php +++ b/lib/php-css-parser/src/Value/Value.php @@ -8,6 +8,10 @@ use Sabberworm\CSS\Parsing\UnexpectedEOFException; use Sabberworm\CSS\Parsing\UnexpectedTokenException; use Sabberworm\CSS\Renderable; +/** + * Abstract base class for specific classes of CSS values: `Size`, `Color`, `CSSString` and `URL`, and another + * abstract subclass `ValueList`. + */ abstract class Value implements Renderable { /** @@ -39,8 +43,9 @@ abstract class Value implements Renderable //Build a list of delimiters and parsed values while ( !($oParserState->comes('}') || $oParserState->comes(';') || $oParserState->comes('!') - || $oParserState->comes(')') - || $oParserState->comes('\\')) + || $oParserState->comes(')') + || $oParserState->comes('\\') + || $oParserState->isEnd()) ) { if (count($aStack) > 0) { $bFoundDelimiter = false; @@ -62,23 +67,30 @@ abstract class Value implements Renderable } // Convert the list to list objects foreach ($aListDelimiters as $sDelimiter) { - if (count($aStack) === 1) { + $iStackLength = count($aStack); + if ($iStackLength === 1) { return $aStack[0]; } - $iStartPosition = null; - while (($iStartPosition = array_search($sDelimiter, $aStack, true)) !== false) { + $aNewStack = []; + for ($iStartPosition = 0; $iStartPosition < $iStackLength; ++$iStartPosition) { + if ($iStartPosition === ($iStackLength - 1) || $sDelimiter !== $aStack[$iStartPosition + 1]) { + $aNewStack[] = $aStack[$iStartPosition]; + continue; + } $iLength = 2; //Number of elements to be joined - for ($i = $iStartPosition + 2; $i < count($aStack); $i += 2, ++$iLength) { + for ($i = $iStartPosition + 3; $i < $iStackLength; $i += 2, ++$iLength) { if ($sDelimiter !== $aStack[$i]) { break; } } $oList = new RuleValueList($sDelimiter, $oParserState->currentLine()); - for ($i = $iStartPosition - 1; $i - $iStartPosition + 1 < $iLength * 2; $i += 2) { + for ($i = $iStartPosition; $i - $iStartPosition < $iLength * 2; $i += 2) { $oList->addListComponent($aStack[$i]); } - array_splice($aStack, $iStartPosition - 1, $iLength * 2 - 1, [$oList]); + $aNewStack[] = $oList; + $iStartPosition += $iLength * 2 - 2; } + $aStack = $aNewStack; } if (!isset($aStack[0])) { throw new UnexpectedTokenException( @@ -101,16 +113,25 @@ abstract class Value implements Renderable */ public static function parseIdentifierOrFunction(ParserState $oParserState, $bIgnoreCase = false) { - $sResult = $oParserState->parseIdentifier($bIgnoreCase); + $oAnchor = $oParserState->anchor(); + $mResult = $oParserState->parseIdentifier($bIgnoreCase); if ($oParserState->comes('(')) { - $oParserState->consume('('); - $aArguments = Value::parseValue($oParserState, ['=', ' ', ',']); - $sResult = new CSSFunction($sResult, $aArguments, ',', $oParserState->currentLine()); - $oParserState->consume(')'); + $oAnchor->backtrack(); + if ($oParserState->streql('url', $mResult)) { + $mResult = URL::parse($oParserState); + } elseif ( + $oParserState->streql('calc', $mResult) + || $oParserState->streql('-webkit-calc', $mResult) + || $oParserState->streql('-moz-calc', $mResult) + ) { + $mResult = CalcFunction::parse($oParserState); + } else { + $mResult = CSSFunction::parse($oParserState, $bIgnoreCase); + } } - return $sResult; + return $mResult; } /** @@ -133,13 +154,6 @@ abstract class Value implements Renderable $oValue = Size::parse($oParserState); } elseif ($oParserState->comes('#') || $oParserState->comes('rgb', true) || $oParserState->comes('hsl', true)) { $oValue = Color::parse($oParserState); - } elseif ($oParserState->comes('url', true)) { - $oValue = URL::parse($oParserState); - } elseif ( - $oParserState->comes('calc', true) || $oParserState->comes('-webkit-calc', true) - || $oParserState->comes('-moz-calc', true) - ) { - $oValue = CalcFunction::parse($oParserState); } elseif ($oParserState->comes("'") || $oParserState->comes('"')) { $oValue = CSSString::parse($oParserState); } elseif ($oParserState->comes("progid:") && $oParserState->getSettings()->bLenientParsing) { @@ -149,7 +163,16 @@ abstract class Value implements Renderable } elseif ($oParserState->comes("U+")) { $oValue = self::parseUnicodeRangeValue($oParserState); } else { - $oValue = self::parseIdentifierOrFunction($oParserState); + $sNextChar = $oParserState->peek(1); + try { + $oValue = self::parseIdentifierOrFunction($oParserState); + } catch (UnexpectedTokenException $e) { + if (\in_array($sNextChar, ['+', '-', '*', '/'], true)) { + $oValue = $oParserState->consume(1); + } else { + throw $e; + } + } } $oParserState->consumeWhiteSpace(); return $oValue; diff --git a/lib/php-css-parser/src/Value/ValueList.php b/lib/php-css-parser/src/Value/ValueList.php index af5348b9673..80b26f97fbf 100644 --- a/lib/php-css-parser/src/Value/ValueList.php +++ b/lib/php-css-parser/src/Value/ValueList.php @@ -4,6 +4,12 @@ namespace Sabberworm\CSS\Value; use Sabberworm\CSS\OutputFormat; +/** + * A `ValueList` represents a lists of `Value`s, separated by some separation character + * (mostly `,`, whitespace, or `/`). + * + * There are two types of `ValueList`s: `RuleValueList` and `CSSFunction` + */ abstract class ValueList extends Value { /** @@ -87,9 +93,11 @@ abstract class ValueList extends Value } /** + * @param OutputFormat|null $oOutputFormat + * * @return string */ - public function render(OutputFormat $oOutputFormat) + public function render($oOutputFormat) { return $oOutputFormat->implode( $oOutputFormat->spaceBeforeListArgumentSeparator($this->sSeparator) . $this->sSeparator diff --git a/lib/thirdpartylibs.xml b/lib/thirdpartylibs.xml index 53036ebae58..536cb4bf1fd 100644 --- a/lib/thirdpartylibs.xml +++ b/lib/thirdpartylibs.xml @@ -331,7 +331,7 @@ All rights reserved. php-css-parser PHP-CSS-Parser A Parser for CSS Files written in PHP. - 8.4.0 + 8.7.0 MIT https://github.com/sabberworm/PHP-CSS-Parser