2

I am new to XSLT and spent a fair amount of time getting to grips with creating an in-line look up map to replace a specific value with another value for a mapped list in XSLT 2.0 only to find out I can only use 1.0. :-(

My question is how can a replicate the below working XSLT 2.0 code in 1.0. I have tried a few things but can't seem to get it working.

Point to note that if there is no map then the element should be empty.

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:template match="node()|@*">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
    </xsl:template>


<xsl:variable name="mapxml" >
<map>
<Country>
    <input value="GB">RZ</input>
    <input value="FR">TH</input>
   </Country>
</map>
 </xsl:variable>


  <xsl:variable name="vMap" 
       select="$mapxml" />


 <xsl:key name="kInputByVal" match="input" 
   use="@value" />


    <xsl:template match="Country/text()">
        <xsl:sequence select= 
         "(key('kInputByVal', ., $vMap/*/Country)[1]/text()
           )[1]
         "/>

 </xsl:template>
</xsl:stylesheet>

Input XML:

 <user>
        <Country>GB</Country>
        <Name>FOO</Name>
        <Address>BAR</Address>
<user>
Mads Hansen
  • 63,927
  • 12
  • 112
  • 147

2 Answers2

4

Here is the equivalent XSLT 1.0 program:

<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:my="http://tempuri.org/dummy"
  exclude-result-prefixes="my"
>
  <xsl:output omit-xml-declaration="yes" indent="yes"/>
  <xsl:strip-space elements="*" />

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

  <my:config>
    <map>
      <Country>
        <input value="GB">RZ</input>
        <input value="FR">TH</input>
      </Country>
    </map>
  </my:config>
  <xsl:variable name="vCountryMap" select="document('')/*/my:config/map/Country/input" />

  <xsl:template match="Country/text()">
    <xsl:value-of select="$vCountryMap[@value = current()]" />
  </xsl:template>
</xsl:stylesheet>

Notes:

  • You can set up extra nodes within the XSLT, simply because XSLT is XML itself. For example for configuration data, like here. You just need to make sure that you use a different namespace for them.
  • Namespace URIs need to be unique, they don't need to point to an existing document. A temporary URI like http://tempuri.org/dummy is perfectly all-right.
  • <xsl:strip-space elements="*" /> instructs the XSLT processor to ignore insignificant whitespace in the input. This helps in creating neatly indented output.
  • document('') refers to the XSLT stylesheet itself. You could also keep the configuration in an extra XML file, if you wanted to.
  • exclude-result-prefixes prevents the leaking of our temp namespace to the output.
  • current() refers to the node the XSLT processor is currently handling. ($vCountryMap[@value = .] would not work, because here the . refers to the XPath context, i.e. the <input> nodes.)
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • Thanks for the quick answer and explanation @Tomalak - that works a treat and helps me understand exactly what is going on :-) – Nick Fedorowicz Sep 07 '15 at 13:42
  • 1
    Have a look at @michael.hor257k's answer as well, as he went the extra mile of getting the key to work. I considered using a key for this task overkill, so I removed it from my solution, but of course it's possible to keep it. If you have very many mappings and very large input documents (in the order of thousands of nodes), using a key will benefit performance measurably. If your inputs are typically rather small, it won't make much of a difference. – Tomalak Sep 07 '15 at 13:43
2

Keys in XSLT 1.0 work only in the current document. In order to use a key to lookup from the stylesheet itself, you must switch the context to the stylesheet before using the key:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="http://example.com/my"
exclude-result-prefixes="my">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="input-by-value" match="input" use="@value" />

<my:map>
    <input value="GB">RZ</input>
    <input value="FR">TH</input>
</my:map>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="Country/text()">
    <xsl:variable name="value" select="." />
    <!-- switch context to stylesheet in order to use key -->
    <xsl:for-each select="document('')">
        <xsl:value-of select="key('input-by-value', $value)"/>
    </xsl:for-each>
 </xsl:template>

</xsl:stylesheet>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51