Pixelastic

You can cut our wings but we will always remember what it was like to fly.

Posts tagged with "CSSTidy"

Some CSS hacks to target IE6 and IE7

After stopping using ugly IE hacks and moving to conditionnal comments to load a special IE stylesheet, I now use conditional comments to mark my body element with classes reflecting the current IE version.

<!--[if IE 6]><body class="ie ie6 ie-lt8 ie-lt9"><![endif]-->
<!--[if IE 7]><body class="ie ie7 ie-lt8 ie-lt9"><![endif]-->
<!--[if IE 8]><body class="ie ie8 ie-lt9"><![endif]-->
<!--[if IE 9]><body class="ie ie9"><![endif]-->
<!--[if !IE]><!--><body class="nie"><!--<![endif]-->

This saves me a lot of trouble : less files to manage and easier fixes to write. I quite happy with this solution and have tested it accross several projects for the past 3 months. It works really well.

I had to work on a legacy project last week, where this technique wasn't implemented but all the css code was still compressed using CSSTidy. And I ran into a couple of issues.

CSSTidy messes the star and underscore hacks

Using the brilliant _property and *property hacks to target IE6 and IE7 does not work in conjunction with CSSTidy.

For the _property hack, the property is kept as-is, with the underscore, but as they are alphabetically arranged, the _background gets added before the background, rendering it absolutly useless.

On the other hand, on the *property, the * gets removed, and the value is merged with the original value of the correct property. Useless too.

Other solutions that worked

To avoid digging into CSSTidy one more time, I tried to find other ways to achieve the same effect.

To target IE6 I used the !ie6 hack by writing .mySelector { property:value !ie6; }. IE6 is dumb enough to understand any !blahblah as !important.

I could have also used the fact that IE6 understands .class1.class2 as .class2, and could have written .ie6.mySelector { property:value; } (of course, you have absolutly no class="ie6" in your code)

To target IE7 I made a custom selector that its parsing engine is the only one to understand : *:first-child + html .mySelector { property:value; }

 

CSS3 gradients with CSSTidy

Gradients are one of the new cool stuff CSS3 brought with it. Like the others cool things, it still suffer from a partial implementation and vendor-specific properties.

It also isn't correctly parsed by CSSTidy. Here I'll show you how to patch your CSSTidy to make it eat gradients correctly.

Quick and dirty patch

First, you'll need to edit the huge parse() method in csstidy.php. You'll have to add a condition to explictly tell CSSTidy not to discard -webkit-gradient and -moz-linear-gradient.

Just open your csstidy.php file, find the parse() method and locate the case 'instr' in the huge switch statement.

if (!($this->str_char === ')' && in_array($string{$i}, $GLOBALS['csstidy']['whitespace']) && !$this->str_in_str)) {
$this->cur_string .= $temp_add;
} else {
if ($this->sub_value=="-webkit-gradient" || $this->sub_value=="-moz-linear-gradient") {
$this->cur_string.=' ';
}
}

In bold, the else part to add. This will make sure your webkit and firefox gradient rules will get processed correctly.

I don't really understand WHY it work, but it does. The parse() method is a huge uncommented mess, it is quite difficult to understand it. There must be a better way, a more generic one than specifying some properties, but I didn't manage to come with anything better than that.

Fortunatly, the next part is cleaner.

Telling CSSTidy which properties not to merge

If you write a css like the following, only the latest (color:white) rule will get through CSSTidy.

body {
color:red;
color:white;
}

That's logical, because CSSTidy will remove any unused CSS declaration. Unfortunatly, this is not what we want, because we need to declare several background: rules, one for Webkit, and one for Firefox.

By looking at CSSTidy source code, we can find that it contain a quick fix to allow the cursor: property to be defined several time (to cope with the old cursor:pointer / cursor:hand issue).

I just extended this quick fix to work for other properties as well, and even managed to allow them to be passed as a config value.

Defining the config value

First, open the csstidy.php file, and around line 310 you should find a list of default config values. Just add the following :

$this->settings['multiple_properties'] = array('cursor', 'background');

