JPM
JPM

Reputation: 2066

XSL Process attributes in an order

I need to process attributes (of any node that has them) in a particular order. For example:

<test>
    <event 
        era  ="modern"
        year ="1996"
        quarter = "first"
        day = "13"
        month= "January"
        bcad ="ad"
        hour ="18"
        minute = "23"
        >The big game began.</event>
    <happening 
        era  ="modern"
        day = "18"
        bcad ="ad"
        month= "February"
        hour ="19"
        minute = "24"
        >The big game ended.</happening>
    <other>Before time existed.</other>
</test>

This

<xsl:template match="test//*">
    <div>
        <xsl:apply-templates select="@*" />
        <xsl:apply-templates />
    </div>
</xsl:template>

<xsl:template match="@*">
    <span class="{name()}">
        <xsl:value-of select="."/>
    </span>
</xsl:template>

would format things as I need them. That is, I'd get

<div><span class="era">modern</span>
     <span class="year">1996</span>
     <span class="quarter">first</span>
     <span class="day">13</span>
     <span class="month">January</span>
     <span class="bcad">ad</span>
     <span class="hour">18</span>
     <span class="minute">23</span>The big game began.</div>
<div><span class="era">modern</span>
     <span class="day">18</span>
     <span class="bcad">ad</span>
     <span class="month">February</span>
     <span class="hour">19</span>
     <span class="minute">24</span>The big game ended.</div>
<div>Before time existed.</div>

(though without the newlines I've added here for legibility).

But the order of the attributes wouldn't (necessarily) be right.

To fix that, I could change <xsl:apply-templates select="@*" /> to <xsl:call-template name="atts" /> and add a template that applies templates in the needed order, like this:

<xsl:template match="test//*">
    <div>
        <xsl:call-template name="atts" />
        <xsl:apply-templates />
    </div>
</xsl:template>

<xsl:template name="atts">
    <xsl:apply-templates select="@era" />
    <xsl:apply-templates select="@bcad" />
    <xsl:apply-templates select="@year" />
    <xsl:apply-templates select="@quarter" />
    <xsl:apply-templates select="@month" />
    <xsl:apply-templates select="@day" />
    <xsl:apply-templates select="@hour" />
    <xsl:apply-templates select="@minute" />        
</xsl:template>

<xsl:template match="@*">
    <span class="{name()}">
        <xsl:value-of select="."/>
    </span>
</xsl:template>

Is this the best-practices way to process attributes in a specified order? I keep wondering if there is a way that uses keys or a global variable.

I need to use XSLT 1.0 and in the real case, there are several dozen attributes, not just eight.

Upvotes: 2

Views: 780

Answers (1)

Martin
Martin

Reputation: 1788

In contrast to elements for example, the order of attributes is not significant in XML, i.e. XPath and XSLT may process them in any order. Thus, the only way to force a given order, is to specify it somehow. One way is to call them explicitly as in your last code example. You can also extract all attribute names and store them in a separate XML file, e.g. something like

<attributes>
  <attribute>era</attribute>
  <attribute>year</attribute>
  <attribute>month</attribute>
  ...
<attributes>

Now you can load these elements with the document() function and iterate over all attribute elements:

<xsl:variable name="attributes" select="document('attributes.xml')//attribute"/>
...
<xsl:template match="*">
  <xsl:variable name="self" select="."/>
  <xsl:for-each select="$attributes">
    <xsl:apply-templates select="$self/@*[name()=current()]"/>
  </xsl:for-each>    
</xsl:template>

Upvotes: 1

Related Questions