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
+
+[](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