Menu

Putting Attributes to Work

April 3, 2002

Bob DuCharme

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 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.

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 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.

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

Automating Stylesheet Creation

Appreciating Libxslt

Push, Pull, Next!

Seeking Equality

The Path of Control

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.