XSLT 2.0 External lookup using key() and document()

I'm pulling what's left of my hair out trying to get a simple external lookup working using Saxon 9.1.0.7.

I have a simple source file dummy.xml:

<something>
   <monkey>
      <genrecode>AAA</genrecode>
   </monkey>
   <monkey>
      <genrecode>BBB</genrecode>
   </monkey>
   <monkey>
      <genrecode>ZZZ</genrecode>
   </monkey>
   <monkey>
      <genrecode>ZER</genrecode>
   </monkey>
</something>

Then the lookup file is GenreSet_124.xml:

<GetGenreMappingObjectsResponse>
   <tuple>
      <old>
         <GenreMapping DepartmentCode="AAA"
                       DepartmentName="AND - NEWS AND CURRENT AFFAIRS"
                       Genre="10 - NEWS"/>
      </old>
   </tuple>
   <tuple>
      <old>
         <GenreMapping DepartmentCode="BBB"
                       DepartmentName="AND - NEWS AND CURRENT AFFAIRS"
                       Genre="11 - NEWS"/>
      </old>
   </tuple>

   ... lots more
</GetGenreMappingObjectsResponse>

What I'm trying to achieve is simply to get hold of the "Genre" value based on the "DepartmentCode" value.

So my XSL looks like:

...
<!-- Set up the genre lookup key -->
<xsl:key name="genre-lookup" match="GenreMapping" use="@DepartmentCode"/>


<xsl:variable name="lookupDoc" select="document('GenreSet_124.xml')"/>

<xsl:template match="/something">
<stuff>
   <xsl:for-each select="monkey">
      <Genre>

       <xsl:apply-templates select="$lookupDoc"> 
         <xsl:with-param name="curr-label" select="genrecode"/> 
      </xsl:apply-templates>

      </Genre>
   </xsl:for-each>
</stuff>
</xsl:template>

<xsl:template match="GetGenreMappingObjectsResponse">
   <xsl:param name="curr-genrecode"/> 

   <xsl:value-of select="key('genre-lookup', $curr-genrecode)/@Genre"/>

</xsl:template>

...


The issue that I have is that I get nothing back. I currently just get

<?xml version="1.0" encoding="UTF-8"?>
<stuff>
   <Genre/>
   <Genre/>
   <Genre/>
   <Genre/>
</stuff>

I have moved all the lookup data to be attributes of GenreMapping, previously as child elements of GenreMapping whenever I entered the template match="GetGenreMappingObjectsResponse" it would just print out all text from every GenreMapping (DepartmentCode, DepartmentName, Genre)!

I can't for the life of me figure out what I am doing wrong. Any helpo/suggestions would be greatly appreciated.

PLease find the current actual XSLT listing:

<?xml version="1.0"?>
<xsl:stylesheet version="2.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema">

<!-- Define the global parameters -->
<xsl:param name="TransformationID"/>
<xsl:param name="TransformationType"/>

<!-- Specify that XML is the desired output type --> 
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>

<!-- Set up the genre matching capability -->
<xsl:key name="genre-lookup" match="GenreMapping" use="@DepartmentCode"/>

<xsl:variable name="documentPath"><xsl:value-of select="concat('GenreSet_',$TransformationID,'.xml')"/></xsl:variable>
<xsl:variable name="lookupDoc" select="document($documentPath)"/>

<!-- Start the first match on the Root level -->
<xsl:template match="/something">
<stuff>
   <xsl:for-each select="monkey">
      <Genre>
      <xsl:apply-templates select="$lookupDoc/*">
         <xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
      </xsl:apply-templates>
      </Genre>
   </xsl:for-each>
</stuff>
</xsl:template >

<xsl:template match="GetGenreMappingObjectsResponse">
   <xsl:param name="curr-genrecode"/>
   <xsl:value-of select="key('genre-lookup', $curr-genrecode, $lookupDoc)/@Genre"/>
</xsl:template>
</xsl:stylesheet>

The TransformationID is alway 124 (so the correct lookup file is opened. The Type is just a name that I am currently not using but intending to.


Solution 1:

In XSLT 2.0 there are two ways you can do what you want:

One is the three-parameter version of the key function. The third parameter lets you specify the root node you want the key to work on (by default it's always the root of the main document):

<xsl:value-of select="key('genre-lookup', $curr-genrecode,$lookupDoc)/@Genre"/>

Another way is to use the key function under the $lookupDoc node:

<xsl:value-of select="$lookupDoc/key('genre-lookup', $curr-genrecode)/@Genre"/>

Both of these methods are documented in the XSLT 2.0 specification on keys, and won't work in XSLT 1.0.

For the sake of completeness, you'd have to rewrite this to not use keys if you're restricted to XSLT 1.0.

<xsl:value-of select="$lookupDoc//GenreMapping[@DepartmentCode = $curr-genrecode]/@Genre"/>

Aha! The problem is the select="$lookupDoc" in your apply-templates call is calling a default template rather than the one you expect, so the parameter is getting lost.

Change it to this:

<xsl:apply-templates select="$lookupDoc/*">
  <xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
</xsl:apply-templates>

That will call your template properly and the key should work.

So the final XSLT sheet should look something like this:

  <xsl:variable name="lookupDoc" select="document('XMLFile2.xml')"/>
  <xsl:key name="genre-lookup" match="GenreMapping" use="@DepartmentCode"/>
  <xsl:template match="/something">
    <stuff>
      <xsl:for-each select="monkey">
        <Genre>
          <xsl:apply-templates select="$lookupDoc/*">
            <xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
          </xsl:apply-templates>
        </Genre>
      </xsl:for-each>
    </stuff>
  </xsl:template>
  <xsl:template match="GetGenreMappingObjectsResponse">
    <xsl:param name="curr-genrecode"/>
    <xsl:value-of select="key('genre-lookup',$curr-genrecode,$lookupDoc)/@Genre"/>
  </xsl:template>