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

6 Answers6

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
  • 45,002
  • 12
  • 73
  • 78
  • thank you. but dont need the value and the overhead! thank you anyway – stefan Sep 01 '10 at 13:45
  • 13
    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
15

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,051
  • 13
  • 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
11

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,661
  • 2
  • 22
  • 35
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
  • 15,412
  • 8
  • 78
  • 89
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,279
  • 4
  • 32
  • 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
  • 610
  • 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\:\(\)\[\]\=\"\.-]+)(?=[,\{])` – 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