oleksii.sapov-erlinger
oleksii.sapov-erlinger

Reputation: 412

Create an array of variables with dynamic names with xslt

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

Upvotes: 0

Views: 331

Answers (1)

Martin Honnen
Martin Honnen

Reputation: 167716

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.

Upvotes: 1

Related Questions