6

I want to parse (in a special way) a CSS file with PHP.

Example:

cssfile.css:

#stuff {
    background-color: red;
}

#content.postclass-subcontent {
    background-color: red;
}

#content2.postclass-subcontent2 {
    background-color: red;
}

And I want that PHP returns me each class name that have the postclass in its name.

The result look like an array having in this example:

arrayentry1:
#content.postclass-subcontent
arrayentry2:
#content2.postclass-subcontent2

But I'm worse at regular expressions. somehow search for "postclass" and then grap the hole line and put into an array.


thank you and i used it to parse a css file simliar to a confic file.

$(function () {
    $.get('main.css', function (data) {
        data = data.match(/(#[a-z0-9]*?\ .?postclass.*?)\s?\{/g);
        if (data) {
            $.each(data, function (index, value) {
                value = value.substring(0, value.length - 2);
                $(value.split(' .')[0]).wrapInner('<div class="' + value.split('.')[1] + '" />');
            });
        }
    });
});

was my final code. so i can wrap easily a div around some hardcode-html without editing the layout. so i just have to edit my cssfile and add there something like

id .postclass-class { some styles }

and my code searchs for the id and wraps the inner content with an div. i needed that for quickfixes when i just have to add a div around something for a clear or a background.

Community
  • 1
  • 1
stefan
  • 63
  • 1
  • 1
  • 4
  • 1
    Do you have any code that you have tried this with if so, posting that would be helpful. – Jim Sep 01 '10 at 13:15
  • possible duplicate of [Parsing CSS by regex](http://stackoverflow.com/questions/236979/parsing-css-by-regex) – Gordon Sep 01 '10 at 13:28

7 Answers7

16

I found a solution:

function parse($file){
    $css = file_get_contents($file);
    preg_match_all( '/(?ims)([a-z0-9\s\.\:#_\-@,]+)\{([^\}]*)\}/', $css, $arr);
    $result = array();
    foreach ($arr[0] as $i => $x){
        $selector = trim($arr[1][$i]);
        $rules = explode(';', trim($arr[2][$i]));
        $rules_arr = array();
        foreach ($rules as $strRule){
            if (!empty($strRule)){
                $rule = explode(":", $strRule);
                $rules_arr[trim($rule[0])] = trim($rule[1]);
            }
        }
        
        $selectors = explode(',', trim($selector));
        foreach ($selectors as $strSel){
            $result[$strSel] = $rules_arr;
        }
    }
    return $result;
}

use:

$css = parse('css/'.$user['blog'].'.php');
$css['#selector']['color'];
Gabriel Anderson
  • 1,304
  • 14
  • 17
  • 1
    Great. It works right out of the box. But it doesn't properly read `base64` format for `url()`. Like: `[#logo] => Array ( [background-image] => url("data [base64,....................] => [background-size] => auto 100% )` – Alex G Feb 16 '15 at 01:55
  • 1
    good one but see some problems - 1) it cannot handle child selector(>) 2) It cannot handle selectors like input[type="button"]:hover. – bhaskarc Mar 17 '15 at 19:44
  • The regex does not match all possible selectors, if someone is looking for a more elaborate one `/([a-z0-9\s\.\:#_\-@,%\[\]()'"=*\\>~\/+]+)\{([^\}]*)\}/gsi` matches all my selectors – Boltgolt Feb 06 '21 at 12:23
15

There is a very good CSS parser class in PHP. Use it. Here is its sample code:

<?php
include("cssparser.php");

$css = new cssparser();
$css->ParseStr("b {font-weight: bold; color: #777777;} b.test{text-decoration: underline;}");
echo $css->Get("b","color");     // returns #777777
echo $css->Get("b.test","color");// returns #777777
echo $css->Get(".test","color"); // returns an empty string
?> 
shamittomar
  • 46,210
  • 12
  • 74
  • 78
  • thank you. but dont need the value and the overhead! thank you anyway – stefan Sep 01 '10 at 13:45
  • 14
    That class dates to 2003-09-20. (And the site also requires you to do annoying registration to be able to download it.) – sumid Oct 10 '13 at 22:45
12

Just for completeness there is also another library for parsing CSS: sabberworm / PHP-CSS-Parser.

Homepage: http://www.sabberworm.com/blog/2010/6/10/php-css-parser
GitHub: http://github.com/sabberworm/PHP-CSS-Parser
Gist: http://packagist.org/packages/sabberworm/php-css-parser
Last update: May 31, 2017 (Stating this because the date in the blog entry may mislead you that it isn't updated anymore.)

Unfortunately this project is bit too robust. From quite simple CSS creates very chatty structure. Also before first use you have to deal with composer (I myself did end-up adding require_once for each file into parser.php).

sumid
  • 1,871
  • 2
  • 25
  • 37
3

In addition to Gabriel Anderson's answer to handle css @media queries, child selector > ,base64 images, and input[type="button"]:hover

function parse_css_selectors($css,$media_queries=true){

    $result = $media_blocks = [];

    //---------------parse css media queries------------------

    if($media_queries==true){

        $media_blocks=parse_css_media_queries($css);
    }

    if(!empty($media_blocks)){

        //---------------get css blocks-----------------

        $css_blocks=$css;

        foreach($media_blocks as $media_block){

            $css_blocks=str_ireplace($media_block,'~£&#'.$media_block.'~£&#',$css_blocks);
        }

        $css_blocks=explode('~£&#',$css_blocks);

        //---------------parse css blocks-----------------

        $b=0;

        foreach($css_blocks as $css_block){

            preg_match('/(\@media[^\{]+)\{(.*)\}\s+/ims',$css_block,$block);

            if(isset($block[2])&&!empty($block[2])){

                $result[$block[1]]=parse_css_selectors($block[2],false);
            }
            else{

                $result[$b]=parse_css_selectors($css_block,false);
            }

            ++$b;
        }
    }
    else{

        //---------------escape base64 images------------------

        $css=preg_replace('/(data\:[^;]+);/i','$1~£&#',$css);

        //---------------parse css selectors------------------

        preg_match_all('/([^\{\}]+)\{([^\}]*)\}/ims', $css, $arr);

        foreach ($arr[0] as $i => $x){

            $selector = trim($arr[1][$i]);

            $rules = explode(';', trim($arr[2][$i]));

            $rules_arr = [];

            foreach($rules as $strRule){

                if(!empty($strRule)){

                    $rule = explode(":", $strRule,2);

                    if(isset($rule[1])){

                        $rules_arr[trim($rule[0])] = str_replace('~£&#',';',trim($rule[1]));
                    }
                    else{
                        //debug
                    }
                }
            }

            $selectors = explode(',', trim($selector));

            foreach ($selectors as $strSel){

                if($media_queries===true){

                    $result[$b][$strSel] = $rules_arr;
                }
                else{

                    $result[$strSel] = $rules_arr;
                }
            }
        }
    }
    return $result;
}

function parse_css_media_queries($css){

    $mediaBlocks = array();

    $start = 0;
    while(($start = strpos($css, "@media", $start)) !== false){

        // stack to manage brackets
        $s = array();

        // get the first opening bracket
        $i = strpos($css, "{", $start);

        // if $i is false, then there is probably a css syntax error
        if ($i !== false){

            // push bracket onto stack
            array_push($s, $css[$i]);

            // move past first bracket
            $i++;

            while (!empty($s)){

                // if the character is an opening bracket, push it onto the stack, otherwise pop the stack
                if ($css[$i] == "{"){

                    array_push($s, "{");
                }
                elseif ($css[$i] == "}"){

                    array_pop($s);
                }

                $i++;
            }

            // cut the media block out of the css and store
            $mediaBlocks[] = substr($css, $start, ($i + 1) - $start);

            // set the new $start to the end of the block
            $start = $i;
        }
    }

    return $mediaBlocks;
}

Resources

RafaSashi
  • 16,483
  • 8
  • 84
  • 94
3
<?php

$css = <<<CSS
#selector { display:block; width:100px; }
#selector a { float:left; text-decoration:none }
CSS;

//
function BreakCSS($css)
{

    $results = array();

    preg_match_all('/(.+?)\s?\{\s?(.+?)\s?\}/', $css, $matches);
    foreach($matches[0] AS $i=>$original)
        foreach(explode(';', $matches[2][$i]) AS $attr)
                if (strlen($attr) > 0) // for missing semicolon on last element, which is legal
                {
                        // Explode on the CSS attributes defined
                        list($name, $value) = explode(':', $attr);
                        $results[$matches[1][$i]][trim($name)] = trim($value);
                }
    return $results;
}
var_dump(BreakCSS($css));

//see its same

Community
  • 1
  • 1
Pramendra Gupta
  • 14,667
  • 4
  • 33
  • 34
0

Here is a quick and dirty standalone hack using regex:

$input = '
#stuff {
    background-color: red;
}

#content.postclass-subcontent {
    background-color: red;
}

