tags:

views:

129

answers:

4

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"&gt;

    <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>
+1  A: 

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"&gt;

<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?

Russ C
That would work if i only had one file.My main problem seems to be that i'm trying to resolve recursively included data only once, i.e. there's no single set of nodes that i could query to get the list i need.
Stroboskop
Ah got you; is it the case that some of these XML files contain duplicate nodes too ?Thinking of something to do with writing an outer script/program that programatically transforms the Xml (in C#/IronPython using the XslCompiledTransform classes f.ex) and making use of XSL:Parameters ?That way, you could load each XML file, use XPath to get each logical block to transform, and combine the results, recursively.It's a bit messy but it might just work.
Russ C
The files themselves don't have duplicate nodes, but the included files could be referenced from several different files that all are referenced by another file. So in the process of collecting all the references, i might end up with the same object several times. I could try to pre-parse the reference structure and make the list i need outside of XSL. But then i could do the whole aggregation outside of XSL, too...
Stroboskop
+1  A: 

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.

0xA3
That sounds like it could work. I could use a file instead of a variable.
Stroboskop
A: 

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.

newtover
+2  A: 

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>
markusk
I'm busy with something else right now, but i'll try that.
Stroboskop