Warm tip: This article is reproduced from stackoverflow.com, please click
xslt xslt-3.0

XSLT: recursive templates on parametrized nodes

发布于 2020-04-07 10:17:54

I am attempting to call a template recursively on a parametrized list of node types.

If I pass these parametrized values to a template, it doesn't recurse. However, if I pass in the values to the template directly, the recursion works as expected.

How can I get the recursion to work while matching against a parametrized value?

(I am using saxon version 9.9.1.6 (home edition) to apply the XSLT transformation)

Input HTML

<p>
<p>paragraph1</p>
<p>paragraph2</p>
<a>link here</a>
</p>

XSLT with direct value for template:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="container" select="p|a"/>    
<xsl:template match="p|a">
    Name: <xsl:value-of select="name()"/>
    Value: <xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>

Output:

<?xml version="1.0" encoding="UTF-8"?>
    Name: p
    Value: 

    Name: p
    Value: paragraph1

    Name: p
    Value: paragraph2

    Name: a
    Value: link here

This is working and what I would expect to happen. But when I try and pass parametrized values to the template, it matches the top-level element but does not match any child elements.

XSLT with parametrized value for template:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="container" select="p|a"/>    
<xsl:template match="$container">
    Name: <xsl:value-of select="name()"/>
    Value: <xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>

Output:

<?xml version="1.0" encoding="UTF-8"?>
    Name: p
    Value: 
paragraph1
paragraph2
link here
Questioner
Richard
Viewed
41
Michael Kay 2020-02-03 18:43

match="$variable" is new syntax in XSLT 3.0 that matches nodes in a node-set held in a global variable. The variable holds the matching nodes, not their names.

Also, select="p|a" selects nodes in the context of the document, which is not what you want. Use select="'p|a'" to set the variable to a string. It helps to use an as attribute, eg. as="node()*" or as="xs:string" to avoid confusion as to what the variable actually is supposed to hold.

To match on the names, use match="*[local-name()=tokenize($container, '\|')]"

Alternatively you could define a static parameter and a shadow attribute:

<xsl:param name="container" select="'p|a'" static="yes"/>

<xsl:template _match="{$container}">...</xsl:template>

Or if you prefer, you could initialize the variable to the set of matching nodes like this:

<xsl:param name="matching-nodes" select="//p | //a"/>

and then match using

<xsl:template match="$matching-nodes"/>

But note this only works if you're matching nodes in the primary source document.