Putting Attributes to Work
April 3, 2002
Once an XML document is read into a source tree, where an XSLT stylesheet can get a hold of it, there's a lot that the stylesheet can do with the source document's attribute values. This month we'll look at how to convert attributes to result tree elements, how to grab attribute values for more general use, how to test attribute values for specific values, and how to define groups of attributes for easier re-use in multiple places in the result tree.
Converting Attributes to Elements
When an XSLT processor sees an xsl:value-of element, it evaluates the
expression in its select attribute and replaces the element with the result of
the evaluation. For example, after evaluating
<xsl:value-of select="2+2"/> in a template it adds the string "4"
to the corresponding place on the result tree and in any output document created from
that
result tree.
This select attribute can take an XPath expression as a value, which gives you
a lot more flexibility. For example, you can refer to an attribute value of the element
serving as the context node or even of some other element. Surround one or more of
these
xsl:value-of elements with an xsl:element element or with the
tags of a literal result element (that is, an element from outside the XSLT namespace
that
an XSLT processor will pass to the result tree unchanged) and you'll add a new element
to
the result tree whose content was an attribute value in the source tree.
For example, the following template rule
<!-- xq173.xsl: converts xq174.xml into xq175.xml --> <xsl:template match="winery"> <wine> <xsl:value-of select="@year"/><xsl:text> </xsl:text> <xsl:value-of select="../@grape"/> </wine> </xsl:template>
will convert the winery element in the following
<wine grape="Cabernet Sauvignon"> <winery year="1998">Los Vascos</winery> </wine>
into this wine element:
<wine>1998 Cabernet Sauvignon</wine>
(Sample stylesheets, input documents, and output documents shown in this article are available in this zip file.)
Here's how the template creates the new wine element:
-
The
@yearvalue in the firstxsl:value-ofelement'sselectattribute starts thewineliteral result element with the value of thewineryelement'syearattribute. -
The
../@grapevalue in the secondxsl:value-ofelement'sselectattribute selects the value of thegrapeattribute in thewineryelement's parent element. -
The
xsl:textelement inserts a space between them.
A wine start- and end-tag pair around the whole thing make a well-formed XML
element for the result tree.
See last December's
"Transforming XML" column for more on this use of the xsl:text element to
add the single space.
Getting Attribute Values and Names
Attributes are nodes of the source tree just like elements are. The most popular way
to get
a particular attribute value is to use the @ prefix, an abbreviation of the
attribute:: axis specifier.
For example, to get the value of the color attribute of the para
element in this short document,
<para color="blue" flavor="mint" author="bd"> Here is a paragraph.</para>
the first xsl:value-of element in the para element's template
rule has "@color" as the value of its select attribute:
<!-- xq178.xsl: converts xq181.xml into xq183.txt --> <xsl:template match="para"> Color: <xsl:value-of select="@color"/> <!-- List the attribute names and values. --> <xsl:for-each select="@*"> attribute name: <xsl:value-of select="name()"/> attribute value: <xsl:value-of select="."/> </xsl:for-each> </xsl:template>
The value "blue" shows up in that part (the first line) of the result.
Color: blue attribute name: color attribute value: blue attribute name: flavor attribute value: mint attribute name: author attribute value: bd
The other result tree lines are added to the result tree by the template's
xsl:for-each element. This instruction goes through all of the
para element's attributes, listing the name and value of each. While the
template's first xsl:value-of element has "@color" as the value of its
select attribute to show that it wants the attribute with that name, the
xsl:for-each element has "@*" as its select attribute value to
show that it wants attributes of any name.
Inside the loop, the template adds four nodes to the result tree for each attribute:
-
The text node "attribute name: " to label the text after it.
-
An
xsl:value-ofelement with the function call "name()" as the value of itsselectattribute. This adds the name of the node to the result tree; in a loop iterating through attribute nodes, it adds the attribute name. -
The text node "attribute value:" to label the text after it.
-
An
xsl:value-ofelement with the abbreviation "." as the value of itsselectattribute. This XPath abbreviation forself::node()gives you the value of the current node -- in this case, the attribute value.
The result of applying this stylesheet to the short source document shown earlier shows each attribute's name and value.
Testing for Attribute Existence and for Specific Attribute Values
Sometimes, when an attribute is optional for a particular element, you want to test
whether
it was specified or not. Other times you want to check whether it has a particular
value.
For example, let's say that when we process the following document, we're not sure
whether
its para element has flavor or font attributes, and
while we know that it has an author attribute, we need to check whether
author has a value of "jm" or not.
<para color="blue" flavor="mint" author="jm"> Fallen cherub, to be weak is miserable</para>
The following template rule adds a short text message to the result tree for each attribute it finds.
<!-- xq182.xsl: converts xq181.xml into xq183.txt --> <xsl:template match="para"> <!-- Is there a flavor attribute? --> <xsl:if test="@flavor"> There is a flavor attribute </xsl:if> <!-- Is there a font attribute? --> <xsl:if test="@font"> There is a font attribute </xsl:if> <!-- Does author="jm"? --> <xsl:if test="@author = 'jm'"> Author equals "jm" </xsl:if> </xsl:template>
The template rule has three xsl:if elements. An xsl:if element
that only has a node name as the value of its test attribute is testing whether
that node exists or not; in the example, the first xsl:if element tests whether
the para element has a flavor attribute. The para
element in the example source document does, so the string "There is a flavor attribute"
shows up in the result.
There is a flavor attribute Author equals "jm"
The second xsl:if element checks for a font attribute. Because
the para element doesn't have one, the string "There is a font attribute" does
not show up in the result. The stylesheet's third xsl:if element goes a step
further than merely checking for the existence of an attribute node: it checks whether
it
has the specific value "jm". (Note how "jm" is enclosed in single quotation marks
in the
stylesheet because the xsl:if element's test attribute value is
enclosed in double quotation marks.) Because that is the attribute's value, the string
'Author equals "jm"' does show up in the result.
Reusing Groups of Attributes
|
Also in Transforming XML |
|
If you need to re-use the same group of attributes in different element types in the
same
result document (for example, to include revDate, author, and
docID attributes in your result document's chapter,
sidebar, and caption elements), you can store them in an
xsl:attribute-set element and then reference the collection with a
use-attribute-sets attribute of the xsl:element instruction.
The following shows a group of xsl:attribute elements in an
xsl:attribute-set element named "lineAttrs" and an xsl:element
instruction that incorporates those attributes with a value of "lineAttrs" for its
use-attribute-sets attribute. Note the plural form of the name
use-attribute-sets -- the value can list more than one attribute set, as long
as spaces separate the names and all the names represent existing
xsl:attribute-set elements.
<!-- xq185.xsl: converts xq168.xml into xq187.xml --> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output omit-xml-declaration="yes"/> <xsl:attribute-set name="lineAttrs"> <xsl:attribute name="status">done</xsl:attribute> <xsl:attribute name="hue"> <xsl:value-of select="@color"/> </xsl:attribute> <xsl:attribute name="number"> <xsl:value-of select="amount"/> </xsl:attribute> <xsl:attribute name="sourceElement"> <xsl:text>src</xsl:text><xsl:value-of select="generate-id()"/> </xsl:attribute> </xsl:attribute-set> <xsl:template match="verse"> <xsl:element name="line" use-attribute-sets="lineAttrs"> <!-- Add one more attribute to the ones in the "lineAttrs" group and override the value of another. --> <xsl:attribute name="author">BD</xsl:attribute> <xsl:attribute name="hue">NO COLOR</xsl:attribute> <xsl:apply-templates/> </xsl:element> </xsl:template> </xsl:stylesheet>
Running this with the following source document
<verse color="red"> <amount>5</amount> </verse>
produces this result (the generate-id() function may create a different value
with your XSLT processor):
<line status="done" hue="NO COLOR" number="5" sourceElement="srcb2a" author="BD"> 5 </line>
In addition to incorporating a named attribute set, the xsl:element
instruction in the example above has two more xsl:attribute elements that
customize the line element's set of attributes:
-
The first adds an
authorattribute to the result tree'slineelements. Along with the four attributes from thexsl:attributeelement, this additional attribute makes a total of five attributes for thelineelements being added to the result tree. -
The second overrides the
hueattribute value set in thelineAttrsattribute set, because anxsl:attributeattribute setting takes precedence over an attribute group attribute setting.
These two xsl:attribute instructions show that when use an attribute set,
you're not stuck with that set -- you can customize it for the element type using
it (in
this case, verse) by adding new attributes or overriding some of the attributes
it declares.
Attributes can play a role in almost any aspect of XSLT development, and this column has outlined just a few of the ways to take advantage of them. See my book XSLT Quickly for even more on ways to take advantage of attribute values in your source documents.