views:

46

answers:

1

I am having trouble getting xsl:sort to understand the scope of the attributes I am referencing. Here is an XML sample document to illustrate:

<Root>
  <DrinkSelections>
    <Drink id=1000 name="Coffee"/>
    <Drink id=1001 name="Water"/>
    <Drink id=1002 name="Tea"/>
    <Drink id=1003 name="Almost But Not Quite Entirely Unlike Tea"/>
  </DrinkSelections>

  <CustomerOrder>
    <Drinks>
      <Drink oid="1001"/>
      <Drink oid="1002"/>
      <Drink oid="1003"/>
    </Drinks>
  </CustomerOrder

</Root>

I want to produce a list of drinks (sorted by name) contained in the CustomerOrder. Here is the XSLT code I am fiddling with:

<xsl:for-each select="/Root/CustomerOrder/Drinks/Drink">
   <xsl:sort select="/Root/DrinkSelections/Drink[@id = @oid]/@name"/>
   <xsl:variable name=var_oid select="@oid"/>
   <xsl:value-of select="/Root/DrinkSelections/Drink[@id = $var_oid]/@name"/>
</xsl:for-each>

Apparently, the xsl:sort command is trying to apply the "oid" attribute to the Drink elements in DrinkSelections, rather than local Drink element.

I can get around this using a variable, as in the xsl:value-of statement. But since xsl:sort must be the first statement after the xsl:for-each statement, I can't insert the xsl:variable statement before xsl:sort.

Is there a way to explicitly state that the attribute value should be taken from the "local" element?

+1  A: 

You are missing the current() function.

<xsl:for-each select="/Root/CustomerOrder/Drinks/Drink">
  <xsl:sort select="/Root/DrinkSelections/Drink[@id = current()/@oid]/@name"/>
  <xsl:value-of select="/Root/DrinkSelections/Drink[@id = current()/@oid]/@name"/>
</xsl:for-each>

But more prominently, you are missing XSL keys, for readability and performance:

<xsl:key name="kDrinkById" match="DrinkSelections/Drink" use="@id" />

<!-- ... later ... -->
<xsl:for-each select="/Root/CustomerOrder/Drinks/Drink">
  <xsl:sort select="key('kDrinkById', @oid)/@name"/>
  <xsl:value-of select="key('kDrinkById', @oid)/@name"/>
</xsl:for-each>

An you are probably not using templates right, because if you did, your xsl:for-each select expression would not start at the root.

<xsl:template match="Root">
  <xsl:apply-templates select="CustomerOrder/Drinks" />
</xsl:template>

<xsl:template match="CustomerOrder/Drinks">
  <xsl:apply-templates select="Drink">
    <xsl:sort select="key('kDrinkById', @oid)/@name"/>
  </xsl:apply-templates>
</xsl:template>

<xsl:template match="CustomerOrder/Drinks/Drink">
  <xsl:value-of select="key('kDrinkById', @oid)/@name"/>
</xsl:template>

Note that I removed the for-each also. Every xsl:for-each avoided is a step towards better XSLT code (very rare exceptions apply).

Tomalak
Thanks! I figured there was some explicit reference available.And thanks also for the approach using a key. I've only messed with keys a couple of times in my somewhat limited XSLT experience.And yes, I do not begin for-each from the root. I was just trying to make the example more concise. My actual code uses a relative reference from within a match template.
e-holder
@edholder: I thought so, but you never know. ;-) The key will speed up things greatly if you have large amounts of data to process this way.
Tomalak