Parse a CSS file with PHP

26,156

Solution 1

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"
}

Solution 2

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'];

Solution 3

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
?> 

Solution 4

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).

Solution 5

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

Share:
26,156

Related videos on Youtube

stefan
Author by

stefan

Updated on July 09, 2022

Comments

  • stefan
    stefan almost 2 years

    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.

    • Jim
      Jim almost 14 years
      Do you have any code that you have tried this with if so, posting that would be helpful.
    • Gordon
      Gordon almost 14 years
      possible duplicate of Parsing CSS by regex
  • stefan
    stefan almost 14 years
    thank you. but dont need the value and the overhead! thank you anyway
  • HorusKol
    HorusKol over 10 years
    What if you have comma separated selectors? or comma-and-newline separated selectors?
  • sumid
    sumid over 10 years
    That class dates to 2003-09-20. (And the site also requires you to do annoying registration to be able to download it.)
  • Alex
    Alex about 10 years
    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\:\(\)\[\]\=\"\.-]+)(?=‌​[,\{])
  • Marais Rossouw
    Marais Rossouw over 9 years
    Only one small problem with this solution, is it doesn't offer support for media queries.
  • Alex G
    Alex G over 9 years
    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% )
  • bhaskarc
    bhaskarc over 9 years
    good one but see some problems - 1) it cannot handle child selector(>) 2) It cannot handle selectors like input[type="button"]:hover.
  • Peter Brand
    Peter Brand over 6 years
    It does offer media queries now.
  • James M. Lay
    James M. Lay almost 5 years
    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'; }.
  • Boltgolt
    Boltgolt over 3 years
    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