A few months back, while working on bread5 (my new, at that time,
ultra-secret web site), I had a thought: wouldn't it be cool if I
could serve up bread5 with any look I wanted, without having to create
entire sites with different layouts by hand. I didn't want to just
change the color scheme like most ?skinnable? sites; I wanted to
create completely different looks without going through too much
trouble. The skin engine that I created for bread5 uses a host of new
technologies to accomplish its tasks; these technologies include php,
XML, XSLT, and XHTML. I've never seen anyone create dynamic sites that
are truly skinnable, so this is all new and uncharted territory for
me. As a result, the method I describe in this article may not be the
best way to do this, but I think it works very well. This is an
advanced tutorial. The reader should be familar with PHP and XML.
The idea is simple: when one wants to create different versions of
one's site, all the data is the same, the only thing that has changed
is the layouts. So joe-webmaster makes up some layouts then goes at
the task of copying-and-pasting his site's data into these layouts. He
does this over and over for every page on his site. There must be a
better way?there is. Since the site's data (such as product
information) is the same for every layout, put all the data in an XML
file and transform the data into the completely different layouts
using XSLT. So, if I wanted to create a new skin for bread5, all I
would have to do is create a few XSL files for the skin, upload it to
the server, and it's done; the user can choose whichever skin he or
she wants to view the site in. Using this method, you can increase the
accessibility of your site just by creating screen reader and WAP
compatible skins. In a few minutes your site can now be easily
accessed by a women using her cell phone, or a blind man sitting at
home.
To get started, take your data and put it in an XML file. For example,
a site's index page might look like this:
HTML Listing #1
...............
<?xml version="1.0" encoding="UTF-8"?>
<mySite page="index" title="Welcome to my site!">
<linkbar>
<item type="link">
<uri>./faq.php</uri>
<title>FAQ</title>
<desc>Frequently Asked Questions about this site</desc>
</item>
<item type="form">
<action>./search.php</action>
<method>post</method>
<input type="text" name="search" maxlength="100"/>
</item>
<!-- other links -->
</linkbar>
<greeting>
<para>
Welcome to my site!
</para>
<para>
Please check out all the sections.
</para>
</greeting>
<news>
<news-story>
<date>November 29, 2001</date>
<story>Some stuff happened.</story>
</news-story>
<news-story>
<date>November 30, 2001</date>
<story>Some more stuff happened.</story>
</news-story>
</news>
</mySite>
Looks pretty simple, but it can be improved upon to make it even
easier for us to maintain. Instead of adding a news story by manually
editing the file, I'd like to pull the stories out of my
database. That way, I can create a simple script to put my news in
the database and then I can easily add news stories without having to
FTP in to the server and edit the file.
We'll be using php's XSLT extension, Sablotron, to transform the XML
file (discussed later on), so why not use its power to add dynamic
content to the XML file also? It's easy to do. Just have php hold the
XML data, pass it to Sablotron and have it pass the data to the XSL
file. For example, to pull news stories out of a MySQL database and
have it added to the XML file, we would do the following:
SQL Listing #1
..............
// code to connect to the database and pull the stories omitted for brevity.
// $db_query holds an SQL query to pull news stories out of the database.
$xsltArgs["stories"]="<news-story>";
while($row= mysql_fetch_array($db_query)
{
$date= $row["date"];
$story= $row["story"];
$xsltArgs["stories"].="
<date>$date</date>
<story>$story</story>";
}
$xsltArgs["stories"].="</news-story>";
$xsltArgs is an array. $xsltArgs["stories"] holds the XML for the
news stories. This variable will be used later when the
transformation takes place. If you want to add more dynamic data to
your site, store it as XML in another slot of the array.
Now that we've created the XML file for the index page, we can create
an XSL file that will be used by Sablotron to transform the XML
file. Here is an XSL file that will transform the XML file into a
simple XHTML web page.
HTML Listing #2
...............
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" encoding="utf-8" method="xhtml"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>My Web Site - <xsl:value-of select="mySite/@title" /></title>
<meta content="My homepage!" name="Description" />
<meta content="My, stuff, cool page" name="Keywords" />
<meta content="ALL" name="Robots" />
<meta content="Copyright 2001 Joe-Webmaster" name="Copyright" />
<meta content="Joe-Webmaster" name="Author" />
<link href="main.css" rel="stylesheet" type="text/css" />
</head>
<body>
<br />
<xsl:call-template name="linkbar" />
<br />
<xsl:call-template name="content" />
</body>
</html>
</xsl:template>
<xsl:template name="linkbar">
<xsl:for-each select="mySite/sidebar/item">
<xsl:if test='@type="link"'>
<a href="{uri}" title="{desc}"><xsl:value-of select="title" /></a><br/>
</xsl:if>
<xsl:if test='@type="form"'>
<form method="{method}" action="{action}">
<xsl:for-each select="input">
<input class="search" type="{@type}" name="{@name}" size="10"
value="Search" maxlength="{@maxlength}" />
</xsl:for-each>
</form>
</xsl:if>
</xsl:for-each> </xsl:template>
<xsl:template name="content">
<strong><xsl:value-of select="mySite/@" /></strong>
<br />
<xsl:for-each select="mySite/greeting/para">
<p>
<xsl:value-of select="."/>
</p>
</xsl:for-each>
<hr width="454" align="center" />
<span class="news">News::</span>
<br />
<div class="news">
<xsl:for-each select="document('arg:/stories')/news-story">
<span class="newstime"><xsl:value-of select="date" /></span>
<div class="newsitem">
<xsl:value-of select="story" />
</div>
<br />
</xsl:for-each>
</div>
</xsl:template>
</xsl:stylesheet>
You could repeat this and create many different XSL files to output
many different pages (such as a WML page for viewing on a WAP enabled
device). If you stored more dynamic content in another slot of the
$xsltArg array, you can retrieve it in your XSL file by using:
document('arg:/<name of slot>')
where <name of slot> would be the name of the slot in the array.
Now all that is left to do is apply the XSL transformation to the XML
file. To do this, I use php's XSLT extension, Sablotron. I should
mention that Sablotron is not enabled in default php installations. To
activate it on a *nix machine, run the following:
./configure --enable-xslt --with-xslt-sablot
If you're being hosted by someone else, ask them to do it for you. I
chose a server side transformation because many browsers lack the
ability to do transformations on the client end. By doing the
transformation on the server, you're guaranteed that the
transformation will occur.
The simplest way to use XSLT in php is to pass the path to both the
XML file and the XSL file to the xslt_run() function like this:
// First create an XSLT processor.
$xh= xslt_create();
// $xsl holds the path to the XSL file. $xml holds the path to the XML
file.
xslt_run($xh, $xsl, $xml, "arg:/_result", NULL, $xsltArgs);
$result= xslt_fetch_result($xh);
// Finally, free the XSLT processor since we're done using it.
xslt_free($xh);
The $result variable will hold the transformed page which you can then
output using echo or do whatever else you may want to do.
The transformation process is a bit different on servers running PHP
4.1 and above. I believe, for completeness, I should explain how it is
done. If you're running a lower of version of php, feel free to skip
this part. With the advent of PHP 4.1, a new interface to the XSLT
engine has been added. Now, to transform an XML file, one does the
following:
// First create an XSLT processor.
$xh = xslt_create();
// Next, pass either two variables, one holding the path to an XSL
file and one
// holding the path to an XML file. $result holds the transformed
data.
$result= xslt_process($xh, $xsl, $xml, NULL, $xsltArgs);
// Finally, free the XSLT processor since we're done using it.
xslt_free($xh);
There you have it, a simple way to create truly skinnable web
sites. What I've just described is pretty basic, you can improve on
it. For example, you can have php automatically determine if the user
is on a WAP device and have it serve up a WAP page. Also, you can
create many skins and have a page that displays a screen shot of each
skin, letting the user choose which one he or she wants to view the
site with. Then, you can store the user's choice in a cookie, so the
next time the user visits the site, the skin he or she chose is
already applied. I hope you learned a lot from this tutorial and
start using this technique. As always, if you find any errors or have
any comments please send them to me (spiderman@witty.com). Kindly
direct questions to the message board. Until next time...