XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

Adding New Elements and Attributes

August 02, 2000

In the first "Transforming XML" column, we saw how an XSLT style sheet can instruct an XSLT processing program to copy, delete, and rename elements being copied from the input to the output. Another common task is the addition of new elements and attributes to the output. Whether you're converting an element to a new attribute, an attribute to a new element, or supplying either with a function's return value or a hardcoded string, you can choose between a quick simple way and a more complex and powerful way to add both elements and attributes to your output.

Adding New Elements

An XSLT processor's main job is to look through a style sheet for the various specialized elements from the XSLT namespace and to execute the instructions specified by those elements on the tree where the input document is stored in memory. When the processor finds elements from outside of the XSLT namespace in any of a style sheet's templates, it passes them along to the result tree and eventually to the output document. We call these "literal result elements."

This makes it easy to add new elements to your output documents: simply add elements from outside of the XSLT namespace inside of the appropriate templates. (If you really want to output elements from the XSLT namespace--for example, to generate style sheets as output--you'll need XSLT's namespace-alias element.)

The following XSLT style sheet demonstrates this. When its first template rule sees a poem element, it outputs its contents with xsl:apply-templates and surrounds those contents with ode tags, effectively renaming the element from poem to ode. However, after that ode start-tag and before the xsl:apply-template element that shows where to put the poem element's contents, it also outputs two new child elements of this ode element: an author element and a year element. The author element has a hard-coded value of "John Milton" that stays the same for all author elements output by this template rule. The year element uses the xsl:value-of element to output the value of the poem element's year attribute.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        version="1.0">

  <xsl:template match="poem">
    <ode>
      <author>John Milton</author>
      <year><xsl:value-of select="@year"/></year>
      <xsl:apply-templates/>
   </ode>
  </xsl:template>

  <xsl:template match="verse">
    <verse><xsl:apply-templates/></verse>
  </xsl:template>

</xsl:stylesheet>

The result is the conversion of a document like the following

<poem year="1667" type="epic">
<verse line="1">Of Man's First Disobedience, and the Fruit</verse>
<verse line="2">Of that Forbidden Tree, whose mortal taste</verse>
</poem>

to this:

<?xml version="1.0" encoding="utf-8"?>
<ode><author>John Milton</author><year>1667</year>
<verse>Of Man's First Disobedience, and the Fruit</verse>
<verse>Of that Forbidden Tree, whose mortal taste</verse>
</ode>

The xsl:element element offers a more flexible way to create new elements for your output. The following style sheet has the same effect as the previous one, but it uses xsl:element elements instead of literal result elements. The XSLT processor looks at their name attributes to see what to call the new elements, and it then outputs the appropriate start- and end-tags for those elements.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="1.0">

  <xsl:template match="poem">
    <ode>
      <xsl:element name="author">John Milton</xsl:element>
      <xsl:element name="year"><xsl:value-of select="@year"/>
      </xsl:element>
      <xsl:apply-templates/>
   </ode>
  </xsl:template>

  <xsl:template match="verse">
    <verse><xsl:apply-templates/></verse>
  </xsl:template>

</xsl:stylesheet>

This name attribute is the key to the advantage of xsl:element elements over literal result elements. It offers greater flexibility, letting you create element names dynamically by concatenating strings, calling functions, or by retrieving element content or attribute values from elsewhere in the document. For example, the following style sheet is similar to the one above except that instead of converting the input poem element into an ode element using an ode literal result element, it uses an xsl:element element to convert it into an element that uses the poem element's type attribute value for a name.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="1.0">

  <xsl:template match="poem">
    <xsl:element name="{@type}">
      <author>John Milton</author>
      <year><xsl:value-of select="@year"/></year>
      <xsl:apply-templates/>
   </xsl:element>
  </xsl:template>

  <xsl:template match="verse">
    <verse><xsl:apply-templates/></verse>
  </xsl:template>

</xsl:stylesheet>

When applied to the poem XML document above, which has a type value of "epic" in its poem document element, it creates an epic element:

<?xml version="1.0" encoding="utf-8"?>
<epic><author>John Milton</author><year>1667</year>
<verse>Nine times the Space that measures Day and Night</verse>
<verse>To mortal men, he with his horrid crew</verse>
</epic>

Using this same technique for specifying element names, a template can add elements to a result tree without even knowing an element's name in advance.

