Recently I’ve been creating xml output for mobile using XSLT. While doing this I ran into a couple of complex problems because of the visual layout I wanted to achieve.
In one XSLT template I create a list of items by iterating over a collection of nodes. In that same template I also want to output several attributes on the node collections parent node. This template also contains a nested template to output the individual list items.
To get the parent node is simple. We can use the “ancestor” axis to indicate what the parent node is:
select="ancestor::Project"
Now you might think that to get an attribute “Client” from the parent, you would simply use square braces “[]” and you’d be wrong. select="ancestor::Project[Client]" or select="ancestor::Project[@Client]" will error out. Instead the XPath definition should indicate the attribute as a node of its parent: select="ancestor::Project/@Client". That’s because an attribute is in fact an attribute node of the element it belongs to. Just make sure you indicate in your XPath that it is an attribute node by using the “@” prefix.
<xsl:template match="Technologies">
<div class="Technologies">
<p><strong><xsl:value-of select="ancestor::Project/@Client"/></strong></p>
<p><xsl:value-of select="ancestor::Project/@Project"/></p>
<ol class="TechnologyItems">
<xsl:apply-templates select="Technology"/>
</ol>
</div>
</xsl:template>
Problem solved. Next problem: I wanted to grab the first two of a set of images that had local paths and no alt values in the xml. Creating an absolute path from the relative one is fairly simple. Instead of “select”, I’ll use “match” while iterating over the collection of image nodes. I need to limit the match to only the first two images. I can do this by using postion() and testing whether the current image is less than or equal to two (note the escaped less than sign because a quoted attribute cannot contain a < character):
<xsl:template match="Images/Image[position() <= 2]">
Next I construct the absolute path to the image from an attribute on the image node. This is really straightforward. Use curly braces around the attribute name:
<img src="http://MySite.com/{@ImageSource}"
Here’s how the template should look so far:
<xsl:template match="Images/Image[position() <= 2]">
<img src="http://MySite.com/{@ImgSource}" class="Illustration" />
</xsl:template>
Finally I had to figure out a way to create an alt value for each image. At first I looked into parsing the attribute value to extract a substring with which to create the alt value. However, since I’m outputting the value directly into a tag’s attribute, I couldn’t take advantage of XSLT advanced text manipulating functions. So I came up with a poor man’s alt alternative (pun intended). Since the images in this case were relevant to a project, I would append some text indicating which image they were in the image collection for that project. Better than nothing. To find which image I’m on I can once again use position(), but inside of curly braces:
<img src="http://MySite.com/{@ImgSource}" alt="Image {position()}: " />
Finally I complete the alt’s value with the value of the image collection’s Project ancestor in curly quotes:
{ ancestor::Project/@Project}
The complete image template is:
<xsl:template match="Images/Image[position() <= 2]">
<img src="http://MySite.com/{@ImgSource}" alt="Image {position()}: { ancestor::Project/@Project}" class="Illustration" />
</xsl:template>
If you enjoy XSLT and have your own favorite tricks and tips, leave me a comment.