Skip to content

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

License

Notifications You must be signed in to change notification settings

MyIntervals/PHP-CSS-Parser

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

PHP CSS Parser

Build Status Coverage Status

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, @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 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 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 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 like font: bold 12px/3 Helvetica, Verdana, sans-serif; (where the value would be a whitespace-separated list of the primitive value bold, a slash-separated list and a comma-separated list).
  • CSSFunction – A special kind of value that also contains a function name and where the values are the function’s arguments. Also handles equals-sign-separated argument lists like filter: alpha(opacity=90);.

Convenience methods

There are a few convenience methods on Document to ease finding, manipulating and deleting rules:

  • getAllDeclarationBlocks() – does what it says; no matter how deeply nested the selectors are. Aliased as getAllSelectors().
  • getAllRuleSets() – does what it says; no matter how deeply nested the rule sets are.
  • getAllValues() – finds all Value objects inside Rules.

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

$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 @namespace at-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.

About

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

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Contributors 36

Languages