If the xsl:element element is so powerful, why bother with literal result elements at all? Because their simplicity makes them easier to use. To output the author and year elements in the examples above, literal result elements are fine.

Adding New Attributes

Adding new attributes to your output can be as simple as adding new elements to your output with literal result elements: in the style sheet, put them in a literal result element's start-tag.

For example, the following style sheet

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:template match="thnad">
 <widget status="done" hue="{@color}" number="{amount}" pos="{position() + 6}"/>
</xsl:template>
</xsl:stylesheet>

renames a thnad element to widget, its color attribute to hue, and it adds three new attributes, turning this document

<thnad color="red">
  <amount>5</amount>
</thnad>

into this:

<?xml version="1.0" encoding="utf-8"?>
<widget status="done" hue="red" number="5" pos="7"/>

The single template rule reads a thnad element and outputs it as a widget element with four attributes that get their values from four very different sources:

  • The first is just the hardcoded string of text "done" that will appear that way in all widget elements that get added to the result tree.

  • The hue attribute takes its value from the color attribute value of the input thnad attribute.

  • The number attribute has the contents of the thnad element's amount child as its value.

  • The pos attribute makes a function call and does a little math with it to create its result value: it adds 6 to the value of the element node's position within its parent node.

Note that the template rule uses curly braces for all but the first attribute. This tells the XSLT processor that their contents are expressions to be evaluated and not plain text like the "done" string in the first attribute. We call these attribute values inside of curly braces "attribute value templates." If the second attribute specification didn't have the curly braces and said hue="@color", that's exactly what would have shown up in the output:

<?xml version="1.0" encoding="utf-8"?>
<widget status="done" hue="@color" number="5" pos="7"/>

Instead of putting the attribute specifications right into the element start-tags in the style sheet, you can specify them using the xsl:attribute element. The following style sheet does the same thing as the last one but uses this specialized element for each attribute specification:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="1.0">

<xsl:template match="thnad">
 <xsl:element name="widget">
  <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="pos"><xsl:value-of select="position() + 6"/></xsl:attribute>
 </xsl:element>
</xsl:template>

</xsl:stylesheet>

Like the names of the elements added to the result tree with the xsl:element element, the names of the attributes added with the xsl:attribute elements are specified with a name attribute, giving you more flexibility than attributes added as part of literal result elements like in the previous style sheet example. For example, a name attribute value of "xyz{position()}" in an xsl:attribute element's start-tag tells the XSLT processor to output an attribute whose name is the string "xyz" followed by the position value of the input element.

You can specify the value of these inserted attributes in an xsl:attribute element either as a literal string--for example, "done" as the value of the status attribute above--or by generating text using the xsl:value-of element, as with the other attribute values in the example.

To show which element these attributes belong to, the xsl:attribute elements are inside of the xsl:element element that adds a widget element to the result tree when the XSLT processor finds a thnad element in the input. As children of an xsl:stylesheet element, an xsl:attribute wouldn't make any sense, because there would be no way to figure out what element type the attribute belonged to.

If you need to re-use a group of attributes in multiple elements of your output (for example, formatting instructions that apply to several HTML element types upon output), you can store them in an xsl:attribute-set element and then reference the collection with the use-attribute-sets attribute of xsl:element.

For example, the following shows a group of xsl:attribute elements in an xsl:attribute-set element named "widgetAttrs" and an xsl:element element that incorporates those attributes with a value of "widgetAttrs" for its use-attribute-sets attribute. Note how this attribute name is in the plural; 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.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="1.0">

<xsl:attribute-set name="widgetAttrs">
  <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="pos"><xsl:value-of select="position() + 6"/></xsl:attribute>
</xsl:attribute-set>

<xsl:template match="thnad">
 <xsl:element name="widget" use-attribute-sets="widgetAttrs">
  <xsl:attribute name="prodmgr">BD</xsl:attribute>
   <xsl:apply-templates/>
 </xsl:element>
</xsl:template>

</xsl:stylesheet>

If an xsl:element element names some attributes with an xsl:use-attribute-sets attribute, it can still use the xsl:attribute element to add more, just as the xsl:template example above adds a "prodmgr" attribute to the "widgetAttrs" set referenced by its template.

We've seen that there are simple, easy ways to add elements and attributes to your output as well as more complex, powerful alternatives. Some of the demonstrations of the more powerful approaches took advantage of other XSLT features not explained here, but we'll examine their use more closely in future "Transforming XML" columns.