#content2.postclass-subcontent2 {
    background-color: red;
}
';

$cssClassName = 'postclass';
preg_match_all('/(#[a-z0-9]*?\.?'.addcslashes($cssClassName, '-').'.*?)\s?\{/', $input, $matches);
var_dump($matches[1]);

Results in:

array(2) {
  [0]=>
  string(29) "#content.postclass-subcontent"
  [1]=>
  string(31) "#content2.postclass-subcontent2"
}
softcr
  • 620
  • 3
  • 10
  • What if you have comma separated selectors? or comma-and-newline separated selectors? – HorusKol Oct 04 '13 at 00:43
  • 1
    Never use Regex for parsing languages. When I had nothing else, I did use a regex to prepend an ID to all selectors, but even with this monster, I kept getting false positives and false negatives that I had to correct using other regexes, or manually. `(?<![-\w:\s@\({+'\.^])(\s*)([\^*>\w\s\:\(\)\[\]\=\"\.-]+)(?=[,\{])` – Alex May 18 '14 at 14:17
  • Regular expressions are best for regular languages. This could work, but would fail if postclass shows up on the right side of a rule. For instance `.explain:before { content: 'uses #my_id.postclass-... in order to specify target classes'; }`. – James M. Lay Jul 01 '19 at 03:49
