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.
composer require sabberworm/php-css-parserTo 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.
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);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.
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);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 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 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– a- RuleSetconstrained 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).
Rules just have a string key (the rule) and a 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- sizevalue 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);.
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- Valueobjects inside- Rules.
- 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)
$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());
    }
}$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);
    }
}$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');
}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.
@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)
}@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;}}#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)
}#header {margin: 10px 2em 1cm 2%;font-family: Verdana,Helvetica,"Gill Sans",sans-serif;color: red !important;}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
    Please have a look at our API and deprecation policy.
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.
- 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
The latest pre-PSR-0 version of this project can be checked with the 0.9.0 tag.