4

How do you write element attributes in a specific order without writing it explicitly?

Consider:

<xsl:template match="Element/@1|@2|@3|@4">
    <xsl:if test="string(.)">
        <span>
            <xsl:value-of select="."/><br/>
        </span>
    </xsl:if>
</xsl:template>

The attributes should appear in the order 1, 2, 3, 4. Unfortunately, you can't guarantee the order of attributes in XML, it could be <Element 2="2" 4="4" 3="3" 1="1">

So the template above will produce the following:

<span>2</span>
<span>4</span>
<span>3</span>
<span>1</span>

Ideally I don't want to test each attribute if it has got a value. I was wondering if I can somehow set an order of my display? Or will I need to do it explicitly and repeating the if test as in:

<xsl:template match="Element">

    <xsl:if test="string(./@1)>
        <span>
            <xsl:value-of select="./@1"/><br/>
        </span>
    </xsl:if>
    ...
    <xsl:if test="string(./@4)>
        <span>
            <xsl:value-of select="./@4"/><br/>
        </span>
    </xsl:if>
</xsl:template>

What can be done in this case?

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
DashaLuna
  • 666
  • 2
  • 12
  • 22
  • Thank you guys for the responses. I haven't been very clear. I don't really need to sort attributes by their names, but instead specify an order myself. Say one doc will need to display them in order 1, 2, 3, 4. The other one in order 2, 3. The other let's say 3, 1, 2, 4. I was wondering if I can store that specific sequence of attribute names somehow.. and then just iterate through them. Basically, to have something concise and very flexible as to displaying a sequence of attribute values. Hope that makes sense? – DashaLuna Feb 18 '10 at 16:55

4 Answers4

7

In an earlier question you seemed to use XSLT 2.0 so I hope this time too an XSLT 2.0 solution is possible.

The order is not determined in the match pattern of a template, rather it is determined when you do xsl:apply-templates. So (with XSLT 2.0) you can simply write a sequence of the attributes in the order you want e.g. <xsl:apply-templates select="@att2, @att1, @att3"/> will process the attributes in that order.

XSLT 1.0 doesn't have sequences, only node-sets. To produce the same result, use xsl:apply-templates in the required order, such as:

<xsl:apply-templates select="@att2"/>
<xsl:apply-templates select="@att1"/>
<xsl:apply-templates select="@att3"/>
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
3

Do not produce XML that relies on the order of the attributes. This is very brittle and I would consider it bad style, to say the least. XML was not designed in that way; <elem a="1" b="2" /> and <elem a="1" b="2" /> are explicitly equivalent.

If you want ordered output, order your output (instead of relying on ordered input).

Furthermore, match="Element/@1|@2|@3|@4" is not equivalent to match="Element/@1|Element/@2|Element/@3|Element/@4", but I'm sure you mean the latter.

That being said, you can do:

<xsl:template match="Element/@1|Element/@2|Element/@3|Element/@4">
  <xsl:if test="string(.)">
    <span>
      <xsl:value-of select="."/><br/>
    </span>
  </xsl:if>
</xsl:template>

<xsl:template match="Element">
  <xsl:apply-templates select="@1|@2|@3|@4">
    <!-- order your output... -->
    <xsl:sort select="name()" />
  </xsl:apply-templates>
</xsl:template>

EDIT: I'll take it as read that @1 etc are just examples, because names cannot actually start with a number in XML.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
1

I'd use xsl:sort on the local-name of the attribute to get the result you want. I'd also use a different mode so the results don't get called by accident somewhere else.

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

  <xsl:template match="Element">
      <xsl:apply-templates select="@*" mode="sorted">
        <xsl:sort select="local-name()" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="Element/@a|@b|@c|@d" mode="sorted">
    <xsl:if test="string(.)">
        <span>
            <xsl:value-of select="."/><br/>
        </span>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>
Mark Worth
  • 926
  • 7
  • 6
0

The clue was is the answer by Martin Honnen

To copy attributes and conditionally add a new attribute to the end of the list of attributes.

Add rel="noopener noreferrer" to all external links.

<xsl:template match="a">
  <xsl:copy>
  <xsl:if test="starts-with(./@href,'http')">
    <xsl:apply-templates select="./@*"/>
    <!-- Insert rel as last node -->
    <xsl:attribute name="rel">noopener noreferrer</xsl:attribute>
  </xsl:if>
      <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
</xsl:template>

<xsl:template match="a/@href|a/@target|a/@rel">
    <!--
    Allowed attribute on anchor
    -->
    <xsl:attribute name="{name()}">
      <xsl:value-of select="."></xsl:value-of>
    </xsl:attribute>
  </xsl:template>

You can also specify the attribute sequence by calling apply templates with each select in the order you want.

<xsl:template match="a">
  <xsl:copy>
  <xsl:if test="starts-with(./@href,'http')">
    <xsl:apply-templates select="./@id"/>
    <xsl:apply-templates select="./@href"/>
    <xsl:apply-templates select="./@target"/>
    <!-- Insert rel as last node -->
    <xsl:attribute name="rel">noopener noreferrer</xsl:attribute>
  </xsl:if>
      <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
</xsl:template>
Chris Mills
  • 390
  • 7
  • 12