简体   繁体   中英

Convert CSS background shorthand to longhand

I am trying to write a function that converts a short hand css declaration of background to long hand. I've written the below function but it has a couple of issues. One it doesn't take into account that background-color can be colour values such as black , yellow . In addition, what if some the properties contain inherit and none ? Here is an example:

url('http://2.bp.blogspot.com/-h28qvOsfm1c/TaGyO_qAFcI/AAAAAAAAA9w/I7zPQLy0zVM/s640/funny-image.jpg') inherit inherit 0 0 #FFFFFF;

Convert the above to CSS long hand. Here is my function, can it be improved to cover other cases?

function rewrite_background($b){

    $long_hand = "";

    $count = count($b); 

    for($i=0; $i < $count; $i++){

        if(stripos($b[$i], '#') !== false){

            $long_hand .= 'background-color: '.$b[$i].'; ';

            unset($b[$i]);

        }else if(stripos($b[$i], 'url') !== false){

            $long_hand .= 'background-image: '.$b[$i].'; ';

            unset($b[$i]);

        }else if((stripos($b[$i], 'repeat') !== false) || (stripos($b[$i], 'no-repeat') !== false) || (stripos($b[$i], 'repeat-x') !== false) || (stripos($b[$i], 'repeat-y') !== false)){

            $long_hand .= 'background-repeat: '.$b[$i].'; ';

            unset($b[$i]);

        }else if((stripos($b[$i], 'scroll') !== false) || (stripos($b[$i], 'fixed') !== false)){

            $long_hand .= 'background-attachment: '.$b[$i].'; ';

            unset($b[$i]);

        }else{

            // not recognized

        }

    }

    $b = array_values($b);

    if(isset($b[0])) $long_hand .= 'background-position: '.$b[0].' '.$b[1].';';  

    return $long_hand;

} 

Class to Parse CSS Background Shortcuts

This class will parse just about any line of background shortcut properties in any order , including those that are invalid according to the specs . For instance, background: top top is treated as background-position: center top .

