Skip to content

tbela99/css

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CSS (A CSS parser and minifier written in PHP)


CI Current version Packagist Documentation Known Vulnerabilities

A CSS parser, beautifier and minifier written in PHP. It supports the following features

Features

  • multibyte characters encoding
  • sourcemap
  • multiprocessing: process large CSS input very fast
  • CSS Nesting module
  • partially implemented CSS Syntax module level 3
  • partial CSS validation
  • CSS colors module level 4
  • parse and render CSS
  • optimize css:
    • merge duplicate rules
    • remove duplicate declarations
    • remove empty rules
    • compute css shorthand (margin, padding, outline, border-radius, font, background)
    • process @import document to reduce the number of HTTP requests
    • remove @charset directive
  • query api with xpath like or class name syntax
  • traverser api to transform the css and ast
  • command line utility

Installation

install using Composer

PHP version >= 8.0

$ composer require tbela99/css

PHP version >= 5.6

$ composer require "tbela99/css:dev-php56-backport"

Requirements

  • PHP version >= 8.0 on master branch.
  • PHP version >= 5.6 supported in this branch
  • mbstring extension

Usage:

h1 { color: green; color: blue; color: black; } h1 { color: #000; color: aliceblue; }

PHP Code

use \TBela\CSS\Parser; $parser = new Parser(); $parser->setContent(' h1 {  color: green;  color: blue;  color: black; }  h1 {  color: #000;  color: aliceblue; }'); echo $parser->parse();

Result

h1 { color: #f0f8ff; }

Parse the css file and generate the AST

use \TBela\CSS\Parser; use \TBela\CSS\Renderer; $parser = new Parser($css); $element = $parser->parse(); // append an existing css file $parser->append('https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css'); // append css string $parser->appendContent($css_string); // pretty print css $css = (string) $element; // minified output $renderer = new Renderer([ 'compress' => true, 'convert_color' => 'hex', 'css_level' => 4, 'sourcemap' => true, 'allow_duplicate_declarations' => false ]); // fast $css = $renderer->renderAst($parser); // or $css = $renderer->renderAst($parser->getAst()); // slow $css = $renderer->render($element); // generate sourcemap -> css/all.css.map $renderer->save($element, 'css/all.css'); // save as json file_put_contents('style.json', json_encode($element));

Load the AST and generate css code

use \TBela\CSS\Renderer; // fastest way to render css $beautify = (new Renderer())->renderAst($parser->setContent($css)->getAst()); // or $beautify = (new Renderer())->renderAst($parser->setContent($css)); // or $css = (new Renderer())->renderAst(json_decode(file_get_contents('style.json')));
use \TBela\CSS\Renderer; $ast = json_decode(file_get_contents('style.json')); $renderer = new Renderer([ 'convert_color' => true, 'compress' => true, // minify the output 'remove_empty_nodes' => true // remove empty css classes ]); $css = $renderer->renderAst($ast);

Sourcemap generation

$renderer = new Renderer([ 'sourcemap' => true ]); // call save and specify the file name // generate sourcemap -> css/all.css.map $renderer->save($element, 'css/all.css');

The CSS Query API

Example: get all background and background-image declarations that contain an image url

$element = Element::fromUrl('https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css'); foreach ($element->query('[@name=background][@value*="url("]|[@name=background-image][@value*="url("]') as $p) { echo "$p\n"; }

result

.form-select { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c /svg%3e") } .form-check-input:checked[type=checkbox] { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/s vg%3e") } ...

Example: Extract Font-src declaration

CSS source

@font-face { font-family: "Bitstream Vera Serif Bold"; src: url("/static/styles/libs/font-awesome/fonts/fontawesome-webfont.fdf491ce5ff5.woff"); } body { background-color: green; color: #fff; font-family: Arial, Helvetica, sans-serif; } h1 { color: #fff; font-size: 50px; font-family: Arial, Helvetica, sans-serif; font-weight: bold; } @media print { @font-face { font-family: MaHelvetica; src: local("Helvetica Neue Bold"), local("HelveticaNeue-Bold"), url(MgOpenModernaBold.ttf); font-weight: bold; } body { font-family: "Bitstream Vera Serif Bold", serif; } p { font-size: 12px; color: #000; text-align: left; } @font-face { font-family: Arial, MaHelvetica; src: local("Helvetica Neue Bold"), local("HelveticaNeue-Bold"), url(MgOpenModernaBold.ttf); font-weight: bold; } }

PHP source

use \TBela\CSS\Parser; $parser = new Parser(); $parser->setContent($css); $stylesheet = $parser->parse(); // get @font-face nodes by class names $nodes = $stylesheet->queryByClassNames('@font-face, .foo .bar'); // or // get all src properties in a @font-face rule $nodes = $stylesheet->query('@font-face/src'); echo implode("\n", array_map('trim', $nodes));

result

@font-face { src: url("/static/styles/libs/font-awesome/fonts/fontawesome-webfont.fdf491ce5ff5.woff"); } @media print { @font-face { src: local("Helvetica Neue Bold"), local("HelveticaNeue-Bold"), url(MgOpenModernaBold.ttf); } } @media print { @font-face { src: local("Helvetica Neue Bold"), local("HelveticaNeue-Bold"), url(MgOpenModernaBold.ttf); } }

render optimized css

$stylesheet->setChildren(array_map(function ($node) { return $node->copy()->getRoot(); }, $nodes)); $stylesheet->deduplicate(); echo $stylesheet;

result

@font-face { src: url(/static/styles/libs/font-awesome/fonts/fontawesome-webfont.fdf491ce5ff5.woff) } @media print { @font-face { src: local("Helvetica Neue Bold"), local(HelveticaNeue-Bold), url(MgOpenModernaBold.ttf) } }

CSS Nesting

table.colortable { & td { text-align:center; &.c { text-transform:uppercase } &:first-child, &:first-child + td { border:1px solid black } } & th { text-align:center; background:black; color:white; } }

render CSS nesting

use TBela\CSS\Parser; echo new Parser($css);

result

table.colortable { & td { text-align: center; &.c { text-transform: uppercase } &:first-child, &:first-child+td { border: 1px solid #000 } } & th { text-align: center; background: #000; color: #fff } }

convert nesting CSS to older representation

use TBela\CSS\Parser; use \TBela\CSS\Renderer; $renderer = new Renderer( ['legacy_rendering' => true]); echo $renderer->renderAst(new Parser($css));

result

table.colortable td { text-align: center } table.colortable td.c { text-transform: uppercase } table.colortable td:first-child, table.colortable td:first-child+td { border: 1px solid #000 } table.colortable th { text-align: center; background: #000; color: #fff }

The Traverser Api

The traverser will iterate over all the nodes and process them with the callbacks provided. It will return a new tree Example using ast

use TBela\CSS\Ast\Traverser; use TBela\CSS\Parser; use TBela\CSS\Renderer; $parser = (new Parser())->load('ast/media.css'); $traverser = new Traverser(); $renderer = new Renderer(['remove_empty_nodes' => true]); $ast = $parser->getAst(); // remove @media print $traverser->on('enter', function ($node) { if ($node->type == 'AtRule' && $node->name == 'media' && $node->value == 'print') { return Traverser::IGNORE_NODE; } }); $newAst = $traverser->traverse($ast); echo $renderer->renderAst($newAst);

Example using an Element instance

use TBela\CSS\Ast\Traverser; use TBela\CSS\Parser; use TBela\CSS\Renderer; $parser = (new Parser())->load('ast/media.css'); $traverser = new Traverser(); $renderer = new Renderer(['remove_empty_nodes' => true]); $element = $parser->parse(); // remove @media print $traverser->on('enter', function ($node) { if ($node->type == 'AtRule' && $node->name == 'media' && $node->value == 'print') { return Traverser::IGNORE_NODE; } }); $newElement = $traverser->traverse($element); echo $renderer->renderAst($newElement);

Build a CSS Document

use \TBela\CSS\Element\Stylesheet; $stylesheet = new Stylesheet(); $rule = $stylesheet->addRule('div'); $rule->addDeclaration('background-color', 'white'); $rule->addDeclaration('color', 'black'); echo $stylesheet;

output

div { background-color: #fff; color: #000; }
$media = $stylesheet->addAtRule('media', 'print'); $media->append($rule);

output

@media print { div { background-color: #fff; color: #000; } }
$div = $stylesheet->addRule('div'); $div->addDeclaration('max-width', '100%'); $div->addDeclaration('border-width', '0px');

output

@media print { div { background-color: #fff; color: #000; } } div { max-width: 100%; border-width: 0; }
$media->append($div);

output

@media print { div { background-color: #fff; color: #000; } div { max-width: 100%; border-width: 0; } }
$stylesheet->insert($div, 0);

output

div { max-width: 100%; border-width: 0; } @media print { div { background-color: #fff; color: #000; } }

Adding existing css

// append css string $stylesheet->appendCss($css_string); // append css file $stylesheet->append('style/main.css'); // append url $stylesheet->append('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/brands.min.css'); 

Performance

Utility methods

The renderer class provides utility methods to format css data

$css = \TBela\CSS\Renderer::fromFile($url_or_file, $renderOptions = [], $parseOptions = []); # $css = \TBela\CSS\Renderer::fromString($css, $renderOptions = [], $parseOptions = []);

Manual parsing and rendering

parsing and rendering ast is 3x faster than parsing an element.

use \TBela\CSS\Element\Parser; use \TBela\CSS\Element\Renderer; $parser = new Parser($css); // parse and render echo (string) $parser; // or render minified css $renderer = new Renderer(['compress' => true]); echo $renderer->renderAst($parser); # or  echo $renderer->renderAst($parser->getAst()); # or // slower - will build a stylesheet object echo $renderer->render($parser->parse());

Parser Options

  • flatten_import: process @import directive and import the content into the css document. default to false.
  • allow_duplicate_rules: allow duplicated rules. By default, duplicate rules except @font-face are merged
  • allow_duplicate_declarations: allow duplicated declarations in the same rule.
  • capture_errors: silently capture parse error if true, otherwise throw a parse exception. Default to true

Renderer Options

  • remove_comments: remove comments.
  • preserve_license: preserve comments starting with '/*!'
  • compress: minify output, will also remove comments
  • remove_empty_nodes: do not render empty css nodes
  • compute_shorthand: compute shorthand declaration
  • charset: preserve @charset. default to false
  • glue: the line separator character. default to '\n'
  • indent: character used to pad lines in css, default to a space character
  • convert_color: convert colors to a format between hex, hsl, rgb, hwb and device-cmyk
  • css_level: produce CSS color level 3 or 4. default to 4
  • allow_duplicate_declarations: allow duplicate declarations.
  • legacy_rendering: convert nesting css. default false

Command line utility

the command line utility is located at './cli/css-parser'

$ ./cli/css-parser -h Usage: $ css-parser [OPTIONS] [PARAMETERS] -v, --version	print version number -h	print help --help	print extended help Parse options: -e, --capture-errors	ignore parse error -f, --file	input css file or url -m, --flatten-import	process @import -I, --input-format	input format: json (ast), serialize (PHP serialized ast) -d, --parse-allow-duplicate-declarations	allow duplicate declaration -p, --parse-allow-duplicate-rules	allow duplicate rule -P, --parse-children-process	maximum children process -M, --parse-multi-processing enable multi-processing parser Render options: -a, --ast	dump ast as JSON -S, --charset	remove @charset -c, --compress	minify output -u, --compute-shorthand	compute shorthand properties -t, --convert-color	convert colors -l, --css-level	css color module -G, --legacy-rendering	convert nested css syntax -o, --output	output file name -F, --output-format	output export format. string (css), json (ast), serialize (PHP serialized ast), json-array, serialize-array, requires --input-format -L, --preserve-license	preserve license comments -C, --remove-comments	remove comments -E, --remove-empty-nodes	remove empty nodes -r, --render-allow-duplicate-declarations	render duplicate declarations -R, --render-multi-processing enable multi-processing renderer -s, --sourcemap	generate sourcemap, requires --file

Minify inline css

$ ./cli/css-parser 'a, div {display:none} b {}' -c # $ echo 'a, div {display:none} b {}' | ./cli/css-parser -c

Minify css file

$ ./cli/css-parser -f nested.css -c # $ ./cli/css-parser -f 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/brands.min.css' -c

Dump ast

$ ./cli/css-parser -f nested.css -f 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css' -c -a # $ ./cli/css-parser 'a, div {display:none} b {}' -c -a # $ echo 'a, div {display:none} b {}' | ./cli/css-parser -c -a

The full documentation can be found here


Thanks to Jetbrains for providing a free PhpStorm license

This was originally a PHP port of https://github.com/reworkcss/css

Packages

 
 
 

Contributors 2

  •  
  •  

Languages