Putting Attributes to Work
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.
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 first xsl:value-of
element's select attribute starts the wine literal
result element with the value of the winery element's
year attribute.
The ../@grape value in the second
xsl:value-of element's select attribute selects the
value of the grape attribute in the winery 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.
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 its select 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 its select attribute. 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 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.
|
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's
line elements. Along with the four attributes from the
xsl:attribute element, this additional attribute makes a
total of five attributes for the line elements being added to
the result tree.
The second overrides the hue attribute value set in the
lineAttrs attribute set, because an xsl: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.
XML.com Copyright © 1998-2006 O'Reilly Media, Inc.