XSLT 2.0 Xpath error "not a node item" - xslt

my first post here - sure hope someone will know the answer!
I have been able to find solutions for many issues I had, but not for this one.
The questions and answers on this site on the same subject did not solve my issue...
I have an xml containing Format specifications like
<Format>
<TagNr>92</TagNr>
<Option>A</Option>
<Format>//[N]15d</Format>
</Format>
<Format>
<TagNr>92</TagNr>
<Option>B</Option>
<Format>//3!a/3!a/15d</Format>
</Format>
TagNr + option is a unique combination within this nodeset.
I defined a key to make using the set of formats easier:
<xsl:key name="xx" match="//Format/Format" use="concat(../TagNr, ../Option)"/>
I can indeed use this key and get the correct value, but only in non-special elements; I get an error "Error in XPath 2.0 expression Not a node item" when using this key within for-each or other constructs like the one below.
What I try to achieve is the following: In other nodes processed there is a string of options for which I wish to retrieve the format for each character.
For example:
<Tag>
<TagNr>92</TagNr>
<Options>AB</Options>
</Tag>
I have been trying lots of variants of the below but no luck:
<xsl:variable name="TN"><xsl:value-of select="TagNr"/></xsl:variable>
<xsl:variable name="optList">
<xsl:analyze-string select="./Options" regex="[A-Z]">
<xsl:matching-substring>
<xsl:variable name="TNO" select="concat($TN, .)"/>
<opt>
<tag><xsl:value-of select="$TNO"/></tag>
<fmt><xsl:value-of select="key('xx', $TNO)"/></fmt>
</opt>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:variable>
Splitting into individual characters using the regex goes fine and when retrieving (only) the value for opt/tag that goes fine too.
But when I add opt/fmt, I run into the mentioned error message for the Xpath expression select="key('xx', $TNO)".
I tried defining a variable based on the key function as suggested in another thread on this site, but did not succeed.
Can anyone help me?

The key() function (with two arguments) searches the document containing the context node. If the context item is not a node - for example, within analyze-string - then you will get this error, because it doesn't know which document to search. The answer is to use the third argument of key() to supply this information.

The problem is that the context changes in your analyze-string element. Maybe the following solution will help you.
For an XML file like that :
<a>
<Format>
<TagNr>92</TagNr>
<Option>A</Option>
<Format>//[N]15d</Format>
</Format>
<Format>
<TagNr>92</TagNr>
<Option>B</Option>
<Format>//3!a/3!a/15d</Format>
</Format>
<Tag>
<TagNr>92</TagNr>
<Options>AB</Options>
</Tag>
</a>
Consider the following XSLT :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
<xsl:output indent="yes"/>
<xsl:key name="xx" match="//Format/Format" use="concat(../TagNr, ../Option)"/>
<xsl:template match="/">
<result>
<xsl:apply-templates select="//Tag"/>
</result>
</xsl:template>
<xsl:template match="Tag">
<xsl:call-template name="createOPT">
<xsl:with-param name="str" as="xs:string" select="Options"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="createOPT">
<xsl:param name="str"/>
<xsl:if test="string-length($str) > 0">
<xsl:variable name="firstChar" select="substring($str,1,1)"/>
<xsl:variable name="TNO" select="concat(TagNr,$firstChar)"/>
<opt>
<tag><xsl:value-of select="$TNO"/></tag>
<fmt><xsl:value-of select="key('xx', $TNO)"/></fmt>
</opt>
<xsl:call-template name="createOPT">
<xsl:with-param name="str" select="substring($str,2)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The result is :
<?xml version="1.0" encoding="UTF-8"?>
<result>
<opt>
<tag>92A</tag>
<fmt>//[N]15d</fmt>
</opt>
<opt>
<tag>92B</tag>
<fmt>//3!a/3!a/15d</fmt>
</opt>
</result>

The easiest XSLT 2.0 way to process a string character by character is the following:
<xsl:for-each select="string-to-codepoints($vStr)">
<xsl:variable name="$vChar" select=
"codepoints-to-string(.)"/>
<!-- Process $vChar here: -->
</xsl:for-each/>
You can combine this with saving the original document context into a variable (say $vDoc) and using this variable as the 3rd argument of the key() function -- which is again an XSLT 2.0 - only feature.
So, you'll have something like:
key('xx', concat($TN, $vChar), $vDoc)
Summary:
Use the string-to-codepoints() and codepoints-to-string() functions for char-by-char processing.
Use the 3-rd argument of the key() function to specify context different from the current.

