CSS post-processing in PHP
While implementing the layout of this site in PHP, I found the tutorial on A List Apart, and wished to implement it here. However, I wanted to be able to change the column widths without having to recalculate manually the different dimensions.
I’ve then written a small PHP which post-process the CSS, and put a .htacces file in the CSS folder in order to ‘replace’ the original CSS by the postprocessed CSS.
Here is an extract from my CSS (you can find the full one here):
/** First define some values.
We put a special pattern at the beginning of the line to
identify it easily in PHP:
@!@ LC=180 # left column width
...
*/
...
/* The use it: */
width: 180px; /* @:@ width: ${LC}px; */
Let me explain the syntax choosen:
- Lines beginning with @!@ are declarations. They will be processed, but the line will be output as-is.
- Lines including @!@ are calculations. They will be calculated, and replaced by the result
- Other lines are left untouched
Doing this, one I’m satisfied with the result, I can just take the output of the script, and my previous ’style.css’, and commented out my .htaccess. My CSS will again be a static CSS. If I uncommned the .htaccess, it will be dynamic again. In essence, the compiled version is also the source.
The PHP processing this is quite simple. I haven’t implemented a full expression evaluator, nor an efficient one: this is intended to be used for development, not for production:
<?php
$filename=$_REQUEST['file'];
// todo: check for path,...
if (preg_match("/[^a-zA-Z0-9_.]/",$file)) {
die("Bad char in file name");
}
header('Content-type: text/css');
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past
$vars=array();
$warns=array();
function cb_def($m) {
global $vars;
$vars[$m[1]]=$m[2];
return "$m[0]"; // doesn't modify the line!
}
function calc($e) {
global $vars;
if (isset($vars[$e])) return $vars[$e];
if (preg_match('/^(d+)$/',$e,$m)) return $m[1]; // constant
if (preg_match('/^(.*)([+-])(.*?)$/',$e,$m)) { // add, substr
$x1=calc($m[1]);
$x2=calc($m[3]);
return($m[2]=='+'?$x1+$x2:$x1+$x2);
}
if (preg_match('/^(.*)(*)(.*?)$/',$e,$m)) { // multiplication
return calc($m[1])*calc($m[3]);
}
global $warns;
global $lineNo;
$warns[]="Can't evaluate '$e' at line $lineNo";
return 0;
}
$handle=fopen($file,"r");
if (!$handle) {
print "/* CSS $filename doesn't exists */";
exit;
}
$lineNo=0;
while(!feof($handle)) {
$line=fgets($handle);
$lineNo++;
$re="/^s*@!@s*([a-zA-Z0-9_]+)s*=s*([0-9]+)s*(#.*)?$/";
$line=preg_replace_callback($re,'cb_def',$line);
if (preg_match('!/*s*@:@(.*)${(.*?)}(.*)*/(.*)$!',$line,$m)) {
$line="$m[1]".calc($m[2])."$m[3]$m[0]";
}
print "$line";
}
fclose($handle);
if (count($warns)) {
print("/* Warnings:n".implode("n",$warns)."n*/n");
}
?>
The last block of the puzzle is the .htacesss file
RewriteEngine on
Options +FollowSymLinks
RewriteRule style.css /wp/wp-content/themes/cipher/processCss.php?file=style.css
Now, I can change the declarations in my CSS file, and just refresh the page using that CSS. After much tweaking, once I’m satisfied, I open my CSS URL, copy the text, past it in my CSS file, and remove the .htacess. I’m back to a static CSS.