Gunilla
Gunilla

Reputation: 329

Looking up XML attributes with wildcard values, using XSL

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

Answers (2)

Tomalak
Tomalak

Reputation: 338228

Assuming that...

  • there only is one wildcard character (*)
  • the wildcard can only occur at the end of the <OptionRef> @id
  • the <Option> @id is 1-5 characters long

You 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, ':&#xA;')" />

   <xsl:for-each select="key($keyname, $id)">
       <xsl:value-of select="@displayName" />
       <xsl:text>&#xA;</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 you can't guarantee a maximum length for @id values, the approach is not suitable.
  • The creation of keys comes with a cost. Creating more keys makes the transformation start slower (but potentially run faster - it's a trade-off).
  • 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(., ':&#xA;')" />
    </xsl:template>
    
    <xsl:template match="Option">
      <xsl:value-of select="@displayName" />
      <xsl:text>&#xA;</xsl:text>
    </xsl:template>
    

Upvotes: 1

michael.hor257k
michael.hor257k

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>: &#10;</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>&#10;</xsl:text>
            </xsl:for-each>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="key('Option_Key', @id)/@displayName"/>
            <xsl:text>&#10;</xsl:text>
        </xsl:otherwise>
    </xsl:choose>
   <xsl:text>&#10;</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

Related Questions