An Example Application

Now that we've learned about CSS, XSL, XSLT, and Web publishing in general, let's go through some more difficult examples. In this section we will build a proof-of-concept Web site, based entirely on XML, for a hypothetical company called Noverant.

Figure 5-8 A DHTML version of the navigation tree.

The Requirements

Noverant is an online CD reseller. They want to build a Web site that has the following characteristics.

  1. Navigation is important. They want buyers to be able to quickly focus in on a particular genre or artist. First and foremost, the site needs a tree-based navigator that will allow users to browse the CD catalog without performing multiple keyword searches. The navigator needs to be dynamic becasue the Noverant CD inventory changes hourly.
  2. The site should be easy to maintain. Noverant doesn't have a team of Web developers on staff (that's why they hired you!) that can make complicated changes to HTML. They do, however, have a number of content authors who need to be able to make frequent changes to the listed content listed.
  3. Noverant wants to be able to display live news items related to the music industry. They plan to purchase a subscription to a news feed service where they can obtain articles over the Internet. Noverant wants to embed these articles in the site's content.
  4. The site should accommodate Internet Explorer 4.0 and later versions and Netscape Navigator 4.71 and later versions. This requirement is particularly difficult to achieve in light of Noverant's dynamic navigation requirement.
  5. The site should be extensible. Should Noverant choose to change its corporate image, it wants to be able to adapt its site using minimal effort. New color schemes, layout strategies, browser technologies, and other changes should not require a complete rebuild of the site.
  6. The site should be scaleable. Noverant anticipates a monthly increase of 15 percent in user traffic as they ramp up their marketing efforts over the next six months. The site should be designed to accommodate this increase in load.
  7. The site should be inexpensive. Noverant operates under a tight budget and doesn't have much cash to spend on expensive content servers and third-party software. It wants a Web site built using off-the-shelf components that can get the job done and last until it becomes a publically traded company.

Requirement Analysis

Although Noverant's requirements are aggressive, they are not impossible (especially by using XML). Let's break the requirements down one by one and think about how our knowledge of XML and XSLT might give us a head start.

Navigation

We've already learned how to use XML to build a generic navigation tree that can be transformed into a Windows Explorer-like UI. The interface side of this requirement should be easy; in fact, we already have an XSLT to help render the HTML. The difficulty will reside in accommodating Noverant's ever-changing CD catalog. We'll need to design the XML navigation structure so that it can be built dynamically whenever the page is loaded. One solution is to change the static Listing 5-1 document into ASP.

Maintainability

XML can help us enable Noverant's Web authors to change the content of the site without damaging the presentation logic. We can define a document schema for them whereby they create simple XML content documents that adhere to a specific structure that can be transformed by us into formatted content on the site. This is another example of how separating content from presentation can cleanly divide job roles in a company.

News Feeds

The Newspaper Association of America created an XML schema to define a uniform distribution format for news content. The News Industry Text Format (NITF) specification appeared in 1998 and defines how content providers, such as MSNBC, should distribute their information in XML format. The NITF specification makes it easy for sites such as the Noverant project to have a news feed. By implementing a single XSLT that is compatible with NITF, we can support almost any news feed Noverant is likely to purchase.

Cross-Browser Compatibility

The issue of cross-browser compatibility has plagued Web developers for years. How can you take advantage of special features in Browser A and support Browser B without developing two different Web sites? XSLT, that's how! One approach is to develop a "skins" model for rendering content. Under this model each page request will cause two documents to be generated: 1) a generic XML document containing all of the content that can be displayed, and 2) a browser-specific or user-specific XSLT (the skin), or both, that will handle translating the content into appropriate markup for the display device. Different skins can be programmed to convert the same content into Internet Explorer pages, Navigator pages, or WML pages for hand-held devices. The implication of the skins model, however, is the existence of a controller page that handles locating XML documents and marrying them with the appropriate skin.

Extensibility

The extensibility XML offers, particularly when using a skins-based browsing model, is obvious. Skins can be modified easily to accommodate new color schemes, layout requirements, content schemas, browser advancements, and so forth. You can even design multiple skins with radically different attitudes for each browser. That way a user could change his or her skin and get a whole new Web site.

Scalability

XML opens up a number of different techniques, in addition to traditional hardware clustering approaches, to help with application scalability. The MSXML 3.0 parser includes a free-threaded model to speed concurrent access to the XML processor. The software also improves server-to-server HTTP communication for quicker exchange of XML, SOAP, or both documents. Web developers can also distribute the load of building and transforming documents across different machines. In Noverant's case the logic that constructs the XML navigation document might be off-loaded to another server. Other strategies include caching documents and compiling XSLTs to speed processing time.

Cost

All of the technologies mentioned in this chapter come free with Microsoft Windows NT and Windows 2000 servers or can be obtained without cost from the Microsoft Web site. No additional software components will be necessary.

The Controller