All color values are fully supported, including: rgb, rgba, hls, hlsa, case-insensitive short-form hex (eg #fff), case-insensitive long-form hex (eg #123Abc), and case-insensitive color names.

!important is now supported.

inherit seemed as though it would be the most challenging problem, but turns out to be the simplest. For this property I referred to http://reference.sitepoint.com/css/inheritvalue , which states:

When you're using shorthand notation such as background, you can't mix inherit with other values. For example, the following background declaration is wrong:

 p { background: #fff inherit left top; }

... inherit must be the only value in the declaration, because there's simply no way of identifying the subproperty to which the value inherit refers—after all, it's not unique within the sequence. In the example above, inherit becomes ambiguous.

To deal with ambiguity, this class simply ignores everything else (except !important) and applies inherit to all properties as if you had used background: inherit .

The Code

<?php
class CSSBackground
{
    private $color_names = array(
        'AliceBlue', 'AntiqueWhite', 'Aqua', 'Aquamarine', 'Azure',
        'Beige', 'Bisque', 'Black', 'BlanchedAlmond', 'Blue',
        'BlueViolet', 'Brown', 'BurlyWood', 'CadetBlue', 'Chartreuse',
        'Chocolate', 'Coral', 'CornflowerBlue', 'Cornsilk', 'Crimson',
        'Cyan', 'DarkBlue', 'DarkCyan', 'DarkGoldenRod', 'DarkGray',
        'DarkGrey', 'DarkGreen', 'DarkKhaki', 'DarkMagenta',
        'DarkOliveGreen', 'Darkorange', 'DarkOrchid', 'DarkRed',
        'DarkSalmon', 'DarkSeaGreen', 'DarkSlateBlue', 'DarkSlateGray',
        'DarkSlateGrey', 'DarkTurquoise', 'DarkViolet', 'DeepPink',
        'DeepSkyBlue', 'DimGray', 'DimGrey', 'DodgerBlue', 'FireBrick',
        'FloralWhite', 'ForestGreen', 'Fuchsia', 'Gainsboro',
        'GhostWhite', 'Gold', 'GoldenRod', 'Gray', 'Grey', 'Green',
        'GreenYellow', 'HoneyDew', 'HotPink', 'IndianRed', 'Indigo',
        'Ivory', 'Khaki', 'Lavender', 'LavenderBlush', 'LawnGreen',
        'LemonChiffon', 'LightBlue', 'LightCoral', 'LightCyan',
        'LightGoldenRodYellow', 'LightGray', 'LightGrey', 'LightGreen',
        'LightPink', 'LightSalmon', 'LightSeaGreen', 'LightSkyBlue',
        'LightSlateGray', 'LightSlateGrey', 'LightSteelBlue', 'LightYellow',
        'Lime', 'LimeGreen', 'Linen', 'Magenta', 'Maroon',
        'MediumAquaMarine', 'MediumBlue', 'MediumOrchid', 'MediumPurple',
        'MediumSeaGreen', 'MediumSlateBlue', 'MediumSpringGreen',
        'MediumTurquoise', 'MediumVioletRed', 'MidnightBlue', 'MintCream',
        'MistyRose', 'Moccasin', 'NavajoWhite', 'Navy', 'OldLace', 'Olive',
        'OliveDrab', 'Orange', 'OrangeRed', 'Orchid', 'PaleGoldenRod',
        'PaleGreen', 'PaleTurquoise', 'PaleVioletRed', 'PapayaWhip',
        'PeachPuff', 'Peru', 'Pink', 'Plum', 'PowderBlue', 'Purple', 'Red',
        'RosyBrown', 'RoyalBlue', 'SaddleBrown', 'Salmon', 'SandyBrown',
        'SeaGreen', 'SeaShell', 'Sienna', 'Silver', 'SkyBlue', 'SlateBlue',
        'SlateGray', 'SlateGrey', 'Snow', 'SpringGreen', 'SteelBlue', 'Tan',
        'Teal', 'Thistle', 'Tomato', 'Turquoise', 'Violet', 'Wheat', 'White',
        'WhiteSmoke', 'Yellow', 'YellowGreen'
    );

    private $m_bgcolor = 'transparent';
    private $m_bgimage = 'none';
    private $m_bgrepeat = 'repeat';
    private $m_bgattachment = 'scroll';
    private $m_bgposition = '0% 0%';
    private $m_bgimportant = false;
    private $m_bg;

    public function __construct($bg)
    {
        // reformat array names for efficient pattern matching
        $this->color_names = '/\b('.implode('|',$this->color_names).')\b/i';

        $this->m_bg = $bg;  // save original

        $bg = $this->parse_important($bg);
        $bg = $this->parse_inherit($bg);
        $bg = $this->parse_color($bg);
        $bg = $this->parse_image($bg);
        $bg = $this->parse_repeat($bg);
        $bg = $this->parse_attachment($bg);
        $bg = $this->parse_position($bg);
    }

    public function original()
    {
        return $this->m_bg;
    }

    public function color()
    {
        return $this->m_bgcolor;
    }

    public function image()
    {
        return $this->m_bgimage;
    }

    public function repeat()
    {
        return $this->m_bgrepeat;
    }

    public function attachment()
    {
        return $this->m_bgattachment;
    }

    public function position()
    {
        return $this->m_bgposition;
    }

    public function important()
    {
        return $this->m_bgimportant;
    }

    private function parse_important($c)
    {
        // check for !important
        if (preg_match('/!important/i', $c, $m))
        {
            $c = str_replace($m[0], '', $c);
            $this->m_bgimportant = true ;
        }

        return $c;
    }

    private function parse_inherit($c)
    {
        // check for !important
        if (preg_match('/inherit/i', $c, $m))
        {
            $this->m_bgcolor = $this->apply_important('inherit');
            $this->m_bgimage = $this->apply_important('inherit');
            $this->m_bgrepeat = $this->apply_important('inherit');
            $this->m_bgattachment = $this->apply_important('inherit');
            $this->m_bgposition = $this->apply_important('inherit');
            $c = '';
        }

        return $c;
    }

    private function parse_color($c)
    {
        // check for hexit color value
        if (preg_match('/#([[:xdigit:]]{3}){1,2}/', $c, $m))
        {
            $c = str_replace($m[0], '', $c);

            $this->m_bgcolor = $this->apply_important($m[0]);
        }

        // check for rgb color value
        elseif (preg_match('/rgb\(\d{0,3}\,\d{0,3},\d{0,3}\)/i', $c, $m))
        {
            $c = str_replace($m[0], '', $c);
            $this->m_bgcolor = $this->apply_important($m[0]);
        }

        // check for rgba color value
        elseif (preg_match('/rgba\(\d{0,3}%?\,\d{0,3}%?,\d{0,3}%?\,\d(\.\d)?\)/i', $c, $m))
        {
            $c = str_replace($m[0], '', $c);
            $this->m_bgcolor = $this->apply_important($m[0]);
        }

        // check for hls color value
        elseif (preg_match('/hls\(\d{0,3}\,\d{0,3}%,\d{0,3}%\)/i', $c, $m))
        {
            $c = str_replace($m[0], '', $c);
            $this->m_bgcolor = $this->apply_important($m[0]);
        }

        // check for hlsa color value
        elseif (preg_match('/hlsa\(\d{0,3}\,\d{0,3}%,\d{0,3}%\,\d(\.\d)?\)/i', $c, $m))
        {
            $c = str_replace($m[0], '', $c);
            $this->m_bgcolor = $this->apply_important($m[0]);
        }

        // check for transparent
        elseif (preg_match('/transparent/i', $c, $m))
        {
            $c = str_replace($m[0], '', $c);
            $this->m_bgcolor = $this->apply_important('transparent');
        }

        // check for color names
        elseif (preg_match($this->color_names, $c, $m))
        {
            $c = str_replace($m[0], '', $c);
            $this->m_bgcolor = $this->apply_important($m[0]);
        }

        return $c;
    }

    private function parse_image($c)
    {
        // check for double word positions
        if (preg_match('/url\((.*?)\)|none/i', $c, $m))
        {
            $c = str_replace($m[0], '', $c);
            if (isset($m[1]))
            {
                $m[0] = str_replace($m[1], urlencode($m[1]), $m[0]);
            }
            $this->m_bgimage = $this->apply_important($m[0]);
        }

        return $c;
    }

    private function parse_repeat($c)
    {
        // check for repeat values
        if (preg_match('/\b(repeat-x|repeat-y|no-repeat|repeat)\b/i', $c, $m))
        {
            $c = str_replace($m[0], '', $c);
            $this->m_bgrepeat = $this->apply_important($m[0]);
        }

        return $c;
    }

    private function parse_attachment($c)
    {
        // check for repeat values
        if (preg_match('/scroll|fixed/i', $c, $m))
        {
            $c = str_replace($m[0], '', $c);
            $this->m_bgattachment = $this->apply_important($m[0]);
        }

        return $c;
    }

    private function parse_position($c)
    {
        // check for position values
        if (preg_match_all('/left|right|center|top|bottom|-?\d+([a-zA-Z]{2}|%?)/i', $c, $m))
        {
            $horz = '0%';
            $vert = '0%';

            if (!isset($m[0][1]))
            {
                $x = strtolower($m[0][0]);
                switch ($x)
                {
                    case 'top':
                    case 'bottom':
                        $horz = 'center';
                        $vert = $x;
                        break;
                    case 'left':
                    case 'right':
                    case 'center':
                        $horz = $x;
                        $vert = 'center';
                        break;
                    default:
                        $horz = is_numeric($x) ? "{$x}px" : $x;
                        $vert = 'center';
                }
            }

            else
            {
                $horz = strtolower($m[0][0]);
                $vert = strtolower($m[0][1]);

                if (($horz === $vert) && in_array($horz, array('left','right')))
                {
                    $vert = 'center';
                }

                if (($horz === $vert) && in_array($horz, array('top','bottom')))
                {
                    $horz = 'center';
                }

                if ($horz === 'top' || $horz === 'bottom')
                {
                    list($horz,$vert) = array($vert,$horz);
                }

                if ($vert === 'left' || $vert === 'right')
                {
                    list($horz,$vert) = array($vert,$horz);
                }
            }

            $this->m_bgposition = $this->apply_important("$horz $vert");
        }

        return $c;
    }

    private function apply_important($prop)
    {
        return $prop . ($this->m_bgimportant ? ' !important' : '');
    }
}

?>

Example Usage

<?php
header('Content-type: text/plain');

$bg = 'url("chess.png") gray 50% repeat fixed';

$cssbg = new CSSBackground($bg);

echo "background: ", $cssbg->original(), "\n\n";
echo "background-color: ", $cssbg->color(), "\n";
echo "background-image: ", $cssbg->image(), "\n";
echo "background-repeat: ", $cssbg->repeat(), "\n";
echo "background-attachment: ", $cssbg->attachment(), "\n";
echo "background-position: ", $cssbg->position(), "\n\n";
echo "!important applied: ", $cssbg->important() ? 'true' : 'false', "\n";
?>

This class was developed through extensive analysis of the w3c specifications for the CSS background property . Additional CSS properties would require the same analytical treatment.

I think the best you will do here is build a close approximation, because your program has to fully understand the CSS in order to transform it at all, and regular expressions are not quite up to the task. You would need a real parsing library instead. But for a start, there is a really nice tutorial here on using the Coco/R parser to process CSS: http://www.codeproject.com/KB/recipes/CSSParser.aspx It will give you a better idea of the scope of the problem.

There is also a PHP class for CSS parsing here http://www.phpclasses.org/package/1289-PHP-CSS-parser-class.html#download but I don't expect much from that.

Instead of searching $b using stripos and assigning the CSS long-hand property based on the results not being FALSE, why don't you just go by sequence? You could use a set pattern every time you formulate the $b property before passing it into your function:

$b[0]=background-attachment;
$b[1]=background-color;
$b[2]=background-image;
$b[3]=background-position;
$b[4]=background-repeat;

Which would mean you'd need to rearrange your short-hand to something like:

$b=array(inherit, #fff, url('example.jpg'), 0 0, inherit);

You can therefore handle any property values (even 'inherit' and 'none') - you just need to ensure you keep the correct sequence throughout and don't miss any properties otherwise the array keys go wrong. You can easily convert CSS colour names to hex values in another PHP function (using a list of all the CSS names ), or you can use this JavaScript function .

I would suggest you get someone with a lot of regexp experience to write you a powerful matching string.

You're going to have real problems doing it your current way, although you can assume:

url(.*) is always going to be a background image

#(.{3})(.{3})? is always a colour (but you may have other words that don't match any other matches here which are also colours)

([1-9][0-9]%|[0-9][0-9]+(px|em)|0|inherit|top|bottom|center|left|right) is background position (and one or two of these)

(repeat|repeat-x|repeat-y|no-repeat|inherit) is always background repeat

Remember that they may be in one of many orders.

It's nearly impossible to do what you want to do perfectly in PHP. The best you'll get would be a very close case with imperfections, for instance, yours won't support rgb, rgba, hsl, hsla color formatting, which is valid.


What you should be doing is find a good php css parser , and work with that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM