1

The transformation I am writing must compose a comma separated string value from a given node set. The resulting string must be sorted according to a random (non-alphabetic) mapping for the first character in the input values.

I came up with this:

<?xml version="1.0" encoding="utf-8"?>    
<xsl:stylesheet    
       version="1.0"    
       xmlns:xsl="http://www.w3.org/1999/XSL/Transform"    
       xmlns:tmp="http://tempuri.org"    
       exclude-result-prefixes="tmp"    
>    
       <xsl:output method="xml" indent="yes"/>

       <tmp:sorting-criterion>    
             <code value="A">5</code>    
             <code value="B">1</code>    
             <code value="C">3</code>    
       </tmp:sorting-criterion>

       <xsl:template match="/InputValueParentNode">    
             <xsl:element name="OutputValues">    
             <xsl:for-each select="InputValue">    
                    <xsl:sort select="document('')/*/tmp:sorting-criterion/code[@value=substring(.,1,1)]" data-type="number"/>    
                    <xsl:value-of select="normalize-space(.)"/>   
                    <xsl:if test="position() != last()">    
                           <xsl:text>,</xsl:text>    
                    </xsl:if>
             </xsl:for-each>    
             </xsl:element>    
       </xsl:template>    
</xsl:stylesheet>

It doesn't work and looks like the XPath document('')/*/tmp:sorting-criterion/code[@value=substring(.,1,1)] does not evaluate as I expect. I've checked to substitute the substring(.,1,1) for a literal and it evaluates to the proper value.

So, am I missing something that makes the sorting XPath expression not to evaluate as I expect or is it simply impossile to do it this way?

If not possible to create a XPath expression that works, is there a work around to achieve my purpose?

Note: I'm constrained to XSLT-1.0

Sample Input:

<?xml version="1.0" encoding="utf-8"?>
<InputValueParentNode>
       <InputValue>A input value</InputValue>
       <InputValue>B input value</InputValue>
       <InputValue>C input value</InputValue>
</InputValueParentNode>

Expected ouput:

<?xml version="1.0" encoding="utf-8"?>
<OutputValues>B input value,C input value,A input value</OutputValues>
Antonio Pérez
  • 6,702
  • 4
  • 36
  • 61

2 Answers2

2

Replace the self::node() abbreviation ., with current() function.

A better predicate would be: starts-with(normalize-space(current()),@value)

  • Using `current()` is the right fix for the expression Antonio posted. – Martin Honnen Dec 15 '10 at 16:04
  • @Alejandro: Thanks a lot, it works! I'll take a closer look at the current/context node differences :) – Antonio Pérez Dec 15 '10 at 16:07
  • @Antonio Perez: You are wellcome. `current()` is an XSLT function returning the context node for the XSLT instruction execution. `.` is the abbreviation for `self::node()` in XPath 1.0 meaning the context node, but this would change with each step in the XPath expression. –  Dec 15 '10 at 16:12
0

Besides changing transformation according to Alejandro´s answer, I found it better to use a XSL variable for th mapping data to avoid declaration of a dummy namespace (tmp) as seen in Dimitre´s answer to another related question.

My final implementation:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
       version="1.0"
       xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
> 
       <xsl:output method="xml" indent="yes"/>

       <xsl:template match="/InputValueParentNode">
             <xsl:variable name="sorting-map">
                    <i code="A" priority="5"/>
                    <i code="B" priority="1"/>
                    <i code="C" priority="3"/>
             </xsl:variable>
             <xsl:variable name="sorting-criterion" select="document('')//xsl:variable[@name='sorting-map']/*"/>

             <xsl:element name="OutputValues">
             <xsl:for-each select="InputValue">
                    <xsl:sort select="$sorting-criterion[@code=substring(normalize-space(current()),1,1)]/@priority" data-type="number"/>
                    <xsl:value-of select="normalize-space(current())"/>
                    <xsl:if test="position() != last()">
                           <xsl:text>,</xsl:text>
                    </xsl:if>
             </xsl:for-each>
             </xsl:element>
       </xsl:template>
</xsl:stylesheet>
Community
  • 1
  • 1
Antonio Pérez
  • 6,702
  • 4
  • 36
  • 61