As mentioned previously, we will need to use a page controller design pattern to pull off the XSLT skin model we want to use. The controller will intercept all Web requests and route information according to our architecture. The flow of control will follow these steps:

  1. The client will send an HTTP request to the controller.asp page and will send a URL argument name view (for example, http://www.noverant.com/controller.asp?view=navigation/navigation_frame) in addition to any other arguments the page might require.
  2. The controller will parse the view argument and will call, via HTTP, a page with the name View.asp (for example, http://www.noverant.com/navigation/navigation_frame.asp). The actual location of the ASP page is arbitrary as long as the controller can locate the page based on the view argument alone.
  3. The called ASP, or view helper, will be responsible for constructing a well-formed and valid XML document to be processed by a skin. The document must conform to the Noverant document schema we define. The ASP can use function calls, database requests, HTTP requests, or any other means to populate the XML document.
  4. The controller will use a heuristic to determine an appropriate skin to apply to the XML returned by the view helper. This controller will certainly consider the browser type (Internet Explorer, Navigator, PDA, and so forth), but might also look at user preferences or other settings before deciding on a skin.
  5. Upon choosing a skin, the controller will load the associated XSLT style sheet, process the view XML, and retrieve the transformed document.
  6. The controller will return the result document to the client.

Figure 5-9 shows how the Controller interacts with the page content, the XSLT skin, and the client.

Figure 5-9 A flow-control diagram of the controller architecture.

A simple implementation of the controller is in controller.asp. The first task the controller must perform is to retrieve the view argument from the page request and use this argument to complete the URL to the appropriate helper page. The name of the view helper follows the convention of View.asp.

 // Get the view argument view  = Request.QueryString("view") base  = Request.ServerVariables("SERVER_NAME")+
Replace(Request.ServerVariables("URL"),"controller.asp","") if Request.ServerVariables("HTTPS") = "ON" Then base = "https://"+base else base = "http://"+base end if xmlurl = base+view+".asp?"

The controller will also need to forward all URL arguments, such as a particular artist, to the helper page. For each item in Request.QueryString, the followig applies:

 if arguments <> "" then arguments = arguments+"&" itemvalue = Request.QueryString(item) arguments = arguments+item+"="+itemvalue Next // Append the argument string to the xmlurl xmlurl = xmlurl+arguments 

Now the controller needs to construct a URL to the XSLT, or skin, responsible for converting the XML data making up the page into the HTML to be returned to the browser. The skin can be chosen based on a variety of factors, including user preference, and browser type. For simplicity's sake the controller will hard-code the skin reference.

 // Contstruct a URL to the appropriate skin // This implementation uses only the Internet Explorer 5.0 skin // Other skins could be used based on the browser type xslurl = base+"xslt/ie5.xsl" 

Now that the controller has built URLs to the appropriate helper page and XSLT, it needs to load the source XML and style XSLT into the MSXML2.DOMDocument control. The controller must also check for any parsing errors that might occur along the way.

 // Load the XML // A production implementation should use the multi-threaded version 
//of the DomDocument. error = false Set source = Server.CreateObject("MSXML2.DOMDocument") source.async = false source.load(xmlurl) Set e = source.parseError if e.errorCode <> 0 then Response.write(e.reason) if e.line > 0 Then Response.write(e.line) Response.write(" ") Response.write(e.linepos) Response.write(" ") Response.write(e.srcText) end if error = true end if // Load the XSLT Set style = Server.CreateObject("MSXML2.DOMDocument") style.async = false style.load(xslurl) Set e = style.parseError Response.write(e.reason) if e.errorCode <> 0 Then Response.write(e.reason) if e.line > 0 Then Response.write(e.line) Response.write(" ") Response.write(e.linepos) Response.write(" ") Response.write(e.srcText) end if error = true end if

If no errors are found the controller transforms the XML and sends the result document back to the client.

 if error = false Then xmlresult = source.transformNode(style) end if Response.write(xmlresult)  %> 

Building the Pages

Now that we have a working version of the controller, let's begin building the actual view helper pages. The goal of the view helper pages is to delegate the construction of different XML components to smaller, more specialized ASPs. This strategy promotes code reusability and extensibility and makes the site much easier to maintain over the long haul. We will cover how to build different kinds of helper pages that are tuned for specific tasks and how to incorporate these pages into the controller architecture. Finally we will create specific helper pages for displaying the Noverant CD database.

The CD Database

Noverant's CD catalog will be stored in a large XML "database." The CD descriptions are contained in an XML file in the db/ directory. An excerpt from this file follows.

 <cds> <cd> <id>152</id> <title>Tallis/Mass for four voices</title> <artist>Oxford Camerata cond. Jeremy Summerly</artist> <genre>classical, choral</genre> </cd> <cd>  <id>1</id>  <title>Great Organ Works/Bach JS</title>  <artist>Koopman</artist>  <genre>classical, organ</genre> </cd> ... </cds> 

All queries against this minidatabase will take the form of XSLT and DOM access. Noverant will likely migrate this information into a more traditional database engine, such as Microsoft's SQL Server, to improve performance as its volume grows. All needs to be changed to accommodate such a transition is an adjustment to the view helper pages to connect to the database, instead of the CD XML document, while constructing the view. Therefore, instead of logic to read and parse the CDs.xml text file, the database-friendly version of the view helper will contain SQL commands to query CD-related tables.

The Noverant Schema

Before we dive into coding the helper pages we will take some time to define the document model that pages in the site will follow by creating an XML schema that describes the site. The significant elements that the site will use are:

  1. <document> The <document> element will form the root node of all content to be shown on the site. You can think of a document node in our document model as serving the same role as the <html> tag in HTML documents.
  2. <navigation> The <navigation> element provides a simple mechanism for defining a hierarchical navigation tree. The XML will contain descriptions, images, hyperlink references, and a list of children elements.
  3. <doclet> A <doclet> will form the primary building block for content on the site. Noverant's content developers will be able to create small <doclets> to define blocks of content to be transformed and incorporated into a page request. A <doclet> will contain a heading, links to one or more images, text, and one or more text excerpts.
  4. <newsitem> A <newsitem> will represent a news story that should appear on the site. The <newsitems> can be authored by Noverant content developers or might be pulled from one or more online news content providers.
  5. <list> The <list> element will represent a list of related items. <list> elements will provide the data model for tables shown in the site. Although the tables will be simple in the first version of the site, the transformation behind them might eventually be extended to allow sorting, grouping, filtering, and other, more advanced reporting features.

    A preliminary version of the schema and a discussion of its elements follows. Note that the schema shown here is an XDR (XML-Data Reduced Language) schema. The MSXML 4.0 parser will support the new XSD (XML Schema Definition) language developed by the W3C. Listing 5-5 shows the schema used for the Noverant Web site.

    Listing 5-5 schema.xml: Describes all the elements and attributes that can be included in a valid Noverant Web page.

     <?xml version="1.0"?> <Schema name="noverant_dom" xmlns="urn:schemas-microsoft-com:xml-data" xmlns:dt="urn:schemas-microsoft-com:datatypes"> <ElementType name="header" content="textOnly" model="closed"/> <ElementType name="image" content="textOnly" model="closed"/> <ElementType name="p" content="textOnly" model="closed"/> <ElementType name="excerpt" content="textOnly" model="closed"/> <ElementType name="id" content="textOnly" model="closed"/> <ElementType name="text" content="eltOnly" model="closed"> <element type="p" minOccurs="0" maxOccurs="*"/> </ElementType> <ElementType name="doclet" order="many" content="eltOnly" model="closed"> <element type="id" minOccurs="0" maxOccurs="1"/> <element type="header" minOccurs="0" maxOccurs="1"/> <element type="image" minOccurs="0" maxOccurs="*"/> <element type="excerpt" minOccurs="0" maxOccurs="*"/> <element type="text" minOccurs="1" maxOccurs="1"/> </ElementType> <ElementType name="title" content="textOnly" model="closed"/> <ElementType name="hl1" content="textOnly" model="closed"/> <ElementType name="hl2" content="textOnly" model="closed"/> <ElementType name="person" content="textOnly" model="closed"/> <ElementType name="bytag" content="textOnly" model="closed"/> <ElementType name="location" content="textOnly" model="closed"/> <ElementType name="story.date" content="textOnly" model="closed"/> <ElementType name="head" order="many" content="mixed" model="open"> <element type="title" minOccurs="0" maxOccurs="1"/> </ElementType> <ElementType name="body.end" content="mixed" model="open"/> <ElementType name="byline" order="many" content="mixed" model="open"> <element type="person" minOccurs="0" maxOccurs="*"/> <element type="bytag" minOccurs="0" maxOccurs="*"/> </ElementType> <ElementType name="dateline" order="many" content="mixed" model="open"> <element type="location" minOccurs="0" maxOccurs="*"/> <element type="story.date" minOccurs="0" maxOccurs="*"/> </ElementType> <ElementType name="headline" order="many" content="eltOnly" model="closed"> <element type="hl1" minOccurs="1" maxOccurs="1"/> <element type="hl2" minOccurs="0" maxOccurs="1"/> </ElementType> <ElementType name="body.head" order="many" content="eltOnly" model="closed"> <element type="headline" minOccurs="1" maxOccurs="1"/> <element type="byline" minOccurs="0" maxOccurs="*"/> <element type="dateline" minOccurs="0" maxOccurs="1"/> </ElementType> <ElementType name="body.content" order="many" content="mixed" model="open"> <element type="p" minOccurs="1" maxOccurs="*"/> </ElementType> <ElementType name="body" order="many" content="eltOnly" model="closed"> <element type="body.head" minOccurs="1" maxOccurs="1"/> <element type="body.content" minOccurs="1" maxOccurs="1"/> <element type="body.end" minOccurs="0" maxOccurs="1"/> </ElementType> <ElementType name="newsitem" order="many" content="eltOnly" model="closed"> <element type="head" minOccurs="1" maxOccurs="1"/> <element type="body" minOccurs="1" maxOccurs="1"/> </ElementType> <ElementType name="column.name" content="textOnly" model="closed"/> <ElementType name="column.description" content="textOnly" model="closed"/> <ElementType name="column" order="many" content="eltOnly" model="closed"> <element type="column.name" minOccurs="1" maxOccurs="1"/> <element type="column.description" minOccurs="1" maxOccurs="1"/> </ElementType> <ElementType name="columns" order="many" content="eltOnly" model="closed"> <element type="column" minOccurs="1" maxOccurs="*"/> </ElementType> <ElementType name="property.name" content="textOnly" model="closed"/> <ElementType name="property.value" content="textOnly" model="closed"/> <ElementType name="property" order="many" content="eltOnly" model="closed"> <element type="property.name" minOccurs="1" maxOccurs="1"/> <element type="property.value" minOccurs="1" maxOccurs="1"/> </ElementType> <ElementType name="item" order="many" content="eltOnly" model="closed"> <element type="property" minOccurs="0" maxOccurs="*"/> </ElementType> <ElementType name="items" order="many" content="eltOnly" model="closed"> <element type="item" minOccurs="0" maxOccurs="*"/> </ElementType> <ElementType name="list" order="many" content="eltOnly" model="closed"> <element type="title" minOccurs="1" maxOccurs="1"/> <element type="columns" minOccurs="1" maxOccurs="1"/> <element type="items" minOccurs="1" maxOccurs="1"/> </ElementType> <ElementType name="description" content="textOnly" model="closed"/> <ElementType name="openImage" content="textOnly" model="closed"/> <ElementType name="closedImage" content="textOnly" model="closed"/> <ElementType name="href" order="many" content="textOnly" model="closed"> <AttributeType name="target" /> </ElementType> <ElementType name="children" order="many" content="eltOnly" model="closed"> <element type="navitem" minOccurs="0" maxOccurs="*"/> </ElementType> <ElementType name="navitem" order="many" content="eltOnly" model="closed"> <element type="description" minOccurs="1" maxOccurs="1"/> <element type="image" minOccurs="0" maxOccurs="1"/> <element type="openImage" minOccurs="0" maxOccurs="1"/> <element type="closedImage" minOccurs="1" maxOccurs="1"/> <element type="href" minOccurs="0" maxOccurs="1"/> <element type="children" minOccurs="0" maxOccurs="1"/> </ElementType> <ElementType name="navigation" order="many" content="eltOnly" model="closed"> <AttributeType name="type" /> <element type="description" minOccurs="1" maxOccurs="1"/> <element type="image" minOccurs="0" maxOccurs="1"/> <element type="openImage" minOccurs="0" maxOccurs="1"/> <element type="closedImage" minOccurs="1" maxOccurs="1"/> <element type="href" minOccurs="0" maxOccurs="1"/> <element type="navitem" minOccurs="0" maxOccurs="*"/> </ElementType> <ElementType name="document" order="many" content="eltOnly" model="closed"> <element type="navigation" minOccurs="0" maxOccurs="*"/> <element type="doclet" minOccurs="0" maxOccurs="*"/> <element type="newsitem" minOccurs="0" maxOccurs="*"/> <element type="list" minOccurs="0" maxOccurs="*"/> </ElementType> </Schema> 

    The first view helper we will write is the navigation view helper. This helper will be responsible for querying the CD database and building a <navigation> tree using ASP script. Noverant wants the <navigation> tree to organize the CD contents by genre and then by artist. When a user clicks on an artist they should see all albums by that artist on the main page.

    We will need to use a combination of XSLT and direct DOM manipulation to construct the tree. XSLT has no easy facility for grouping nodes by a common element, such as genre, and eliminating duplicates. Thus, we will use DOM manipulation to create an unsorted tree of genres and artists and then use XSLT to sort the branches. Listing 5-6 shows the nav_by_genre.asp view helper that is responsible for creating the CD <navigation> tree.

    Listing 5-6 nav_by_genre.asp: ASP page that creates a <navigation> tree out of the genres and artists found in the CD database.

     <% xmlurl = Server.MapPath("../db/CDs.xml") Set source = Server.CreateObject("MSXML2.DOMDocument") source.async = false source.load(xmlurl) Set e = source.parseError if e.errorCode <> 0 then Response.write(e.reason) if e.line > 0 Then  Response.write(e.line)  Response.write(" ")  Response.write(e.linepos)  Response.write(" ")  Response.write(e.srcText) end if error = true end if Set genres = Server.CreateObject("MSXML2.DOMDocument") Set children = genres.createElement("children") genres.appendChild(children) Set cds = source.getElementsByTagName("cd") for x = 0 To cds.Length - 1 Set cd = cds.item(x) genre = cd.selectSingleNode("genre").text if genre <> "" then Set existing = genres.selectNodes("children/navitem[description = '"+genre+"']") if existing.length = 0 then Set navelem = genres.createElement("navitem") Set descelem = genres.createElement("description") Set desctext = genres.createTextNode(genre) Set imgelem = genres.createElement("image") Set imgtext = genres.createTextNode("images/item.gif") Set oielem = genres.createElement("openImage") Set oitext = genres.createTextNode("images/open.gif") Set cielem = genres.createElement("closedImage") Set citext = genres.createTextNode("images/closed.gif") imgelem.appendChild(imgtext) oielem.appendChild(oitext) cielem.appendChild(citext) descelem.appendChild(desctext) navelem.appendChild(imgelem) navelem.appendChild(oielem) navelem.appendChild(cielem) navelem.appendChild(descelem) children.appendChild(navelem) artist = cd.selectSingleNode("artist").text Set artists = genres.createElement("children") Set artistnavelem = genres.createElement("navitem") Set artistdescelem = genres.createElement("description") Set artistdesctext = genres.createTextNode(artist) Set artistimgelem = genres.createElement("image") Set artistimgtext = genres.createTextNode("images/item.gif") artistimgelem.appendChild(artistimgtext) artistdescelem.appendChild(artistdesctext) artistnavelem.appendChild(artistdescelem) artistnavelem.appendChild(artistimgelem) artists.appendChild(artistnavelem) navelem.appendChild(artists) else artist = cd.selectSingleNode("artist").text Set existingartists = genres.selectNodes("children/ navitem[description = '"+genre+"']/children/navitem[description = '"+artist+"']") if existingartists.length = 0 then Set artists = existing.item(0).selectSingleNode("children") Set artistnavelem = genres.createElement("navitem") Set artistdescelem = genres.createElement("description") Set artistdesctext = genres.createTextNode(artist) Set artistimgelem = genres.createElement("image") Set artistimgtext = genres.createTextNode("images/item.gif") artistimgelem.appendChild(artistimgtext) artistdescelem.appendChild(artistdesctext) artistnavelem.appendChild(artistdescelem) artistnavelem.appendChild(artistimgelem) artists.appendChild(artistnavelem) end if end if end if next xslurl = Server.MapPath("nav_by_genre.xsl") Set style = Server.CreateObject("MSXML2.DOMDocument") style.async = false style.load(xslurl) Set e = style.parseError Response.write(e.reason) if e.errorCode <> 0 Then Response.write(e.reason) if e.line > 0 Then  Response.write(e.line)  Response.write(" ")  Response.write(e.linepos)  Response.write(" ")  Response.write(e.srcText) end if end if xmldata = genres.transformNode(style) %> <navigation type="tree"> <description>Noverant</description> <openImage>images/open.gif</openImage> <navitem> <description>Welcome to Noverant</description> <image>images/item.gif</image> <href target="main">controller.asp?view=main</href> </navitem> <navitem> <description>CD's by genre</description> <openImage>images/open.gif</openImage> <closedImage>images/closed.gif</closedImage> <image>images/item.gif</image> <children> <%= xmldata %> </children> </navitem> </navigation> 

    Listing 5-7 shows the nav_by_genre.xsl transformation. The nav_by_genre.asp script, shown in Listing 5-6, uses this transformation to sort the tree by genre and then by artist.

    Listing 5-7 Nav_by_genre.xsl: Sorts a collection of <navitems> by their description elements.

     <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="html"/> <xsl:template match="/children"> <children> <xsl:apply-templates select="navitem"> <xsl:sort select="description"/> </xsl:apply-templates> </children> </xsl:template> <xsl:template match="navitem"> <navitem> <description><xsl:value-of select="description"/></description> <openImage><xsl:value-of select="openImage"/></openImage> <closedImage><xsl:value-of select="closedImage"/></closedImage> <image><xsl:value-of select="image"/></image> <xsl:if test="children"> <children> <xsl:apply-templates select="children/navitem"> <xsl:sort select="description"/> </xsl:apply-templates> </children> </xsl:if> <xsl:if test="not(children)"> <href target="main">controller.asp?view=artist/list&amp; artist=<xsl:value-of select="description"/></href> </xsl:if> </navitem> </xsl:template> </xsl:stylesheet> 

    Now that we have built a helper to construct a valid <navigation> tree, we're almost finished. Recall that, according to our Noverant schema, a <navigation> element needs a <document> element to contain it before it can be successfully transformed. We will create a front-end helper to wrap the results of the helper we just wrote by using a <document> tag. So why not include the <document> tag in the original helper? Doing so will preclude us from including <navigation> elements in the same document with other elements. The creation of a proxy helper page makes the navigation of the site more extensible. For example, we could later write a "nav_by_artist" helper that is conditionally called by the navigation helper. Listing 5-8 shows the Navigation_frame.asp script, which is responsible for loading the correct navigation scheme.

    Listing 5-8 Navigation_frame.asp: A proxy to call the correct navigation helper.

     <document> <% Server.execute("nav_by_genre.asp") %> </document> 

    We're done with the navigation helper! Now we need to write the XSLT to render our tree. Our first skin for the site will be designed specifically for the Internet Explorer 5.0 browser. This skin will pull together all the transformation logic for <documents>, <navigationtrees>, <doclets>, <lists>, and <newsitems>.

    The document template will initialize the result document with the proper HTML header, scripts, and style elements. It also creates a container for all of the Noverant document elements by building a table and calling the <xsl:apply-templates> element for all its children. Notice that the document template controls the positioning of the <navigation>, <newsitem>, <doclet>, and <list> child elements.

     <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="1.0"> <xsl:output method="html"/> <xsl:template match="document"> <html> <head> <script language="JavaScript"> function expand(divid) { eval("closed_"+divid).style.display = 'none'; eval("open_"+divid).style.display = ''; } function collapse(divid) { eval("open_"+divid).style.display = 'none'; eval("closed_"+divid).style.display = ''; } </script> <style> a:visited {background-color:#C7D2DE; color:black;
    text-decoration: underline} a:link {background-color:#C7D2DE; color:black;
    text-decoration: underline} a:active {background-color:activecaption; color:captiontext; text-decoration: underline} a:hover {background-color:#C7D2DE; color:black;
    text-decoration: underline} .clsHeading {font-family: verdana; color: black; font-size: 11; font-weight: 800;} .clsEntryText {padding-top: 2px; font-family: verdana; color: black;
    font-size: 11; font-weight: 400; background-color:#C7D2DE;} .clsWarningText {font-family: verdana; white-space:pre; color: #B80A2D;
    font-size: 11; font-weight: 600; width:550;
    background-color:#EFE7EA;} .clsCopy {font-family: verdana; color: black; font-size: 11;
    font-weight: 400; background-color:#FFFFFF;} .doclet {margin: 5px; padding:8px; display:block;
    border:solid 1px; padding-top:2px; font-family:verdana; color:black;
    font-size: 13px; font-weight: 400; background-color:white;} .docletHeading {padding-top: 2px; font-family: verdana;
    color: #003366; font-size: 20; font-weight: 400;} .docletImage {display:inline; padding-top: 2px; font-family: verdana;
    color: black; font-size: 11; font-weight: 400;} .docletText {display:inline; padding-top: 2px; font-family: verdana;
    color: black; font-size: 12; font-weight: 400;} .docletExcerpt {display:inline; padding-top: 2px;
    font-family: verdana; color: black; font-size: 11; font-weight: 400;} .newsItem {margin:5px; vertical-align: top; padding:8px;
    display:inline; border:solid 1px; padding-top:2px; font-family:
    verdana; color:black; font-size: 13px; font-weight: 400; background-color:white; width:300px} .newsHl1 {padding-top: 2px; font-family: verdana; color: #003366;
    font-size: 20; font-weight: 400;} .newsHl2 {padding-top: 2px; font-style:italic; font-family: verdana;
    color: black; font-size: 14; font-weight: 400;} .newsBlPerson {padding: 2px; font-family: verdana; color: black;
    font-size: 14; text-decoration:underline; font-weight: 400; text-align:right;} .newsBlTag {padding: 2px; font-family: verdana; color: black;
    font-size: 11; font-weight: 600; text-align:right;} .newsDlLoc {display:inline; font-family: verdana; color: black;
    font-size: 12; font-weight: 600;} .newsDlDate {display:inline; font-family: verdana; color: black;
    font-size: 12; font-weight: 600;} .newsBody {font-family: times; color: black; font-size: 12px;
    font-family:verdana; font-weight: 400; text-align:justify;} .list {margin:5px; vertical-align: top; padding:8px; display:inline; border:solid 1px; padding-top:2px; font-family: verdana; color:black; font-size: 13px; font-weight: 400; background-color:white; width:600px} .listHeading {padding-top: 2px; font-family: verdana; color: #003366;
    font-size: 20; font-weight: 400;} .listText {display:inline; padding-top: 2px; font-family: verdana;
    color: black; font-size: 14; font-weight: 400;} </style> </head> <body style="background-color:#C7D2DE;"> <table width="100%" cellspacing="5"> <tr> <td nowrap="yes"> <xsl:apply-templates select="navigation"/> </td> <td align="left" valign="top"> <xsl:apply-templates select="doclet"/> <xsl:apply-templates select="newsitem"/> <xsl:apply-templates select="list"/> </td> <td> </td> </tr> </table> </body> </html> </xsl:template>

    The next transformation we define is the <list> transformation. The following XSLT will convert a generic list structure into an HTML table.

     <xsl:template match="list"> <div  > <table> <tr> <td colspan="{count(columns/column)}"><font class= "docletHeading"><xsl:value-of select="title"/></font></td> </tr> <tr> <xsl:for-each select="columns/column"> <th align="left"><font ><b><xsl:value-of select="description"/>
    </b></font></th> </xsl:for-each> </tr> <xsl:for-each select="items/item"> <tr> <xsl:for-each select="property"> <td><font ><xsl:value-of select= "property.value"/></font></td> </xsl:for-each> </tr> </xsl:for-each> </table> </div> </xsl:template>

    The <newsitem> transformation will build a news article format similar to those found on professional news sites. Notice that all of the styling is controlled by embedded CSS code.

     <xsl:template match="newsitem"> <div  > <div ><xsl:value-of select="body/body.head/ headline/hl1"/></div> <div ><xsl:value-of select="body/body.head/ headline/hl2"/></div> <div ><xsl:value-of select="body/body.head/byline/person"/></div> <div ><xsl:value-of select="body/body.head/byline/bytag"/></div> <div ><xsl:value-of select="body/body.head/dateline/location"/>, </div> <div ><xsl:value-of select="body/body.head/dateline/story.date"/></div> <div > <xsl:for-each select="body/body.content/p"><xsl:value-of select="."/><p/></xsl:for-each> </div> </div> </xsl:template> 

    The <doclets> will take the form of small, self-contained blocks of content. The transformation for this type of element is relatively straightforward.

     <xsl:template match="doclet"> <div  > <div ><xsl:value-of select="header"/></div> <div > <xsl:if test="image"> <img height="100" src="/books/4/456/1/html/2/images/{image}" align="left"/> </xsl:if> <xsl:for-each select="text/p"><xsl:value-of select="."/><p/>
    </xsl:for-each> </div> </div> </xsl:template> </xsl:stylesheet>

    Finally, we need to include the <navigation> transformations into our style sheet. Listing 5-9 shows the IE5 skin in its entirety. The document transformation arranges all the possible components of a Noverant Web page. This transformation is responsible for high-level layout and formatting.

    Listing 5-9 IE5.xsl: First skin for the Internet Explorer 5 browser, covering all of the elements found in the Noverant document schema.

     <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="html"/> <xsl:template match="document"> <html> <head> <script language="JavaScript"> function expand(divid) {  eval("closed_"+divid).style.display = 'none';  eval("open_"+divid).style.display = ''; } function collapse(divid) {  eval("open_"+divid).style.display = 'none';  eval("closed_"+divid).style.display = ''; } </script> <style> a:visited {background-color:#C7D2DE; color:black; 
    text-decoration: underline} a:link {background-color:#C7D2DE; color:black;
    text-decoration: underline} a:active {background-color:activecaption; color:
    captiontext; text-decoration: underline} a:hover {background-color:#C7D2DE; color:black;
    text-decoration: underline} .clsHeading {font-family: verdana; color: black; font-size: 11;
    font-weight: 800;} .clsEntryText {padding-top: 2px; font-family: verdana;
    color: black; font-size: 11; font-weight: 400; background-color:#C7D2DE;} .clsWarningText {font-family: verdana; white-space:pre;
    color: #B80A2D; font-size: 11; font-weight: 600; width:550; background-color:#EFE7EA;} .clsCopy {font-family: verdana; color: black; font-size: 11;
    font-weight: 400; background-color:#FFFFFF;} .doclet {margin: 5px; padding:8px; display:block;
    border:solid 1px; padding-top:2px; font-family:verdana; color:black;
    font-size: 13px; font-weight: 400; background-color:white;} .docletHeading {padding-top: 2px; font-family: verdana;
    color: #003366; font-size: 20; font-weight: 400;} .docletImage {display:inline; padding-top: 2px;
    font-family: verdana; color: black; font-size: 11; font-weight: 400;} .docletText {display:inline; padding-top: 2px; font-family: verdana;
    color: black; font-size: 12; font-weight: 400;} .docletExcerpt {display:inline; padding-top: 2px;
    font-family: verdana; color: black; font-size: 11; font-weight: 400;} .newsItem {margin:5px; vertical-align: top; padding:8px;
    display:inline; border:solid 1px; padding-top:2px; font-family:
    verdana; color:black; font-size: 13px; font-weight: 400; background-color:white; width:300px} .newsHl1 {padding-top: 2px; font-family: verdana; color: #003366;
    font-size: 20; font-weight: 400;} .newsHl2 {padding-top: 2px; font-style:italic; font-family: verdana;
    color: black; font-size: 14; font-weight: 400;} .newsBlPerson {padding: 2px; font-family: verdana; color: black;
    font-size: 14; text-decoration:underline; font-weight: 400; text-align:right;} .newsBlTag {padding: 2px; font-family: verdana; color: black;
    font-size: 11; font-weight: 600; text-align:right;} .newsDlLoc {display:inline; font-family: verdana; color: black;
    font-size: 12; font-weight: 600;} .newsDlDate {display:inline; font-family: verdana; color: black;
    font-size: 12; font-weight: 600;} .newsBody {font-family: times; color: black; font-size: 12px;
    font-family:verdana; font-weight: 400; text-align:justify;} .list {margin:5px; vertical-align: top; padding:8px;
    display:inline; border:solid 1px; padding-top:2px; font-family:
    verdana; color:black; font-size: 13px; font-weight: 400; background-color:white; width:600px} .listHeading {padding-top: 2px; font-family: verdana;
    color: #003366; font-size: 20; font-weight: 400;} .listText {display:inline; padding-top: 2px; font-family: verdana;
    color: black; font-size: 14; font-weight: 400;} </style> </head> <body style="background-color:#C7D2DE;"> <table width="100%" cellspacing="5"> <tr> <td nowrap="yes"> <xsl:apply-templates select="navigation"/> </td> <td align="left" valign="top"> <xsl:apply-templates select="doclet"/> <xsl:apply-templates select="newsitem"/> <xsl:apply-templates select="list"/> </td> <td> </td> </tr> </table> </body> </html> </xsl:template> <xsl:template match="list"> <div > <table> <tr> <td colspan="{count(columns/column)}"><font class= "docletHeading"><xsl:value-of select="title"/></font></td> </tr> <tr> <xsl:for-each select="columns/column"> <th align="left"><font ><b><xsl:value-of
    select="description"/></b></font></th> </xsl:for-each> </tr> <xsl:for-each select="items/item"> <tr> <xsl:for-each select="property"> <td><font >
    <xsl:value-of select="property.value"/></font></td> </xsl:for-each> </tr> </xsl:for-each> </table> </div> </xsl:template> <xsl:template match="newsitem"> <div > <div ><xsl:value-of select="body/body.head/ headline/hl1"/></div> <div ><xsl:value-of select="body/body.head/ headline/hl2"/></div> <div ><xsl:value-of select="body/body.head/byline/person"/></div> <div ><xsl:value-of select="body/body.head/byline/bytag"/></div> <div ><xsl:value-of select="body/body.head/dateline/location"/>, </div> <div ><xsl:value-of select="body/body.head/dateline/story.date"/></div> <div > <xsl:for-each select="body/body.content/p"><xsl:value-of select="."/><p/></xsl:for-each> </div> </div> </xsl:template> <xsl:template match="doclet"> <div > <div ><xsl:value-of select="header"/></div> <div > <xsl:if test="image"> <img height="100" src="/books/4/456/1/html/2/images/{image}" align="left"/> </xsl:if> <xsl:for-each select="text/p"><xsl:value-of select="."/> <p/></xsl:for-each> </div> </div> </xsl:template> <xsl:template match="navigation[@type='tree']"> <div style="padding-left:5px" > <xsl:if test="openImage"><img src="/books/4/456/1/html/2/{openImage}"/>&#160;</xsl:if> <xsl:if test="href"><a href="{href}"> <xsl:if test="href/@target"><xsl:attribute name= "target"><xsl:value-of select="href/@target"/></xsl:attribute> <xsl:if> <xsl:value-of select="description"/></a> </xsl:if> <xsl:if test="not(href)"><xsl:value-of select="description"/> </xsl:if> <xsl:apply-templates select="navitem"/> </div> </xsl:template> <xsl:template match="navitem"> <xsl:choose> <xsl:when test="children"> <xsl:variable name="childcnt" select="count(.//navitem)"/> <div > <xsl:for-each select="ancestor::navitem"> <xsl:choose> <xsl:when test="count(following-sibling::navitem) > 0"> <img src="/books/4/456/1/html/2//xmlbook/images/line.gif"/> </xsl:when> <xsl:otherwise> <img src="/books/4/456/1/html/2//xmlbook/images/nothing.gif"/> </xsl:otherwise> </xsl:choose> </xsl:for-each> <img src="/books/4/456/1/html/2//xmlbook/images/folder_plus.gif" style= "cursor:hand" onClick="expand('{generate-id()}')"/> <xsl:if test="closedImage"><img src="/books/4/456/1/html/2/{closedImage}" />&#160;</xsl:if> <xsl:if test="href"><a href="{href}"> <xsl:if test="href/@target"><xsl:attribute name= "target"><xsl:value-of select="href/@target"/></xsl:attribute> </xsl:if> <xsl:value-of select="description"/></a> </xsl:if> <xsl:if test="not(href)"><xsl:value-of select= "description"/></xsl:if> (<xsl:value-of select="$childcnt"/> artist<xsl:if test="$childcnt != 1">s</xsl:if>) </div> <div style="display:none"> <xsl:for-each select="ancestor::navitem"> <xsl:choose> <xsl:when test="count(following-sibling::navitem) > 0"> <img src="/books/4/456/1/html/2//xmlbook/images/line.gif"/> </xsl:when> <xsl:otherwise> <img src="/books/4/456/1/html/2//xmlbook/images/nothing.gif"/> </xsl:otherwise> </xsl:choose> </xsl:for-each> <img src="/books/4/456/1/html/2//xmlbook/images/folder_minus.gif" style= "cursor:hand" onClick="collapse('{generate-id()}')"/> <xsl:if test="openImage"><img src="/books/4/456/1/html/2/{openImage}"/>&#160; </xsl:if> <xsl:if test="href"><a href="{href}"> <xsl:if test="href/@target"><xsl:attribute name= "target"><xsl:value-of select="href/@target"/></xsl:attribute> </xsl:if> <xsl:value-of select="description"/></a> </xsl:if> <xsl:if test="not(href)"><xsl:value-of select= "description"/></xsl:if> (<xsl:value-of select="$childcnt"/> artist<xsl:if test="$childcnt != 1">s</xsl:if>) <xsl:apply-templates select="children"/> </div> </xsl:when> <xsl:otherwise> <div > <xsl:for-each select="ancestor::navitem"> <xsl:choose> <xsl:when test="count(following-sibling::navitem) > 0"> <img src="/books/4/456/1/html/2//xmlbook/images/line.gif"/> </xsl:when> <xsl:otherwise> <img src="/books/4/456/1/html/2//xmlbook/images/nothing.gif"/> </xsl:otherwise> </xsl:choose> </xsl:for-each> <img src="/books/4/456/1/html/2//xmlbook/images/divider.gif" onClick= "collapse('{generate-id()}')"/> <xsl:if test="image"><img src="/books/4/456/1/html/2/{image}"/>&#160;</xsl:if> <xsl:if test="href"><a href="{href}"> <xsl:if test="href/@target"><xsl:attribute name= "target"><xsl:value-of select="href/@target"/></xsl:attribute> </xsl:if> <xsl:value-of select="description"/></a> </xsl:if> <xsl:if test="not(href)"><xsl:value-of select= "description"/></xsl:if> </div> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet>

    Now let's put the code to the test by calling the controller! Figure 5-10 shows the nav_by_genre view helper.

    Figure 5-10 A view of the Noverant navigation tree.

    The Doclet View Helper

    Now we need to write a view helper that will contain the doclet XML. Doclets can be embedded directly in a helper document or can be retrieved dynamically from an external doclet database. For the time being Noverant is willing to write its doclets manually inside a doclet view helper.

    This view helper will be (thankfully!) much easier to write than the navigation helper. Noverant's design decision to write doclets manually means the helper can take the form of the doclet code itself. An example follows:

     <doclet> <id>main</id> <image>cd.jpg</image> <header>Welcome to Noverant</header> <excerpt>Do you like tapes or CD's?</excerpt> <text> <p> Headquartered in Raleigh, NC, Noverant.com is revolutionizing the 
    way you buy music. We buy directly from the world's leading CD
    publishers to bring the hottest CDs to your door fast! </p> <p> Our award-winning e-commerce site, Noverant.com, leverages the
    power of XML to help you comb through our vast selection of musical
    offerings. Can't find what you're looking for? No sweat! Just drop us a
    line and we'll hunt it down for you. </p> <p> Noverant.com guarantees that you will have your purchase in hand
    within 24 hours of ordering. If you don't we'll send you another one
    for free. Also, make sure to check out our wishlist feature. Don't you
    hate it when you can think of a hundred CDs you want at home, only to
    draw a blank when you walk in the record store? With our wishlist you
    can keep track of the albums you want to buy before you buy them! When
    you're ready to spend some cash you'll know exactly what to get! </p> </text> </doclet>

    The News View Helper

    The news view helper will, for now, be as easy to write as the doclet view helper. Valid news items can be written directly into the news view helper. Once Noverant signs their contracts with their news content providers, however, the news view helper will need to be extended to handle remote content. If the content provider has SOAP or FTP services configured to distribute news electronically, the handler can connect to the machine dynamically and download the content. Depending on the format of the vendor's news items, the helper might need to massage the data into Noverant's news format through the use of XSLTs.

    The Artist View Helper

    The artist view helper will be called whenever someone clicks on an artist in the site. The helper can expect to be passed an artist argument by the controller. The helper will query the CD database via an XPath expression to extract all the CDs matching the selected artist. Before we can look up any information about the artist, the CDs.xml database must be loaded. This is accomplished by loading the file into the DOMDocument control.

     <% xmlurl = Server.MapPath("../db/CDs.xml") Set source = Server.CreateObject("MSXML2.DOMDocument") source.async = false source.load(xmlurl) Set e = source.parseError if e.errorCode <> 0 then Response.write(e.reason) if e.line > 0 Then  Response.write(e.line)  Response.write(" ")  Response.write(e.linepos)  Response.write(" ")  Response.write(e.srcText) end if error = true end if 

    Extracting all of the CDs for a particular artist is a simple matter for the view helper. The DOMDocument control's selectNodes( ) method allows any XPath expression to be run on the source document.

     artist = Request.QueryString("artist") Set cds = source.selectNodes("cds/cd[artist='"+artist+"']")  %> 

    Now that the relevant CDs have been extracted from the database, the view helper must prepare the XML list structure to be returned to the controller. An ASP For-loop will be used to iterate over the collection generated by the XPath expression to create each entry in the list document. Listing 5-10 shows the artist helper page responsible for assembling a Noverant XML document for a specific artist.

    Listing 5-10 List.asp: Retrieving a musician's CDs and constructing an XML result list with the artist view helper page.

     <document> <doclet>...</doclet> <list> <title> CD Titles by <%= artist %> </title> <columns> <column> <column.name>title</column.name> <column.description>CD Title</column.description> </column> </columns> <items> <% For x = 0 to cds.length - 1 Set cd = cds.item(x) title = cd.selectSingleNode("title").text %> <item> <property> <property.name>title</property.name> <property.value> <%= title %> </property.value> </property> </item> <% Next %> </items> </list> </document>  <% xmlurl = Server.MapPath("../db/CDs.xml") Set source = Server.CreateObject("MSXML2.DOMDocument") source.async = false source.load(xmlurl) Set e = source.parseError if e.errorCode <> 0 then Response.write(e.reason) if e.line > 0 Then  Response.write(e.line)  Response.write(" ")  Response.write(e.linepos)  Response.write(" ")  Response.write(e.srcText) end if error = true end if artist = Request.QueryString("artist") Set cds = source.selectNodes("cds/cd[artist='"+artist+"']")  %> <document> <doclet>...</doclet> <list> <title> CD Titles by <%= artist %> </title> <columns> <column> <column.name>title</column.name> <column.description>CD Title</column.description> </column> </columns> <items> <% For x = 0 to cds.length - 1 Set cd = cds.item(x) title = cd.selectSingleNode("title").text %> <item> <property> <property.name>title</property.name> <property.value> <%= title %> </property.value> </property> </item> <% Next %> </items> </list> </document> 

    Bringing it All Together

    Next we will build a page to combine our doclets and news items together into a single document to be displayed on the site. This page will be easy to author; all it needs to do is rely on the view helpers that we've already written.

     <document> <% Server.execute("doclet/doclet.asp") %> <% Server.execute("news/news.asp") %> </document> 

    The main page can be tested by issuing an HTTP request directly to the controller and referencing the view in this URL string:

     http://localhost/xmlbook/chapter 8/noverant/controller.asp?view=main 

    Figure 5-11 is the result.

    Figure 5-11 The front page for the site, including a <doclet> and a <newsitem> transformation.

    This looks great! All that's left for the front page is to create a logo and put together a frame set, as shown in Listing 5-11 through Listing 5-13.

    Listing 5-11 index.htm: This file contains the frame set that organizes the layout of the site.

     <html> <title>Noverant Inc</title> <frameset rows="55,*" frameborder="no" framespacing="0"> <frame name="masthead" src="/books/4/456/1/html/2/masthead.htm" marginwidth="0"  marginheight="0" scrolling="no" frameborder="no" framespacing="0"> <frame name="body" src="/books/4/456/1/html/2/main.htm" marginwidth="0" marginheight="0" 
    scrolling="no" frameborder="no"> </frameset> </html>

    Listing 5-12 masthead.htm: Contains an image to be displayed at the top of all pages in the site.

     <html> <body> <img src="/books/4/456/1/html/2/images/logo.gif"> </body> </html> 

    Listing 5-13 main.htm: Divides frame into two vertical sections, one for global navigation and the other for site content.

     <html> <title>Noverant Inc</title> <frameset cols="20%,*" frameborder="yes" framespacing="0"> <frame name="nav" src="/books/4/456/1/html/2/controller.asp?view=navigation/navigation_frame" 
    marginwidth="5" marginheight="0" scrolling="auto" frameborder="no" framespacing="0"> <frame name="main" src="/books/4/456/1/html/2/controller.asp?view=main" marginwidth="0"
    marginheight="0" scrolling="auto" frameborder="no"> </frameset> </html>

    Figure 5-12 The full front page, with separate frames for the masthead, navigation tree, and main body.

    Figure 5-12 shows how it looks when we load Listing 5-11 in Internet Explorer. Figure 5-13 shows the artist view helper in action.

    Figure 5-13 A list of CD titles, displayed by clicking on the name by the selected musician.



XML Programming
XML Programming Bible
ISBN: 0764538292
EAN: 2147483647
Year: 2002
Pages: 134

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net