This will define the default list of properties that are allowed to be defined several times in a css rule.

Next, we'll edit the set_cfg() method to allow the passing of array values. Just replace the else statement with this one :

else if(isset($this->settings[$setting]) && $value !== '') {
// Merging array settings
if (is_array($value) && is_array($this->settings[$setting])) {
$this->settings[$setting] = array_merge($this->settings[$setting], $value);
} else {
// Setting classic setting
$this->settings[$setting] = $value;
}

if ($setting === 'template') {
$this->_load_template($this->settings['template']);
}
return true;
}

You can now pass a list of properties to be added to the existing list by calling ->set_cfg('multiple_properties', array('property1', 'property2'));

Now, find the css_add_property() method, and around line 1066, change the if (strtolower($property) == 'cursor') if statement to this more generic one :

if (in_array($property, $this->get_cfg('multiple_properties')))

And now, in csstidy_print.php, find the _print() method, and replace the case PROPERTY block with this (more concise) one :

case PROPERTY:
// Converting back multiple properties
$multipleProperties = $this->parser->get_cfg('multiple_properties');
foreach($multipleProperties as $property) {
$propertyLength = strlen($property);
if (substr($token[1], 0, $propertyLength)==$property) $token[1] = $property;
}

// Applying correct casing
$caseProperties = $this->parser->get_cfg('case_properties');
if ($caseProperties==2) $token[1] = strtoupper($token[1]);
if ($caseProperties==1) $token[1] = strtolower($token[1]);

$out .= $template[4] . $this->_htmlsp($token[1], $plain) . ':' . $template[5];
break;

And that's it

You now can have gradients compressed with CSSTidy. Well sort of, because this is just a quick and dirty patch, as I'm not the creator of CSSTidy.

This could surely be improved in a less hacky way, for example by compressing the color code used in the gradients...

@font-face with multiple fonts and CSSTidy

The .woff font extension is the standard-to-go in terms of font embedding on the web.

You should add them first in the order of fonts you're loading, before the .ttf/.otf ones.

@font-face {
font-family: "Unibody8SmallCaps Regular";
src: url('fonts/unibody_8-smallcaps-webfont.woff') format('woff'), url('fonts/unibody_8-smallcaps-webfont.ttf') format('truetype');
}

The interesting thing to note is that you cannot omit the quotes around the format('woff')/format('truetype') part. Otherwise the font won't be recognized (at least by FF3.6).

CSSTidy seems to have a bug when there are multiple format() declarations in a rule, it removes quotes in each of them except the last one, thus making the whole rule unparsable by the browser.

So I started, again, to dig into the CSSTidy code and see what I could do about that.

I updated the csstidy.php file at around line 847 and changed the if statement to look like this :

if($this->sub_value != '') {
$this->sub_value_arr[] = $this->sub_value;
foreach($this->sub_value_arr as &$sub_value) {
if (substr($sub_value, 0, 6) == 'format') {
$sub_value = str_replace(array('format(', ')'), array('format("', '")'), $sub_value);
}
}
$this->sub_value = '';
}

This way all sub values of the src: rule will be correctly parsed, and not only the last one.

Updated CSSTidy files

CSSTidy allows you to optimize your CSS files by aggregating them in one unique file, removing useless spaces and comments as well as running some other optimizations on multiple declarations and shorthand notations.

But if, like me, you're still using the 1.3 version available on the official website, you may have run into issues when dealing with the latest CSS3 goodies.

I've been hacking this library in the last year, adding support for @font-face and for proprietary prefixed properties values, but today I was faced with some other issues that I wasn't able to resolve.

CSSTidy does not use regexp to parse the file, it reads it one char at a time and does its optimization at the same time, and it is sometimes quite hard to follow.

Anyway, I just discovered that the Sourceforge page of the project is still up and running and that a commit was added today, so the project is not dead ! I also posted two bug reports and one (dummy) patch.

Here's the link, you got a download link at the bottom : http://csstidy.svn.sourceforge.net/viewvc/csstidy/trunk/