Apache's eXtended Server Side Includes
Pages: 1, 2, 3
Staying Up to Date
A standard element in most sites is the copyright notice, usually placed at the bottom of every page. Copyright notices comprise the copyright year and holder, and some statement regarding the retention or availability of the rights; for example, "All rights reserved." You can easily add such a "static" copyright notice in a fashion similar to that in Listing 2. However, web pages often evolve over time, with updates and modifications, and may include parts copyrighted in different years. Keeping track of which pages are copyrighted and when can be a maintenance nightmare. This is partly the reason that it is common to see copyrights that span from the first year the site went live to the current year, irrespective of when a single page's most recent update actually occurred. Listing 5 presents how XSSI can help you dynamically create such a copyright notice.
Listing 5. Sitewide copyright notice
<!--#config timefmt="%Y" -->
<p>©2003-<!--#echo var="DATE_LOCAL" -->
Kostas Pentikousis. All rights reserved.</p>
This approach has two steps. First, it must figure out what is the current
year. The current date and time is always available through the environment
variable DATE_LOCAL. However, the page should display only the
current year, so it needs to select the appropriate time format. The
instruction
<!--#config timefmt="%Y" -->
causes the display of only part of the date/time variables, such as
DATE_LOCAL: the four-digit year (%Y). After this
configuration, the next step is to display the current time using the
environment variable DATE_LOCAL. If you decide that the copyright
notice should not span to the current year but, instead, until the year in
which the page had its last update, replace DATE_LOCAL with
LAST_MODIFIED.
Having already specified the time format, echo will display the
current year only. This time format will be in effect for the remainder of the
document, unless reconfigured. You can assign
timefmt any string, which may include special time-conversion
characters. Table 1 presents a partial listing of available conversion
characters.
| Conversion character | Replaced by |
|---|---|
%A or %a |
Locale's full or abbreviated weekday name |
%e |
Day of the month: decimal number without a leading zero |
%B or %b |
Locale's full or abbreviated month name |
%Y |
Locale's 4-digit year |
For example,
<!--#config timefmt="Today is %B %e, %Y." -->
<p><!--#echo var="DATE_LOCAL"--></p>
will produce
<p>Today is March 13, 2005</p>
Your options are limited only by the conversion characters and formats supported by the strftime() function available on your system. Finally, remember that there is more than one way to do it: Listing 6 presents a different implementation of the copyright notice, which produces the same output as that of Listing 5.
Listing 6. Sitewide copyright notice redux
<!--#config timefmt="2003-%Y" --> <p>©<!--#echo var="DATE_LOCAL" -->
Kostas Pentikousis. All rights reserved.</p>
Any file can include others, which in turn can contain more file inclusions and other dynamically created content. Listing 7 illustrates this by including the file in Listing 5 (in bold), exactly as shown earlier. This recursion is a very powerful XSSI feature not available in many competitive technologies. The rest of this article takes advantage of this feature to dynamically generate multiple versions of a document and create hierarchical menus with XSSI.
Listing 7. Inclusion of static (top-menu.html) and dynamic (copy.shtml) content
<html>
<head>
<!--#set var="who" value="World" -->
<title>Hello <!--#echo var="who" -->!</title>
</head>
<body>
<!--#include file="top-menu.html"-->
<h1>Hello <!--#echo var="who" -->!</h1>
<!--#include file="copy.shtml"-->
</body>
</html>
Printer-Friendly Versions Made Easy
Many sites offer each article in two different versions. One is heavy on graphics, pictures, navigational aids, possibly ads, and may partition each article to several parts or pages. The other version is simpler, often dubbed "printer friendly," and presents the same content in a single part but without most of the ads and navigational aids. Ideally you should be able to store each article in one document and allow for many different views or presentation formats. Note that you cannot really achieve this with, say, alternative style sheets. The alternative versions do include the same text, which could have been formatted using alternative style sheets, but they also include a variety of ancillary elements whose appearance style sheets alone cannot control. By making good use of cascaded style sheets and XSSI, it's possible to achieve this without the need for database lookups, content transformations, or storing the same content twice.
First, there must be a way to differentiate between the different versions a user may be requesting. For example, the user should be able to request the media-rich version with the URL
http://www.example.org/hello-world.shtml
and the printer-friendly version by asking for
http://www.example.org/hello-world.shtml?print
The trick is to scan the requested URL for the string print and
set a variable to indicate which format the user has requested. The
environment variable QUERY_STRING proves very useful in this case.
Its value is the string past the ? up to the end of the URL,
i.e. exactly where the code needs to focus its attention. In the first case,
when the media-rich version is requested, QUERY_STRING is empty
(""). In the second case, it contains the string print. Listing 8
uses an XSSI if else statement to check whether
QUERY_STRING contains the string print.
Listing 8. Use of a conditional statement to determine whether the printer-friendly version is requested
<!--#if expr="$QUERY_STRING = /print/" -->
<!--#set var="view" value="print" -->
<!--#else -->
<!--#set var="view" value="fancy" -->
<!--#endif -->
Seasoned programmers may find the if else syntax slightly
verbose. Remember, however, that XSSI directives should always be formatted as
valid SGML comments. The condition fits in the expr argument
inside double quotes ("").
So far, this has shown only how to display variables using the
echo directive, which expects the variable name as its argument.
On the other hand, the conditional statement (if) operates with
the value of a variable. The code in Listing 8 accesses the value
stored in QUERY_STRING by prepending the dollar sign
($) to the variable name (QUERY_STRING). If the pattern
match succeeds, then view is set to print; otherwise it is
set to fancy. Listing 9 includes the code shown in Listing 8 and uses
the view variable to direct the browser to load the appropriate
style sheet, named either print.css or fancy.css, before
displaying the page. This highlights that XSSI instructions can appear anywhere
in the content of a file. A few words of caution, though: make sure that you
keep your single (') and double (") quotes, both of which (X)HTML and XSSI
support, always balanced.
Listing 9. Use of a variable defined in another file
<html>
<head>
<!--#include file="meta.shtml" -->
<link rel="stylesheet" href="<!--#echo var='view'-->.css" type="text/css">
<!--#set var="who" value="World" -->
<title>Hello <!--#echo var="who" -->!</title>
</head>
<body>
<!--#include file="top-menu.shtml"-->
<h1>Hello <!--#echo var="who" -->!</h1>
<!--#include file="footer.shtml" -->
</body>
</html>
Although view is set in a different file
(meta.shtml, from Listing 8), it's still usable in the current file
(Listing 9). In programming language terminology, the scope of each XSSI
variable is from the point of definition to the end of the document. Keep in
mind that if a variable is undefined or the code never assigned it a value,
echo will display (none).
Of course, you can use conditional statements not only with style sheets,
but also with any kind of document content. For example, Listing 10 presents
the new, XSSI-enabled navigation bar, which is not present in the printer-friendly version. In the same way, it's possible to replace a Flash ad with a
simple image banner in the printer-friendly version. Listing 11 employs the
same technique to include a link conditionally. If view is not
equal to (!=) the string print, it adds a link to the
printer-friendly version. Otherwise, it displays only the copyright notice.
Listing 10. This navigation bar does not appear in the printer-friendly version
<!--#if expr="$view != print" -->
<p>Hello: <a href="?World">World</a> - Africa -
Antarctica - America - Asia - Europe - Oceania</p>
<!--#endif -->
Listing 11. This content is common to all pages on the site and is placed in a single file (footer.shtml)
<!--#if expr="$view != print" -->
<p><a href="?print">Printer-friendly version</a></p>
<!--#endif -->
<!--#config timefmt="2003-%Y" -->
<p>©<!--#echo var="DATE_LOCAL" --> Kostas Pentikousis. All rights reserved.</p>
These examples use two different kinds of string matching. First, there's a pattern match in:
<!--#if expr="$QUERY_STRING = /print/" -->
This tries to match print anywhere in the contents of
$QUERY_STRING. You can read this instruction as "If the
expression $QUERY_STRING matches 'print', then." On the
other hand,
<!--#if expr="$QUERY_STRING = print" -->
does an exact match on $QUERY_STRING. This directive
essentially means "If the expression $QUERY_STRING is
'print', then." One implication of using pattern matching is that if the user
requests any of the following:
http://www.example.org/hello-world.shtml?print-this
http://www.example.org/hello-world.shtml?printANDthis
http://www.example.org/hello-world.shtml?still_printer-friendly
Apache will serve the printer-friendly version. If the code uses exact matching instead, all three will result in serving the media-rich version instead.
Checking for inequality can prove quite useful as well. For example, to make
sure that view does not contain the string print
(pattern matching) as follows, write:
<!--#if expr="$view != /print/" -->
You may wonder why Listing 8 set a variable (view) instead of using
if else statements and the same pattern matching on
$QUERY_STRING several times. Pattern matching is computationally
more expensive than exact matching, especially with long strings.
QUERY_STRING can be long, so it is likely more efficient to do a
single pattern match, set a variable, and then do multiple exact matches to
determine which (X)HTML fragments to include. In general it is a good idea to
avoid recurring pattern matches and, if possible, to replace (redundant) pattern
matches with exact matches.
Conditional Redirects
Taking advantage of environment variables and conditional statements, you
can use XSSI to redirect visitors to the most appropriate page based on the
make and version of their browser, their IP address, fully qualified domain
name, the time of day, and so on. Remember that you can always nest
if elif else statements. Listing 12 illustrates a very simple case
in which you want to redirect users from your local networks (*.example.org) to
one page (http://www.example.org/local) and users from any other domain to
another one (http://www.example.org/visitors). Note that this is a good
solution only if you do not have administration access to Apache configuration
files. Otherwise, you should use
URL redirects
instead.
Listing 12. Conditional redirect using XSSI
<!--#if expr="$REMOTE_HOST = /example.org/" -->
<meta http-equiv="refresh" content="0;URL=/local">
<!--#elif expr="$REMOTE_HOST = '' " -->
<meta http-equiv="refresh" content="0;URL=/not_fully_qualified_hostname">
<!--#else -->
<meta http-equiv="refresh" content="0;URL=/visitors">
<!--#endif -->
Article Partitioning
Several online magazines partition their articles in an effort to increase ad revenue by creating more spots for ads. For example, splitting each article into two parts means that the site can double the number of banners, buttons, and "unobtrusive ads" it serves. There is also an old-style rule dictating that pages should be about one and a half "screens" long. In this fashion, the user does not have to scroll more than once or twice while browsing a page. For example, suppose that the article about World has two parts. The entire article content is stored once at
http://www.example.org/hello-world.shtml
This URL will also display the first part. The second part has a URL of
http://www.example.org/hello-world.shtml?p=2
In order to make the little application more robust, you have chosen to
display the first part of the article unless the user explicitly requests the
second one. That way if, for example, a user adds random characters after the
?, she will get a valid response and not an error message. In fact,
the first part is the "proper" response, because more often than not articles
progress serially and readers should read them in that order. Of course, you
would also like to have a printer-friendly version that includes both parts
available from:
http://www.example.org/hello-world.shtml?print
Listing 13 illustrates that four lines of XSSI code are enough to bisect the
article. First, check that the user did not explicitly request the second page
($QUERY_STRING != 'p=2'). If he did, the first if
else (in bold) will fail and the first part is skipped. Instead, the
code displays the second part because the second condition ($QUERY_STRING
= 'p=2') is met. If the user requests a printer-friendly version, both
parts will display. This guarantees robustness.
Listing 13. Article partitioning with XSSI
<html>
<head>
<!--#include file="meta.shtml" -->
<link rel="stylesheet" href="<!--#echo var="view"-->.css" type="text/css">
<!--#set var="title" value="World Statistics" -->
<title><!--#echo var="title" --></title>
</head>
<body>
<h1><!--#echo var="title" --> </h1>
<!--#if expr="$QUERY_STRING != 'p=2' || $view = print" -->
Text for part 1 here...
<!--#set var="p" value="2" -->
<!--#endif -->>
<!--#if expr="$QUERY_STRING = 'p=2' || $view = print" -->
Text for part 2 here..
<!--#set var="p" value="1" -->
<!--#endif -->
<!--#include file="article-tools.shtml" -->
<!--#include file="copy.shtml" -->
</body>
</html>
An important component of article partitioning is the navigational aids that
help the visitor move from one part to the next. First, the code needs to
determine which part is currently on display. Because the article has only two
parts, a single variable (p in Listing 13) will do: you simply need to
set p to the value of the complementing part. Based
on this value, the code in article-tools.shtml (Listing 14) generates
either a navigation label of Next:
<p><a href="?p=2">Next</a></p>
or Previous:
<p><a href="?p=1">Previous</a></p>
Note that the first conditional statement in Listing 14 ensures that neither of the labels appear in the printer-friendly version. Listing 14 includes extraneous indentation in order to make the syntax easier to read. A real-world application would have put the entire snippet in three lines:
Listing 14. "Previous"/"Next" navigation label generation
<!--#if expr="$view != print" -->
<p>
<a href="?p=<!--#echo var='p' -->">
<!--#if expr="$p = 1" -->
Previous
<!--#else -->
Next
<!--#endif -->
</a>
</p>
<!--#endif -->
An alternative version of article-tools.shtml (Listing 15)
generates the "next" page number. With modification, this technique can also
partition an article into more than two parts. However, XSSI lacks a repetitive
or conditional loop instruction (such as for or
while) and basic arithmetic functions. Thus, if you wish to divide
an article into several pieces, adding the partitioning code by hand quickly
becomes tedious.
Listing 15. Generation of numeric navigation labels
<!--#if expr="$view != print" -->
<p>
Page
<!--#if expr="$p = 1" -->
<a href="?p=1" -->1</a> | 2
<!--#else -->
1 | <a href="?p=2" -->2</a>
<!--#endif -->
</p>
<!--#endif -->
I find fragmenting a short article in more than two parts to be quite frustrating for the user. Nevertheless, doing so for a long report, user documentation, or even a book that has distinct parts or sections is a good idea in general. In fact, this technique is best to apply when the text has various independent sections that share a common introductory or concluding segment. In that case, partitioning also has the advantage of permitting the reader to bookmark each section individually, while at the same time providing many different search engine entry points. It is also a great feature if the site audience includes mobile users or visitors connecting through low-bandwidth connections. The following section explains how to take article partitioning with XSSI one step further.
