escutcheon

Detailed Example

Amazon Web Services

This document walks through the XSLT stylesheet that drives the creation of a compact view of an Amazon wishlist as described in the entry here. The stylesheet itself generates calls to Amazon Web Services (AWS) using the document() function.

The document() function is one of the more powerful tools available to XSLT as it allows multiple documents to be injected into the stylesheet processing. Their nodes are matched as if they were part of the original source document; in fact, in this particular example, the source document is a stub with a single empty element and all other elements are obtained by multiple calls to the Amazon Web Services via the document() function.

Download Source Code

All the files in this example are in the ZIP file here.

The Source Document

The source XML document contains only a single empty element:

<amazon/>

Namespaces

Because documents returned by calls to AWS are in a specific namespace, that namespace must be declared in the stylesheet in order to match an element in that namespace.

<xsl:stylesheet version="1.0" xmlns:amazon="http://
webservices.amazon.com/AWSECommerceService/2005-07-26"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="amazon">

Amazon changes the services periodically, but maintains earlier versions. These earlier versions can be called by setting a "Version" query parameter when making the service call.

It no version is specified, the latest version is used and the elements will be in the lastest namespace. Any stylesheet that reference the older namespace will no longer match any of the resulting elements. The bottom line is: make calls using a specific version. This way the code that processes the documents won't suddenly stop working without warning.

Includes and Parameters

Because my stylesheets build up calls to various services, I put the version identifier in a parameter and store it in a standalone file, version.xsl, so it can just be included. The same is true for the developer's key which must be supplied with every call; that is stored in the file key.xsl.

The first step then is to include these two files:

<xsl:include href="version.xsl"/>
<xsl:include href="key.xsl"/>

This particular example also requires that two parameters be passed into the transformer, the id of the wishlist to be displayed and the locale of the particular Amazon site (i.e., the top-level domain of the site, ".com", ".co.uk", ".ca"):

<xsl:param name="id"/>
<xsl:param name="locale"/>
fleuron

Templates

Root

To kick off processing, the first XSLT template to be matched selects on the <amazon> element, in the default namespace. This template sets up the core HTML layout and generates a call to AWS to determine the total number of pages in the wishlist. I'll skip the trivial HTML layout and describe the web service call:

<xsl:variable name="total-pages"
  select="document($pages)//amazon:TotalPages"/>

The URL of the call to AWS is built up using the concat() string concatination function and stored in the variable named "pages". This is passed to the document() function which makes the actual call to AWS and returns a document. As can be seen in the example above, standard XPath references can select specific elements from that returned document. In this case, the <TotalPages> element is select and stored in the variable named "total-pages". This will be used later to iterate through all the pages in the wishlist. Note that any reference to an element or attribute returned from AWS must prefixed with the declared namespace, in this case "amazon".

In order to show all the items of a wishlist in a single page, each wishlist page must be requested. This can be done by making multiple calls using the document() function, requesting each page from 1 up to the total-pages calculated previously.

Because XSLT is a functional language (such as Lisp) as opposed to a procedural language (such as Java or C) looping must be done using recursion. Here a recursive template is explicitly called using the "total-pages" count and a seed value of "1", i.e., the first page:

<xsl:call-template name="counter">
  <xsl:with-param name="total"
      select="number($total-pages)"/>
  <xsl:with-param name="page" select="1"/>
</xsl:call-template>
fleuron
Recursive Iterator

This template again uses the document() function to request a specific wishlist page from AWS. It then calls itself, this time incrementing the page by 1. It will continue recursively requesting pages until the "total" count has been reached:

<xsl:template name="counter">
  <xsl:param name="total"/>
  <xsl:param name="page"/>

  <xsl:variable name="lookup"
    select="concat($lookup, '&amp;ProductPage=', $page)

  <xsl:if test="$page <= $total">
    <xsl:apply-templates select="document($lookup)"/>
    <xsl:call-template name="counter">
      <xsl:with-param name="total" select="$total"/>
      <xsl:with-param name="page" select="$page + 1"/>
    </xsl:call-template>
  </xsl:if>

</xsl:template>

What is interesting about these calls to the document() function is that the returned document elements can be matched just like elements from the normal source document.

fleuron
Items

In this case we are interested in matching <amazon:Item> elements, i.e., wishlist <Item> elements from the "amazon" namespace.

<xsl:template match="amazon:Item">

  <tr>
    <xsl:apply-templates select="amazon:ItemAttributes"/>
  </tr>

</xsl:template>

As can be seen in the stylesheet source, there is a bit of work to determine if the wishlist item has already been purchased or not, but otherwise it's only purpose is to setup a table row and then to call another template selecting the <amazon:ItemAttributes> to fill in the columns with title, author and price info.

fleuron
Item Attributes

This template again matches elements generated by calls to the document function, so the match attribute references the "amazon" namespace:

<xsl:template match="amazon:ItemAttributes">
  <!--  see source for details -->
</xsl:template>