Reputation: 329
The task of my little piece of XSL is to go through all the OptionRef tags in the XML file and to print the id and the displayName attributes for the OptionRef tags. The id´s are looked-up in the Options section to find displayName.
And now, the problem: Some of the OptionRef id attributes use wildcards (*) as part of their values. These id´s cannot be looked-up with regards to displayName, unless they first get resolved to real id´s found in the Options section. Is there some way to extend the XSL to do this kind of "globbing"?
I would like to print the id´s and displayNames of all the id´s that are matching the wildcard. For instance "A01*" will match both "A0101" and "A0102" in the Options section, so these id´s and their display names should be printed.
This is a sample of the XML:
<?xml version="1.0" encoding="UTF-8"?>
<OptionList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Group>
<OptionRef id="A0102"/>
<OptionRef id="A03"/>
<OptionRef id="A04"/>
<OptionRef id="A0101"/>
<OptionRef id="A0102"/>
<OptionRef id="B01"/>
</Group>
<Options>
<Option displayName="Option A02" id="A02"/>
<Option displayName="Option A03" id="A03"/>
<Option displayName="Option A0101" id="A0101"/>
<Option displayName="Option A0102" id="A0102"/>
<Option displayName="Option A04" id="A04"/>
<Option displayName="Option B01" id="B01"/>
</Options>
<Rules>
<Opportunities>
<OptionRef id="A01*">
<OptionRef id="A03"/>
<OptionRef id="A04"/>
</OptionRef>
</Opportunities>
<Problems>
<Problem>
<OptionRef id="A03"/>
<OptionRef id="A04"/>
</Problem>
</Problems>
</Rules>
</OptionList>
This is the XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" indent="yes"/>
<xsl:key name="Option_Key" match="Option" use="@id"/>
<xsl:template match="OptionRef" >
<xsl:value-of select="@id"/>:
<xsl:value-of select="key('Option_Key', @id)/@displayName"/>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1
Views: 1964
Reputation: 338228
Assuming that...
*
)<OptionRef>
@id<Option>
@id is 1-5 characters longYou can use:
<xsl:key name="Option_Key_1" match="Option" use="substring(@id, 1, 1)"/>
<xsl:key name="Option_Key_2" match="Option" use="substring(@id, 1, 2)"/>
<xsl:key name="Option_Key_3" match="Option" use="substring(@id, 1, 3)"/>
<xsl:key name="Option_Key_4" match="Option" use="substring(@id, 1, 4)"/>
<xsl:key name="Option_Key_5" match="Option" use="substring(@id, 1, 5)"/>
<xsl:template match="OptionRef">
<xsl:variable name="id" select="substring-before(concat(@id, '*'), '*')" />
<xsl:variable name="keyname" select="concat('Option_Key_', string-length($id))" />
<xsl:value-of select="concat(@id, ':
')" />
<xsl:for-each select="key($keyname, $id)">
<xsl:value-of select="@displayName" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
Output with your input XML:
A0102: Option A0102 A03: Option A03 A04: Option A04 A0101: Option A0101 A0102: Option A0102 B01: Option B01 A01*: Option A0101 Option A0102 A03: Option A03 A04: Option A04 A03: Option A03 A04: Option A04
To support @id values up to length N
, add more keys:
<!-- ... -->
<xsl:key name="Option_Key_N" match="Option" use="substring(@id, 1, N)"/>
Final thoughts:
If your input documents do not contain very many <OptionRef>
elements, the run-time benefit you get from a key is negligible. If you care about transformation performance, measure the alternatives. If you don't, you do not need a key at all and can work with XPath directly:
<xsl:variable name="options" select="/*/Options/Option" />
<xsl:template match="OptionRef[contains(@id, '*')]">
<xsl:apply-templates select="@id" />
<xsl:apply-templates select="$options[starts-with(@id, substring-before(current()/@id, '*'))]" />
</xsl:template>
<xsl:template match="OptionRef">
<xsl:apply-templates select="@id" />
<xsl:apply-templates select="$options[@id = current()/@id]" />
</xsl:template>
<xsl:template match="OptionRef/@id">
<xsl:value-of select="concat(., ':
')" />
</xsl:template>
<xsl:template match="Option">
<xsl:value-of select="@displayName" />
<xsl:text>
</xsl:text>
</xsl:template>
Upvotes: 1
Reputation: 116993
How about something like:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="text" encoding="UTF-8"/>
<xsl:key name="Option_Key" match="Option" use="@id"/>
<xsl:variable name="options" select="/OptionList/Options/Option" />
<xsl:template match="OptionRef">
<xsl:value-of select="@id"/>
<xsl:text>: </xsl:text>
<xsl:choose>
<xsl:when test="contains(@id, '*')">
<xsl:for-each select="$options[starts-with(@id, substring-before(current()/@id, '*'))]">
<xsl:value-of select="@displayName"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="key('Option_Key', @id)/@displayName"/>
<xsl:text> </xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>
Applied to you example input, the result will be:
A0102:
Option A0102
A03:
Option A03
A04:
Option A04
A0101:
Option A0101
A0102:
Option A0102
B01:
Option B01
A01*:
Option A0101
Option A0102
A03:
Option A03
A04:
Option A04
Upvotes: 2