0

I would like to create an array of variables with dynamic names and mode names. Also, these variables are a chain which processes the document-node() stepwise. So instead of writing like this:

<xsl:variable name="A">
    <xsl:apply-templates mode="A" select="."/>
</xsl:variable>
<xsl:variable name="B">
    <xsl:apply-templates mode="B" select="$A"/>
</xsl:variable>
<xsl:variable name="C">
    <xsl:apply-templates mode="C" select="$B"/>
</xsl:variable>

I would like to do something like this:

<xsl:variable name="mode-names" select="'A', 'B', 'C'"/>

<xsl:variable name="vars">

    <xsl:for-each select="$mode-names">
        <xsl:variable name="pos" select="position()" />     

        <xsl:variable name="{.}">
            <xsl:apply-templates mode="{.}" select="if ($pos=1) then . else $modi[$pos -1]"/>
        </xsl:variable>

    </xsl:for-each>
</xsl:variable>

Error message: Invalid variable name: Invalid QName {{.}}

xslt 3.0

3
  • Well, the definition of the mode attribute in the spec w3.org/TR/xslt-30/#applying-templates does not suggest it allows an expression computed at run-time. Nor does the definition of the name attribute for xsl:variable in w3.org/TR/xslt-30/#variables show anything that suggests you can compute a name at run-time. So unless you try to construct XSLT dynamically in the same stylesheet where you then use the transform function to execute it I don't see how you could construct mode names at run-time. Commented Mar 6, 2019 at 16:21
  • 1
    Rather than saying you want to use language constructs that don't exist, why not tell us what problem you are trying to solve? That is, what is your input and what is your desired output? Then we can advise you how to solve it using language constructs that DO exist. Commented Mar 6, 2019 at 20:32
  • I just wanted to have a clearer code and avoid redundancies. As I have 10 modes, and write 10 intermediate result-documents (it works well dynamically), I just didn't want to write 10 times 'variable' or 'result-document'. Also, I wanted to have the list of my modes in one place like an array. Anyway, @Martin Honnen, thank you for the very detailed answer and useful links. Commented Mar 7, 2019 at 10:46

1 Answer 1

1

It is not clear how you would want to use the xsl:variable name="{.}" later anyway, if you want to store more than one item in a variable you can of course use a sequence of items, so for your case of creating various document nodes you could use a variable of type document-node()* which denotes a sequence of documents/document nodes.

So an example using three (static) modes to be applied and have each result stored as an item in that variable would be

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

  <xsl:output indent="yes"/>

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

  <xsl:template match="foo" mode="A">
      <bar>
          <xsl:apply-templates mode="#current"/>
      </bar>
  </xsl:template>

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

  <xsl:template match="bar" mode="B">
      <whatever>
          <xsl:apply-templates mode="#current"/>
      </whatever>
  </xsl:template>

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

  <xsl:template match="text()" mode="C">
      <xsl:value-of select="upper-case(.)"/>
  </xsl:template>

  <xsl:variable name="results" as="document-node()*">
      <xsl:variable name="r1">
          <xsl:apply-templates mode="A"/>
      </xsl:variable>
      <xsl:sequence select="$r1"/>
      <xsl:variable name="r2">
          <xsl:apply-templates select="$r1" mode="B"/>
      </xsl:variable>
      <xsl:sequence select="$r2"/>
      <xsl:apply-templates select="$r2" mode="C"/>
  </xsl:variable>

  <xsl:template match="/">
      <results>
          <xsl:for-each select="$results">
              <result step="{position()}">
                  <xsl:copy-of select="."/>
              </result>
          </xsl:for-each>
      </results>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/3NJ38YX/1

As I said already in a comment, there is no way to construct a mode name at run-time, unless you construct the whole stylesheet on the fly and run it then with the transform function:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:axsl="http://www.w3.org/1999/XSL/Transform-alias"
    exclude-result-prefixes="#all"
    version="3.0">

    <xsl:param name="mode-names" as="xs:string*" select="'A', 'B', 'C'"/>

    <xsl:param name="stylesheet-template-string" as="xs:string"><![CDATA[
        <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            xmlns:xs="http://www.w3.org/2001/XMLSchema" version="3.0" exclude-result-prefixes="#all">

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

            <xsl:template match="foo" mode="A">
                <bar>
                    <xsl:apply-templates mode="#current"/>
                </bar>
            </xsl:template>

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

            <xsl:template match="bar" mode="B">
                <whatever>
                    <xsl:apply-templates mode="#current"/>
                </whatever>
            </xsl:template> 

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

            <xsl:template match="text()" mode="C">
                <xsl:value-of select="upper-case(.)"/>
            </xsl:template>            

        </xsl:stylesheet>
    ]]></xsl:param>

    <xsl:param name="stylesheet-template" select="parse-xml($stylesheet-template-string)"/>

    <xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl"/>

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

    <xsl:variable name="stylesheet">
        <xsl:apply-templates select="$stylesheet-template" mode="construct-stylesheet"/>
    </xsl:variable>

    <xsl:template match="xsl:stylesheet | xsl:transform" mode="construct-stylesheet">
        <xsl:copy>
            <xsl:apply-templates select="@* , node()" mode="#current"/>

            <axsl:variable name="results" as="document-node()*">
                <xsl:iterate select="$mode-names">
                    <axsl:variable name="result-{.}">
                        <axsl:apply-templates select="{if (position() eq 1) then '.' else '$result-' || subsequence($mode-names, position() - 1, 1)}" mode="{.}"/>
                    </axsl:variable>
                    <axsl:sequence select="$result-{.}"/>
                </xsl:iterate>
            </axsl:variable>

            <axsl:template match="/">
                <results>
                    <axsl:for-each select="$results">
                        <result step="{{position()}}">
                            <axsl:copy-of select="."/>
                        </result>
                    </axsl:for-each>
                </results>
            </axsl:template>          
        </xsl:copy>
    </xsl:template>

    <xsl:output indent="yes"/>

    <xsl:template match="/">
        <xsl:sequence
            select="transform(map {
            'source-node' : .,
            'stylesheet-node' : $stylesheet
            })?output"/>
    </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/3NJ38YX/2 for one order of modes and https://xsltfiddle.liberty-development.net/3NJ38YX/3 for a different order.

The example has one indirection to be self-contained, the stylesheet to be used is passed in as a string parameter but would or could of course be passed in as a document node or parsed from a file with the doc function in the same way.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.