Autoría | Ultima modificación | Ver Log |
# PHP CSS Parser[](https://github.com/sabberworm/PHP-CSS-Parser/actions/)A Parser for CSS Files written in PHP. Allows extraction of CSS files into a data structure, manipulation of said structure and output as (optimized) CSS.## Usage### Installation using Composer```bashcomposer require sabberworm/php-css-parser```### ExtractionTo use the CSS Parser, create a new instance. The constructor takes the following form:```phpnew \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#### CharsetThe charset option will only be used if the CSS file does not contain an `@charset` declaration. UTF-8 is the default, so you won’t have to create a settings object at all if you don’t intend to change that.```php$settings = \Sabberworm\CSS\Settings::create()->withDefaultCharset('windows-1252');$parser = new \Sabberworm\CSS\Parser($css, $settings);```#### Strict parsingTo have the parser throw an exception when encountering invalid/unknown constructs (as opposed to trying to ignore them and carry on parsing), supply a thusly configured `\Sabberworm\CSS\Settings` object:```php$parser = new \Sabberworm\CSS\Parser(file_get_contents('somefile.css'),\Sabberworm\CSS\Settings::create()->beStrict());```Note that this will also disable a workaround for parsing the unquoted variant of the legacy IE-specific `filter` rule.#### Disable multibyte functionsTo 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);```### ManipulationThe resulting data structure consists mainly of five basic types: `CSSList`, `RuleSet`, `Rule`, `Selector` and `Value`. There are two additional types used: `Import` and `Charset`, which you won’t use often.#### CSSList`CSSList` represents a generic CSS container, most likely containing declaration blocks (rule sets with a selector), but it may also contain at-rules, charset declarations, etc.To access the items stored in a `CSSList` – like the document you got back when calling `$parser->parse()` –, use `getContents()`, then iterate over that collection and use `instanceof` to check whether you’re dealing with another `CSSList`, a `RuleSet`, a `Import` or a `Charset`.To append a new item (selector, media query, etc.) to an existing `CSSList`, construct it using the constructor for this class and use the `append($oItem)` method.#### RuleSet`RuleSet` is a container for individual rules. The most common form of a rule set is one constrained by a selector. The following concrete subtypes exist:* `AtRuleSet` – for generic at-rules for generic at-rules which are not covered by specific classes, i.e., not `@import`, `@charset` or `@media`. A common example for this is `@font-face`.* `DeclarationBlock` – a `RuleSet` constrained by a `Selector`; contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the matching elements.Note: A `CSSList` can contain other `CSSList`s (and `Import`s as well as a `Charset`), while a `RuleSet` can only contain `Rule`s.If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)` (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules).#### Rule`Rule`s just have a string key (the rule) and a `Value`.#### Value`Value` is an abstract class that only defines the `render` method. The concrete subclasses for atomic value types are:* `Size` – consists of a numeric `size` value and a unit.* `Color` – colors can be input in the form #rrggbb, #rgb or schema(val1, val2, …) but are always stored as an array of ('s' => val1, 'c' => val2, 'h' => val3, …) and output in the second form.* `CSSString` – this is just a wrapper for quoted strings to distinguish them from keywords; always output with double quotes.* `URL` – URLs in CSS; always output in `URL("")` notation.There is another abstract subclass of `Value`, `ValueList`: A `ValueList` represents a lists of `Value`s, separated by some separation character (mostly `,`, whitespace, or `/`).There are two types of `ValueList`s:* `RuleValueList` – The default type, used to represent all multivalued rules like `font: bold 12px/3 Helvetica, Verdana, sans-serif;` (where the value would be a whitespace-separated list of the primitive value `bold`, a slash-separated list and a comma-separated list).* `CSSFunction` – A special kind of value that also contains a function name and where the values are the function’s arguments. Also handles equals-sign-separated argument lists like `filter: alpha(opacity=90);`.#### Convenience methodsThere are a few convenience methods on `Document` to ease finding, manipulating and deleting rules:* `getAllDeclarationBlocks()` – does what it says; no matter how deeply nested the selectors are. Aliased as `getAllSelectors()`.* `getAllRuleSets()` – does what it says; no matter how deeply nested the rule sets are.* `getAllValues()` – finds all `Value` objects inside `Rule`s.## To-Do* More convenience methods (like `selectorsWithElement($sId/Class/TagName)`, `attributesOfType($type)`, `removeAttributesOfType($type)`)* Real multibyte support. Currently, only multibyte charsets whose first 255 code points take up only one byte and are identical with ASCII are supported (yes, UTF-8 fits this description).* Named color support (using `Color` instead of an anonymous string literal)## Use cases### Use `Parser` to prepend an ID to all selectors```php$myId = "#my_id";$parser = new \Sabberworm\CSS\Parser($css);$cssDocument = $parser->parse();foreach ($cssDocument->getAllDeclarationBlocks() as $block) {foreach ($block->getSelectors() as $selector) {// Loop over all selector parts (the comma-separated strings in a// selector) and prepend the ID.$selector->setSelector($myId.' '.$selector->getSelector());}}```### Shrink all absolute sizes to half```php$parser = new \Sabberworm\CSS\Parser($css);$cssDocument = $parser->parse();foreach ($cssDocument->getAllValues() as $value) {if ($value instanceof CSSSize && !$value->isRelative()) {$value->setSize($value->getSize() / 2);}}```### Remove unwanted rules```php$parser = new \Sabberworm\CSS\Parser($css);$cssDocument = $parser->parse();foreach($cssDocument->getAllRuleSets() as $oRuleSet) {// Note that the added dash will make this remove all rules starting with// `font-` (like `font-size`, `font-weight`, etc.) as well as a potential// `font` rule.$oRuleSet->removeRule('font-');$oRuleSet->removeRule('cursor');}```### OutputTo 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:```phpprint $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; }}```<details><summary><b>Structure (<code>var_dump()</code>)</b></summary>```phpclass 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)}```</details>#### 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;}```<details><summary><b>Structure (<code>var_dump()</code>)</b></summary>```phpclass 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)}```</details>#### 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`.