1
votes

Is there any way in XSL to update a global variable?

I want to check what elements i already transformed and act accordingly. That would require me to somehow add the names of the elements to some sort of list and update it each time a new element is transformed.

But since xsl:variable isn't "variable" in the sense one would expect, i have no way of adding anything to it once it has been defined.

I have multiple included data files, so using xsl functions that only know the current set of nodes will not help.

== Edit ==

This is what my transformation looks like now. But it will include files that are repeatedly referenced in different sub-files every time.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" />

    <xsl:template match="@*|node()">
        <xsl:copy>
           <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <!-- include the contents of referenced files -->
    <xsl:template match="reference">
        <xsl:apply-templates select="document(@url)/data/node()" />
    </xsl:template>

</xsl:transform>

And the data files would look something like this:

<data>
    <reference url="another_data_file.xml"/>
    ... other stuff ...
</data>
4
It is difficult to give advice without knowing more details on what you are trying to do. Possibly you are not trying to solve the problem the "XSLT way". Maybe you can post a minimal input and output document? - Dirk Vollmar
Hey, you absolutely don't define a problem here. Please, specify your problem! This is an example of poorly defined problem. - Dimitre Novatchev
How about the question in the very first sentence? - Stroboskop
The answer is known: it isn't possible. But this isn't the problem you have. If you define the real problem, people will come up with solutions that don't need to update a variable. You cannot imagine what complex problems have been solved with XSLT and its functional nature (the feature that variables are immutable) has never been an obstacle for these solutions. - Dimitre Novatchev

4 Answers

7
votes

XSLT is a functional language, and does not allow variables to be updated. If you need to aggregate results over several steps, the usual approach is to use a recursive template. Example:

<xsl:template name="transform-elements">
    <xsl:param name="elements-to-process" select="/.."/>
    <xsl:param name="processed-elements" select="/.."/>
    <xsl:if test="$elements-to-process">
        <xsl:variable name="element" select="$elements-to-process[1]"/>

        <!-- ... Do stuff with $element ...-->

        <!-- Recursively invoke template for remaining elements -->
        <xsl:call-template name="transform-elements">
            <xsl:with-param name="elements-to-process" 
                            select="$elements-to-process[position() != 1]"/>
            <xsl:with-param name="processed-elements" 
                            select="$processed-elements|$element"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>
1
votes

Sadly, there isn't a direct way, XSL Variables are Assign Once only, but that can be assigned conditionally.

However, variables defined in a block are only accessible to that block and its children, perhaps inverting your logic and doing it iteratively would work instead ?

That way, you can't process stuff that's been transformed already, as it's already been completed.

Have a look into the usages of xsl:Key and xsl:for-each, this will let you order the nodes you transform.

To quote from w3schools, using this XML:

<persons>
  <person name="Tarzan" id="050676"/>
  <person name="Donald" id="070754"/>
  <person name="Dolly" id="231256"/>
</persons> 

And this XSL:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:key name="preg" match="person" use="@id"/>

<xsl:template match="/">
  <html>
  <body>
  <xsl:for-each select="key('preg','050676')">
    <p>
    Id: <xsl:value-of select="@id"/><br />
    Name: <xsl:value-of select="@name"/>
    </p>
  </xsl:for-each>
  </body>
  </html>
</xsl:template>

</xsl:stylesheet> 

Will find the person with the ID of '050676'. Perhaps using this method with more defined key would give you the structure you need?

1
votes

If your input data is spread over several documents it might be a good idea to split the transformation process into several steps.

Add a pre-processing transformation which pulls the relevant sections from the input documents into a single intermediate document. This document can then be transformed with a simple XSLT and you might not run into the problems which you are currently facing.

0
votes

I am not sure about the size of the documents you are trying to parse, but as a solution for relatively small XML documents you can output the result to a variable and having applied the extension function node-set (from exslt, or msxsl, etc.) apply transformation to the contents of the variable, excluding duplicate nodes as if you would process a single XML document.