XML:
<root>
<item>
<key>mustBeSECONDKey</key>
<value>MustBeSECONDValue</value>
</item>
<item>
<key>mustBeFIRSTKey</key>
<value>MustBeFIRSTValue</value>
</item>
</root>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<file>
<xsl:for-each select="root/item">
<xsl:if test="key[text()='mustBeFIRSTKey']">
<xsl:element name="someCustomTagName">
<xsl:value-of select="value" />
</xsl:element>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="root/item">
<xsl:if test="key[text()='mustBeSECONDKey']">
<xsl:element name="anotherNameOfATag">
<xsl:value-of select="value" />
</xsl:element>
</xsl:if>
</xsl:for-each>
</file>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<file>
<someCustomTagName>MustBeFIRSTValue</someCustomTagName>
<anotherNameOfATag>MustBeSECONDValue</anotherNameOfATag>
</file>
The idea here is that I want to make sure that the tags end up in the output document in an order that I specify. So I'm doing that by basically saying "look through everything and find the first thing, then look through everything and find the second thing." This obviously works.
However - and this would be the crux of this question - I imagine that there must be a more efficient way to accomplish this goal (possibly more than one way). What is it?
And there's a further wrinkle. Suppose that mustBeFIRSTKey
has two possible values, MustBeFIRSTValueUno
and MustBeFIRSTValueNi
. I then want to map these two values to another set of two values, Gazpacho
and Sushi
, respectively. So
<item>
<key>mustBeFIRSTKey</key>
<value>MustBeFIRSTValueNi</value>
</item>
becomes
<mustBeFIRSTKey>Sushi</mustBeFIRSTKey>
Edit: I've discovered my problem was mostly a conceptual one looking at the Java side of things. During the setup of my Transformer
I was doing this:
StreamSource xsltSource = new StreamSource(ClassLoader.getSystemResourceAsStream(transformLocation));
I should have been doing this:
StreamSource xsltSource = new StreamSource(ClassLoader.getSystemResource(transformLocation).toString());
That StreamSource
constructor sets the systemId
, which allows me to use @Ian_Robert's more clever solution from below, as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- I discovered I could merge the $map and $inlineMap variables. -->
<xsl:variable name="inlineMap" select="document('')//xsl:variable[@name = 'inlineMap']/map">
<map>
<key from="mustBeFIRSTKey" to="someCustomTagName" />
<key from="mustBeSECONDKey" to="anotherNameOfATag">
<value from="MustBeSECONDValueUno" to="Gazpacho" />
<value from="MustBeSECONDValueNi" to="Sushi" />
</key>
</map>
</xsl:variable>
<xsl:key name="valueMap" match="value" use="concat(../@from, '|', @from)" />
<xsl:template match="root">
<xsl:variable name="items" select="item" />
<file>
<xsl:for-each select="$inlineMap">
<xsl:for-each select="key">
<xsl:apply-templates select="$items[key = current()/@from]">
<xsl:with-param name="elementName" select="@to"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:for-each>
</file>
</xsl:template>
<xsl:template match="item">
<xsl:param name="elementName" />
<xsl:variable name="currentItem" select="." />
<xsl:element name="{$elementName}">
<xsl:for-each select="$inlineMap">
<xsl:variable name="value" select="key('valueMap', concat($currentItem/key, '|', $currentItem/value))" />
<xsl:choose>
<xsl:when test="$value">
<xsl:value-of select="$value/@to" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$currentItem/value" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This example strips away the logic for handling values without mappings, because for my problem, there are none.