Search code examples
xmlxslt

XSLT "contains" function when referring to a variable


Still learning XML/XSLT so please forgive me if I'm doing something silly.

I'm trying to filter nodes of an XML file based on a master list of "types" (which is also within the file). I'm creating a variable from the node containing the master list, but when I try to refer to that variable in a "contains" function, it is not filtering correctly.

What am I doing wrong here?

XML file:

<?xml version="1.0" encoding="UTF-8"?>
<users>
<types>1600,1601,1602,1603</types>

<user><name>Joe</name><type>1600</type></user>
<user><name>Susan</name><type>1601</type></user>
<user><name>Alex</name><type>3</type></user>
<user><name>Jenny</name><type>4</type></user>
</users>

XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <users>
        <!-- store type list in variable -->
        <xsl:variable name="var_typelist" select="users/types"/>
        <!-- cycle through users -->
            <xsl:for-each select="users/user">
        <!-- store user data in a variable -->
            <xsl:variable name="var_user" select="*"></xsl:variable>
        <!-- check if user type matches one in the list -->
            <xsl:choose>
                <xsl:when test="contains($var_typelist,type)">
        <!-- rebuild file with user details -->
                    <users>
                        <xsl:copy-of select="$var_user"/>
                    </users>
                </xsl:when>
                <xsl:otherwise>
                </xsl:otherwise>
            </xsl:choose>
            </xsl:for-each> 
        </users>            
    </xsl:template>
</xsl:stylesheet>

I would expect this to remove Alex and Jenny, but it's only removing Jenny. It seems to think that Alex's '3' is found in the master list, but it's 1603. And if I remove 1603 from the master list, it filters Alex out too.

It seems that the "contains" is looking at each character in the master list (1, 6, 0, 3, etc.) instead of each type (1603). If that's the case, how do I make it look at each type?

Any advice would be greatly appreciated.

Many thanks, Alan


Solution

  • It seems that the "contains" is looking at each character in the master list (1, 6, 0, 3, etc.)

    No, the contains() function is looking for the occurrence of one string within another. In your example, the string "3" is contained in the string "1600,1601,1602,1603".

    Since you seem to be using XSLT 2.0, you could simply tokenize the types and then do a direct comparison:

    <xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    
    <xsl:template match="/users">
        <xsl:variable name="types" select="tokenize(types, ',')"/>
        <users>
            <xsl:copy-of select="user[type=$types]"/>
        </users>            
    </xsl:template>
    
    </xsl:stylesheet>
    

    In XSLT 1.0 you would have to do:

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    
    <xsl:template match="/users">
        <xsl:variable name="types" select="concat(',', types, ',')"/>
        <users>
            <xsl:copy-of select="user[contains($types, concat(',', type, ','))]"/>
        </users>            
    </xsl:template>
    
    </xsl:stylesheet>