m4
I'd like to give an example of how to do this using the m4 macro processor, rather than the C preprocessor. The example is for a CSS fixed-width three-column layout, and demonstrates the flexibility of the macro processor approach.
Say you want a layout that's 768 pixels wide, divided into three columns, 164, 448, and 156 pixels wide respectively. There's padding applied to each column, and you have a hack in there to make sure it works in (nearly) all browsers. To get some of the values you need, you have to apply a little arithmetic to the column and padding widths.
If you ever wanted to change the widths, you'd have to recalculate everything. We'll use m4 to do all the hard work for us, and make it very easy to make new layouts with different widths.
Please note that this approach can be used for any layout type, not just the one presented here.
OK, time to look at some code. The CSS we want to end up with is this:
* { margin: 0; padding : 0; }
#wrapper { width: 768px; }
#col_container { width: 448px; padding-left: 164px; padding-right: 156px; }
#col_container .column { position: relative; float: left; }
* html #col_left { width: 164px; w\idth: 148px; }
#col_left { width: 148px; margin-left: -612px; padding: 0px 8px 0px 8px; }
* html #col_middle { width: 448px; w\idth: 416px; }
#col_middle { width: 416px; padding: 0px 16px 0px 16px; }
* html #col_right { width: 156px; w\idth: 140px; }
#col_right { width: 140px; margin-right: -156px; padding: 0px 8px 0px 8px; }
#footer { clear: both; }
I won't go into details about how all the values were arrived at. You'll see the calculations in the next bit of code.
Normally, you'd create a .css file and put all that code in there. What we're going to do, though, is create a .m4 file (actually, the extension is irrelevant) and use the m4 program to generate our .css file.
Here's the .m4 file:
changecom(`~')
define(`LINK_COLOUR', 2f78c0)
define(`HOVER_COLOUR', 2fc0c0)
define(`COL_LEFT_FULL_WIDTH', 164)
define(`COL_LEFT_LEFT_PAD', 8)
define(`COL_LEFT_RIGHT_PAD', 8)
define(`COL_LEFT_WIDTH', eval(COL_LEFT_FULL_WIDTH - COL_LEFT_LEFT_PAD - COL_LEFT_RIGHT_PAD))
define(`COL_MIDDLE_FULL_WIDTH', 448)
define(`COL_MIDDLE_LEFT_PAD', 16)
define(`COL_MIDDLE_RIGHT_PAD', 16)
define(`COL_MIDDLE_WIDTH', eval(COL_MIDDLE_FULL_WIDTH - COL_MIDDLE_LEFT_PAD - COL_MIDDLE_RIGHT_PAD))
define(`COL_RIGHT_FULL_WIDTH', 156)
define(`COL_RIGHT_LEFT_PAD', 8)
define(`COL_RIGHT_RIGHT_PAD', 8)
define(`COL_RIGHT_WIDTH', eval(COL_RIGHT_FULL_WIDTH - COL_RIGHT_LEFT_PAD - COL_RIGHT_RIGHT_PAD))
define(`FULL_WIDTH', eval(COL_LEFT_FULL_WIDTH + COL_MIDDLE_FULL_WIDTH + COL_RIGHT_FULL_WIDTH))
a:link {color:#LINK_COLOUR;}
a:visited {color:#LINK_COLOUR;}
a:hover {color:#HOVER_COLOUR;}
a:active {color:#HOVER_COLOUR;}
* { margin: 0; padding : 0; }
#wrapper { width: FULL_WIDTH()px; }
#col_container { width: COL_MIDDLE_FULL_WIDTH()px; padding-left: COL_LEFT_FULL_WIDTH()px; padding-right: COL_RIGHT_FULL_WIDTH()px; }
#col_container .column { position: relative; float: left; }
* html #col_left { width: COL_LEFT_FULL_WIDTH()px; w\idth: COL_LEFT_WIDTH()px; }
#col_left { width: COL_LEFT_WIDTH()px; margin-left: -eval(COL_MIDDLE_FULL_WIDTH + COL_LEFT_FULL_WIDTH)px; padding: 0px COL_LEFT_RIGHT_PAD()px 0px COL_LEFT_LEFT_PAD()px; }
* html #col_middle { width: COL_MIDDLE_FULL_WIDTH()px; w\idth: COL_MIDDLE_WIDTH()px; }
#col_middle { width: COL_MIDDLE_WIDTH()px; padding: 0px COL_MIDDLE_RIGHT_PAD()px 0px COL_MIDDLE_LEFT_PAD()px; }
* html #col_right { width: COL_RIGHT_FULL_WIDTH()px; w\idth: COL_RIGHT_WIDTH()px; }
#col_right { width: COL_RIGHT_WIDTH()px; margin-right: -COL_RIGHT_FULL_WIDTH()px; padding: 0px COL_RIGHT_RIGHT_PAD()px 0px COL_RIGHT_LEFT_PAD()px; }
#footer { clear: both; }
It contains a couple of other things in there as well as the layout.
If we run this command line:
m4 test.m4 > test.css
(assuming that the file was called test.m4) then the m4 program will generate a file called test.css with the following content:
a:link {color:#2f78c0;}
a:visited {color:#2f78c0;}
a:hover {color:#2fc0c0;}
a:active {color:#2fc0c0;}
* { margin: 0; padding : 0; }
#wrapper { width: 768px; }
#col_container { width: 448px; padding-left: 164px; padding-right: 156px; }
#col_container .column { position: relative; float: left; }
* html #col_left { width: 164px; w\idth: 148px; }
#col_left { width: 148px; margin-left: -612px; padding: 0px 8px 0px 8px; }
* html #col_middle { width: 448px; w\idth: 416px; }
#col_middle { width: 416px; padding: 0px 16px 0px 16px; }
* html #col_right { width: 156px; w\idth: 140px; }
#col_right { width: 140px; margin-right: -156px; padding: 0px 8px 0px 8px; }
#footer { clear: both; }
How's that? Exactly what we were after.
If you study our .m4 file, you'll see definitions for column widths and paddings. You can simply change any of them and all the dependent values will be calculated automatically, saving you a lot of work and error-proofing your code.
I prefer this to the php approach for a number of reasons:
- It doesn't rely on a web server, meaning I can experiment with my css on my, or any, local machine.
- The generated CSS file is just like any other CSS file, and will be cached by browsers, saving a hit on every page on your site. The PHP version won't be cached in browsers.
- It doesn't require the PHP interpreter to run on each hit, saving time and resources.
- PHP is really for dynamic content. Preprocessing is for static content.
Now, let's take a look at our .m4 file in detail.
The first line:
changecom(`~')
changes m4's comment delimiter from the default '#' to '~'. Otherwise, text coming after '#' would be seen as comments.
define(`COL_LEFT_FULL_WIDTH', 164)
defines a macro called COL_LEFT_FULL_WIDTH and sets its expansion to 164. Simple stuff, really.
define(`COL_LEFT_WIDTH', eval(COL_LEFT_FULL_WIDTH - COL_LEFT_LEFT_PAD - COL_LEFT_RIGHT_PAD))
defines a macro called COL_LEFT_WIDTH and sets it to the result of the operation evaluated by the
eval builtin macro.
a:link {color:#LINK_COLOUR;}
calls the macro
LINK_COLOUR, replacing the text
LINK_COLOUR with the text
2f78c0.
#wrapper { width: FULL_WIDTH()px; }
The macro call here has braces after it, otherwise we'd have FULL_WIDTHpx as our token and the macro processor would not recognise the macro.
#col_left { width: COL_LEFT_WIDTH()px; margin-left: -eval(COL_MIDDLE_FULL_WIDTH + COL_LEFT_FULL_WIDTH)px; padding: 0px COL_LEFT_RIGHT_PAD()px 0px COL_LEFT_LEFT_PAD()px; }
This line has a number of macro expansions, including an
eval macro call for some arithmetic.
You can compare the .m4 file to the generated .css file to see how the macros are expanded.
The m4 program is available as standard on Unix computers, including Linux and Mac. It's also
available for Windows.