0

I would like to do

<xsl:variable name="myPattern" select="node1|node2"/>
<xsl:template match="$myPattern">
    ...
</xsl:template>
<xsl:template  match="/">
    ...
    <xsl:for-each select="distinct-values(//$myPattern/name/text()">
        ...
    </xsl:for-each>
</xsl:template>

I tried this with XSLT version 2.0 and 3.0 to no avail. Any hints?

Reason: The pattern is a little more complicated and I would like to use it in several places and not just this match.

EDIT:

I solved my problem for now by accepting the fact that the variable does not contain the string/pattern, but the result nodes. If I modify it to

<xsl:variable name="myNodes" select="//(node1|node2)"/>
<xsl:template match="$myNodes">
    ...
</xsl:template>
<xsl:template  match="/">
    ...
    <xsl:for-each select="distinct-values($myNodes/name/text()">
        ...
    </xsl:for-each>
</xsl:template>

it works fine.

I still wonder why it is not possible to simply store the string in the variable and use it wherever literal strings are allowed.

mdr
  • 187
  • 1
  • 11
  • 2
    XSLT 3.0 allows you to use e.g. `match="$xyz"` and explains "$xyz matches any node that is present in the value of the variable $xyz". So you will need to show us a minimal but complete sample of XML, XSLT, output you expect, output or error you get, together with information about the used XSLT processor, to allow us to tell what is wrong. Obviously I would expect your variable declaration to rather look like e.g. ``, unless the elements you are looking for are really root elements. – Martin Honnen Sep 07 '16 at 10:32
  • 1
    @MartinHonnen: You are right. I wanted to keep the question short for others to save time when searching for an answer. I think your comment is even better than your answer, since this is what I did to solve it: ``. – mdr Sep 07 '16 at 11:50
  • @mdr You would have to use some sort of `evaluate()` function to convert a string into XPath expression (if that's what you mean). – michael.hor257k Sep 07 '16 at 12:10
  • @michael.hor257k: I guess, that is what I am looking for. Can you show me, how? – mdr Sep 07 '16 at 12:24
  • 1
    @mdr, see the edit of my answer, if you want a textual replacement use shadow attributes. – Martin Honnen Sep 07 '16 at 12:24

2 Answers2

3

I would suggest using a function rather than a variable:

<xsl:function name="_:myPattern" as="xs:boolean">
  <xsl:param name="node" as="node()"/>
  <xsl:sequence select="self::node1() | self::node2()"/>
</xsl:function>

<xsl:template match="node()[_:myPattern(.)]">
 ...
</xsl:template>
Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • This seems to do what I intended: Evaluating the pattern, wherever the variable (or rather function) is used. But: Whoa! Its not something you would call easily maintainable for the occasional XSLT user/programmer. Therefore I will stick with the simpler one (see my EDIT). But I sure learned a lot. – mdr Sep 07 '16 at 12:07
  • Thank you. I couldn't get this to work, not sure if its a typo? I tried exists($node/(self::node1 | self::node2)) which seems to work. Or, perhaps $node/(self::node1 | self::node2) with `element() ?` as the return type. – yas May 05 '17 at 18:52
  • 1
    Without paging the entire thread back into my brain, I suspect I intended `` – Michael Kay May 05 '17 at 21:53
2

As for textual replacement, with XSLT 3.0 you can use use a static parameter with a string value and then so called shadow attributes (https://www.w3.org/TR/xslt-30/#shadow-attributes):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math" exclude-result-prefixes="xs math"
    version="3.0">

    <xsl:param name="myPattern" static="yes" as="xs:string" select="'node1|node2'"/>

    <xsl:template _match="{$myPattern}">
        <matched name="{node-name()}">
            <xsl:apply-templates/>
        </matched>
    </xsl:template>

    <xsl:template match="/*">
        <xsl:copy>
            <xsl:for-each _select="distinct-values(//{$myPattern}/text())">
                <value>
                    <xsl:value-of select="."/>
                </value>
            </xsl:for-each>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

That transforms

<root>
    <node1>a</node1>
    <node2>1</node2>
    <node1>a</node1>
</root>

into

<root><value>a</value><value>1</value>
        <matched name="node1">a</matched>
        <matched name="node2">1</matched>
        <matched name="node1">a</matched>
</root>

In XSLT 3.0 you can use a variable or parameter reference for the match pattern of a template but it is not a textual replacement that happens, rather "$xyz matches any node that is present in the value of the variable $xyz" (https://www.w3.org/TR/xslt-30/#pattern-examples).

So with the XSLT being

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

    <xsl:param name="delete" select="//*[contains-token(@class, 'foo')]"/>

    <xsl:mode on-no-match="shallow-copy"/>

    <xsl:template match="$delete"/>

</xsl:stylesheet>

and the XML input being

<html>
    <head>
        <title>test</title>
    </head>
    <body>
        <p class="foobar bar">Paragraph 1.</p>
        <p class="foo bar">Paragraph 2.</p>
        <p class="bar">Paragraph 3.</p>
        <p class="foo">Paragraph 4.</p>
    </body>
</html>

a conforming XSLT 3.0 processor like Saxon 9.7 EE outputs

<html>
    <head>
        <title>test</title>
    </head>
    <body>
        <p class="foobar bar">Paragraph 1.</p>

        <p class="bar">Paragraph 3.</p>

    </body>
</html>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110