Part 1 of this series introduced a problem specification: to build a weather badge that can be inserted easily into any Web page. The weather badge is constructed using Ajax techniques and uses data provided by the United States National Weather Service (NWS). That NWS data is provided in an XML format, updated every 15 minutes.
This series looks at four separate approaches for implementing the weather badge. In the first installment, the first approach used an Apache Web server rule to proxy the NWS XML data into the browser. Then, with JavaScript code, it extracted the required data from the DOM, reformatted it into HTML, and displayed it.
This installment looks at the second and third approaches. These two approaches share one thing in common: they both use XSLT.
XSLT is a language to query XML and transform XML into other formats. This is precisely the problem I have with my weather data—it's packaged as XML, but I want something more user- (and browser-) friendly. And the NWS data contains a lot more information than the weather badge requires. Some technique is required to extract just the data items I need. XSLT can handle both these requirements.
It's beyond the scope of this article to provide an XSLT tutorial. To find out more about XSLT, see the developerWorks article "What kind of language is XSLT? (see Resources).
The XSLT language differs from many other computer languages in that its syntax is valid XML. This can be a trifle confusing if you're used to the C, Java™, Perl, or Python languages.
Because both my second and third approaches to the weather badge use the same XSLT, I'll introduce and explain it first. Later I'll show how the XSLT piece fits into the overall solutions.
First, review the NWS XML data format. Listing 1 shows an abbreviated example.
Listing 1. Sample NWS XML data file KNGU.xml (abridged)
<?xml version="1.0" encoding="ISO-8859-1"?>
<current_observation version="1.0"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation=
"http://www.weather.gov/data/current_obs/current_observation.xsd">
<credit>NOAA's National Weather Service</credit>
<credit_URL>http://weather.gov/</credit_URL>
<image>
<url>http://weather.gov/images/xml_logo.gif</url>
<title>NOAA's National Weather Service</title>
<link>http://weather.gov</link>
</image>
<suggested_pickup>15 minutes after the hour</suggested_pickup>
<suggested_pickup_period>60</suggested_pickup_period>
<location>Norfolk, Naval Air Station, VA</location>
<station_id>KNGU</station_id>
<latitude>36.94</latitude>
<longitude>-76.28</longitude>
<observation_time>
Last Updated on Jan 7, 2:53 pm EST
</observation_time>
<observation_time_rfc822>
Mon, 7 Jan 2008 14:53:00 -0500 EST
</observation_time_rfc822>
<weather>Fair</weather>
<temperature_string>74 F (23 C)</temperature_string>
<temp_f>74</temp_f>
<temp_c>23</temp_c>
<relative_humidity>34</relative_humidity>
<wind_string>From the Southwest at 9 Gusting to 18 MPH</wind_string>
<wind_dir>Southwest</wind_dir>
<wind_degrees>240</wind_degrees>
<visibility_mi>10.00</visibility_mi>
<icon_url_base>
http://weather.gov/weather/images/fcicons/
</icon_url_base>
<icon_url_name>
skc.jpg
</icon_url_name>
<disclaimer_url>http://weather.gov/disclaimer.html</disclaimer_url>
<copyright_url>http://weather.gov/disclaimer.html</copyright_url>
<privacy_policy_url>http://weather.gov/notice.html</privacy_policy_url>
</current_observation>
|
I'm only interested in the data highlighted in Listing 1, so the first job of my XSLT program is to extract those desired elements. The second job is to reformat that subset of data into HTML for display in the browser.
Listing 2 shows the XSLT program used to accomplish those two goals.
Listing 2. weather2html.xsl, the weather data XSLT program
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" />
<xsl:template match="/current_observation">
<center>
<b><xsl:value-of select="location" /></b><br/>
<xsl:value-of select="weather" /><br/>
<xsl:variable name="icon_url_base" select="icon_url_base"/>
<xsl:variable name="icon_url_name" select="icon_url_name"/>
<img border='0' src='{$icon_url_base}{$icon_url_name}'/><br/>
<xsl:value-of select="temperature_string" /><br/>
Wind: <xsl:value-of select="wind_string" /><br/>
Humidity: <xsl:value-of select="relative_humidity" />%<br/>
Visibility: <xsl:value-of select="visibility_mi" /> miles<br/>
<br/><span style='font-size: 0.8em; font-weight: bold;'>
<xsl:value-of select="observation_time" /></span><br/>
</center>
</xsl:template>
</xsl:stylesheet>
|
First, notice this line:
<xsl:output method="html" /> |
This clues in the XSLT processor that the output is HTML, not the default XML format.
The XSLT program looks like a mixture of HTML and XML, and that's just
what it is. Any XML elements that begin
<xsl:
are XSLT
language statements. Anything else is text that is output
verbatim.
For example, look at this line:
<b><xsl:value-of select="location" /></b><br/> |
The xsl:value-of element directs the XSLT processor to find the
location element in the XML input file, extract its value, and
output that value in place of the xsl:value-of tag. The resulting
output looks like this:
<b>Norfolk, Naval Air Station, VA</b><br/> |
As with other programming languages, like Perl or Ruby, you execute XSLT by running it through a language interpreter. This is often called an XSLT processor. But XSLT is not a general-purpose programming language—it exists to translate a single XML data file. So most XSLT processors require two input files: the XSLT program and the XML file it transforms.
Many Linux® distributions include a command-line XSLT processor called
xsltproc. This is valuable for debugging
and fine-tuning XSLT scripts, as are other similar tools.
Xsltproc expects two parameters: the XSLT program and the XML that
XSLT program acts upon. Suppose I previously downloaded the NWS XML
data for Norfolk Naval Air Station in Virginia to the file KNGU.xml
(KNGU is the four-character unique Station ID described in Part 1).
I can use this command to test my XSLT program from Listing 2:
xsltproc weather2html.xsl KNGU.xml |
The XSLT processor applies the translations described in weather2html.xsl to input file KNGU.xml and writes the results to standard output. That output is shown in Listing 3.
Listing 3. Output from XSLT processing of KNGU.xml
<center>
<b>Norfolk, Naval Air Station, VA</b><br>
Fair<br>
<img border="0"
src="http://weather.gov/weather/images/fcicons/skc.jpg"><br>
57 F (14 C)<br>
Wind: From the West at 7 MPH<br>
Humidity: 58%<br>
Visibility: 7.00 miles<br>
<br>
<span style="font-size: 0.8em; font-weight: bold;">
Last Updated on Oct 12, 7:53 am EDT
</span>
<br>
</center>
|
Approach 2: XSLT on the server
I want to apply the XSLT approach to the weather badge problem. In Approach 2, a server-side script pulls the data from the NWS server, uses XSLT to transform that XML to HTML, then sends the HTML snippet back to the browser. The browser then just inserts the snippet into a DIV tag.
Figure 1 shows the data pipeline used in this approach. Data flows from the NWS server into my server, where the server-side script converts XML to HTML. The pipeline ends in the browser, where the HTML is received and inserted into the Web page.
Figure 1. The data pipeline for Approach 2
I need a server-side program that can perform the XSLT translation.
I write this in Perl, as shown in Listing 4, but it can as easily be implemented in
another language. The script is invoked by an XMLHttpRequest call on
the browser. The Ajax JavaScript code sends the server-side
script a single parameter: the four-character NWS Station ID needed
to retrieve the XML data file from the NWS
server.
Listing 4. The weather_xml2html.cgi Perl script
#!/usr/bin/perl
use strict;
my $DATA_DIR = "/var/www/html/xml_weather/data";
my $XSL_FILE = "$DATA_DIR/weather2html.xsl";
my $XSLTPROC = "/usr/bin/xsltproc";
my $HTTP_BIN = "/usr/bin/wget -q -O - ";
my $URL_FORMAT = "http://www.nws.noaa.gov/data/current_obs/%s.xml";
# Get the NOAA location key:
my $location = $ENV{QUERY_STRING};
# Minimal sanity check of the location key:
if ($location !~ /^[\d\w]{4}$/) {
print "Content-type: text/html\n\n";
print "Unknown location ($location).\n";
exit 1;
}
# Build URL:
my $url = sprintf ($URL_FORMAT, $location);
# Build Command:
my $cmd = "$HTTP_BIN '$url' 2> /dev/null | $XSLTPROC $XSL_FILE - ";
print "Content-type: text/html\n\n";
open (my $IPIPE, "$cmd |");
while (<$IPIPE>) {
print $_;
}
close $IPIPE;
|
My Apache Web server runs the script in Listing 4. The Apache
QUERY_STRING environment variable provides
the script with the four-character Station ID.
The Perl script uses two external commands: xsltproc and wget.
Xsltproc is the command-line XSLT processor
I described earlier. Wget
is a free utility (available from the Free Software Foundation). It's
preinstalled on most Linux distributions. Wget can grab a Web page
(or other Web resource) off the Web. My Perl script uses wget to get
the XML file from the NWS server.
The Perl script constructs a command pipeline that, if executed on the Linux command line, looks like this:
/usr/bin/wget -q -O - http://www.nws.noaa.gov/data/current_obs/KNGU.xml \ | /usr/bin/xsltproc /var/www/html/xml_weather/data/weather2html.xsl - |
The output from this command is read into the Perl script and sent to
standard output. Remember that this script was invoked using
XMLHttpRequest by the browser JavaScript code, so this output is sent
back to the browser as a response.
Unlike the method used in Approach 1, no Apache proxy rule is required. The weather_xml2html.cgi script serves as an intelligent proxy, and because this script resides on my server, I avoid the same domain problem described in Part 1 (see Resources.
The server returns the formatted HTML for the weather badge, so the client-side weather badge library has little work to do. Listing 5 shows the JavaScript code required for this second approach.
Listing 5. The
weather_badge_intel_proxy.js implementation of weather_badge()
function weather_badge (nws_id, div_name) {
var ajax = new Ajax
("/cgi-bin/xml_weather/weather_xml2html.cgi?",
nws_id,
"GET",
function (req) {
var div = document.getElementById (div_name);
div.innerHTML = req.responseText;
}
);
ajax.request ();
}
|
All that's required of this version of weather_badge() is to call the
server script (providing it with the proper NWS station ID), then
insert the returned HTML into the target DIV tag using its innerHTML
property.
This approach places the greater share of processing on the Web server. If only a few users are hitting the server, or if the server has lots of memory and cycles to spare, this approach works well.
Likewise, if you know your users are running on underpowered old desktop machines, this approach makes sense. The browser has little to do: just make the request and wait for the server to handle the heavy lifting.
The third weather badge implementation again uses XSLT. This time, XSLT processing occurs in the browser. The same weather2html.xsl XSLT program (Listing 2) is used here. Figure 2 shows the data pipeline used in this approach.
Figure 2. The data pipeline for Approach 3
The major browsers (Microsoft® Windows® Internet Explorer®, Firefox, and Opera) all support
XSLT processing in some form. Firefox and Opera implement the
XSLTProcessor object. Internet Explorer
handles XSLT processing by extending the Document model.
Listing 6 shows the implementation of the Approach 3 weather_badge(). As in
Approach 1, an Apache Web proxy rule must be in place so the
browser can access the NWS server. (Remember that
Ajax applications can only access data from the same server that
delivered the original Web page. See Part 1 for more on this same
domain problem.)
Listing 6. The
weather_badge_cs_xslt.js implementation of weather_badge()
function weather_badge (nws_id, div_name) {
// Get the XML file from the server.
var ajax = new Ajax ("/nws_currobs/" + nws_id + ".xml", "", "GET", null);
ajax.setAsync (false);
ajax.request ();
var xml_doc = ajax.req.responseXML;
// Get the XSLT from the server.
ajax = new Ajax ("/xml_weather/data/weather2html.xsl", "", "GET", null);
ajax.setAsync (false);
ajax.request ();
var xsl_doc = ajax.req.responseXML;
var div = document.getElementById (div_name);
// Use object detection to find out if we have
// Firefox/Mozilla/Opera or IE XSLT support.
if (typeof XSLTProcessor != "undefined") {
var xsl_proc = new XSLTProcessor ();
xsl_proc.importStylesheet (xsl_doc);
var node = xsl_proc.transformToFragment (xml_doc, document);
div.innerHTML = "";
div.appendChild (node);
}
else if (typeof xml_doc.transformNode != "undefined") {
div.innerHTML = xml_doc.transformNode (xsl_doc);
}
else {
div.innerHTML = "XSLT not supported in browser.";
}
}
|
In Approach 1, I needed to retrieve the XML file from the NWS server.
In this new approach, I also need to retrieve the XSLT file from my
own server. The first half of Listing 6 shows the code needed to
bring these new files into the weather_badge() function. These are
returned through XMLHttpRequest as the JavaScript Document objects xml_doc and xsl_doc.
The second half of this weather_badge()
function applies the XSLT
transformation. Because Internet Explorer handles this differently
from the other browsers, I need to figure out which type of browser
I'm running on. I do this with object detection. I use the
JavaScript typeof operator to check for the
existence of the objects needed to perform the transformation.
If XSLTProcessor is defined, I have a Firefox or Opera browser. In
this case, I instantiate a new XSLTProcessor object and import the
XSLT style sheet into that object. Then I transform the XML weather
data using the object's transformToFragment method. This method
returns a document fragment, not the HTML text. This means I can't
insert the results into the DIV tag using innerHTML, as I did in the
first two approaches. The solution is nearly as easy: using appendChild, I can insert the resulting
document fragment into the DOM tree of the page.
If XSLTProcessor is not defined, but the
transformNode method is, then
I assume the JavaScript program is running in Internet Explorer. In
this case, a single line of code is all that I need to perform the
XSLT translation and insert the results into the DIV tag:
div.innerHTML = xml_doc.transformNode (xsl_doc); |
This third approach uses some elements of the first two approaches. An Apache Web proxy brings the XML back to the browser for further processing, and XSLT handles the actual translation from XML to HTML.
To invoke an XSLT processor inside a browser is more computer-intensive than Approach 1, which merely accessed the needed data elements directly from the DOM tree. For this simple example, the extra compute time is unlikely to be noticed by the user. But if the XML is large or the XSLT translation is complex, then the user might notice an unacceptable delay while the browser churns out the results.
The flip side of this is that the code needed to manually trudge through the complex DOM tree of a large XML file can lead to JavaScript code that's difficult to write and more difficult to maintain.
You also need to consider the type of browser and computer your users are running. Are these high-end workstations with memory and processor power to spare? Or are they old, underpowered machines? The answers to these questions determine if complex JavaScript XSLT processing is a good idea for your application.
In the final installment of this series, I'll look at the final approach to this problem—a solution that's radically different from anything I've presented so far. It's so different, you might argue it's not even Ajax. This last solution sidesteps the same domain problem that's vexed these first three solutions, but it raises a whole new set of issues. Then I'll compare the four approaches and touch on a few of the many approaches I didn't present.
| Description | Name | Size | Download method |
|---|---|---|---|
| Sample code for this series | x-xmlajax.zip | 194KB | HTTP |
Information about download methods
Learn
-
XML processing in Ajax, Part 1: Four approaches (Mark Pruett, developerWorks, March 2007): You can solve any programming problem in multiple right ways. Start with the Document Object Model (DOM) tree in this series that looks at four different approaches to create an Ajax weather widget.
-
XML
processing in Ajax, Part 3: JSON and avoiding proxies (Mark Pruett, developerWorks,
March 2007): In this last installment, use a public Web service, JavaScript Object Notation (JSON), and dynamic script tags for the final approach to the weather badge project.
-
What kind of language is
XSLT? (Michael Kay, developerWorks, February 2001, updated April 2005): In this analysis and overview, find a good introduction to XSLT, where the language comes from, what it's good at, and why you should use it.
-
Mastering Ajax, Part
1: Introduction to Ajax (Brett McLaughlin, developerWorks, December 2005): Explore this multi-part series that looks at Ajax, a productive approach to building Web sites, and how it works.
-
Ajax
resource center on IBM developerWorks: Check out this resource on how to create more interactive Web apps with Ajax technologies.
-
IBM XML certification: Find out how you can become an IBM-Certified Developer in XML and related technologies.
-
XML technical library: See the developerWorks XML Zone for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks.
-
developerWorks technical events and webcasts: Stay current with technology in these sessions.
- The technology
bookstore: Browse for books on these and other technical topics.
Get products and technologies
-
IBM trial software: Build your next development project with trial software available for download directly from developerWorks.
Discuss
-
XML zone discussion forums: Participate in any of several XML-related discussions.
-
developerWorks XML zone: Share your thoughts: After you read this article, post your comments and thoughts in this forum. The XML zone editors moderate the forum and welcome your input.
-
developerWorks blogs: Check out these blogs and get involved in the developerWorks community.
Mark Pruett is a Systems Architect at Dominion. He's a contributing author to O'Reilly Media's book Ajax Hacks and author of the O'Reilly Shortcuts Ajax and Web Services and Yahoo! Pipes.
Comments (Undergoing maintenance)