Related

XSLT replace variable element text

I have an example document that looks like this
<document>
<memo>
<to>Allen</to>
<p>Hello! My name is <bold>Josh</bold></p>
<p>It's nice to meet you <bold>Allen</bold>. I hope that we get to meet up more often.</p>
<from>Josh</from>
<memo>
</document>
and this is my XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:company="http://my.company">
<xsl:output method="html"/>
<xsl:variable name="link" select="company:generate-link()"/>
<xsl:template match="/document/memo">
<h1>To: <xsl:value-of select="to"/></h1>
<xsl:for-each select="p">
<p><xsl:apply-templates select="node()" mode="paragraph"/></p>
</xsl:for-each>
<xsl:if test="from">
<p>From: <strong><xsl:value-of select="from"/></strong></p>
</xsl:if>
<xsl:copy-of select="$link"/>
</xsl:template>
<xsl:template match="bold" mode="paragraph">
<b><xsl:value-of select="."/></b>
</xsl:template>
<xsl:template match="text()" mode="paragraph">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
and the variable link contains the following example node:
When I do a copy-of out the variable link it prints out the node correctly (but obviously without any text). I want to insert it into the document and replace the text using XSLT. For example, the text could be:
View all of <xsl:value-of select="/document/memo/from"/>'s documents.
So the resulting document would look like:
<h1>To: Allen</h1>
<p>Hello! My name is <b>Josh</b></p>
<p>It's nice to meet you <b>Allen</b>. I hope that we get to meet up more often.</p>
<from>Josh</from>
View all of Josh's documents.
I have been searching the internet on how to do this but I couldn't find anything. If anyone could help I would appreciate it a lot!
Thanks,
David.
You haven't said which XSLT processor that is nor have you shown the code of that extension function to allow us to understand what it returns but based on your comment saying it returns a node you can usually process it further with templates so if you use <xsl:apply-templates select="$link"/> and then write a template
<xsl:template match="a[#href]">
<xsl:copy>
<xsl:copy-of select="#*"/>
View all of <xsl:value-of select="$main-doc/document/memo/from"/>'s documents.
</xsl:copy>
</xsl:template>
where you declare a global variable <xsl:variable name="main-doc" select="/"/> you should be able to transform the node returned from your function.

Using XSLT 2.0 to parse the values of multiple attributes into an array-like structure

