views:

993

answers:

2

Hi,

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"&gt;

<!-- 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.

+1  A: 

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>

(Source of information)

Welbog
HI there,Thanks for the response.I have already tried (and now retried) both of these (from within the master template, I don't call apply-template using this form) and both return nothing. I still get the empty elements?!There must be something I'm doing wrong in the setup of the key maybe?regards
Phil
This might be a specific issue with your XSLT compiler, but using `$lookupDoc/*` should cover your bases.
Welbog
Hello again,Using the updated code I no longer get empty elements I get them full of whitespace (24 lines worth).I am now truly puzzled.
Phil
Is there more to your XML than your example lets on?
Welbog
Also, have you tried it the key-less way?
Welbog
PLease see the updated XSLT listing above in the my question. regards.Will try the key-less way also.
Phil
A: 

OK, so this is a bit mental and I don't claim to understand it but it works (sounds like a career in software).

The issue I was having is that when I call the apply-templates and pass in the external document as a variable it never matched any templates even ones called "Genremapping".

So I used a wildcard to catch it, also when calling the apply-templates I narrowed down the node set to be the child I was interested in. This was pretty bonkers a I could print out the name of the node and see "GenreMapping" yet it never went into any template I had called "GenreMapping" choosing instead to only ever go to "*" template.

What this means is that my new template match gets called for every single GenreMapping node there is so it may be a little inefficient. What I realised then was all I needed to do was print sometihng out if a predicate matched.

So it looks like this now (no key used whatsoever):

...
<xsl:template match="/something">
<stuff>
   <xsl:for-each select="monkey">
      <Genre>
         <key_value><xsl:value-of select="genrecode"/></key_value>
         <xsl:variable name="key_val"><xsl:value-of select="genrecode"/></xsl:variable>
         <code>
      <xsl:apply-templates select="$lookupDoc/*/*/*/*">
         <xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
      </xsl:apply-templates>

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


<xsl:template match="*">
   <xsl:param name="curr-genrecode"/>
   <xsl:value-of select=".[@DepartmentCode = $curr-genrecode]/@Genre"/>
</xsl:template>
...

All of which output: Note, the last key_value correctly doesn't have a code entry as there is no match in the lookup document.

<?xml version="1.0" encoding="UTF-8"?>
<stuff xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;
   <Genre>
      <key_value>AAA</key_value>
      <code>10 - NEWS</code>
   </Genre>
   <Genre>
      <key_value>AAA</key_value>
      <code>10 - NEWS</code>
   </Genre>
   <Genre>
      <key_value>BBB</key_value>
      <code>11 - NEWS</code>
   </Genre>
   <Genre>
      <key_value>SVVS</key_value>
      <code/>
   </Genre>
</stuff>

Answer on a postcode. Thanks for the help Welbog.

Phil
I'm glad you found a working solution, even though it's a weird one.
Welbog