8889841cPK[PP README.mdnuW+A# 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 is used only if no `@charset` declaration is found in the CSS file. 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 choke on invalid rules, supply a thusly configured `\Sabberworm\CSS\Settings` object: ```php $parser = new \Sabberworm\CSS\Parser( file_get_contents('somefile.css'), \Sabberworm\CSS\Settings::create()->beStrict() ); ``` #### 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. `CSSList` has the following concrete subtypes: * `Document` – representing the root of a CSS file. * `MediaQuery` – represents a subsection of a `CSSList` that only applies to an output device matching the contained media query. 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 which do not match the ones specifically mentioned like `@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` instance or a rule name; optionally suffixed by a dash to remove all related rules). #### Rule `Rule`s just have a key (the rule) and a value. These values are all instances of 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 multi-valued 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 your selectors are. Aliased as `getAllSelectors()`. * `getAllRuleSets()` – does what it says; no matter how deeply nested your 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`. PK[L!! CHANGELOG.mdnuW+A# Revision History ## 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. PK[K''src/Rule/Rule.phpnuW+A */ private $aIeHack; /** * @var int */ protected $iLineNo; /** * @var int */ protected $iColNo; /** * @var array */ protected $aComments; /** * @param string $sRule * @param int $iLineNo * @param int $iColNo */ public function __construct($sRule, $iLineNo = 0, $iColNo = 0) { $this->sRule = $sRule; $this->mValue = null; $this->bIsImportant = false; $this->aIeHack = []; $this->iLineNo = $iLineNo; $this->iColNo = $iColNo; $this->aComments = []; } /** * @return Rule * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ public static function parse(ParserState $oParserState) { $aComments = $oParserState->consumeWhiteSpace(); $oRule = new Rule( $oParserState->parseIdentifier(!$oParserState->comes("--")), $oParserState->currentLine(), $oParserState->currentColumn() ); $oRule->setComments($aComments); $oRule->addComments($oParserState->consumeWhiteSpace()); $oParserState->consume(':'); $oValue = Value::parseValue($oParserState, self::listDelimiterForRule($oRule->getRule())); $oRule->setValue($oValue); if ($oParserState->getSettings()->bLenientParsing) { while ($oParserState->comes('\\')) { $oParserState->consume('\\'); $oRule->addIeHack($oParserState->consume()); $oParserState->consumeWhiteSpace(); } } $oParserState->consumeWhiteSpace(); if ($oParserState->comes('!')) { $oParserState->consume('!'); $oParserState->consumeWhiteSpace(); $oParserState->consume('important'); $oRule->setIsImportant(true); } $oParserState->consumeWhiteSpace(); while ($oParserState->comes(';')) { $oParserState->consume(';'); } $oParserState->consumeWhiteSpace(); return $oRule; } /** * @param string $sRule * * @return array */ private static function listDelimiterForRule($sRule) { if (preg_match('/^font($|-)/', $sRule)) { return [',', '/', ' ']; } return [',', ' ', '/']; } /** * @return int */ public function getLineNo() { return $this->iLineNo; } /** * @return int */ public function getColNo() { return $this->iColNo; } /** * @param int $iLine * @param int $iColumn * * @return void */ public function setPosition($iLine, $iColumn) { $this->iColNo = $iColumn; $this->iLineNo = $iLine; } /** * @param string $sRule * * @return void */ public function setRule($sRule) { $this->sRule = $sRule; } /** * @return string */ public function getRule() { return $this->sRule; } /** * @return RuleValueList|null */ public function getValue() { return $this->mValue; } /** * @param RuleValueList|null $mValue * * @return void */ public function setValue($mValue) { $this->mValue = $mValue; } /** * @param array> $aSpaceSeparatedValues * * @return RuleValueList * * @deprecated will be removed in version 9.0 * Old-Style 2-dimensional array given. Retained for (some) backwards-compatibility. * Use `setValue()` instead and wrap the value inside a RuleValueList if necessary. */ public function setValues(array $aSpaceSeparatedValues) { $oSpaceSeparatedList = null; if (count($aSpaceSeparatedValues) > 1) { $oSpaceSeparatedList = new RuleValueList(' ', $this->iLineNo); } foreach ($aSpaceSeparatedValues as $aCommaSeparatedValues) { $oCommaSeparatedList = null; if (count($aCommaSeparatedValues) > 1) { $oCommaSeparatedList = new RuleValueList(',', $this->iLineNo); } foreach ($aCommaSeparatedValues as $mValue) { if (!$oSpaceSeparatedList && !$oCommaSeparatedList) { $this->mValue = $mValue; return $mValue; } if ($oCommaSeparatedList) { $oCommaSeparatedList->addListComponent($mValue); } else { $oSpaceSeparatedList->addListComponent($mValue); } } if (!$oSpaceSeparatedList) { $this->mValue = $oCommaSeparatedList; return $oCommaSeparatedList; } else { $oSpaceSeparatedList->addListComponent($oCommaSeparatedList); } } $this->mValue = $oSpaceSeparatedList; return $oSpaceSeparatedList; } /** * @return array> * * @deprecated will be removed in version 9.0 * Old-Style 2-dimensional array returned. Retained for (some) backwards-compatibility. * Use `getValue()` instead and check for the existence of a (nested set of) ValueList object(s). */ public function getValues() { if (!$this->mValue instanceof RuleValueList) { return [[$this->mValue]]; } if ($this->mValue->getListSeparator() === ',') { return [$this->mValue->getListComponents()]; } $aResult = []; foreach ($this->mValue->getListComponents() as $mValue) { if (!$mValue instanceof RuleValueList || $mValue->getListSeparator() !== ',') { $aResult[] = [$mValue]; continue; } if ($this->mValue->getListSeparator() === ' ' || count($aResult) === 0) { $aResult[] = []; } foreach ($mValue->getListComponents() as $mValue) { $aResult[count($aResult) - 1][] = $mValue; } } return $aResult; } /** * Adds a value to the existing value. Value will be appended if a `RuleValueList` exists of the given type. * Otherwise, the existing value will be wrapped by one. * * @param RuleValueList|array $mValue * @param string $sType * * @return void */ public function addValue($mValue, $sType = ' ') { if (!is_array($mValue)) { $mValue = [$mValue]; } if (!$this->mValue instanceof RuleValueList || $this->mValue->getListSeparator() !== $sType) { $mCurrentValue = $this->mValue; $this->mValue = new RuleValueList($sType, $this->iLineNo); if ($mCurrentValue) { $this->mValue->addListComponent($mCurrentValue); } } foreach ($mValue as $mValueItem) { $this->mValue->addListComponent($mValueItem); } } /** * @param int $iModifier * * @return void */ public function addIeHack($iModifier) { $this->aIeHack[] = $iModifier; } /** * @param array $aModifiers * * @return void */ public function setIeHack(array $aModifiers) { $this->aIeHack = $aModifiers; } /** * @return array */ public function getIeHack() { return $this->aIeHack; } /** * @param bool $bIsImportant * * @return void */ public function setIsImportant($bIsImportant) { $this->bIsImportant = $bIsImportant; } /** * @return bool */ public function getIsImportant() { return $this->bIsImportant; } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { $sResult = "{$this->sRule}:{$oOutputFormat->spaceAfterRuleName()}"; if ($this->mValue instanceof Value) { //Can also be a ValueList $sResult .= $this->mValue->render($oOutputFormat); } else { $sResult .= $this->mValue; } if (!empty($this->aIeHack)) { $sResult .= ' \\' . implode('\\', $this->aIeHack); } if ($this->bIsImportant) { $sResult .= ' !important'; } $sResult .= ';'; return $sResult; } /** * @param array $aComments * * @return void */ public function addComments(array $aComments) { $this->aComments = array_merge($this->aComments, $aComments); } /** * @return array */ public function getComments() { return $this->aComments; } /** * @param array $aComments * * @return void */ public function setComments(array $aComments) { $this->aComments = $aComments; } } PK[C!Gsrc/Comment/Commentable.phpnuW+A $aComments * * @return void */ public function addComments(array $aComments); /** * @return array */ public function getComments(); /** * @param array $aComments * * @return void */ public function setComments(array $aComments); } PK[QKsrc/Comment/Comment.phpnuW+AsComment = $sComment; $this->iLineNo = $iLineNo; } /** * @return string */ public function getComment() { return $this->sComment; } /** * @return int */ public function getLineNo() { return $this->iLineNo; } /** * @param string $sComment * * @return void */ public function setComment($sComment) { $this->sComment = $sComment; } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { return '/*' . $this->sComment . '*/'; } } PK[Tsrc/OutputFormat.phpnuW+Aset('Space*Rules', "\n");`) */ public $sSpaceAfterRuleName = ' '; /** * @var string */ public $sSpaceBeforeRules = ''; /** * @var string */ public $sSpaceAfterRules = ''; /** * @var string */ public $sSpaceBetweenRules = ''; /** * @var string */ public $sSpaceBeforeBlocks = ''; /** * @var string */ public $sSpaceAfterBlocks = ''; /** * @var string */ public $sSpaceBetweenBlocks = "\n"; /** * Content injected in and around at-rule blocks. * * @var string */ public $sBeforeAtRuleBlock = ''; /** * @var string */ public $sAfterAtRuleBlock = ''; /** * This is what’s printed before and after the comma if a declaration block contains multiple selectors. * * @var string */ public $sSpaceBeforeSelectorSeparator = ''; /** * @var string */ public $sSpaceAfterSelectorSeparator = ' '; /** * This is what’s printed after the comma of value lists * * @var string */ public $sSpaceBeforeListArgumentSeparator = ''; /** * @var string */ public $sSpaceAfterListArgumentSeparator = ''; /** * @var string */ public $sSpaceBeforeOpeningBrace = ' '; /** * Content injected in and around declaration blocks. * * @var string */ public $sBeforeDeclarationBlock = ''; /** * @var string */ public $sAfterDeclarationBlockSelectors = ''; /** * @var string */ public $sAfterDeclarationBlock = ''; /** * Indentation character(s) per level. Only applicable if newlines are used in any of the spacing settings. * * @var string */ public $sIndentation = "\t"; /** * Output exceptions. * * @var bool */ public $bIgnoreExceptions = false; /** * @var OutputFormatter|null */ private $oFormatter = null; /** * @var OutputFormat|null */ private $oNextLevelFormat = null; /** * @var int */ private $iIndentationLevel = 0; public function __construct() { } /** * @param string $sName * * @return string|null */ public function get($sName) { $aVarPrefixes = ['a', 's', 'm', 'b', 'f', 'o', 'c', 'i']; foreach ($aVarPrefixes as $sPrefix) { $sFieldName = $sPrefix . ucfirst($sName); if (isset($this->$sFieldName)) { return $this->$sFieldName; } } return null; } /** * @param array|string $aNames * @param mixed $mValue * * @return self|false */ public function set($aNames, $mValue) { $aVarPrefixes = ['a', 's', 'm', 'b', 'f', 'o', 'c', 'i']; if (is_string($aNames) && strpos($aNames, '*') !== false) { $aNames = [ str_replace('*', 'Before', $aNames), str_replace('*', 'Between', $aNames), str_replace('*', 'After', $aNames), ]; } elseif (!is_array($aNames)) { $aNames = [$aNames]; } foreach ($aVarPrefixes as $sPrefix) { $bDidReplace = false; foreach ($aNames as $sName) { $sFieldName = $sPrefix . ucfirst($sName); if (isset($this->$sFieldName)) { $this->$sFieldName = $mValue; $bDidReplace = true; } } if ($bDidReplace) { return $this; } } // Break the chain so the user knows this option is invalid return false; } /** * @param string $sMethodName * @param array $aArguments * * @return mixed * * @throws \Exception */ public function __call($sMethodName, array $aArguments) { if (strpos($sMethodName, 'set') === 0) { return $this->set(substr($sMethodName, 3), $aArguments[0]); } elseif (strpos($sMethodName, 'get') === 0) { return $this->get(substr($sMethodName, 3)); } elseif (method_exists(OutputFormatter::class, $sMethodName)) { return call_user_func_array([$this->getFormatter(), $sMethodName], $aArguments); } else { throw new \Exception('Unknown OutputFormat method called: ' . $sMethodName); } } /** * @param int $iNumber * * @return self */ public function indentWithTabs($iNumber = 1) { return $this->setIndentation(str_repeat("\t", $iNumber)); } /** * @param int $iNumber * * @return self */ public function indentWithSpaces($iNumber = 2) { return $this->setIndentation(str_repeat(" ", $iNumber)); } /** * @return OutputFormat */ public function nextLevel() { if ($this->oNextLevelFormat === null) { $this->oNextLevelFormat = clone $this; $this->oNextLevelFormat->iIndentationLevel++; $this->oNextLevelFormat->oFormatter = null; } return $this->oNextLevelFormat; } /** * @return void */ public function beLenient() { $this->bIgnoreExceptions = true; } /** * @return OutputFormatter */ public function getFormatter() { if ($this->oFormatter === null) { $this->oFormatter = new OutputFormatter($this); } return $this->oFormatter; } /** * @return int */ public function level() { return $this->iIndentationLevel; } /** * Creates an instance of this class without any particular formatting settings. * * @return self */ public static function create() { return new OutputFormat(); } /** * Creates an instance of this class with a preset for compact formatting. * * @return self */ public static function createCompact() { $format = self::create(); $format->set('Space*Rules', "")->set('Space*Blocks', "")->setSpaceAfterRuleName('') ->setSpaceBeforeOpeningBrace('')->setSpaceAfterSelectorSeparator(''); return $format; } /** * Creates an instance of this class with a preset for pretty formatting. * * @return self */ public static function createPretty() { $format = self::create(); $format->set('Space*Rules', "\n")->set('Space*Blocks', "\n") ->setSpaceBetweenBlocks("\n\n")->set('SpaceAfterListArgumentSeparator', ['default' => '', ',' => ' ']); return $format; } } PK[<src/Value/Color.phpnuW+A $aColor * @param int $iLineNo */ public function __construct(array $aColor, $iLineNo = 0) { parent::__construct(implode('', array_keys($aColor)), $aColor, ',', $iLineNo); } /** * @return Color|CSSFunction * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ public static function parse(ParserState $oParserState) { $aColor = []; if ($oParserState->comes('#')) { $oParserState->consume('#'); $sValue = $oParserState->parseIdentifier(false); if ($oParserState->strlen($sValue) === 3) { $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2]; } elseif ($oParserState->strlen($sValue) === 4) { $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2] . $sValue[3] . $sValue[3]; } if ($oParserState->strlen($sValue) === 8) { $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()), 'a' => new Size( round(self::mapRange(intval($sValue[6] . $sValue[7], 16), 0, 255, 0, 1), 2), null, true, $oParserState->currentLine() ), ]; } else { $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 { $sColorMode = $oParserState->parseIdentifier(true); $oParserState->consumeWhiteSpace(); $oParserState->consume('('); $bContainsVar = false; $iLength = $oParserState->strlen($sColorMode); for ($i = 0; $i < $iLength; ++$i) { $oParserState->consumeWhiteSpace(); if ($oParserState->comes('var')) { $aColor[$sColorMode[$i]] = CSSFunction::parseIdentifierOrFunction($oParserState); $bContainsVar = true; } else { $aColor[$sColorMode[$i]] = Size::parse($oParserState, true); } if ($bContainsVar && $oParserState->comes(')')) { // With a var argument the function can have fewer arguments break; } $oParserState->consumeWhiteSpace(); if ($i < ($iLength - 1)) { $oParserState->consume(','); } } $oParserState->consume(')'); if ($bContainsVar) { return new CSSFunction($sColorMode, array_values($aColor), ',', $oParserState->currentLine()); } } return new Color($aColor, $oParserState->currentLine()); } /** * @param float $fVal * @param float $fFromMin * @param float $fFromMax * @param float $fToMin * @param float $fToMax * * @return float */ private static function mapRange($fVal, $fFromMin, $fFromMax, $fToMin, $fToMax) { $fFromRange = $fFromMax - $fFromMin; $fToRange = $fToMax - $fToMin; $fMultiplier = $fToRange / $fFromRange; $fNewVal = $fVal - $fFromMin; $fNewVal *= $fMultiplier; return $fNewVal + $fToMin; } /** * @return array */ public function getColor() { return $this->aComponents; } /** * @param array $aColor * * @return void */ public function setColor(array $aColor) { $this->setName(implode('', array_keys($aColor))); $this->aComponents = $aColor; } /** * @return string */ public function getColorDescription() { return $this->getName(); } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { // Shorthand RGB color values if ($oOutputFormat->getRGBHashNotation() && implode('', array_keys($this->aComponents)) === 'rgb') { $sResult = sprintf( '%02x%02x%02x', $this->aComponents['r']->getSize(), $this->aComponents['g']->getSize(), $this->aComponents['b']->getSize() ); return '#' . (($sResult[0] == $sResult[1]) && ($sResult[2] == $sResult[3]) && ($sResult[4] == $sResult[5]) ? "$sResult[0]$sResult[2]$sResult[4]" : $sResult); } return parent::render($oOutputFormat); } } PK[Oܧ src/Value/CSSString.phpnuW+AsString = $sString; parent::__construct($iLineNo); } /** * @return CSSString * * @throws SourceException * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ public static function parse(ParserState $oParserState) { $sBegin = $oParserState->peek(); $sQuote = null; if ($sBegin === "'") { $sQuote = "'"; } elseif ($sBegin === '"') { $sQuote = '"'; } if ($sQuote !== null) { $oParserState->consume($sQuote); } $sResult = ""; $sContent = null; if ($sQuote === null) { // Unquoted strings end in whitespace or with braces, brackets, parentheses while (!preg_match('/[\\s{}()<>\\[\\]]/isu', $oParserState->peek())) { $sResult .= $oParserState->parseCharacter(false); } } else { while (!$oParserState->comes($sQuote)) { $sContent = $oParserState->parseCharacter(false); if ($sContent === null) { throw new SourceException( "Non-well-formed quoted string {$oParserState->peek(3)}", $oParserState->currentLine() ); } $sResult .= $sContent; } $oParserState->consume($sQuote); } return new CSSString($sResult, $oParserState->currentLine()); } /** * @param string $sString * * @return void */ public function setString($sString) { $this->sString = $sString; } /** * @return string */ public function getString() { return $this->sString; } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { $sString = addslashes($this->sString); $sString = str_replace("\n", '\A', $sString); return $oOutputFormat->getStringQuotingType() . $sString . $oOutputFormat->getStringQuotingType(); } } PK[YP P src/Value/CalcFunction.phpnuW+AconsumeUntil('(', false, true)); $oCalcList = new CalcRuleValueList($oParserState->currentLine()); $oList = new RuleValueList(',', $oParserState->currentLine()); $iNestingLevel = 0; $iLastComponentType = null; while (!$oParserState->comes(')') || $iNestingLevel > 0) { $oParserState->consumeWhiteSpace(); if ($oParserState->comes('(')) { $iNestingLevel++; $oCalcList->addListComponent($oParserState->consume(1)); $oParserState->consumeWhiteSpace(); continue; } elseif ($oParserState->comes(')')) { $iNestingLevel--; $oCalcList->addListComponent($oParserState->consume(1)); $oParserState->consumeWhiteSpace(); continue; } if ($iLastComponentType != CalcFunction::T_OPERAND) { $oVal = Value::parsePrimitiveValue($oParserState); $oCalcList->addListComponent($oVal); $iLastComponentType = CalcFunction::T_OPERAND; } else { if (in_array($oParserState->peek(), $aOperators)) { if (($oParserState->comes('-') || $oParserState->comes('+'))) { if ( $oParserState->peek(1, -1) != ' ' || !($oParserState->comes('- ') || $oParserState->comes('+ ')) ) { throw new UnexpectedTokenException( " {$oParserState->peek()} ", $oParserState->peek(1, -1) . $oParserState->peek(2), 'literal', $oParserState->currentLine() ); } } $oCalcList->addListComponent($oParserState->consume(1)); $iLastComponentType = CalcFunction::T_OPERATOR; } else { throw new UnexpectedTokenException( sprintf( 'Next token was expected to be an operand of type %s. Instead "%s" was found.', implode(', ', $aOperators), $oVal ), '', 'custom', $oParserState->currentLine() ); } } $oParserState->consumeWhiteSpace(); } $oList->addListComponent($oCalcList); $oParserState->consume(')'); return new CalcFunction($sFunction, $oList, ',', $oParserState->currentLine()); } } PK[FȽ!!src/Value/Value.phpnuW+AiLineNo = $iLineNo; } /** * @param array $aListDelimiters * * @return RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string * * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ public static function parseValue(ParserState $oParserState, array $aListDelimiters = []) { /** @var array $aStack */ $aStack = []; $oParserState->consumeWhiteSpace(); //Build a list of delimiters and parsed values while ( !($oParserState->comes('}') || $oParserState->comes(';') || $oParserState->comes('!') || $oParserState->comes(')') || $oParserState->comes('\\')) ) { if (count($aStack) > 0) { $bFoundDelimiter = false; foreach ($aListDelimiters as $sDelimiter) { if ($oParserState->comes($sDelimiter)) { array_push($aStack, $oParserState->consume($sDelimiter)); $oParserState->consumeWhiteSpace(); $bFoundDelimiter = true; break; } } if (!$bFoundDelimiter) { //Whitespace was the list delimiter array_push($aStack, ' '); } } array_push($aStack, self::parsePrimitiveValue($oParserState)); $oParserState->consumeWhiteSpace(); } // Convert the list to list objects foreach ($aListDelimiters as $sDelimiter) { if (count($aStack) === 1) { return $aStack[0]; } $iStartPosition = null; while (($iStartPosition = array_search($sDelimiter, $aStack, true)) !== false) { $iLength = 2; //Number of elements to be joined for ($i = $iStartPosition + 2; $i < count($aStack); $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) { $oList->addListComponent($aStack[$i]); } array_splice($aStack, $iStartPosition - 1, $iLength * 2 - 1, [$oList]); } } if (!isset($aStack[0])) { throw new UnexpectedTokenException( " {$oParserState->peek()} ", $oParserState->peek(1, -1) . $oParserState->peek(2), 'literal', $oParserState->currentLine() ); } return $aStack[0]; } /** * @param bool $bIgnoreCase * * @return CSSFunction|string * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ public static function parseIdentifierOrFunction(ParserState $oParserState, $bIgnoreCase = false) { $sResult = $oParserState->parseIdentifier($bIgnoreCase); if ($oParserState->comes('(')) { $oParserState->consume('('); $aArguments = Value::parseValue($oParserState, ['=', ' ', ',']); $sResult = new CSSFunction($sResult, $aArguments, ',', $oParserState->currentLine()); $oParserState->consume(')'); } return $sResult; } /** * @return CSSFunction|CSSString|LineName|Size|URL|string * * @throws UnexpectedEOFException * @throws UnexpectedTokenException * @throws SourceException */ public static function parsePrimitiveValue(ParserState $oParserState) { $oValue = null; $oParserState->consumeWhiteSpace(); if ( is_numeric($oParserState->peek()) || ($oParserState->comes('-.') && is_numeric($oParserState->peek(1, 2))) || (($oParserState->comes('-') || $oParserState->comes('.')) && is_numeric($oParserState->peek(1, 1))) ) { $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) { $oValue = self::parseMicrosoftFilter($oParserState); } elseif ($oParserState->comes("[")) { $oValue = LineName::parse($oParserState); } elseif ($oParserState->comes("U+")) { $oValue = self::parseUnicodeRangeValue($oParserState); } else { $oValue = self::parseIdentifierOrFunction($oParserState); } $oParserState->consumeWhiteSpace(); return $oValue; } /** * @return CSSFunction * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ private static function parseMicrosoftFilter(ParserState $oParserState) { $sFunction = $oParserState->consumeUntil('(', false, true); $aArguments = Value::parseValue($oParserState, [',', '=']); return new CSSFunction($sFunction, $aArguments, ',', $oParserState->currentLine()); } /** * @return string * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ private static function parseUnicodeRangeValue(ParserState $oParserState) { $iCodepointMaxLength = 6; // Code points outside BMP can use up to six digits $sRange = ""; $oParserState->consume("U+"); do { if ($oParserState->comes('-')) { $iCodepointMaxLength = 13; // Max length is 2 six digit code points + the dash(-) between them } $sRange .= $oParserState->consume(1); } while (strlen($sRange) < $iCodepointMaxLength && preg_match("/[A-Fa-f0-9\?-]/", $oParserState->peek())); return "U+{$sRange}"; } /** * @return int */ public function getLineNo() { return $this->iLineNo; } } PK[ag ^ ^ src/Value/ValueList.phpnuW+A */ protected $aComponents; /** * @var string */ protected $sSeparator; /** * phpcs:ignore Generic.Files.LineLength * @param array|RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string $aComponents * @param string $sSeparator * @param int $iLineNo */ public function __construct($aComponents = [], $sSeparator = ',', $iLineNo = 0) { parent::__construct($iLineNo); if (!is_array($aComponents)) { $aComponents = [$aComponents]; } $this->aComponents = $aComponents; $this->sSeparator = $sSeparator; } /** * @param RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string $mComponent * * @return void */ public function addListComponent($mComponent) { $this->aComponents[] = $mComponent; } /** * @return array */ public function getListComponents() { return $this->aComponents; } /** * @param array $aComponents * * @return void */ public function setListComponents(array $aComponents) { $this->aComponents = $aComponents; } /** * @return string */ public function getListSeparator() { return $this->sSeparator; } /** * @param string $sSeparator * * @return void */ public function setListSeparator($sSeparator) { $this->sSeparator = $sSeparator; } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { return $oOutputFormat->implode( $oOutputFormat->spaceBeforeListArgumentSeparator($this->sSeparator) . $this->sSeparator . $oOutputFormat->spaceAfterListArgumentSeparator($this->sSeparator), $this->aComponents ); } } PK[p##src/Value/RuleValueList.phpnuW+Aemsrc/Value/CalcRuleValueList.phpnuW+Aimplode(' ', $this->aComponents); } } PK[hrsrc/Value/Size.phpnuW+A */ const ABSOLUTE_SIZE_UNITS = ['px', 'cm', 'mm', 'mozmm', 'in', 'pt', 'pc', 'vh', 'vw', 'vmin', 'vmax', 'rem']; /** * @var array */ const RELATIVE_SIZE_UNITS = ['%', 'em', 'ex', 'ch', 'fr']; /** * @var array */ const NON_SIZE_UNITS = ['deg', 'grad', 'rad', 's', 'ms', 'turns', 'Hz', 'kHz']; /** * @var array>|null */ private static $SIZE_UNITS = null; /** * @var float */ private $fSize; /** * @var string|null */ private $sUnit; /** * @var bool */ private $bIsColorComponent; /** * @param float|int|string $fSize * @param string|null $sUnit * @param bool $bIsColorComponent * @param int $iLineNo */ public function __construct($fSize, $sUnit = null, $bIsColorComponent = false, $iLineNo = 0) { parent::__construct($iLineNo); $this->fSize = (float)$fSize; $this->sUnit = $sUnit; $this->bIsColorComponent = $bIsColorComponent; } /** * @param bool $bIsColorComponent * * @return Size * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ public static function parse(ParserState $oParserState, $bIsColorComponent = false) { $sSize = ''; if ($oParserState->comes('-')) { $sSize .= $oParserState->consume('-'); } while (is_numeric($oParserState->peek()) || $oParserState->comes('.')) { if ($oParserState->comes('.')) { $sSize .= $oParserState->consume('.'); } else { $sSize .= $oParserState->consume(1); } } $sUnit = null; $aSizeUnits = self::getSizeUnits(); foreach ($aSizeUnits as $iLength => &$aValues) { $sKey = strtolower($oParserState->peek($iLength)); if (array_key_exists($sKey, $aValues)) { if (($sUnit = $aValues[$sKey]) !== null) { $oParserState->consume($iLength); break; } } } return new Size((float)$sSize, $sUnit, $bIsColorComponent, $oParserState->currentLine()); } /** * @return array> */ private static function getSizeUnits() { if (!is_array(self::$SIZE_UNITS)) { self::$SIZE_UNITS = []; foreach (array_merge(self::ABSOLUTE_SIZE_UNITS, self::RELATIVE_SIZE_UNITS, self::NON_SIZE_UNITS) as $val) { $iSize = strlen($val); if (!isset(self::$SIZE_UNITS[$iSize])) { self::$SIZE_UNITS[$iSize] = []; } self::$SIZE_UNITS[$iSize][strtolower($val)] = $val; } krsort(self::$SIZE_UNITS, SORT_NUMERIC); } return self::$SIZE_UNITS; } /** * @param string $sUnit * * @return void */ public function setUnit($sUnit) { $this->sUnit = $sUnit; } /** * @return string|null */ public function getUnit() { return $this->sUnit; } /** * @param float|int|string $fSize */ public function setSize($fSize) { $this->fSize = (float)$fSize; } /** * @return float */ public function getSize() { return $this->fSize; } /** * @return bool */ public function isColorComponent() { return $this->bIsColorComponent; } /** * Returns whether the number stored in this Size really represents a size (as in a length of something on screen). * * @return false if the unit an angle, a duration, a frequency or the number is a component in a Color object. */ public function isSize() { if (in_array($this->sUnit, self::NON_SIZE_UNITS, true)) { return false; } return !$this->isColorComponent(); } /** * @return bool */ public function isRelative() { if (in_array($this->sUnit, self::RELATIVE_SIZE_UNITS, true)) { return true; } if ($this->sUnit === null && $this->fSize != 0) { return true; } return false; } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $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; return preg_replace(["/$sPoint/", "/^(-?)0\./"], ['.', '$1.'], $sSize) . ($this->sUnit === null ? '' : $this->sUnit); } } PK[FE66src/Value/CSSFunction.phpnuW+A $aArguments * @param string $sSeparator * @param int $iLineNo */ public function __construct($sName, $aArguments, $sSeparator = ',', $iLineNo = 0) { if ($aArguments instanceof RuleValueList) { $sSeparator = $aArguments->getListSeparator(); $aArguments = $aArguments->getListComponents(); } $this->sName = $sName; $this->iLineNo = $iLineNo; parent::__construct($aArguments, $sSeparator, $iLineNo); } /** * @return string */ public function getName() { return $this->sName; } /** * @param string $sName * * @return void */ public function setName($sName) { $this->sName = $sName; } /** * @return array */ public function getArguments() { return $this->aComponents; } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { $aArguments = parent::render($oOutputFormat); return "{$this->sName}({$aArguments})"; } } PK[s3src/Value/PrimitiveValue.phpnuW+A $aComponents * @param int $iLineNo */ public function __construct(array $aComponents = [], $iLineNo = 0) { parent::__construct($aComponents, ' ', $iLineNo); } /** * @return LineName * * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ public static function parse(ParserState $oParserState) { $oParserState->consume('['); $oParserState->consumeWhiteSpace(); $aNames = []; do { if ($oParserState->getSettings()->bLenientParsing) { try { $aNames[] = $oParserState->parseIdentifier(); } catch (UnexpectedTokenException $e) { if (!$oParserState->comes(']')) { throw $e; } } } else { $aNames[] = $oParserState->parseIdentifier(); } $oParserState->consumeWhiteSpace(); } while (!$oParserState->comes(']')); $oParserState->consume(']'); return new LineName($aNames, $oParserState->currentLine()); } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { return '[' . parent::render(OutputFormat::createCompact()) . ']'; } } PK[r  src/Value/URL.phpnuW+AoURL = $oURL; } /** * @return URL * * @throws SourceException * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ public static function parse(ParserState $oParserState) { $bUseUrl = $oParserState->comes('url', true); if ($bUseUrl) { $oParserState->consume('url'); $oParserState->consumeWhiteSpace(); $oParserState->consume('('); } $oParserState->consumeWhiteSpace(); $oResult = new URL(CSSString::parse($oParserState), $oParserState->currentLine()); if ($bUseUrl) { $oParserState->consumeWhiteSpace(); $oParserState->consume(')'); } return $oResult; } /** * @return void */ public function setURL(CSSString $oURL) { $this->oURL = $oURL; } /** * @return CSSString */ public function getURL() { return $this->oURL; } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { return "url({$this->oURL->render($oOutputFormat)})"; } } PK[q&src/Parsing/UnexpectedEOFException.phpnuW+AiLineNo = $iLineNo; if (!empty($iLineNo)) { $sMessage .= " [line no: $iLineNo]"; } parent::__construct($sMessage); } /** * @return int */ public function getLineNo() { return $this->iLineNo; } } PK[jyBffsrc/Parsing/OutputException.phpnuW+A */ private $aText; /** * @var int */ private $iCurrentPosition; /** * @var string */ private $sCharset; /** * @var int */ private $iLength; /** * @var int */ private $iLineNo; /** * @param string $sText * @param int $iLineNo */ public function __construct($sText, Settings $oParserSettings, $iLineNo = 1) { $this->oParserSettings = $oParserSettings; $this->sText = $sText; $this->iCurrentPosition = 0; $this->iLineNo = $iLineNo; $this->setCharset($this->oParserSettings->sDefaultCharset); } /** * @param string $sCharset * * @return void */ public function setCharset($sCharset) { $this->sCharset = $sCharset; $this->aText = $this->strsplit($this->sText); if (is_array($this->aText)) { $this->iLength = count($this->aText); } } /** * @return string */ public function getCharset() { return $this->sCharset; } /** * @return int */ public function currentLine() { return $this->iLineNo; } /** * @return int */ public function currentColumn() { return $this->iCurrentPosition; } /** * @return Settings */ public function getSettings() { return $this->oParserSettings; } /** * @param bool $bIgnoreCase * * @return string * * @throws UnexpectedTokenException */ public function parseIdentifier($bIgnoreCase = true) { $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) { if (preg_match('/[a-zA-Z0-9\x{00A0}-\x{FFFF}_-]/Sux', $sCharacter)) { $sResult .= $sCharacter; } else { $sResult .= '\\' . $sCharacter; } } if ($bIgnoreCase) { $sResult = $this->strtolower($sResult); } return $sResult; } /** * @param bool $bIsForIdentifier * * @return string|null * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ public function parseCharacter($bIsForIdentifier) { if ($this->peek() === '\\') { if ( $bIsForIdentifier && $this->oParserSettings->bLenientParsing && ($this->comes('\0') || $this->comes('\9')) ) { // Non-strings can contain \0 or \9 which is an IE hack supported in lenient parsing. return null; } $this->consume('\\'); if ($this->comes('\n') || $this->comes('\r')) { return ''; } if (preg_match('/[0-9a-fA-F]/Su', $this->peek()) === 0) { return $this->consume(1); } $sUnicode = $this->consumeExpression('/^[0-9a-fA-F]{1,6}/u', 6); if ($this->strlen($sUnicode) < 6) { // Consume whitespace after incomplete unicode escape if (preg_match('/\\s/isSu', $this->peek())) { if ($this->comes('\r\n')) { $this->consume(2); } else { $this->consume(1); } } } $iUnicode = intval($sUnicode, 16); $sUtf32 = ""; for ($i = 0; $i < 4; ++$i) { $sUtf32 .= chr($iUnicode & 0xff); $iUnicode = $iUnicode >> 8; } return iconv('utf-32le', $this->sCharset, $sUtf32); } if ($bIsForIdentifier) { $peek = ord($this->peek()); // Ranges: a-z A-Z 0-9 - _ if ( ($peek >= 97 && $peek <= 122) || ($peek >= 65 && $peek <= 90) || ($peek >= 48 && $peek <= 57) || ($peek === 45) || ($peek === 95) || ($peek > 0xa1) ) { return $this->consume(1); } } else { return $this->consume(1); } return null; } /** * @return array|void * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ public function consumeWhiteSpace() { $comments = []; do { while (preg_match('/\\s/isSu', $this->peek()) === 1) { $this->consume(1); } if ($this->oParserSettings->bLenientParsing) { try { $oComment = $this->consumeComment(); } catch (UnexpectedEOFException $e) { $this->iCurrentPosition = $this->iLength; return; } } else { $oComment = $this->consumeComment(); } if ($oComment !== false) { $comments[] = $oComment; } } while ($oComment !== false); return $comments; } /** * @param string $sString * @param bool $bCaseInsensitive * * @return bool */ public function comes($sString, $bCaseInsensitive = false) { $sPeek = $this->peek(strlen($sString)); return ($sPeek == '') ? false : $this->streql($sPeek, $sString, $bCaseInsensitive); } /** * @param int $iLength * @param int $iOffset * * @return string */ public function peek($iLength = 1, $iOffset = 0) { $iOffset += $this->iCurrentPosition; if ($iOffset >= $this->iLength) { return ''; } return $this->substr($iOffset, $iLength); } /** * @param int $mValue * * @return string * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ public function consume($mValue = 1) { if (is_string($mValue)) { $iLineCount = substr_count($mValue, "\n"); $iLength = $this->strlen($mValue); if (!$this->streql($this->substr($this->iCurrentPosition, $iLength), $mValue)) { throw new UnexpectedTokenException($mValue, $this->peek(max($iLength, 5)), $this->iLineNo); } $this->iLineNo += $iLineCount; $this->iCurrentPosition += $this->strlen($mValue); return $mValue; } else { if ($this->iCurrentPosition + $mValue > $this->iLength) { throw new UnexpectedEOFException($mValue, $this->peek(5), 'count', $this->iLineNo); } $sResult = $this->substr($this->iCurrentPosition, $mValue); $iLineCount = substr_count($sResult, "\n"); $this->iLineNo += $iLineCount; $this->iCurrentPosition += $mValue; return $sResult; } } /** * @param string $mExpression * @param int|null $iMaxLength * * @return string * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ public function consumeExpression($mExpression, $iMaxLength = null) { $aMatches = null; $sInput = $iMaxLength !== null ? $this->peek($iMaxLength) : $this->inputLeft(); if (preg_match($mExpression, $sInput, $aMatches, PREG_OFFSET_CAPTURE) === 1) { return $this->consume($aMatches[0][0]); } throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression', $this->iLineNo); } /** * @return Comment|false */ public function consumeComment() { $mComment = false; if ($this->comes('/*')) { $iLineNo = $this->iLineNo; $this->consume(1); $mComment = ''; while (($char = $this->consume(1)) !== '') { $mComment .= $char; if ($this->comes('*/')) { $this->consume(2); break; } } } if ($mComment !== false) { // We skip the * which was included in the comment. return new Comment(substr($mComment, 1), $iLineNo); } return $mComment; } /** * @return bool */ public function isEnd() { return $this->iCurrentPosition >= $this->iLength; } /** * @param array|string $aEnd * @param string $bIncludeEnd * @param string $consumeEnd * @param array $comments * * @return string * * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, array &$comments = []) { $aEnd = is_array($aEnd) ? $aEnd : [$aEnd]; $out = ''; $start = $this->iCurrentPosition; while (!$this->isEnd()) { $char = $this->consume(1); if (in_array($char, $aEnd)) { if ($bIncludeEnd) { $out .= $char; } elseif (!$consumeEnd) { $this->iCurrentPosition -= $this->strlen($char); } return $out; } $out .= $char; if ($comment = $this->consumeComment()) { $comments[] = $comment; } } if (in_array(self::EOF, $aEnd)) { return $out; } $this->iCurrentPosition = $start; throw new UnexpectedEOFException( 'One of ("' . implode('","', $aEnd) . '")', $this->peek(5), 'search', $this->iLineNo ); } /** * @return string */ private function inputLeft() { return $this->substr($this->iCurrentPosition, -1); } /** * @param string $sString1 * @param string $sString2 * @param bool $bCaseInsensitive * * @return bool */ public function streql($sString1, $sString2, $bCaseInsensitive = true) { if ($bCaseInsensitive) { return $this->strtolower($sString1) === $this->strtolower($sString2); } else { return $sString1 === $sString2; } } /** * @param int $iAmount * * @return void */ public function backtrack($iAmount) { $this->iCurrentPosition -= $iAmount; } /** * @param string $sString * * @return int */ public function strlen($sString) { if ($this->oParserSettings->bMultibyteSupport) { return mb_strlen($sString, $this->sCharset); } else { return strlen($sString); } } /** * @param int $iStart * @param int $iLength * * @return string */ private function substr($iStart, $iLength) { if ($iLength < 0) { $iLength = $this->iLength - $iStart + $iLength; } if ($iStart + $iLength > $this->iLength) { $iLength = $this->iLength - $iStart; } $sResult = ''; while ($iLength > 0) { $sResult .= $this->aText[$iStart]; $iStart++; $iLength--; } return $sResult; } /** * @param string $sString * * @return string */ private function strtolower($sString) { if ($this->oParserSettings->bMultibyteSupport) { return mb_strtolower($sString, $this->sCharset); } else { return strtolower($sString); } } /** * @param string $sString * * @return array */ private function strsplit($sString) { if ($this->oParserSettings->bMultibyteSupport) { if ($this->streql($this->sCharset, 'utf-8')) { return preg_split('//u', $sString, -1, PREG_SPLIT_NO_EMPTY); } else { $iLength = mb_strlen($sString, $this->sCharset); $aResult = []; for ($i = 0; $i < $iLength; ++$i) { $aResult[] = mb_substr($sString, $i, 1, $this->sCharset); } return $aResult; } } else { if ($sString === '') { return []; } else { return str_split($sString); } } } /** * @param string $sString * @param string $sNeedle * @param int $iOffset * * @return int|false */ private function strpos($sString, $sNeedle, $iOffset) { if ($this->oParserSettings->bMultibyteSupport) { return mb_strpos($sString, $sNeedle, $iOffset, $this->sCharset); } else { return strpos($sString, $sNeedle, $iOffset); } } } PK[yW'(src/Parsing/UnexpectedTokenException.phpnuW+AsExpected = $sExpected; $this->sFound = $sFound; $this->sMatchType = $sMatchType; $sMessage = "Token “{$sExpected}” ({$sMatchType}) not found. Got “{$sFound}”."; if ($this->sMatchType === 'search') { $sMessage = "Search for “{$sExpected}” returned no results. Context: “{$sFound}”."; } elseif ($this->sMatchType === 'count') { $sMessage = "Next token was expected to have {$sExpected} chars. Context: “{$sFound}”."; } elseif ($this->sMatchType === 'identifier') { $sMessage = "Identifier expected. Got “{$sFound}”"; } elseif ($this->sMatchType === 'custom') { $sMessage = trim("$sExpected $sFound"); } parent::__construct($sMessage, $iLineNo); } } PK[src/Settings.phpnuW+AbMultibyteSupport = extension_loaded('mbstring'); } /** * @return self new instance */ public static function create() { return new Settings(); } /** * @param bool $bMultibyteSupport * * @return self fluent interface */ public function withMultibyteSupport($bMultibyteSupport = true) { $this->bMultibyteSupport = $bMultibyteSupport; return $this; } /** * @param string $sDefaultCharset * * @return self fluent interface */ public function withDefaultCharset($sDefaultCharset) { $this->sDefaultCharset = $sDefaultCharset; return $this; } /** * @param bool $bLenientParsing * * @return self fluent interface */ public function withLenientParsing($bLenientParsing = true) { $this->bLenientParsing = $bLenientParsing; return $this; } /** * @return self fluent interface */ public function beStrict() { return $this->withLenientParsing(false); } } PK[Tsrc/CSSList/Document.phpnuW+AcurrentLine()); CSSList::parseList($oParserState, $oDocument); return $oDocument; } /** * Gets all `DeclarationBlock` objects recursively. * * @return array */ public function getAllDeclarationBlocks() { /** @var array $aResult */ $aResult = []; $this->allDeclarationBlocks($aResult); return $aResult; } /** * Gets all `DeclarationBlock` objects recursively. * * @return array * * @deprecated will be removed in version 9.0; use `getAllDeclarationBlocks()` instead */ public function getAllSelectors() { return $this->getAllDeclarationBlocks(); } /** * Returns all `RuleSet` objects found recursively in the tree. * * @return array */ public function getAllRuleSets() { /** @var array $aResult */ $aResult = []; $this->allRuleSets($aResult); return $aResult; } /** * Returns all `Value` objects found recursively in the tree. * * @param CSSList|RuleSet|string $mElement * the `CSSList` or `RuleSet` to start the search from (defaults to the whole document). * If a string is given, it is used as rule name filter. * @param bool $bSearchInFunctionArguments whether to also return Value objects used as Function arguments. * * @return array * * @see RuleSet->getRules() */ public function getAllValues($mElement = null, $bSearchInFunctionArguments = false) { $sSearchString = null; if ($mElement === null) { $mElement = $this; } elseif (is_string($mElement)) { $sSearchString = $mElement; $mElement = $this; } /** @var array $aResult */ $aResult = []; $this->allValues($mElement, $aResult, $sSearchString, $bSearchInFunctionArguments); return $aResult; } /** * Returns all `Selector` objects 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). * * @param string|null $sSpecificitySearch * An optional filter by specificity. * May contain a comparison operator and a number or just a number (defaults to "=="). * * @return array * @example `getSelectorsBySpecificity('>= 100')` * */ public function getSelectorsBySpecificity($sSpecificitySearch = null) { /** @var array $aResult */ $aResult = []; $this->allSelectors($aResult, $sSpecificitySearch); return $aResult; } /** * Expands all shorthand properties to their long value. * * @return void */ public function expandShorthands() { foreach ($this->getAllDeclarationBlocks() as $oDeclaration) { $oDeclaration->expandShorthands(); } } /** * Create shorthands properties whenever possible. * * @return void */ public function createShorthands() { foreach ($this->getAllDeclarationBlocks() as $oDeclaration) { $oDeclaration->createShorthands(); } } /** * Overrides `render()` to make format argument optional. * * @param OutputFormat|null $oOutputFormat * * @return string */ public function render(OutputFormat $oOutputFormat = null) { if ($oOutputFormat === null) { $oOutputFormat = new OutputFormat(); } return parent::render($oOutputFormat); } /** * @return bool */ public function isRootList() { return true; } } PK[1nsrc/CSSList/KeyFrame.phpnuW+AvendorKeyFrame = null; $this->animationName = null; } /** * @param string $vendorKeyFrame */ public function setVendorKeyFrame($vendorKeyFrame) { $this->vendorKeyFrame = $vendorKeyFrame; } /** * @return string|null */ public function getVendorKeyFrame() { return $this->vendorKeyFrame; } /** * @param string $animationName */ public function setAnimationName($animationName) { $this->animationName = $animationName; } /** * @return string|null */ public function getAnimationName() { return $this->animationName; } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { $sResult = "@{$this->vendorKeyFrame} {$this->animationName}{$oOutputFormat->spaceBeforeOpeningBrace()}{"; $sResult .= parent::render($oOutputFormat); $sResult .= '}'; return $sResult; } /** * @return bool */ public function isRootList() { return false; } /** * @return string|null */ public function atRuleName() { return $this->vendorKeyFrame; } /** * @return string|null */ public function atRuleArgs() { return $this->animationName; } } PK[c=n==src/CSSList/CSSList.phpnuW+A */ protected $aComments; /** * @var array */ protected $aContents; /** * @var int */ protected $iLineNo; /** * @param int $iLineNo */ public function __construct($iLineNo = 0) { $this->aComments = []; $this->aContents = []; $this->iLineNo = $iLineNo; } /** * @return void * * @throws UnexpectedTokenException * @throws SourceException */ public static function parseList(ParserState $oParserState, CSSList $oList) { $bIsRoot = $oList instanceof Document; if (is_string($oParserState)) { $oParserState = new ParserState($oParserState, Settings::create()); } $bLenientParsing = $oParserState->getSettings()->bLenientParsing; while (!$oParserState->isEnd()) { $comments = $oParserState->consumeWhiteSpace(); $oListItem = null; if ($bLenientParsing) { try { $oListItem = self::parseListItem($oParserState, $oList); } catch (UnexpectedTokenException $e) { $oListItem = false; } } else { $oListItem = self::parseListItem($oParserState, $oList); } if ($oListItem === null) { // List parsing finished return; } if ($oListItem) { $oListItem->setComments($comments); $oList->append($oListItem); } $oParserState->consumeWhiteSpace(); } if (!$bIsRoot && !$bLenientParsing) { throw new SourceException("Unexpected end of document", $oParserState->currentLine()); } } /** * @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|DeclarationBlock|null|false * * @throws SourceException * @throws UnexpectedEOFException * @throws UnexpectedTokenException */ private static function parseListItem(ParserState $oParserState, CSSList $oList) { $bIsRoot = $oList instanceof Document; if ($oParserState->comes('@')) { $oAtRule = self::parseAtRule($oParserState); if ($oAtRule instanceof Charset) { if (!$bIsRoot) { throw new UnexpectedTokenException( '@charset may only occur in root document', '', 'custom', $oParserState->currentLine() ); } if (count($oList->getContents()) > 0) { throw new UnexpectedTokenException( '@charset must be the first parseable token in a document', '', 'custom', $oParserState->currentLine() ); } $oParserState->setCharset($oAtRule->getCharset()->getString()); } 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()); } } else { return null; } } } else { return DeclarationBlock::parse($oParserState, $oList); } } /** * @param ParserState $oParserState * * @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|null * * @throws SourceException * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ private static function parseAtRule(ParserState $oParserState) { $oParserState->consume('@'); $sIdentifier = $oParserState->parseIdentifier(); $iIdentifierLineNum = $oParserState->currentLine(); $oParserState->consumeWhiteSpace(); if ($sIdentifier === 'import') { $oLocation = URL::parse($oParserState); $oParserState->consumeWhiteSpace(); $sMediaQuery = null; if (!$oParserState->comes(';')) { $sMediaQuery = trim($oParserState->consumeUntil([';', ParserState::EOF])); } $oParserState->consumeUntil([';', ParserState::EOF], true, true); return new Import($oLocation, $sMediaQuery ?: null, $iIdentifierLineNum); } elseif ($sIdentifier === 'charset') { $sCharset = CSSString::parse($oParserState); $oParserState->consumeWhiteSpace(); $oParserState->consumeUntil([';', ParserState::EOF], true, true); return new Charset($sCharset, $iIdentifierLineNum); } elseif (self::identifierIs($sIdentifier, 'keyframes')) { $oResult = new KeyFrame($iIdentifierLineNum); $oResult->setVendorKeyFrame($sIdentifier); $oResult->setAnimationName(trim($oParserState->consumeUntil('{', false, true))); CSSList::parseList($oParserState, $oResult); if ($oParserState->comes('}')) { $oParserState->consume('}'); } return $oResult; } elseif ($sIdentifier === 'namespace') { $sPrefix = null; $mUrl = Value::parsePrimitiveValue($oParserState); if (!$oParserState->comes(';')) { $sPrefix = $mUrl; $mUrl = Value::parsePrimitiveValue($oParserState); } $oParserState->consumeUntil([';', ParserState::EOF], true, true); if ($sPrefix !== null && !is_string($sPrefix)) { throw new UnexpectedTokenException('Wrong namespace prefix', $sPrefix, 'custom', $iIdentifierLineNum); } if (!($mUrl instanceof CSSString || $mUrl instanceof URL)) { throw new UnexpectedTokenException( 'Wrong namespace url of invalid type', $mUrl, 'custom', $iIdentifierLineNum ); } return new CSSNamespace($mUrl, $sPrefix, $iIdentifierLineNum); } else { // Unknown other at rule (font-face or such) $sArgs = trim($oParserState->consumeUntil('{', false, true)); if (substr_count($sArgs, "(") != substr_count($sArgs, ")")) { if ($oParserState->getSettings()->bLenientParsing) { return null; } else { throw new SourceException("Unmatched brace count in media query", $oParserState->currentLine()); } } $bUseRuleSet = true; foreach (explode('/', AtRule::BLOCK_RULES) as $sBlockRuleName) { if (self::identifierIs($sIdentifier, $sBlockRuleName)) { $bUseRuleSet = false; break; } } if ($bUseRuleSet) { $oAtRule = new AtRuleSet($sIdentifier, $sArgs, $iIdentifierLineNum); RuleSet::parseRuleSet($oParserState, $oAtRule); } else { $oAtRule = new AtRuleBlockList($sIdentifier, $sArgs, $iIdentifierLineNum); CSSList::parseList($oParserState, $oAtRule); if ($oParserState->comes('}')) { $oParserState->consume('}'); } } return $oAtRule; } } /** * Tests an identifier for a given value. Since identifiers are all keywords, they can be vendor-prefixed. * We need to check for these versions too. * * @param string $sIdentifier * @param string $sMatch * * @return bool */ private static function identifierIs($sIdentifier, $sMatch) { return (strcasecmp($sIdentifier, $sMatch) === 0) ?: preg_match("/^(-\\w+-)?$sMatch$/i", $sIdentifier) === 1; } /** * @return int */ public function getLineNo() { return $this->iLineNo; } /** * Prepends an item to the list of contents. * * @param RuleSet|CSSList|Import|Charset $oItem * * @return void */ public function prepend($oItem) { array_unshift($this->aContents, $oItem); } /** * Appends an item to tje list of contents. * * @param RuleSet|CSSList|Import|Charset $oItem * * @return void */ public function append($oItem) { $this->aContents[] = $oItem; } /** * Splices the list of contents. * * @param int $iOffset * @param int $iLength * @param array $mReplacement * * @return void */ public function splice($iOffset, $iLength = null, $mReplacement = null) { array_splice($this->aContents, $iOffset, $iLength, $mReplacement); } /** * Removes an item from the CSS list. * * @param RuleSet|Import|Charset|CSSList $oItemToRemove * May be a RuleSet (most likely a DeclarationBlock), a Import, * a Charset or another CSSList (most likely a MediaQuery) * * @return bool whether the item was removed */ public function remove($oItemToRemove) { $iKey = array_search($oItemToRemove, $this->aContents, true); if ($iKey !== false) { unset($this->aContents[$iKey]); return true; } return false; } /** * Replaces an item from the CSS list. * * @param RuleSet|Import|Charset|CSSList $oOldItem * May be a `RuleSet` (most likely a `DeclarationBlock`), an `Import`, a `Charset` * or another `CSSList` (most likely a `MediaQuery`) * * @return bool */ public function replace($oOldItem, $mNewItem) { $iKey = array_search($oOldItem, $this->aContents, true); if ($iKey !== false) { if (is_array($mNewItem)) { array_splice($this->aContents, $iKey, 1, $mNewItem); } else { array_splice($this->aContents, $iKey, 1, [$mNewItem]); } return true; } return false; } /** * @param array $aContents */ public function setContents(array $aContents) { $this->aContents = []; foreach ($aContents as $content) { $this->append($content); } } /** * Removes a declaration block from the CSS list if it matches all given selectors. * * @param DeclarationBlock|array|string $mSelector the selectors to match * @param bool $bRemoveAll whether to stop at the first declaration block found or remove all blocks * * @return void */ public function removeDeclarationBlockBySelector($mSelector, $bRemoveAll = false) { if ($mSelector instanceof DeclarationBlock) { $mSelector = $mSelector->getSelectors(); } if (!is_array($mSelector)) { $mSelector = explode(',', $mSelector); } foreach ($mSelector as $iKey => &$mSel) { if (!($mSel instanceof Selector)) { if (!Selector::isValid($mSel)) { throw new UnexpectedTokenException( "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.", $mSel, "custom" ); } $mSel = new Selector($mSel); } } foreach ($this->aContents as $iKey => $mItem) { if (!($mItem instanceof DeclarationBlock)) { continue; } if ($mItem->getSelectors() == $mSelector) { unset($this->aContents[$iKey]); if (!$bRemoveAll) { return; } } } } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { $sResult = ''; $bIsFirst = true; $oNextLevel = $oOutputFormat; if (!$this->isRootList()) { $oNextLevel = $oOutputFormat->nextLevel(); } foreach ($this->aContents as $oContent) { $sRendered = $oOutputFormat->safely(function () use ($oNextLevel, $oContent) { return $oContent->render($oNextLevel); }); if ($sRendered === null) { continue; } if ($bIsFirst) { $bIsFirst = false; $sResult .= $oNextLevel->spaceBeforeBlocks(); } else { $sResult .= $oNextLevel->spaceBetweenBlocks(); } $sResult .= $sRendered; } if (!$bIsFirst) { // Had some output $sResult .= $oOutputFormat->spaceAfterBlocks(); } return $sResult; } /** * Return true if the list can not be further outdented. Only important when rendering. * * @return bool */ abstract public function isRootList(); /** * @return array */ public function getContents() { return $this->aContents; } /** * @param array $aComments * * @return void */ public function addComments(array $aComments) { $this->aComments = array_merge($this->aComments, $aComments); } /** * @return array */ public function getComments() { return $this->aComments; } /** * @param array $aComments * * @return void */ public function setComments(array $aComments) { $this->aComments = $aComments; } } PK[zzsrc/CSSList/AtRuleBlockList.phpnuW+AsType = $sType; $this->sArgs = $sArgs; } /** * @return string */ public function atRuleName() { return $this->sType; } /** * @return string */ public function atRuleArgs() { return $this->sArgs; } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { $sArgs = $this->sArgs; if ($sArgs) { $sArgs = ' ' . $sArgs; } $sResult = $oOutputFormat->sBeforeAtRuleBlock; $sResult .= "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{"; $sResult .= parent::render($oOutputFormat); $sResult .= '}'; $sResult .= $oOutputFormat->sAfterAtRuleBlock; return $sResult; } /** * @return bool */ public function isRootList() { return false; } } PK[eHettsrc/CSSList/CSSBlockList.phpnuW+A $aResult * * @return void */ protected function allDeclarationBlocks(array &$aResult) { foreach ($this->aContents as $mContent) { if ($mContent instanceof DeclarationBlock) { $aResult[] = $mContent; } elseif ($mContent instanceof CSSBlockList) { $mContent->allDeclarationBlocks($aResult); } } } /** * @param array $aResult * * @return void */ protected function allRuleSets(array &$aResult) { foreach ($this->aContents as $mContent) { if ($mContent instanceof RuleSet) { $aResult[] = $mContent; } elseif ($mContent instanceof CSSBlockList) { $mContent->allRuleSets($aResult); } } } /** * @param CSSList|Rule|RuleSet|Value $oElement * @param array $aResult * @param string|null $sSearchString * @param bool $bSearchInFunctionArguments * * @return void */ protected function allValues($oElement, array &$aResult, $sSearchString = null, $bSearchInFunctionArguments = false) { if ($oElement instanceof CSSBlockList) { foreach ($oElement->getContents() as $oContent) { $this->allValues($oContent, $aResult, $sSearchString, $bSearchInFunctionArguments); } } elseif ($oElement instanceof RuleSet) { foreach ($oElement->getRules($sSearchString) as $oRule) { $this->allValues($oRule, $aResult, $sSearchString, $bSearchInFunctionArguments); } } elseif ($oElement instanceof Rule) { $this->allValues($oElement->getValue(), $aResult, $sSearchString, $bSearchInFunctionArguments); } elseif ($oElement instanceof ValueList) { if ($bSearchInFunctionArguments || !($oElement instanceof CSSFunction)) { foreach ($oElement->getListComponents() as $mComponent) { $this->allValues($mComponent, $aResult, $sSearchString, $bSearchInFunctionArguments); } } } else { // Non-List `Value` or `CSSString` (CSS identifier) $aResult[] = $oElement; } } /** * @param array $aResult * @param string|null $sSpecificitySearch * * @return void */ protected function allSelectors(array &$aResult, $sSpecificitySearch = null) { /** @var array $aDeclarationBlocks */ $aDeclarationBlocks = []; $this->allDeclarationBlocks($aDeclarationBlocks); foreach ($aDeclarationBlocks as $oBlock) { foreach ($oBlock->getSelectors() as $oSelector) { if ($sSpecificitySearch === null) { $aResult[] = $oSelector; } else { $sComparator = '==='; $aSpecificitySearch = explode(' ', $sSpecificitySearch); $iTargetSpecificity = $aSpecificitySearch[0]; if (count($aSpecificitySearch) > 1) { $sComparator = $aSpecificitySearch[0]; $iTargetSpecificity = $aSpecificitySearch[1]; } $iTargetSpecificity = (int)$iTargetSpecificity; $iSelectorSpecificity = $oSelector->getSpecificity(); $bMatches = false; switch ($sComparator) { case '<=': $bMatches = $iSelectorSpecificity <= $iTargetSpecificity; break; case '<': $bMatches = $iSelectorSpecificity < $iTargetSpecificity; break; case '>=': $bMatches = $iSelectorSpecificity >= $iTargetSpecificity; break; case '>': $bMatches = $iSelectorSpecificity > $iTargetSpecificity; break; default: $bMatches = $iSelectorSpecificity === $iTargetSpecificity; break; } if ($bMatches) { $aResult[] = $oSelector; } } } } } } PK[|'ss src/RuleSet/DeclarationBlock.phpnuW+A */ private $aSelectors; /** * @param int $iLineNo */ public function __construct($iLineNo = 0) { parent::__construct($iLineNo); $this->aSelectors = []; } /** * @param CSSList|null $oList * * @return DeclarationBlock|false * * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ public static function parse(ParserState $oParserState, $oList = null) { $aComments = []; $oResult = new DeclarationBlock($oParserState->currentLine()); try { $aSelectorParts = []; $sStringWrapperChar = false; do { $aSelectorParts[] = $oParserState->consume(1) . $oParserState->consumeUntil(['{', '}', '\'', '"'], false, false, $aComments); if (in_array($oParserState->peek(), ['\'', '"']) && substr(end($aSelectorParts), -1) != "\\") { if ($sStringWrapperChar === false) { $sStringWrapperChar = $oParserState->peek(); } elseif ($sStringWrapperChar == $oParserState->peek()) { $sStringWrapperChar = false; } } } while (!in_array($oParserState->peek(), ['{', '}']) || $sStringWrapperChar !== false); $oResult->setSelectors(implode('', $aSelectorParts), $oList); if ($oParserState->comes('{')) { $oParserState->consume(1); } } catch (UnexpectedTokenException $e) { if ($oParserState->getSettings()->bLenientParsing) { if (!$oParserState->comes('}')) { $oParserState->consumeUntil('}', false, true); } return false; } else { throw $e; } } $oResult->setComments($aComments); RuleSet::parseRuleSet($oParserState, $oResult); return $oResult; } /** * @param array|string $mSelector * @param CSSList|null $oList * * @throws UnexpectedTokenException */ public function setSelectors($mSelector, $oList = null) { if (is_array($mSelector)) { $this->aSelectors = $mSelector; } else { $this->aSelectors = explode(',', $mSelector); } foreach ($this->aSelectors as $iKey => $mSelector) { if (!($mSelector instanceof Selector)) { if ($oList === null || !($oList instanceof KeyFrame)) { if (!Selector::isValid($mSelector)) { throw new UnexpectedTokenException( "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.", $mSelector, "custom" ); } $this->aSelectors[$iKey] = new Selector($mSelector); } else { if (!KeyframeSelector::isValid($mSelector)) { throw new UnexpectedTokenException( "Selector did not match '" . KeyframeSelector::SELECTOR_VALIDATION_RX . "'.", $mSelector, "custom" ); } $this->aSelectors[$iKey] = new KeyframeSelector($mSelector); } } } } /** * Remove one of the selectors of the block. * * @param Selector|string $mSelector * * @return bool */ public function removeSelector($mSelector) { if ($mSelector instanceof Selector) { $mSelector = $mSelector->getSelector(); } foreach ($this->aSelectors as $iKey => $oSelector) { if ($oSelector->getSelector() === $mSelector) { unset($this->aSelectors[$iKey]); return true; } } return false; } /** * @return array * * @deprecated will be removed in version 9.0; use `getSelectors()` instead */ public function getSelector() { return $this->getSelectors(); } /** * @param Selector|string $mSelector * @param CSSList|null $oList * * @return void * * @deprecated will be removed in version 9.0; use `setSelectors()` instead */ public function setSelector($mSelector, $oList = null) { $this->setSelectors($mSelector, $oList); } /** * @return array */ public function getSelectors() { return $this->aSelectors; } /** * Splits shorthand declarations (e.g. `margin` or `font`) into their constituent parts. * * @return void */ public function expandShorthands() { // border must be expanded before dimensions $this->expandBorderShorthand(); $this->expandDimensionsShorthand(); $this->expandFontShorthand(); $this->expandBackgroundShorthand(); $this->expandListStyleShorthand(); } /** * Creates shorthand declarations (e.g. `margin` or `font`) whenever possible. * * @return void */ public function createShorthands() { $this->createBackgroundShorthand(); $this->createDimensionsShorthand(); // border must be shortened after dimensions $this->createBorderShorthand(); $this->createFontShorthand(); $this->createListStyleShorthand(); } /** * Splits shorthand border declarations (e.g. `border: 1px red;`). * * Additional splitting happens in expandDimensionsShorthand. * * Multiple borders are not yet supported as of 3. * * @return void */ public function expandBorderShorthand() { $aBorderRules = [ 'border', 'border-left', 'border-right', 'border-top', 'border-bottom', ]; $aBorderSizes = [ 'thin', 'medium', 'thick', ]; $aRules = $this->getRulesAssoc(); foreach ($aBorderRules as $sBorderRule) { if (!isset($aRules[$sBorderRule])) { continue; } $oRule = $aRules[$sBorderRule]; $mRuleValue = $oRule->getValue(); $aValues = []; if (!$mRuleValue instanceof RuleValueList) { $aValues[] = $mRuleValue; } else { $aValues = $mRuleValue->getListComponents(); } foreach ($aValues as $mValue) { if ($mValue instanceof Value) { $mNewValue = clone $mValue; } else { $mNewValue = $mValue; } if ($mValue instanceof Size) { $sNewRuleName = $sBorderRule . "-width"; } elseif ($mValue instanceof Color) { $sNewRuleName = $sBorderRule . "-color"; } else { if (in_array($mValue, $aBorderSizes)) { $sNewRuleName = $sBorderRule . "-width"; } else { $sNewRuleName = $sBorderRule . "-style"; } } $oNewRule = new Rule($sNewRuleName, $oRule->getLineNo(), $oRule->getColNo()); $oNewRule->setIsImportant($oRule->getIsImportant()); $oNewRule->addValue([$mNewValue]); $this->addRule($oNewRule); } $this->removeRule($sBorderRule); } } /** * Splits shorthand dimensional declarations (e.g. `margin: 0px auto;`) * into their constituent parts. * * Handles `margin`, `padding`, `border-color`, `border-style` and `border-width`. * * @return void */ public function expandDimensionsShorthand() { $aExpansions = [ 'margin' => 'margin-%s', 'padding' => 'padding-%s', 'border-color' => 'border-%s-color', 'border-style' => 'border-%s-style', 'border-width' => 'border-%s-width', ]; $aRules = $this->getRulesAssoc(); foreach ($aExpansions as $sProperty => $sExpanded) { if (!isset($aRules[$sProperty])) { continue; } $oRule = $aRules[$sProperty]; $mRuleValue = $oRule->getValue(); $aValues = []; if (!$mRuleValue instanceof RuleValueList) { $aValues[] = $mRuleValue; } else { $aValues = $mRuleValue->getListComponents(); } $top = $right = $bottom = $left = null; switch (count($aValues)) { case 1: $top = $right = $bottom = $left = $aValues[0]; break; case 2: $top = $bottom = $aValues[0]; $left = $right = $aValues[1]; break; case 3: $top = $aValues[0]; $left = $right = $aValues[1]; $bottom = $aValues[2]; break; case 4: $top = $aValues[0]; $right = $aValues[1]; $bottom = $aValues[2]; $left = $aValues[3]; break; } foreach (['top', 'right', 'bottom', 'left'] as $sPosition) { $oNewRule = new Rule(sprintf($sExpanded, $sPosition), $oRule->getLineNo(), $oRule->getColNo()); $oNewRule->setIsImportant($oRule->getIsImportant()); $oNewRule->addValue(${$sPosition}); $this->addRule($oNewRule); } $this->removeRule($sProperty); } } /** * Converts shorthand font declarations * (e.g. `font: 300 italic 11px/14px verdana, helvetica, sans-serif;`) * into their constituent parts. * * @return void */ public function expandFontShorthand() { $aRules = $this->getRulesAssoc(); if (!isset($aRules['font'])) { return; } $oRule = $aRules['font']; // reset properties to 'normal' per http://www.w3.org/TR/21/fonts.html#font-shorthand $aFontProperties = [ 'font-style' => 'normal', 'font-variant' => 'normal', 'font-weight' => 'normal', 'font-size' => 'normal', 'line-height' => 'normal', ]; $mRuleValue = $oRule->getValue(); $aValues = []; if (!$mRuleValue instanceof RuleValueList) { $aValues[] = $mRuleValue; } else { $aValues = $mRuleValue->getListComponents(); } foreach ($aValues as $mValue) { if (!$mValue instanceof Value) { $mValue = mb_strtolower($mValue); } if (in_array($mValue, ['normal', 'inherit'])) { foreach (['font-style', 'font-weight', 'font-variant'] as $sProperty) { if (!isset($aFontProperties[$sProperty])) { $aFontProperties[$sProperty] = $mValue; } } } elseif (in_array($mValue, ['italic', 'oblique'])) { $aFontProperties['font-style'] = $mValue; } elseif ($mValue == 'small-caps') { $aFontProperties['font-variant'] = $mValue; } elseif ( in_array($mValue, ['bold', 'bolder', 'lighter']) || ($mValue instanceof Size && in_array($mValue->getSize(), range(100, 900, 100))) ) { $aFontProperties['font-weight'] = $mValue; } elseif ($mValue instanceof RuleValueList && $mValue->getListSeparator() == '/') { list($oSize, $oHeight) = $mValue->getListComponents(); $aFontProperties['font-size'] = $oSize; $aFontProperties['line-height'] = $oHeight; } elseif ($mValue instanceof Size && $mValue->getUnit() !== null) { $aFontProperties['font-size'] = $mValue; } else { $aFontProperties['font-family'] = $mValue; } } foreach ($aFontProperties as $sProperty => $mValue) { $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo()); $oNewRule->addValue($mValue); $oNewRule->setIsImportant($oRule->getIsImportant()); $this->addRule($oNewRule); } $this->removeRule('font'); } /** * Converts shorthand background declarations * (e.g. `background: url("chess.png") gray 50% repeat fixed;`) * into their constituent parts. * * @see http://www.w3.org/TR/21/colors.html#propdef-background * * @return void */ public function expandBackgroundShorthand() { $aRules = $this->getRulesAssoc(); if (!isset($aRules['background'])) { return; } $oRule = $aRules['background']; $aBgProperties = [ 'background-color' => ['transparent'], 'background-image' => ['none'], 'background-repeat' => ['repeat'], 'background-attachment' => ['scroll'], 'background-position' => [ new Size(0, '%', null, false, $this->iLineNo), new Size(0, '%', null, false, $this->iLineNo), ], ]; $mRuleValue = $oRule->getValue(); $aValues = []; if (!$mRuleValue instanceof RuleValueList) { $aValues[] = $mRuleValue; } else { $aValues = $mRuleValue->getListComponents(); } if (count($aValues) == 1 && $aValues[0] == 'inherit') { foreach ($aBgProperties as $sProperty => $mValue) { $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo()); $oNewRule->addValue('inherit'); $oNewRule->setIsImportant($oRule->getIsImportant()); $this->addRule($oNewRule); } $this->removeRule('background'); return; } $iNumBgPos = 0; foreach ($aValues as $mValue) { if (!$mValue instanceof Value) { $mValue = mb_strtolower($mValue); } if ($mValue instanceof URL) { $aBgProperties['background-image'] = $mValue; } elseif ($mValue instanceof Color) { $aBgProperties['background-color'] = $mValue; } elseif (in_array($mValue, ['scroll', 'fixed'])) { $aBgProperties['background-attachment'] = $mValue; } elseif (in_array($mValue, ['repeat', 'no-repeat', 'repeat-x', 'repeat-y'])) { $aBgProperties['background-repeat'] = $mValue; } elseif ( in_array($mValue, ['left', 'center', 'right', 'top', 'bottom']) || $mValue instanceof Size ) { if ($iNumBgPos == 0) { $aBgProperties['background-position'][0] = $mValue; $aBgProperties['background-position'][1] = 'center'; } else { $aBgProperties['background-position'][$iNumBgPos] = $mValue; } $iNumBgPos++; } } foreach ($aBgProperties as $sProperty => $mValue) { $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo()); $oNewRule->setIsImportant($oRule->getIsImportant()); $oNewRule->addValue($mValue); $this->addRule($oNewRule); } $this->removeRule('background'); } /** * @return void */ public function expandListStyleShorthand() { $aListProperties = [ 'list-style-type' => 'disc', 'list-style-position' => 'outside', 'list-style-image' => 'none', ]; $aListStyleTypes = [ 'none', 'disc', 'circle', 'square', 'decimal-leading-zero', 'decimal', 'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha', 'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'armenian', 'georgian', 'cjk-ideographic', 'hiragana', 'hira-gana-iroha', 'katakana-iroha', 'katakana', ]; $aListStylePositions = [ 'inside', 'outside', ]; $aRules = $this->getRulesAssoc(); if (!isset($aRules['list-style'])) { return; } $oRule = $aRules['list-style']; $mRuleValue = $oRule->getValue(); $aValues = []; if (!$mRuleValue instanceof RuleValueList) { $aValues[] = $mRuleValue; } else { $aValues = $mRuleValue->getListComponents(); } if (count($aValues) == 1 && $aValues[0] == 'inherit') { foreach ($aListProperties as $sProperty => $mValue) { $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo()); $oNewRule->addValue('inherit'); $oNewRule->setIsImportant($oRule->getIsImportant()); $this->addRule($oNewRule); } $this->removeRule('list-style'); return; } foreach ($aValues as $mValue) { if (!$mValue instanceof Value) { $mValue = mb_strtolower($mValue); } if ($mValue instanceof Url) { $aListProperties['list-style-image'] = $mValue; } elseif (in_array($mValue, $aListStyleTypes)) { $aListProperties['list-style-types'] = $mValue; } elseif (in_array($mValue, $aListStylePositions)) { $aListProperties['list-style-position'] = $mValue; } } foreach ($aListProperties as $sProperty => $mValue) { $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo()); $oNewRule->setIsImportant($oRule->getIsImportant()); $oNewRule->addValue($mValue); $this->addRule($oNewRule); } $this->removeRule('list-style'); } /** * @param array $aProperties * @param string $sShorthand * * @return void */ public function createShorthandProperties(array $aProperties, $sShorthand) { $aRules = $this->getRulesAssoc(); $aNewValues = []; foreach ($aProperties as $sProperty) { if (!isset($aRules[$sProperty])) { continue; } $oRule = $aRules[$sProperty]; if (!$oRule->getIsImportant()) { $mRuleValue = $oRule->getValue(); $aValues = []; if (!$mRuleValue instanceof RuleValueList) { $aValues[] = $mRuleValue; } else { $aValues = $mRuleValue->getListComponents(); } foreach ($aValues as $mValue) { $aNewValues[] = $mValue; } $this->removeRule($sProperty); } } if (count($aNewValues)) { $oNewRule = new Rule($sShorthand, $oRule->getLineNo(), $oRule->getColNo()); foreach ($aNewValues as $mValue) { $oNewRule->addValue($mValue); } $this->addRule($oNewRule); } } /** * @return void */ public function createBackgroundShorthand() { $aProperties = [ 'background-color', 'background-image', 'background-repeat', 'background-position', 'background-attachment', ]; $this->createShorthandProperties($aProperties, 'background'); } /** * @return void */ public function createListStyleShorthand() { $aProperties = [ 'list-style-type', 'list-style-position', 'list-style-image', ]; $this->createShorthandProperties($aProperties, 'list-style'); } /** * Combines `border-color`, `border-style` and `border-width` into `border`. * * Should be run after `create_dimensions_shorthand`! * * @return void */ public function createBorderShorthand() { $aProperties = [ 'border-width', 'border-style', 'border-color', ]; $this->createShorthandProperties($aProperties, 'border'); } /** * Looks for long format CSS dimensional properties * (margin, padding, border-color, border-style and border-width) * and converts them into shorthand CSS properties. * * @return void */ public function createDimensionsShorthand() { $aPositions = ['top', 'right', 'bottom', 'left']; $aExpansions = [ 'margin' => 'margin-%s', 'padding' => 'padding-%s', 'border-color' => 'border-%s-color', 'border-style' => 'border-%s-style', 'border-width' => 'border-%s-width', ]; $aRules = $this->getRulesAssoc(); foreach ($aExpansions as $sProperty => $sExpanded) { $aFoldable = []; foreach ($aRules as $sRuleName => $oRule) { foreach ($aPositions as $sPosition) { if ($sRuleName == sprintf($sExpanded, $sPosition)) { $aFoldable[$sRuleName] = $oRule; } } } // All four dimensions must be present if (count($aFoldable) == 4) { $aValues = []; foreach ($aPositions as $sPosition) { $oRule = $aRules[sprintf($sExpanded, $sPosition)]; $mRuleValue = $oRule->getValue(); $aRuleValues = []; if (!$mRuleValue instanceof RuleValueList) { $aRuleValues[] = $mRuleValue; } else { $aRuleValues = $mRuleValue->getListComponents(); } $aValues[$sPosition] = $aRuleValues; } $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo()); if ((string)$aValues['left'][0] == (string)$aValues['right'][0]) { if ((string)$aValues['top'][0] == (string)$aValues['bottom'][0]) { if ((string)$aValues['top'][0] == (string)$aValues['left'][0]) { // All 4 sides are equal $oNewRule->addValue($aValues['top']); } else { // Top and bottom are equal, left and right are equal $oNewRule->addValue($aValues['top']); $oNewRule->addValue($aValues['left']); } } else { // Only left and right are equal $oNewRule->addValue($aValues['top']); $oNewRule->addValue($aValues['left']); $oNewRule->addValue($aValues['bottom']); } } else { // No sides are equal $oNewRule->addValue($aValues['top']); $oNewRule->addValue($aValues['left']); $oNewRule->addValue($aValues['bottom']); $oNewRule->addValue($aValues['right']); } $this->addRule($oNewRule); foreach ($aPositions as $sPosition) { $this->removeRule(sprintf($sExpanded, $sPosition)); } } } } /** * Looks for long format CSS font properties (e.g. `font-weight`) and * tries to convert them into a shorthand CSS `font` property. * * At least `font-size` AND `font-family` must be present in order to create a shorthand declaration. * * @return void */ public function createFontShorthand() { $aFontProperties = [ 'font-style', 'font-variant', 'font-weight', 'font-size', 'line-height', 'font-family', ]; $aRules = $this->getRulesAssoc(); if (!isset($aRules['font-size']) || !isset($aRules['font-family'])) { return; } $oOldRule = isset($aRules['font-size']) ? $aRules['font-size'] : $aRules['font-family']; $oNewRule = new Rule('font', $oOldRule->getLineNo(), $oOldRule->getColNo()); unset($oOldRule); foreach (['font-style', 'font-variant', 'font-weight'] as $sProperty) { if (isset($aRules[$sProperty])) { $oRule = $aRules[$sProperty]; $mRuleValue = $oRule->getValue(); $aValues = []; if (!$mRuleValue instanceof RuleValueList) { $aValues[] = $mRuleValue; } else { $aValues = $mRuleValue->getListComponents(); } if ($aValues[0] !== 'normal') { $oNewRule->addValue($aValues[0]); } } } // Get the font-size value $oRule = $aRules['font-size']; $mRuleValue = $oRule->getValue(); $aFSValues = []; if (!$mRuleValue instanceof RuleValueList) { $aFSValues[] = $mRuleValue; } else { $aFSValues = $mRuleValue->getListComponents(); } // But wait to know if we have line-height to add it if (isset($aRules['line-height'])) { $oRule = $aRules['line-height']; $mRuleValue = $oRule->getValue(); $aLHValues = []; if (!$mRuleValue instanceof RuleValueList) { $aLHValues[] = $mRuleValue; } else { $aLHValues = $mRuleValue->getListComponents(); } if ($aLHValues[0] !== 'normal') { $val = new RuleValueList('/', $this->iLineNo); $val->addListComponent($aFSValues[0]); $val->addListComponent($aLHValues[0]); $oNewRule->addValue($val); } } else { $oNewRule->addValue($aFSValues[0]); } $oRule = $aRules['font-family']; $mRuleValue = $oRule->getValue(); $aFFValues = []; if (!$mRuleValue instanceof RuleValueList) { $aFFValues[] = $mRuleValue; } else { $aFFValues = $mRuleValue->getListComponents(); } $oFFValue = new RuleValueList(',', $this->iLineNo); $oFFValue->setListComponents($aFFValues); $oNewRule->addValue($oFFValue); $this->addRule($oNewRule); foreach ($aFontProperties as $sProperty) { $this->removeRule($sProperty); } } /** * @return string * * @throws OutputException */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string * * @throws OutputException */ public function render(OutputFormat $oOutputFormat) { 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->implode( $oOutputFormat->spaceBeforeSelectorSeparator() . ',' . $oOutputFormat->spaceAfterSelectorSeparator(), $this->aSelectors ); $sResult .= $oOutputFormat->sAfterDeclarationBlockSelectors; $sResult .= $oOutputFormat->spaceBeforeOpeningBrace() . '{'; $sResult .= parent::render($oOutputFormat); $sResult .= '}'; $sResult .= $oOutputFormat->sAfterDeclarationBlock; return $sResult; } } PK[**src/RuleSet/RuleSet.phpnuW+A */ private $aRules; /** * @var int */ protected $iLineNo; /** * @var array */ protected $aComments; /** * @param int $iLineNo */ public function __construct($iLineNo = 0) { $this->aRules = []; $this->iLineNo = $iLineNo; $this->aComments = []; } /** * @return void * * @throws UnexpectedTokenException * @throws UnexpectedEOFException */ public static function parseRuleSet(ParserState $oParserState, RuleSet $oRuleSet) { while ($oParserState->comes(';')) { $oParserState->consume(';'); } while (!$oParserState->comes('}')) { $oRule = null; if ($oParserState->getSettings()->bLenientParsing) { try { $oRule = Rule::parse($oParserState); } catch (UnexpectedTokenException $e) { try { $sConsume = $oParserState->consumeUntil(["\n", ";", '}'], true); // We need to “unfind” the matches to the end of the ruleSet as this will be matched later if ($oParserState->streql(substr($sConsume, -1), '}')) { $oParserState->backtrack(1); } else { while ($oParserState->comes(';')) { $oParserState->consume(';'); } } } catch (UnexpectedTokenException $e) { // We’ve reached the end of the document. Just close the RuleSet. return; } } } else { $oRule = Rule::parse($oParserState); } if ($oRule) { $oRuleSet->addRule($oRule); } } $oParserState->consume('}'); } /** * @return int */ public function getLineNo() { return $this->iLineNo; } /** * @param Rule|null $oSibling * * @return void */ public function addRule(Rule $oRule, Rule $oSibling = null) { $sRule = $oRule->getRule(); if (!isset($this->aRules[$sRule])) { $this->aRules[$sRule] = []; } $iPosition = count($this->aRules[$sRule]); if ($oSibling !== null) { $iSiblingPos = array_search($oSibling, $this->aRules[$sRule], true); if ($iSiblingPos !== false) { $iPosition = $iSiblingPos; $oRule->setPosition($oSibling->getLineNo(), $oSibling->getColNo() - 1); } } if ($oRule->getLineNo() === 0 && $oRule->getColNo() === 0) { //this node is added manually, give it the next best line $rules = $this->getRules(); $pos = count($rules); if ($pos > 0) { $last = $rules[$pos - 1]; $oRule->setPosition($last->getLineNo() + 1, 0); } } array_splice($this->aRules[$sRule], $iPosition, 0, [$oRule]); } /** * Returns all rules matching the given rule name * * @example $oRuleSet->getRules('font') // returns array(0 => $oRule, …) or array(). * * @example $oRuleSet->getRules('font-') * //returns an array of all rules either beginning with font- or matching font. * * @param Rule|string|null $mRule * Pattern to search for. If null, returns all rules. * If the pattern ends with a dash, all rules starting with the pattern are returned * as well as one matching the pattern with the dash excluded. * Passing a Rule behaves like calling `getRules($mRule->getRule())`. * * @return array */ public function getRules($mRule = null) { if ($mRule instanceof Rule) { $mRule = $mRule->getRule(); } /** @var array $aResult */ $aResult = []; foreach ($this->aRules as $sName => $aRules) { // Either no search rule is given or the search rule matches the found rule exactly // or the search rule ends in “-” and the found rule starts with the search rule. if ( !$mRule || $sName === $mRule || ( strrpos($mRule, '-') === strlen($mRule) - strlen('-') && (strpos($sName, $mRule) === 0 || $sName === substr($mRule, 0, -1)) ) ) { $aResult = array_merge($aResult, $aRules); } } usort($aResult, function (Rule $first, Rule $second) { if ($first->getLineNo() === $second->getLineNo()) { return $first->getColNo() - $second->getColNo(); } return $first->getLineNo() - $second->getLineNo(); }); return $aResult; } /** * Overrides all the rules of this set. * * @param array $aRules The rules to override with. * * @return void */ public function setRules(array $aRules) { $this->aRules = []; foreach ($aRules as $rule) { $this->addRule($rule); } } /** * Returns all rules matching the given pattern and returns them in an associative array with the rule’s name * as keys. This method exists mainly for backwards-compatibility and is really only partially useful. * * Note: This method loses some information: Calling this (with an argument of `background-`) on a declaration block * like `{ background-color: green; background-color; rgba(0, 127, 0, 0.7); }` will only yield an associative array * containing the rgba-valued rule while `getRules()` would yield an indexed array containing both. * * @param Rule|string|null $mRule $mRule * Pattern to search for. If null, returns all rules. If the pattern ends with a dash, * all rules starting with the pattern are returned as well as one matching the pattern with the dash * excluded. Passing a Rule behaves like calling `getRules($mRule->getRule())`. * * @return array */ public function getRulesAssoc($mRule = null) { /** @var array $aResult */ $aResult = []; foreach ($this->getRules($mRule) as $oRule) { $aResult[$oRule->getRule()] = $oRule; } return $aResult; } /** * Removes a rule from this RuleSet. This accepts all the possible values that `getRules()` accepts. * * If given a Rule, it will only remove this particular rule (by identity). * If given a name, it will remove all rules by that name. * * Note: this is different from pre-v.2.0 behaviour of PHP-CSS-Parser, where passing a Rule instance would * remove all rules with the same name. To get the old behaviour, use `removeRule($oRule->getRule())`. * * @param Rule|string|null $mRule * pattern to remove. If $mRule is null, all rules are removed. If the pattern ends in a dash, * all rules starting with the pattern are removed as well as one matching the pattern with the dash * excluded. Passing a Rule behaves matches by identity. * * @return void */ public function removeRule($mRule) { if ($mRule instanceof Rule) { $sRule = $mRule->getRule(); if (!isset($this->aRules[$sRule])) { return; } foreach ($this->aRules[$sRule] as $iKey => $oRule) { if ($oRule === $mRule) { unset($this->aRules[$sRule][$iKey]); } } } else { foreach ($this->aRules as $sName => $aRules) { // Either no search rule is given or the search rule matches the found rule exactly // or the search rule ends in “-” and the found rule starts with the search rule or equals it // (without the trailing dash). if ( !$mRule || $sName === $mRule || (strrpos($mRule, '-') === strlen($mRule) - strlen('-') && (strpos($sName, $mRule) === 0 || $sName === substr($mRule, 0, -1))) ) { unset($this->aRules[$sName]); } } } } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { $sResult = ''; $bIsFirst = true; foreach ($this->aRules as $aRules) { foreach ($aRules as $oRule) { $sRendered = $oOutputFormat->safely(function () use ($oRule, $oOutputFormat) { return $oRule->render($oOutputFormat->nextLevel()); }); if ($sRendered === null) { continue; } if ($bIsFirst) { $bIsFirst = false; $sResult .= $oOutputFormat->nextLevel()->spaceBeforeRules(); } else { $sResult .= $oOutputFormat->nextLevel()->spaceBetweenRules(); } $sResult .= $sRendered; } } if (!$bIsFirst) { // Had some output $sResult .= $oOutputFormat->spaceAfterRules(); } return $oOutputFormat->removeLastSemicolon($sResult); } /** * @param array $aComments * * @return void */ public function addComments(array $aComments) { $this->aComments = array_merge($this->aComments, $aComments); } /** * @return array */ public function getComments() { return $this->aComments; } /** * @param array $aComments * * @return void */ public function setComments(array $aComments) { $this->aComments = $aComments; } } PK[[~src/RuleSet/AtRuleSet.phpnuW+AsType = $sType; $this->sArgs = $sArgs; } /** * @return string */ public function atRuleName() { return $this->sType; } /** * @return string */ public function atRuleArgs() { return $this->sArgs; } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { $sArgs = $this->sArgs; if ($sArgs) { $sArgs = ' ' . $sArgs; } $sResult = "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{"; $sResult .= parent::render($oOutputFormat); $sResult .= '}'; return $sResult; } } PK[0 src/Property/CSSNamespace.phpnuW+A */ protected $aComments; /** * @param string $mUrl * @param string|null $sPrefix * @param int $iLineNo */ public function __construct($mUrl, $sPrefix = null, $iLineNo = 0) { $this->mUrl = $mUrl; $this->sPrefix = $sPrefix; $this->iLineNo = $iLineNo; $this->aComments = []; } /** * @return int */ public function getLineNo() { return $this->iLineNo; } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { return '@namespace ' . ($this->sPrefix === null ? '' : $this->sPrefix . ' ') . $this->mUrl->render($oOutputFormat) . ';'; } /** * @return string */ public function getUrl() { return $this->mUrl; } /** * @return string|null */ public function getPrefix() { return $this->sPrefix; } /** * @param string $mUrl * * @return void */ public function setUrl($mUrl) { $this->mUrl = $mUrl; } /** * @param string $sPrefix * * @return void */ public function setPrefix($sPrefix) { $this->sPrefix = $sPrefix; } /** * @return string */ public function atRuleName() { return 'namespace'; } /** * @return array */ public function atRuleArgs() { $aResult = [$this->mUrl]; if ($this->sPrefix) { array_unshift($aResult, $this->sPrefix); } return $aResult; } /** * @param array $aComments * * @return void */ public function addComments(array $aComments) { $this->aComments = array_merge($this->aComments, $aComments); } /** * @return array */ public function getComments() { return $this->aComments; } /** * @param array $aComments * * @return void */ public function setComments(array $aComments) { $this->aComments = $aComments; } } PK[\k k src/Property/Selector.phpnuW+A\~]+)[\w]+ # elements | \:{1,2}( # pseudo-elements after|before|first-letter|first-line|selection )) /ix'; /** * regexp for specificity calculations * * @var string */ const SELECTOR_VALIDATION_RX = '/ ^( (?: [a-zA-Z0-9\x{00A0}-\x{FFFF}_^$|*="\'~\[\]()\-\s\.:#+>]* # any sequence of valid unescaped characters (?:\\\\.)? # a single escaped character (?:([\'"]).*?(?setSelector($sSelector); if ($bCalculateSpecificity) { $this->getSpecificity(); } } /** * @return string */ public function getSelector() { return $this->sSelector; } /** * @param string $sSelector * * @return void */ public function setSelector($sSelector) { $this->sSelector = trim($sSelector); $this->iSpecificity = null; } /** * @return string */ public function __toString() { return $this->getSelector(); } /** * @return int */ public function getSpecificity() { if ($this->iSpecificity === null) { $a = 0; /// @todo should exclude \# as well as "#" $aMatches = null; $b = substr_count($this->sSelector, '#'); $c = preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->sSelector, $aMatches); $d = preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->sSelector, $aMatches); $this->iSpecificity = ($a * 1000) + ($b * 100) + ($c * 10) + $d; } return $this->iSpecificity; } } PK[K$A src/Property/Charset.phpnuW+A */ protected $aComments; /** * @param string $sCharset * @param int $iLineNo */ public function __construct($sCharset, $iLineNo = 0) { $this->sCharset = $sCharset; $this->iLineNo = $iLineNo; $this->aComments = []; } /** * @return int */ public function getLineNo() { return $this->iLineNo; } /** * @param string $sCharset * * @return void */ public function setCharset($sCharset) { $this->sCharset = $sCharset; } /** * @return string */ public function getCharset() { return $this->sCharset; } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { return "@charset {$this->sCharset->render($oOutputFormat)};"; } /** * @return string */ public function atRuleName() { return 'charset'; } /** * @return string */ public function atRuleArgs() { return $this->sCharset; } /** * @param array $aComments * * @return void */ public function addComments(array $aComments) { $this->aComments = array_merge($this->aComments, $aComments); } /** * @return array */ public function getComments() { return $this->aComments; } /** * @param array $aComments * * @return void */ public function setComments(array $aComments) { $this->aComments = $aComments; } } PK[) src/Property/Import.phpnuW+A */ protected $aComments; /** * @param URL $oLocation * @param string $sMediaQuery * @param int $iLineNo */ public function __construct(URL $oLocation, $sMediaQuery, $iLineNo = 0) { $this->oLocation = $oLocation; $this->sMediaQuery = $sMediaQuery; $this->iLineNo = $iLineNo; $this->aComments = []; } /** * @return int */ public function getLineNo() { return $this->iLineNo; } /** * @param URL $oLocation * * @return void */ public function setLocation($oLocation) { $this->oLocation = $oLocation; } /** * @return URL */ public function getLocation() { return $this->oLocation; } /** * @return string */ public function __toString() { return $this->render(new OutputFormat()); } /** * @return string */ public function render(OutputFormat $oOutputFormat) { return "@import " . $this->oLocation->render($oOutputFormat) . ($this->sMediaQuery === null ? '' : ' ' . $this->sMediaQuery) . ';'; } /** * @return string */ public function atRuleName() { return 'import'; } /** * @return array */ public function atRuleArgs() { $aResult = [$this->oLocation]; if ($this->sMediaQuery) { array_push($aResult, $this->sMediaQuery); } return $aResult; } /** * @param array $aComments * * @return void */ public function addComments(array $aComments) { $this->aComments = array_merge($this->aComments, $aComments); } /** * @return array */ public function getComments() { return $this->aComments; } /** * @param array $aComments * * @return void */ public function setComments(array $aComments) { $this->aComments = $aComments; } } PK[ EJ!!src/Property/AtRule.phpnuW+A]* # any sequence of valid unescaped characters (?:\\\\.)? # a single escaped character (?:([\'"]).*?(?oFormat = $oFormat; } /** * @param string $sName * @param string|null $sType * * @return string */ public function space($sName, $sType = null) { $sSpaceString = $this->oFormat->get("Space$sName"); // If $sSpaceString is an array, we have multiple values configured // depending on the type of object the space applies to if (is_array($sSpaceString)) { if ($sType !== null && isset($sSpaceString[$sType])) { $sSpaceString = $sSpaceString[$sType]; } else { $sSpaceString = reset($sSpaceString); } } return $this->prepareSpace($sSpaceString); } /** * @return string */ public function spaceAfterRuleName() { return $this->space('AfterRuleName'); } /** * @return string */ public function spaceBeforeRules() { return $this->space('BeforeRules'); } /** * @return string */ public function spaceAfterRules() { return $this->space('AfterRules'); } /** * @return string */ public function spaceBetweenRules() { return $this->space('BetweenRules'); } /** * @return string */ public function spaceBeforeBlocks() { return $this->space('BeforeBlocks'); } /** * @return string */ public function spaceAfterBlocks() { return $this->space('AfterBlocks'); } /** * @return string */ public function spaceBetweenBlocks() { return $this->space('BetweenBlocks'); } /** * @return string */ public function spaceBeforeSelectorSeparator() { return $this->space('BeforeSelectorSeparator'); } /** * @return string */ public function spaceAfterSelectorSeparator() { return $this->space('AfterSelectorSeparator'); } /** * @param string $sSeparator * * @return string */ public function spaceBeforeListArgumentSeparator($sSeparator) { return $this->space('BeforeListArgumentSeparator', $sSeparator); } /** * @param string $sSeparator * * @return string */ public function spaceAfterListArgumentSeparator($sSeparator) { return $this->space('AfterListArgumentSeparator', $sSeparator); } /** * @return string */ public function spaceBeforeOpeningBrace() { return $this->space('BeforeOpeningBrace'); } /** * Runs the given code, either swallowing or passing exceptions, depending on the `bIgnoreExceptions` setting. * * @param string $cCode the name of the function to call * * @return string|null */ public function safely($cCode) { if ($this->oFormat->get('IgnoreExceptions')) { // If output exceptions are ignored, run the code with exception guards try { return $cCode(); } catch (OutputException $e) { return null; } // Do nothing } else { // Run the code as-is return $cCode(); } } /** * Clone of the `implode` function, but calls `render` with the current output format instead of `__toString()`. * * @param string $sSeparator * @param array $aValues * @param bool $bIncreaseLevel * * @return string */ public function implode($sSeparator, array $aValues, $bIncreaseLevel = false) { $sResult = ''; $oFormat = $this->oFormat; if ($bIncreaseLevel) { $oFormat = $oFormat->nextLevel(); } $bIsFirst = true; foreach ($aValues as $mValue) { if ($bIsFirst) { $bIsFirst = false; } else { $sResult .= $sSeparator; } if ($mValue instanceof Renderable) { $sResult .= $mValue->render($oFormat); } else { $sResult .= $mValue; } } return $sResult; } /** * @param string $sString * * @return string */ public function removeLastSemicolon($sString) { if ($this->oFormat->get('SemicolonAfterLastRule')) { return $sString; } $sString = explode(';', $sString); if (count($sString) < 2) { return $sString[0]; } $sLast = array_pop($sString); $sNextToLast = array_pop($sString); array_push($sString, $sNextToLast . $sLast); return implode(';', $sString); } /** * @param string $sSpaceString * * @return string */ private function prepareSpace($sSpaceString) { return str_replace("\n", "\n" . $this->indent(), $sSpaceString); } /** * @return string */ private function indent() { return str_repeat($this->oFormat->sIndentation, $this->oFormat->level()); } } PK[k//src/Parser.phpnuW+AoParserState = new ParserState($sText, $oParserSettings, $iLineNo); } /** * @param string $sCharset * * @return void */ public function setCharset($sCharset) { $this->oParserState->setCharset($sCharset); } /** * @return void */ public function getCharset() { // Note: The `return` statement is missing here. This is a bug that needs to be fixed. $this->oParserState->getCharset(); } /** * @return Document * * @throws SourceException */ public function parse() { return Document::parse($this->oParserState); } } PK[L((src/Renderable.phpnuW+A=5.6.20", "ext-iconv": "*" }, "require-dev": { "phpunit/phpunit": "^4.8.36", "codacy/coverage": "^1.4" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" }, "autoload": { "psr-4": { "Sabberworm\\CSS\\": "src/" } }, "autoload-dev": { "psr-4": { "Sabberworm\\CSS\\Tests\\": "tests/" } }, "scripts": { "ci": [ "@ci:static" ], "ci:php:fixer": "@php ./.phive/php-cs-fixer.phar --config=config/php-cs-fixer.php fix --dry-run -v --show-progress=dots bin src tests", "ci:php:sniffer": "@php ./.phive/phpcs.phar --standard=config/phpcs.xml bin src tests", "ci:php:stan": "@php ./.phive/phpstan.phar --configuration=config/phpstan.neon", "ci:static": [ "@ci:php:fixer", "@ci:php:sniffer", "@ci:php:stan" ], "fix:php": [ "@fix:php:fixer", "@fix:php:sniffer" ], "fix:php:fixer": "@php ./.phive/php-cs-fixer.phar --config=config/php-cs-fixer.php fix bin src tests", "fix:php:sniffer": "@php ./.phive/phpcbf.phar --standard=config/phpcs.xml bin src tests", "phpstan:baseline": "@php ./.phive/phpstan.phar --configuration=config/phpstan.neon --generate-baseline=config/phpstan-baseline.neon" }, "scripts-descriptions": { "ci": "Runs all dynamic and static code checks (i.e. currently, only the static checks).", "ci:php:fixer": "Checks the code style with PHP CS Fixer.", "ci:php:sniffer": "Checks the code style with PHP_CodeSniffer.", "ci:php:stan": "Checks the types with PHPStan.", "ci:static": "Runs all static code analysis checks for the code.", "fix:php": "Autofixes all autofixable issues in the PHP code.", "fix:php:fixer": "Fixes autofixable issues found by PHP CS Fixer.", "fix:php:sniffer": "Fixes autofixable issues found by PHP_CodeSniffer.", "phpstand:baseline": "Updates the PHPStan baseline file to match the code." } } PK[ePPLICENSEnuW+AMIT 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. PK[PP README.mdnuW+APK[L!! PCHANGELOG.mdnuW+APK[K''ssrc/Rule/Rule.phpnuW+APK[C!Gsrc/Comment/Commentable.phpnuW+APK[QKsrc/Comment/Comment.phpnuW+APK[Tڡsrc/OutputFormat.phpnuW+APK[<src/Value/Color.phpnuW+APK[Oܧ src/Value/CSSString.phpnuW+APK[YP P src/Value/CalcFunction.phpnuW+APK[FȽ!!src/Value/Value.phpnuW+APK[ag ^ ^  src/Value/ValueList.phpnuW+APK[p##%src/Value/RuleValueList.phpnuW+APK[>emsrc/Value/CalcRuleValueList.phpnuW+APK[hrsrc/Value/Size.phpnuW+APK[FE66.src/Value/CSSFunction.phpnuW+APK[s3\5src/Value/PrimitiveValue.phpnuW+APK[ԙ  6src/Value/LineName.phpnuW+APK[r  =src/Value/URL.phpnuW+APK[q&)Esrc/Parsing/UnexpectedEOFException.phpnuW+APK[cܚ22Fsrc/Parsing/SourceException.phpnuW+APK[jyBffIsrc/Parsing/OutputException.phpnuW+APK[44Jsrc/Parsing/ParserState.phpnuW+APK[yW'(src/Parsing/UnexpectedTokenException.phpnuW+APK[src/Settings.phpnuW+APK[Tsrc/CSSList/Document.phpnuW+APK[1nosrc/CSSList/KeyFrame.phpnuW+APK[c=n==Esrc/CSSList/CSSList.phpnuW+APK[zzsrc/CSSList/AtRuleBlockList.phpnuW+APK[eHettcsrc/CSSList/CSSBlockList.phpnuW+APK[|'ss #src/RuleSet/DeclarationBlock.phpnuW+APK[**tsrc/RuleSet/RuleSet.phpnuW+APK[[~src/RuleSet/AtRuleSet.phpnuW+APK[0 src/Property/CSSNamespace.phpnuW+APK[\k k src/Property/Selector.phpnuW+APK[K$A src/Property/Charset.phpnuW+APK[) src/Property/Import.phpnuW+APK[ EJ!!4src/Property/AtRule.phpnuW+APK[!src/Property/KeyframeSelector.phpnuW+APK[,PRsrc/OutputFormatter.phpnuW+APK[k//src/Parser.phpnuW+APK[L((8src/Renderable.phpnuW+APK[Ԅ composer.jsonnuW+APK[ePPLICENSEnuW+APK++F