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
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
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
an XSLT processor will pass to the result tree unchanged) and you'll add a new element
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>
<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
@yearvalue in the first
selectattribute starts the
wineliteral result element with the value of the
../@grapevalue in the second
selectattribute selects the value of the
grapeattribute in the
wineryelement's parent element.
xsl:textelement inserts a space between them.
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
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
element in this short document,
<para color="blue" flavor="mint" author="bd"> Here is a paragraph.</para>
xsl:value-of element in the
para element's template
rule has "@color" as the value of its
<!-- 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
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.
xsl:value-ofelement with the function call "name()" as the value of its
selectattribute. 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.
xsl:value-ofelement with the abbreviation "." as the value of its
selectattribute. This XPath abbreviation for
self::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.
Sometimes, when an attribute is optional for a particular element, you want to test
it was specified or not. Other times you want to check whether it has a particular
For example, let's say that when we process the following document, we're not sure
para element has
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
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
para element has a
flavor attribute. The
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"
xsl:if element checks for a
font attribute. Because
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
has the specific value "jm". (Note how "jm" is enclosed in single quotation marks
stylesheet because the
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
result document (for example, to include
docID attributes in your result document's
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
The following shows a group of
xsl:attribute elements in an
xsl:attribute-set element named "lineAttrs" and an
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
<!-- 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
instruction in the example above has two more
xsl:attribute elements that
line element's set of attributes:
The first adds an
authorattribute to the result tree's
lineelements. Along with the four attributes from the
xsl:attributeelement, this additional attribute makes a total of five attributes for the
lineelements being added to the result tree.
The second overrides the
hueattribute value set in the
lineAttrsattribute set, because an
xsl:attributeattribute setting takes precedence over an attribute group attribute setting.
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
verse) by adding new attributes or overriding some of the attributes
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.