Re: [xsl] Determining if an optional attribute is present

Subject: Re: [xsl] Determining if an optional attribute is present
From: Evan Lenz <evan@xxxxxxxxxxxx>
Date: Wed, 18 Oct 2006 05:37:36 -0700
Hi Sam,

Here's another approach you might want to consider. Sometimes when considering how to implement something, I ask myself the question, "What is the most direct/simplest way of expressing my intention?" In this case, my intention is to add the default attribute revision="0" to the elements <Method>, <Cal>, and <Blank>.

So I would like to write something as concise and direct as this:

 <xsl:template mode="default-attributes" match="Method | Cal | Blank">
   <xsl:attribute name="revision">0</xsl:attribute>
 </xsl:template>

And it turns out I can do exactly that, provided that I implement the generic identity rule with a hook for adding default attributes:

 <xsl:template match="@*|node()">
   <xsl:copy>
     <xsl:apply-templates mode="default-attributes" select="."/>
     <xsl:apply-templates select="@*|node()"/>
   </xsl:copy>
 </xsl:template>

         <!-- By default, don't add any default attributes -->
         <xsl:template mode="default-attributes" match="*"/>


That way, it's a very simple process to add more default attributes to other elements. Just provide another rule in the "default-attributes" mode.


This pattern can be extended to other aspects of the identity rule that you want to override. For example, the following implementation of the identity rule lets you pinpoint exactly what part you want to override, whether it be an element's contents, its attributes, any default attributes, or an attribute's value. To override something, just supply a rule in the applicable mode ("content", "attributes", "default-attributes", or "attribute-value", respectively). Such hooks allow me to avoid repeating myself later.

 <!-- Identity rule for child nodes -->
 <xsl:template match="node()">
   <xsl:copy>
     <xsl:apply-templates mode="default-attributes" select="."/>
     <xsl:apply-templates mode="attributes" select="."/>
     <xsl:apply-templates mode="content" select="."/>
   </xsl:copy>
 </xsl:template>

         <!-- By default, don't add default attributes -->
         <xsl:template mode="default-attributes match="*"/>

         <!-- By default, process all of the element's attributes -->
         <xsl:template mode="attributes" match="*">
           <xsl:apply-templates select="@*"/>
         </xsl:template>

         <!-- By default, process the element's children -->
         <xsl:template mode="content" match="*">
           <xsl:apply-templates/>
         </xsl:template>


<!-- Identity rule for attributes --> <xsl:template match="@*"> <xsl:attribute name="{name()}" namespace="{namespace-uri()}"> <xsl:apply-templates mode="attribute-value" select="."/> </xsl:attribute> </xsl:template>

         <!-- By default, use the given value of the attribute -->
         <xsl:template mode="attribute-value" match="@*">
           <xsl:value-of select="."/>
         </xsl:template>


I haven't needed to make it more extensible than that, but depending on the task at hand, I could also imagine providing hooks to override the name and namespace URI of each node.


I have a feeling you are going to stick with the implementation that you've already arrived at, because it works, and because (at least with the XSLT processor that you're using), it outputs the attributes in the order that you wanted. But I would underscore Michael and David's point that you should not rely on attributes to appear in any particular order.

Evan Lenz

"How XSLT Works"
http://www.oreilly.com/catalog/xsltpr/chapter/index.html

Current Thread