0

I was looking for a function to read css data (string or file).

All the solutions listed here are great!!!

But the function that was most useful to me was the from Gabriel Anderson

I renamed the function and extended it a bit, counting curly brakes and so on. The function is now able to read files or strings with css content.

Faulty css data generate error messages that are output under the key 'debug-errors-cssreader'.

  1. If the curly brakes are unequal, there is an error message.
  2. If it is a file and it doesn't exist then there is an error message.
  3. If the string empty (string or filename), too.

You can also use a regExp search pattern to filter the output.

$cssStr = 'path/to/file.css';      // or CSS code
$returnSelectorOnly = true;        // (optional) return css selector only
$regExpFilter = '/(your regExp)/'; // your (preg_match) pattern

$css = cssReader($cssStr, $returnSelectorOnly, $regExpFilter);

I have also extended it so that you can only display the selectors in an array.

I use the function, for example, to read out icofont/fontawesome css files, I only output the css selectors and process them further.

I have therefore programmed overview pages to see the icons that I have available.

Here a little example

$css = cssReader('icofont.css', true, '/(.icofont-)(.*?)(:before)/');
echo "<pre>";
echo var_dump($css);
echo "</pre>";

Output

array(2105) {
  [0]=>
  string(29) ".icofont-angry-monster:before"
  [1]=>
  string(23) ".icofont-bathtub:before"
  [2]=>
  string(26) ".icofont-bird-wings:before"
  [3]=>
  string(19) ".icofont-bow:before"
  [4]=>
  string(25) ".icofont-brain-alt:before"
  [5]=>
  string(29) ".icofont-butterfly-alt:before"
  [6]=>
  string(22) ".icofont-castle:before"
  [7]=>
  string(23) ".icofont-circuit:before"
  [8]=>
  string(20) ".icofont-dart:before"
  [9]=>
  string(24) ".icofont-dice-alt:before"

...

}

