Here is a way to do what you intend, using an <xsl:key>
and otherwise following your method two.
The sample input file (data.xml):
<?xml version="1.0" encoding="utf-8"?>
<input>
<data>001</data>
<data>002</data>
<data>005</data>
</input>
The sample map file (map.xml):
<?xml version="1.0" encoding="utf-8"?>
<map default="??">
<entry key="001">RZ</entry>
<entry key="002">TH</entry>
<entry key="003">SC</entry>
</map>
The sample XSL stylesheet, explanation follows:
<?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" encoding="utf-8" indent="yes"/>
<xsl:param name="map-file" select="string('map.xml')" />
<xsl:variable name="map-doc" select="document($map-file)" />
<xsl:variable name="default-value" select="$map-doc/map/@default" />
<xsl:key name="map" match="/map/entry" use="@key" />
<xsl:template match="/input">
<output>
<xsl:apply-templates select="data" />
</output>
</xsl:template>
<xsl:template match="data">
<xsl:variable name="raw-value" select="." />
<xsl:variable name="mapped-value">
<xsl:for-each select="$map-doc">
<xsl:value-of select="key('map', $raw-value)" />
</xsl:for-each>
</xsl:variable>
<data>
<xsl:choose>
<xsl:when test="$mapped-value = ''">
<xsl:value-of select="$default-value" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$mapped-value" />
</xsl:otherwise>
</xsl:choose>
</data>
</xsl:template>
</xsl:stylesheet>
What this does is:
- use
document()
to open map.xml
, saving the resulting node-set to a variable
- save the default value for further reference
- prepare an
<xsl:key>
to work against the "map" node set
- use
<xsl:for-each>
not as a loop, but as a means to switch the execution context before calling the key()
function - otherwise key()
would work against the "data" document and return nothing
- find the corresponding node with the
key()
function, save it in a variable
- check the variable value on output - if it is empty, use the default value
- repeat (through
<xsl:apply-templates>
)
The credit for the neat <xsl:for-each>
trick goes to Jeni Tennison, who described the technique on the XSL mailing list. Be sure to read the thread.
Output of running the stylesheet against data.xml:
<?xml version="1.0" encoding="utf-8"?>
<output>
<data>RZ</data>
<data>TH</data>
<data>??</data>
</output>
All of this is XSLT 1.0. I'm convinced a better/more elegant version exists that makes use of the advantages XSLT 2.0 offers, but unfortunately I'm not overly familiar with XSLT 2.0. Maybe someone else posts a better solution.
EDIT
Through Dimitre Novatchev's hint in the comments, I was able to create a a considerably shorter and more preferable stylesheet:
<?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" encoding="utf-8" indent="yes"/>
<xsl:param name="map-file" select="string('map.xml')" />
<xsl:variable name="map-doc" select="document($map-file)" />
<xsl:variable name="default" select="$map-doc/map/default[1]" />
<xsl:key name="map" match="/map/entry" use="@key" />
<xsl:template match="/input">
<output>
<xsl:apply-templates select="data" />
</output>
</xsl:template>
<xsl:template match="data">
<xsl:variable name="raw-value" select="." />
<data>
<xsl:for-each select="$map-doc">
<xsl:value-of select="(key('map', $raw-value)|$default)[1]" />
</xsl:for-each>
</data>
</xsl:template>
</xsl:stylesheet>
However, this one requires a slightly different map file to work in XSLT 1.0:
<?xml version="1.0" encoding="utf-8"?>
<map>
<entry key="001">RZ</entry>
<entry key="002">TH</entry>
<entry key="003">SC</entry>
<!-- default entry must be last in document -->
<default>??</default>
</map>