I have an XSLT in which I create (from the input data) intermediary variables like the following (hard-coded example, but dynamic in nature):
<xsl:variable name="variableX">
<ValidCode CodePart="CP1" Code="C1"/>
<ValidCode CodePart="CP2" Code="C2"/>
<ValidCode CodePart="CP1" Code="C3"/>
<ValidCode CodePart="CP2" Code="C4"/>
<ValidCode CodePart="CP2" Code="C5"/>
</xsl:variable>
I wish to loop over the distinct occurrences of CodePart values. In XSLT 2.0 it's easy:
<xsl:for-each select="distinct-values($variableX/ValidCode/@CodePart)">...</xsl:for-each>
But how best do this in XSLT 1.0?
Note that I can't use a key, as it's a dynamically determined variable and not part of the input file.
My input file does contain a list of all possible code parts as follows:
<root>
<CodePart><value>CP1</value></CodePart>
<CodePart><value>CP2</value></CodePart>
<CodePart><value>CP3</value></CodePart>
</root>
So I thought of looping over //CodePart/value
instead, ensuring uniqueness for starters. But then I need some Xpath expression that includes the condition
"value occurs in the node-set of all $variableX/ValidCode/@CodePart values"
and use something like
<xsl:for-each select="//CodePart[..condition..]/value">...</xsl:for-each>
Is there a simple form of the Xpath expression I am looking for?
Or is another approach preferable?
Note that I can't use a key, as it's a dynamically determined variable and not part of the input file.
This simply isn't true.
Here is a short, simple and most efficient solution using keys (btw, not using any conditional instructions or xsl:for-each
at all :) ):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kCodeByPart" match="ValidCode" use="@CodePart"/>
<xsl:variable name="variableX">
<ValidCode CodePart="CP1" Code="C1"/>
<ValidCode CodePart="CP2" Code="C2"/>
<ValidCode CodePart="CP1" Code="C3"/>
<ValidCode CodePart="CP2" Code="C4"/>
<ValidCode CodePart="CP2" Code="C5"/>
</xsl:variable>
<xsl:variable name="vCodes" select="ext:node-set($variableX)/*"/>
<xsl:template match="/">
<xsl:apply-templates select=
"$vCodes[generate-id()
=
generate-id(key('kCodeByPart', @CodePart)[1])
]"/>
</xsl:template>
<xsl:template match="ValidCode">
Code Part: <xsl:value-of select="@CodePart"/>
Codes: <xsl:apply-templates select=
"key('kCodeByPart', @CodePart)/@Code"/>
</xsl:template>
<xsl:template match="@Code">
<xsl:value-of select="concat(., ' ')"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used), the wanted, correct result is produced:
Code Part: CP1
Codes: C1 C3
Code Part: CP2
Codes: C2 C4 C5
Explanation:
As specified in the W3C XSLT 1.0 Recommendation:
"The key can be used to retrieve a key from a document other than the document containing the context node."
In the provided solution we don't even use this possibility -- both the context node and the retrieved key are from the same document -- the temporary tree contained in $vCodes
.
Again there, it is specified that a key can be used for more than one document:
"A stylesheet declares a set of keys for each document using the xsl:key element."
To put it in simple words: the indexing is performed on the current document (the document that contains the context (current) node).
In XPath 1.0, you can emulate the distinct-values
function by looking for preceding siblings with the same value.
I have simplified your first document to this so it can be easier reproduced (without even using XSLT):
<?xml version="1.0"?>
<root>
<ValidCode CodePart="CP1" Code="C1"/>
<ValidCode CodePart="CP2" Code="C2"/>
<ValidCode CodePart="CP1" Code="C3"/>
<ValidCode CodePart="CP2" Code="C4"/>
<ValidCode CodePart="CP2" Code="C5"/>
<ValidCode CodePart="CP3" Code="C5"/>
</root>
Then, the following XPath expression will select only one CodePart
attribute with each of the values that occur in the document:
//ValidCode/@CodePart[not(../preceding-sibling::ValidCode/@CodePart = .)]
So, the condition for the CodePart
attributes to select is that there is no preceding sibling of the <ValidCode>
element whose CodePart
attribute has the same value as the currently examined (selected) CodePart
attribute.
I don't think you can use directly an XPath - but you should be able to check for only the valid values like this:
<xsl:for-each select="//CodePart/value">
<xsl:variable name="CodePart" select="."/>
<xsl:if test="$variableX/ValidCode[@CodePart=$CodePart]">
. . .
</xsl:if>
</xsl:for-each>
or more simply (thanks to the comments):
<xsl:for-each select="//CodePart/value">
<xsl:if test="$variableX/ValidCode[@CodePart=current()]">
. . .
</xsl:if>
</xsl:for-each>
If $variableX
is not a node set but an XML fragment it needs to be converted to a node set - this is implementation-dependent, using Microsoft processors:
<xsl:for-each select="//CodePart/value" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:if test="msxsl:node-set($variableX)/ValidCode[@CodePart=current()]">
. . .
</xsl:if>
</xsl:for-each>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With