sabberworm / php-css-parser
Parser for CSS Files written in PHP
Installs: 168 516 608
Dependents: 80
Suggesters: 1
Security: 1
Stars: 1 805
Watchers: 24
Forks: 148
Open Issues: 152
pkg:composer/sabberworm/php-css-parser
Requires
- php: ^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0
- ext-iconv: *
- thecodingmachine/safe: ^1.3 || ^2.5 || ^3.3
Requires (Dev)
- php-parallel-lint/php-parallel-lint: 1.4.0
- phpstan/extension-installer: 1.4.3
- phpstan/phpstan: 1.12.28 || 2.1.25
- phpstan/phpstan-phpunit: 1.4.2 || 2.0.7
- phpstan/phpstan-strict-rules: 1.6.2 || 2.0.6
- phpunit/phpunit: 8.5.46
- rawr/phpunit-data-provider: 3.3.1
- rector/rector: 1.2.10 || 2.1.7
- rector/type-perfect: 1.0.0 || 2.1.0
Suggests
- ext-mbstring: for parsing UTF-8 CSS
- dev-main / 9.2.x-dev
- v9.1.0
- v9.0.0
- v8.x-dev
- v8.9.0
- v8.8.0
- v8.7.0
- v8.6.0
- v8.5.2
- v8.5.1
- 8.5.0
- 8.4.0
- 8.3.1
- 8.3.0
- 8.2.1
- 8.2.0
- 8.1.1
- 8.1.0
- 8.0.1
- 8.0.0
- 7.0.4
- 7.0.3
- 7.0.2
- 7.0.1
- 7.0.0
- 6.0.2
- 6.0.1
- 6.0.0
- 5.2.1
- 5.2.0
- 5.1.3
- 5.1.2
- 5.1.1
- 5.1.0
- 5.0.9
- 5.0.8
- 5.0.7
- 5.0.6
- 5.0.5
- 5.0.4
- 5.0.3
- 5.0.2
- 5.0.1
- 5.0.0
- 4.0.1
- 4.0.0
- 3.0.1
- 3.0.0
- 2.0.1
- 2.0.0
- 1.0.1
- 1.0.0
- dev-task/dev-deps
- dev-feature/create-class-diagram
- dev-docs/update-class-diagram
- dev-task/phpunit-9
- dev-task/phpstan-level-7
- dev-v8/bugfix/selector-parsing
- dev-docs/class-diagram-review
- dev-task/deprecate/atruleargs
- dev-cleanup/declaration-block-test
- dev-bugfix/style-value-strings
- dev-fix-set-get-charset
- dev-selector-parsing
- dev-refactor-parsing-logic
This package is auto-updated.
Last update: 2025-10-27 13:08:26 UTC
README
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
composer require sabberworm/php-css-parser
Extraction
To use the CSS Parser, create a new instance. The constructor takes the following form:
new \Sabberworm\CSS\Parser($css);
To read a file, for example, you’d do the following:
$parser = new \Sabberworm\CSS\Parser(file_get_contents('somefile.css')); $cssDocument = $parser->parse();
The resulting CSS document structure can be manipulated prior to being output.
Options
Charset
The charset option will only be used if the CSS file does not contain an @charset declaration. UTF-8 is the default, so you won’t have to create a settings object at all if you don’t intend to change that.
$settings = \Sabberworm\CSS\Settings::create() ->withDefaultCharset('windows-1252'); $parser = new \Sabberworm\CSS\Parser($css, $settings);
Strict parsing
To have the parser throw an exception when encountering invalid/unknown constructs (as opposed to trying to ignore them and carry on parsing), supply a thusly configured \Sabberworm\CSS\Settings object:
$parser = new \Sabberworm\CSS\Parser( file_get_contents('somefile.css'), \Sabberworm\CSS\Settings::create()->beStrict() );
Note that this will also disable a workaround for parsing the unquoted variant of the legacy IE-specific filter rule.
Disable multibyte functions
To achieve faster parsing, you can choose to have PHP-CSS-Parser use regular string functions instead of mb_* functions. This should work fine in most cases, even for UTF-8 files, as all the multibyte characters are in string literals. Still it’s not recommended using this with input you have no control over as it’s not thoroughly covered by test cases.
$settings = \Sabberworm\CSS\Settings::create()->withMultibyteSupport(false); $parser = new \Sabberworm\CSS\Parser($css, $settings);
Manipulation
The resulting data structure consists mainly of five basic types: CSSList, RuleSet, Rule, Selector and Value. There are two additional types used: Import and Charset, which you won’t use often.
CSSList
CSSList represents a generic CSS container, most likely containing declaration blocks (rule sets with a selector), but it may also contain at-rules, charset declarations, etc.
To access the items stored in a CSSList – like the document you got back when calling $parser->parse() –, use getContents(), then iterate over that collection and use instanceof to check whether you’re dealing with another CSSList, a RuleSet, a Import or a Charset.
To append a new item (selector, media query, etc.) to an existing CSSList, construct it using the constructor for this class and use the append($oItem) method.
RuleSet
RuleSet is a container for individual rules. The most common form of a rule set is one constrained by a selector. The following concrete subtypes exist:
AtRuleSet– for generic at-rules for generic at-rules which are not covered by specific classes, i.e., not@import,@charsetor@media. A common example for this is@font-face.DeclarationBlock– aRuleSetconstrained by aSelector; 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 CSSLists (and Imports as well as a Charset), while a RuleSet can only contain Rules.
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
Rules 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 numericsizevalue 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 inURL("")notation.
There is another abstract subclass of Value, ValueList: A ValueList represents a lists of Values, separated by some separation character (mostly ,, whitespace, or /).
There are two types of ValueLists:
RuleValueList– The default type, used to represent all multivalued rules likefont: bold 12px/3 Helvetica, Verdana, sans-serif;(where the value would be a whitespace-separated list of the primitive valuebold, 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 likefilter: alpha(opacity=90);.
Convenience methods
There are a few convenience methods on Document to ease finding, manipulating and deleting rules:
getAllDeclarationBlocks()– does what it says; no matter how deeply nested the selectors are. Aliased asgetAllSelectors().getAllRuleSets()– does what it says; no matter how deeply nested the rule sets are.getAllValues()– finds allValueobjects insideRules.
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
Colorinstead of an anonymous string literal)
Use cases
Use Parser to prepend an ID to all selectors
$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
$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
$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():
$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:
$format = \Sabberworm\CSS\OutputFormat::create() ->indentWithSpaces(4)->setSpaceBetweenRules("\n"); print $cssDocument->render($format);
Or use one of the predefined formats:
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
@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())
class Sabberworm\CSS\CSSList\Document#4 (2) { protected $contents => array(4) { [0] => class Sabberworm\CSS\Property\Charset#6 (2) { private $charset => class Sabberworm\CSS\Value\CSSString#5 (2) { private $string => string(5) "utf-8" protected $lineNumber => int(1) } protected $lineNumber => int(1) } [1] => class Sabberworm\CSS\RuleSet\AtRuleSet#7 (4) { private $type => string(9) "font-face" private $arguments => string(0) "" private $rules => array(2) { 'font-family' => array(1) { [0] => class Sabberworm\CSS\Rule\Rule#8 (4) { private $rule => string(11) "font-family" private $value => class Sabberworm\CSS\Value\CSSString#9 (2) { private $string => string(10) "CrassRoots" protected $lineNumber => int(4) } private $isImportant => bool(false) protected $lineNumber => int(4) } } 'src' => array(1) { [0] => class Sabberworm\CSS\Rule\Rule#10 (4) { private $rule => string(3) "src" private $value => class Sabberworm\CSS\Value\URL#11 (2) { private $url => class Sabberworm\CSS\Value\CSSString#12 (2) { private $string => string(15) "../media/cr.ttf" protected $lineNumber => int(5) } protected $lineNumber => int(5) } private $isImportant => bool(false) protected $lineNumber => int(5) } } } protected $lineNumber => int(3) } [2] => class Sabberworm\CSS\RuleSet\DeclarationBlock#13 (3) { private $selectors => array(2) { [0] => class Sabberworm\CSS\Property\Selector#14 (2) { private $selector => string(4) "html" private $specificity => NULL } [1] => class Sabberworm\CSS\Property\Selector#15 (2) { private $selector => string(4) "body" private $specificity => NULL } } private $rules => array(1) { 'font-size' => array(1) { [0] => class Sabberworm\CSS\Rule\Rule#16 (4) { private $rule => string(9) "font-size" private $value => class Sabberworm\CSS\Value\Size#17 (4) { private $size => double(1.6) private $unit => string(2) "em" private $isColorComponent => bool(false) protected $lineNumber => int(9) } private $isImportant => bool(false) protected $lineNumber => int(9) } } } protected $lineNumber => int(8) } [3] => class Sabberworm\CSS\CSSList\KeyFrame#18 (4) { private $vendorKeyFrame => string(9) "keyframes" private $animationName => string(6) "mymove" protected $contents => array(2) { [0] => class Sabberworm\CSS\RuleSet\DeclarationBlock#19 (3) { private $selectors => array(1) { [0] => class Sabberworm\CSS\Property\Selector#20 (2) { private $selector => string(4) "from" private $specificity => NULL } } private $rules => array(1) { 'top' => array(1) { [0] => class Sabberworm\CSS\Rule\Rule#21 (4) { private $rule => string(3) "top" private $value => class Sabberworm\CSS\Value\Size#22 (4) { private $size => double(0) private $unit => string(2) "px" private $isColorComponent => bool(false) protected $lineNumber => int(13) } private $isImportant => bool(false) protected $lineNumber => int(13) } } } protected $lineNumber => int(13) } [1] => class Sabberworm\CSS\RuleSet\DeclarationBlock#23 (3) { private $selectors => array(1) { [0] => class Sabberworm\CSS\Property\Selector#24 (2) { private $selector => string(2) "to" private $specificity => NULL } } private $rules => array(1) { 'top' => array(1) { [0] => class Sabberworm\CSS\Rule\Rule#25 (4) { private $rule => string(3) "top" private $value => class Sabberworm\CSS\Value\Size#26 (4) { private $size => double(200) private $unit => string(2) "px" private $isColorComponent => bool(false) protected $lineNumber => int(14) } private $isImportant => bool(false) protected $lineNumber => int(14) } } } protected $lineNumber => int(14) } } protected $lineNumber => int(12) } } protected $lineNumber => int(1) }
Output (render())
@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
#header { margin: 10px 2em 1cm 2%; font-family: Verdana, Helvetica, "Gill Sans", sans-serif; color: red !important; }
Structure (var_dump())
class Sabberworm\CSS\CSSList\Document#4 (2) { protected $contents => array(1) { [0] => class Sabberworm\CSS\RuleSet\DeclarationBlock#5 (3) { private $selectors => array(1) { [0] => class Sabberworm\CSS\Property\Selector#6 (2) { private $selector => string(7) "#header" private $specificity => NULL } } private $rules => array(3) { 'margin' => array(1) { [0] => class Sabberworm\CSS\Rule\Rule#7 (4) { private $rule => string(6) "margin" private $value => class Sabberworm\CSS\Value\RuleValueList#12 (3) { protected $components => array(4) { [0] => class Sabberworm\CSS\Value\Size#8 (4) { private $size => double(10) private $unit => string(2) "px" private $isColorComponent => bool(false) protected $lineNumber => int(2) } [1] => class Sabberworm\CSS\Value\Size#9 (4) { private $size => double(2) private $unit => string(2) "em" private $isColorComponent => bool(false) protected $lineNumber => int(2) } [2] => class Sabberworm\CSS\Value\Size#10 (4) { private $size => double(1) private $unit => string(2) "cm" private $isColorComponent => bool(false) protected $lineNumber => int(2) } [3] => class Sabberworm\CSS\Value\Size#11 (4) { private $size => double(2) private $unit => string(1) "%" private $isColorComponent => bool(false) protected $lineNumber => int(2) } } protected $separator => string(1) " " protected $lineNumber => int(2) } private $isImportant => bool(false) protected $lineNumber => int(2) } } 'font-family' => array(1) { [0] => class Sabberworm\CSS\Rule\Rule#13 (4) { private $rule => string(11) "font-family" private $value => class Sabberworm\CSS\Value\RuleValueList#15 (3) { protected $components => array(4) { [0] => string(7) "Verdana" [1] => string(9) "Helvetica" [2] => class Sabberworm\CSS\Value\CSSString#14 (2) { private $string => string(9) "Gill Sans" protected $lineNumber => int(3) } [3] => string(10) "sans-serif" } protected $sSeparator => string(1) "," protected $lineNumber => int(3) } private $isImportant => bool(false) protected $lineNumber => int(3) } } 'color' => array(1) { [0] => class Sabberworm\CSS\Rule\Rule#16 (4) { private $rule => string(5) "color" private $value => string(3) "red" private $isImportant => bool(true) protected $lineNumber => int(4) } } } protected $lineNumber => int(1) } } protected $lineNumber => int(1) }
Output (render())
#header {margin: 10px 2em 1cm 2%;font-family: Verdana,Helvetica,"Gill Sans",sans-serif;color: red !important;}
Class diagram
classDiagram
direction LR
class Anchor {
}
class AtRule {
<<interface>>
}
class AtRuleBlockList {
}
class AtRuleSet {
}
class CSSBlockList {
<<abstract>>
}
class CSSElement {
<<interface>>
}
class CSSFunction {
}
class CSSList {
<<abstract>>
}
class CSSListItem {
<<interface>>
}
class CSSNamespace {
}
class CSSString {
}
class CalcFunction {
}
class CalcRuleValueList {
}
class Charset {
}
class Color {
}
class Comment {
}
class Commentable {
<<interface>>
}
class DeclarationBlock {
}
class Document {
}
class Import {
}
class KeyFrame {
}
class KeyframeSelector {
}
class LineName {
}
class OutputException {
}
class OutputFormat {
}
class OutputFormatter {
}
class Parser {
}
class ParserState {
}
class Positionable {
<<interface>>
}
class PrimitiveValue {
<<abstract>>
}
class Renderable {
<<interface>>
}
class Rule {
}
class RuleContainer {
<<interface>>
}
class RuleSet {
}
class RuleValueList {
}
class Selector {
}
class Settings {
}
class Size {
}
class SourceException {
}
class SpecificityCalculator {
}
class URL {
}
class UnexpectedEOFException {
}
class UnexpectedTokenException {
}
class Value {
<<abstract>>
}
class ValueList {
<<abstract>>
}
Anchor ..> ParserState: dependency
CSSListItem <|-- AtRule: inheritance
AtRule <|.. AtRuleBlockList: realization
CSSBlockList <|-- AtRuleBlockList: inheritance
AtRule <|.. AtRuleSet: realization
RuleSet <|-- AtRuleSet: inheritance
CSSList <|-- CSSBlockList: inheritance
Renderable <|-- CSSElement: inheritance
ValueList <|-- CSSFunction: inheritance
CSSElement <|.. CSSList: realization
CSSListItem <|.. CSSList: realization
CSSList ..> Charset: dependency
CSSList ..> Import: dependency
Positionable <|.. CSSList: realization
Commentable <|-- CSSListItem: inheritance
Renderable <|-- CSSListItem: inheritance
AtRule <|.. CSSNamespace: realization
Positionable <|.. CSSNamespace: realization
PrimitiveValue <|-- CSSString: inheritance
CSSFunction <|-- CalcFunction: inheritance
RuleValueList <|-- CalcRuleValueList: inheritance
AtRule <|.. Charset: realization
Charset ..> CSSString: dependency
Positionable <|.. Charset: realization
CSSFunction <|-- Color: inheritance
Positionable <|.. Comment: realization
Renderable <|.. Comment: realization
CSSElement <|.. DeclarationBlock: realization
CSSListItem <|.. DeclarationBlock: realization
Positionable <|.. DeclarationBlock: realization
RuleContainer <|.. DeclarationBlock: realization
DeclarationBlock ..> RuleSet : dependency
DeclarationBlock ..> Selector: dependency
CSSBlockList <|-- Document: inheritance
AtRule <|.. Import: realization
Positionable <|.. Import: realization
AtRule <|.. KeyFrame: realization
CSSList <|-- KeyFrame: inheritance
Selector <|-- KeyframeSelector: inheritance
ValueList <|-- LineName: inheritance
SourceException <|-- OutputException: inheritance
OutputFormat ..> OutputFormatter: dependency
OutputFormatter ..> OutputFormat: dependency
Parser ..> ParserState: dependency
ParserState ..> Settings: dependency
Value <|-- PrimitiveValue: inheritance
CSSElement <|.. Rule: realization
Commentable <|.. Rule: realization
Positionable <|.. Rule: realization
Rule ..> RuleValueList: dependency
CSSElement <|.. RuleSet: realization
CSSListItem <|.. RuleSet: realization
Positionable <|.. RuleSet: realization
RuleSet ..> Rule: dependency
RuleContainer <|.. RuleSet: realization
ValueList <|-- RuleValueList: inheritance
Renderable <|.. Selector: realization
PrimitiveValue <|-- Size: inheritance
Exception <|-- SourceException: inheritance
Positionable <|.. SourceException: realization
URL ..> CSSString: dependency
PrimitiveValue <|-- URL: inheritance
UnexpectedTokenException <|-- UnexpectedEOFException: inheritance
SourceException <|-- UnexpectedTokenException: inheritance
CSSElement <|.. Value: realization
Positionable <|.. Value: realization
Value <|-- ValueList: inheritance
CSSList ..> CSSList: dependency
CSSList ..> Comment: dependency
CSSList ..> RuleSet: dependency
CSSNamespace ..> Comment: dependency
Charset ..> Comment: dependency
Import ..> Comment: dependency
OutputFormat ..> OutputFormat: dependency
Rule ..> Comment: dependency
RuleSet ..> Comment: dependency
ValueList ..> Value: dependency
Loading
API and deprecation policy
Please have a look at our API and deprecation policy.
Contributing
Contributions in the form of bug reports, feature requests, or pull requests are more than welcome. 🙏 Please have a look at our contribution guidelines to learn more about how to contribute to PHP-CSS-Parser.
Contributors/Thanks to
- oliverklee for lots of refactorings, code modernizations and CI integrations
- raxbg for contributions to parse
calc, grid lines, and various bugfixes. - westonruter for bugfixes and improvements.
- FMCorz for many patches and suggestions, for being able to parse comments and IE hacks (in lenient mode).
- Lullabot for a patch that allows to know the line number for each parsed token.
- ju1ius for the specificity parsing code and the ability to expand/compact shorthand properties.
- ossinkine for a 150 time performance boost.
- GaryJones for lots of input and https://css-specificity.info/.
- docteurklein for output formatting and
CSSList->remove()inspiration. - nicolopignatelli for PSR-0 compatibility.
- diegoembarcadero for keyframe at-rule parsing.
- goetas for
@namespaceat-rule support. - ziegenberg for general housekeeping and cleanup.
- View full list
Misc
Legacy Support
The latest pre-PSR-0 version of this project can be checked with the 0.9.0 tag.