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
@year
value in the firstxsl:value-of
element'sselect
attribute starts thewine
literal result element with the value of thewinery
element'syear
attribute. -
The
../@grape
value in the secondxsl:value-of
element'sselect
attribute selects the value of thegrape
attribute in thewinery
element's parent element. -
The
xsl:text
element 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-of
element with the function call "name()" as the value of itsselect
attribute. 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-of
element with the abbreviation "." as the value of itsselect
attribute. 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
author
attribute to the result tree'sline
elements. Along with the four attributes from thexsl:attribute
element, this additional attribute makes a total of five attributes for theline
elements being added to the result tree. -
The second overrides the
hue
attribute value set in thelineAttrs
attribute set, because anxsl:attribute
attribute 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.