Maybe someone else needs the function just like me, below I put the entire function in including the example.

Thanks again for the great function!

function cssReader($cssStr, $returnSelectorOnly = false, $regExpFilter = "") {
    $css = false;
    $result = array();
    $error = array();
    $debug = true;
    $isfile = false;
    $filename = @trim(@pathinfo($cssStr)['filename']);
    if ($cssStr != "" && $filename != "") {
        $isfile = true;
    }
    // checking for is file and file exists
    if (is_file($cssStr)) {
        $cssStr = file_get_contents($cssStr);
        $countCurlyBrakes_open = substr_count($cssStr, "{");
        $countCurlyBrakes_close = substr_count($cssStr, "}");
        if ($countCurlyBrakes_open && $countCurlyBrakes_close) {
            if ($countCurlyBrakes_open == $countCurlyBrakes_close) {
                $css = $cssStr;
            } else {
                // debug        
                $error[] = "#1 File error: The counting of '{' or '}' was different, '{' = ".$countCurlyBrakes_open." and '}' = ".$countCurlyBrakes_close.".";
            }
        } else {
            // debug        
            $error[] = "#2 File error: Curly braces error, the counting of '{' or '}' was 0 (zero).";
        }
    } else {
        if ($isfile) {
            // debug 
            $error[] = "#3 File error: '".$cssStr."' the file does not exist.";
        }
    }
    // checking for is not a file and has no file extension and is shorter than 2049 characters
    // !!! // Technically speaking, your URL should never be longer than 2,048 characters. Any long than this and Internet Explorer won’t be able to load your page
    if (!$isfile) {
        if (!empty($cssStr)) {
            $countCurlyBrakes_open = substr_count($cssStr, "{");
            $countCurlyBrakes_close = substr_count($cssStr, "}");
            if ($countCurlyBrakes_open && $countCurlyBrakes_close) {
                if ($countCurlyBrakes_open == $countCurlyBrakes_close) {
                    $css = $cssStr;
                } else {
                    // debug        
                    $error[] = "#4 String error: The counting of '{' or '}' was different, '{' = ".$countCurlyBrakes_open." and '}' = ".$countCurlyBrakes_close.".";
                }
            } else {
                // debug        
                $error[] = "#5 String error: Curly braces error, the counting of '{' or '}' was 0 (zero).";
            }
        } else {
            // debug
            $error[] = "#6 String error: (string) $cssStr was empty.";
        }
    }
    // place errors on top of the array
    if ($debug && count($error)) {
        $result['debug-errors-cssreader'] = $error;
    }
    $regExp = "/(?ims)([a-z0-9\s\.\:#_\-@,]+)\{([^\}]*)\}/";
    preg_match_all(''.$regExp.'', $css, $arr);
    foreach ($arr[0] as $i => $x){
        $selector = trim($arr[1][$i]);
        if ($returnSelectorOnly == false || $returnSelectorOnly == "" || !$returnSelectorOnly) {
            $rules = explode(';', trim($arr[2][$i]));
            $rules_arr = array();
            foreach ($rules as $strRule){
                if (!empty($strRule)){
                    $rule = explode(":", $strRule);
                    $rules_arr[trim($rule[0])] = trim($rule[1]);
                }
            }
        }
        $selectors = explode(',', trim($selector));
        foreach ($selectors as $strSel){
            if ($returnSelectorOnly == false || $returnSelectorOnly == "" || !$returnSelectorOnly) {
                $result[$strSel] = $rules_arr;
            } else {
                if ($regExpFilter == false || $regExpFilter == "" || !$regExpFilter) {
                    $result[] = $strSel;
                } else {
                    if (preg_match($regExpFilter, $strSel)) {
                        $result[] = $strSel;
                    }
                }
            }
        }
    }
    return $result;
}

$css = cssReader('icofont.css', true, '/(.icofont-)(.*?)(:before)/');
echo "<pre>";
echo var_dump($css);
echo "</pre>";
MaZzIMo24
  • 139
  • 1
  • 6