I'd like to be able to select all the attributes of a certain type in a document (for example, //#arch) and then take that node set and parse the values out into second node set. When I say "parse", in specific I mean I want to turn a node set like this:
arch="value1;value2;value3"
arch="value1:value4"
into a node set like this:
arch="value1"
arch="value2"
arch="value3"
arch="value1"
arch="value4"
or something like that; I want to get the individual values out of the attributes and into their own node.
If I can get it to that state, I've got plenty of methods for sorting and duplicate removal, after which I'd be using the finished node set for a publishing task.
I'm not so much looking for an tidy answer here as an approach. I know that XSLT cannot do dynamic arrays, but that's not the same as not being able to do something like dynamic arrays or something that mimics the important part of the functionality.
One thought that has occurred to me is that I could count the nodes in the first node set, and the number of delimiters, calculate the number of entries that the second node set would need and create it (somehow), and use the substring functions to parse out the first node set into the second node set.
There's usually a way working around XSLT's issues; has anyone worked their way around this one before?
Thanks for any help,
Jeff.
I think what you're looking for is a sequence. A sequence can be either nodes or atomic values (see http://www.w3.org/TR/xslt20/#constructing-sequences).
Here's an example showing the construction of a sequence and then iterating over it. The sequence is the atomic values from #arch, but it could also be nodes.
XML Input
<doc>
<foo arch="value1;value2;value3"/>
<foo arch="value1:value4"/>
</doc>
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="archSequence" as="item()*">
<xsl:for-each select="//#arch">
<xsl:for-each select="tokenize(.,'[;:]')">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/*">
<sequence>
<xsl:for-each select="$archSequence">
<item><xsl:value-of select="."/></item>
</xsl:for-each>
</sequence>
</xsl:template>
</xsl:stylesheet>
XML Output
<sequence>
<item>value1</item>
<item>value2</item>
<item>value3</item>
<item>value1</item>
<item>value4</item>
</sequence>
Example of a sequence of elements (same output):
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="archSequence" as="element()*">
<xsl:for-each select="//#arch">
<xsl:for-each select="tokenize(.,'[;:]')">
<item><xsl:value-of select="."/></item>
</xsl:for-each>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/*">
<sequence>
<xsl:for-each select="$archSequence">
<xsl:copy-of select="."/>
</xsl:for-each>
</sequence>
</xsl:template>
</xsl:stylesheet>
You can use the tokenize function in a for expression to get a sequence of the separate values, then create an attribute node for each one. However, since XSLT doesn't let you create a bare attribute node with no element parent, you'll have to use a trick like this:
<xsl:variable name="archElements">
<xsl:for-each select="for $attr in $initialNodeSet
return tokenize($attr, '[:;]')">
<dummy arch="{.}" />
</xsl:for-each>
</xsl:variable>
and then $archElements/dummy/#arch should be the set of separated arch attribute nodes that you require.
Complete example:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes" />
<xsl:template match="/">
<xsl:variable name="inputData">
<a arch="value1;value2;value3" />
<a arch="value1:value4" />
</xsl:variable>
<!-- create an example node set containing the two arch attribute nodes -->
<xsl:variable name="initialNodeSet" select="$inputData/a/#arch" />
<!-- tokenize and generate one arch attribute node for each value -->
<xsl:variable name="archElements">
<xsl:for-each select="for $attr in $initialNodeSet
return tokenize($attr, '[:;]')">
<dummy arch="{.}" />
</xsl:for-each>
</xsl:variable>
<!-- output to verify -->
<r>
<xsl:for-each select="$archElements/dummy/#arch">
<c><xsl:copy-of select="."/></c>
</xsl:for-each>
</r>
</xsl:template>
</xsl:stylesheet>
When run over any input document (the content is ignored) this produces
<?xml version="1.0" encoding="UTF-8"?>
<r>
<c arch="value1"/>
<c arch="value2"/>
<c arch="value3"/>
<c arch="value1"/>
<c arch="value4"/>
</r>

XSLT - how to apply a template to every node of the type?

I am quite new to xsl and functional programming, so I'll be grateful for help on this one:
I have a template that transforms some xml and provides an output. The problem is that there are many elements of type xs:date, all in different contexts, that must be localized. I use a concatenation of substrings of these xs:dates to produce a localized date pattern strings.
As you can guess this causes a lot of copy-paste "substring-this and substring-that". How can I write a template that will automatically transform all the elements of type xs:date to localized strings preserving all the context-aware transformations?
My xsl is something like this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/">
...
<input value="{substring(/select/a/date 9,2)}.{substring(/select/a/date, 6,2)}.{substring(/select/a/date 1,4)}">
...
<!-- assume that following examples are also with substrings -->
<div><xsl:value-of select="different-path/to_date"/></div>
...
<table>
<tr><td><xsl:value-of select="path/to/another/date"/></td></tr>
</table>
<apply-templates/>
</xsl:template>
<xsl:template match="something else">
<!-- more dates here -->
</xsl:template>
</xsl:stylesheet>
I hope I managed to make my question clear =)
UPD: Here is an example of xml:
<REQUEST>
<header>
<... />
<ref>
<ref_date type="xs:date">1970-01-01</ref_date>
</ref>
</header>
<general>
<.../>
<info>
<.../>
<date type="xs:date">1970-01-01</date>
<ExpireDate type="xs:date">1970-01-01</ExpireDate>
<RealDate type="xs:date">1970-01-01</RealDate>
<templateDetails>template details</templateDetails>
<effectiveDate type="xs:date">1970-01-01</effectiveDate>
</info>
<party>
<.../>
<date type="xs:date">1970-01-01</date>
</party>
<!-- many other parts of such kind -->
</general>
</REQUEST>
As for the output, there are many different options. The main thing is that these values must be set as a value of different html objects, such as tables, input fields and so on. You can see an example in the xsl listing.
P.S. I'm using xsl 1.0.
If you did a schema-aware XSLT 2.0 transformation, you wouldn't need all those type='xs:date' attributes: defining it in the schema as a date would be enough. You could then match the attributes with
<xsl:template match="attribute(*, xs:date)">
What you could do is add a template to match any element which has an #type attribute of 'xs:date', and do you substring manipulation in there
<xsl:template match="*[#type='xs:date']">
<xsl:value-of select="translate(., '-', '/')" />
</xsl:template>
In this case I am just replacing the hyphens by slashes as an example.
Then, instead of using xsl:value-of....
<div><xsl:value-of select="different-path/to_date"/></div>
You could use xsl:apply-templates
<div><xsl:apply-templates select="different-path/to_date"/></div>
Consider this XSLT as an example
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*[#type='xs:date']">
<xsl:copy>
<xsl:value-of select="translate(., '-', '/')" />
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In this case, all this XSLT is doing is copying the XML document as-is, but changing the date elements.
If you wanted to use the date template for other elements, or values, you could also make it a named-template, like so
<xsl:template match="*[#type='xs:date']" name="date">
<xsl:param name="date" select="." />
<xsl:value-of select="translate($date, '-', '/')" />
</xsl:template>
This would allow you to also call it much like a function. For example, to format a data and add as an attribute you could do the following:
<input>
<xsl:attribute name="value">
<xsl:call-template name="date">
<xsl:with-param name="date" select="/select/a/date" />
</xsl:call-template>
</xsl:attribute>
</input>

Calling the same xsl:template for different node names of the same complex type

I'm trying to keep my xsl DRY and as a result I wanted to call the same template for 2 sections of an XML document which happen to be the same complex type (ContactDetails and AltContactDetails). Given the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<RootNode>
<Name>Bob</Name>
<ContactDetails>
<Address>
<Line1>1 High Street</Line1>
<Town>TownName</Town>
<Postcode>AB1 1CD</Postcode>
</Address>
<Email>test#test.com</Email>
</ContactDetails>
<AltContactDetails>
<Address>
<Line1>3 Market Square</Line1>
<Town>TownName</Town>
<Postcode>EF2 2GH</Postcode>
</Address>
<Email>bob#bob.com</Email>
</AltContactDetails>
</RootNode>
I wrote an XSL Stylesheet as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<PersonsName>
<xsl:value-of select="RootNode/Name"/>
</PersonsName>
<xsl:call-template name="ContactDetails">
<xsl:with-param name="data"><xsl:value-of select="RootNode/ContactDetails"/></xsl:with-param>
<xsl:with-param name="elementName"><xsl:value-of select="'FirstAddress'"/></xsl:with-param>
</xsl:call-template>
<xsl:call-template name="ContactDetails">
<xsl:with-param name="data"><xsl:value-of select="RootNode/AltContactDetails"/></xsl:with-param>
<xsl:with-param name="elementName"><xsl:value-of select="'SecondAddress'"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="ContactDetails">
<xsl:param name="data"></xsl:param>
<xsl:param name="elementName"></xsl:param>
<xsl:element name="{$elementName}">
<FirstLine>
<xsl:value-of select="$data/Address/Line1"/>
</FirstLine>
<Town>
<xsl:value-of select="$data/Address/Town"/>
</Town>
<PostalCode>
<xsl:value-of select="$data/Address/Postcode"/>
</PostalCode>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When i try to run the style sheet it's complaining to me that I need to:
To use a result tree fragment in a path expression, either use exsl:node-set() or specify version 1.1
I don't want to go to version 1.1.. So does anyone know how to get the exsl:node-set() working for the above example?
Or if someone knows of a better way to apply the same template to 2 different sections then that would also really help me out?
Thanks
Dave
You are rolling this up from the wrong end (the wrong end being nearly always: trying to apply the imperative programming paradigm to XSLT).
This is really easy to do via template matching.
<xsl:template match="RootNode">
<PersonsName>
<xsl:value-of select="Name"/>
</PersonsName>
<xsl:apply-templates select="ContactDetails|AltContactDetails" />
</xsl:template>
<xsl:template match="ContactDetails|AltContactDetails">
<xsl:copy>
<FirstLine>
<xsl:value-of select="Address/Line1"/>
</FirstLine>
<Town>
<xsl:value-of select="Address/Town"/>
</Town>
<PostalCode>
<xsl:value-of select="Address/Postcode"/>
</PostalCode>
</xsl:copy>
</xsl:template>
Let go of the notion that you have to tell the XSLT processor what to do (through making named templates and calling them, "imperative style").
The XSLT processor chooses what templates to call. Starting at the root (/) it recursively checks for matching templates for every node it visits. It traverses your input XML all on its own - your only job is it to supply it with matching templates for those nodes you want to have processed in a special way.
You can drop in a custom template for those nodes that need special treatment and trust your XSLT processor with calling it once they come up. All you need to make sure in your templates is that traversal goes on by declaring an appropriate <xsl:apply-templates />.
Why not make the template
<xsl:template match="ContactDetails|AltContactDetails">
and do the test to determine the output element name inside the template instead?

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