XSLT and Temporary Documents

I am trying to process an xml file that has several different groups of states, such as

<root> <childgroup>16</childgroup> <setstate>init</setstate> <child1>...</child1> <child2>...</child2> <setstate>process<setstate> <child2>...</child2> <child3>...</child3> ..... <childgroup>17</childgroup> ... 

I need to actually get something like

 <childgroup no="16"> <state statename="init"> <child1>...</child1> <child2>...</child2> </state> <state statename="process"> <child2>...</child2> <child3>...</child3> </state> </childgroup> <childgroup no="17"> ... 

code>

I did the simple part, which is going together and adding the chgrpno attribute and the stateid attribute to all the children (it makes a copy of all the elements except the child group and state, adding the attribute to these two.

 <xsl:template match="/"> <xsl:apply-templates mode="numb"/> </xsl:template> 

This works, and as a result, all the children have an attribute, so I could rearrange them in the next pass, and the states have numbers so that I can do the same later. But, trying to follow the example of M. Kay with "temporary documents" when I try to do

 <xsl:variable name="nmb"> <xsl:apply-templates mode="numb"/> </xsl:variable> <xsl:template match="/"> <xsl:copy-of select="$nmb"/> </xsl:template> 

then it just returns the original to me, and all the changes that I made in the first pass disappeared. So what am I doing wrong here?

I am using XSLT 1.0 and not XSLT 2.0 explicitly.

(edit: of course, I named the variable, forgot to copy it).

+4
source share
4 answers

Here is an example of how to approach grouping with XSLT 1.0 in one step; style sheet

 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output indent="yes"/> <xsl:key name="k1" match="root/*[not(self::childgroup)]" use="generate-id(preceding-sibling::childgroup[1])"/> <xsl:key name="k2" match="root/*[not(self::childgroup) and not(self::setstate)]" use="concat(generate-id(preceding-sibling::childgroup[1]), '|', generate-id(preceding-sibling::setstate[1]))"/> <xsl:template match="root"> <xsl:copy> <xsl:apply-templates select="childgroup"/> </xsl:copy> </xsl:template> <xsl:template match="childgroup"> <childgroup no="{.}"> <xsl:apply-templates select="key('k1', generate-id())[self::setstate]"/> </childgroup> </xsl:template> <xsl:template match="setstate"> <state statename="{.}"> <xsl:copy-of select="key('k2', concat(generate-id(preceding-sibling::childgroup[1]), '|', generate-id()))"/> </state> </xsl:template> </xsl:stylesheet> 

converts input sample

 <root> <childgroup>16</childgroup> <setstate>init</setstate> <child1>...</child1> <child2>...</child2> <setstate>process</setstate> <child2>...</child2> <child3>...</child3> <childgroup>17</childgroup> <setstate>init</setstate> <child1>...</child1> <child2>...</child2> <setstate>process</setstate> <child2>...</child2> <child3>...</child3> </root> 

in

 <root> <childgroup no="16"> <state statename="init"> <child1>...</child1> <child2>...</child2> </state> <state statename="process"> <child2>...</child2> <child3>...</child3> </state> </childgroup> <childgroup no="17"> <state statename="init"> <child1>...</child1> <child2>...</child2> </state> <state statename="process"> <child2>...</child2> <child3>...</child3> </state> </childgroup> </root> 
+3
source

The key thing in multi-pass processing with XSLT 1.0 is that the variable containing the result of the first pass does not actually contain an XML document (tree).

This variable contes RTF (result-tree-fragment). RTFs are defined only in XSLT 1.0. The only operation that can be performed with RTF is <xsl:copy-of> or <xsl:value-of> or passing it as a parameter - all that relates to RTF as a string.

By definition, any attempt to embed RTFs with an XPath expression that has location tests in it should fail.

A workaround is to use an extension function, usually called xxx: node -set () (but Xalan uses the name "nodeet" no dash), where the prefix "xxx:" bound to a specific one implemented in the XSLT processor. As Martin Honnen recommends, to achieve some degree of portability, you should try to use the EXSLT common: nodeset () extension whenever it is implemented by a specific XSLT processor.

Here is an example of two-pass processing with XSLT 1.0 :

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="num[not(. mod 2)]"/> <xsl:template match="/"> <xsl:variable name="vrtfPass1"> <xsl:apply-templates/> </xsl:variable> <nums> <xsl:apply-templates mode="pass2" select="ext:node-set($vrtfPass1)/*"/> </nums> </xsl:template> <xsl:template match="num" mode="pass2"> <x> <xsl:apply-templates/> </x> </xsl:template> </xsl:stylesheet> 

When applied to this XML document :

 <nums> <num>01</num> <num>02</num> <num>03</num> <num>04</num> <num>05</num> <num>06</num> <num>07</num> <num>08</num> <num>09</num> <num>10</num> </nums> 

this transformation in the first pass creates a filtered tree in which there are only num elements with an odd value. Then the second pass renames each num element to x . Final result:

 <nums> <x>01</x> <x>03</x> <x>05</x> <x>07</x> <x>09</x> </nums> 
+2
source

Another ( single-pass ) possible approach (not to mention simpler):

  • copy node-sets as variables
  • use variable references to construct predicates of intersecting nodes - sets of adjacent siblings of the same group recursively

 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output indent="yes"/> <xsl:template match="text()"/> <xsl:template match="*" mode="childx"> <xsl:copy-of select="."/> </xsl:template> <xsl:template match="childgroup"> <xsl:variable name="fw" select="following-sibling::*"/> <childgroup no="{.}"> <xsl:variable name="fwrw" select="$fw[self::childgroup][1] /preceding-sibling::*"/> <xsl:apply-templates select="following-sibling::*[ count( . | $fwrw ) = count( $fwrw ) or count( $fwrw )=0] [self::setstate] " mode="setstate"/> </childgroup> </xsl:template> <xsl:template match="setstate" mode="setstate"> <xsl:variable name="fw" select="following-sibling::*"/> <state name="{.}"> <xsl:variable name="fwrw" select="$fw[ self::setstate or self::childgroup ][1]/ preceding-sibling::*"/> <xsl:apply-templates select="following-sibling::*[ count( . | $fwrw) = count( $fwrw ) or count($fwrw) = 0]" mode="childx"/> </state> </xsl:template> </xsl:stylesheet> 

When applied to the following XML:

 <root> <childgroup>16</childgroup> <setstate>init</setstate> <child1>...</child1> <child2>...</child2> <setstate>process</setstate> <child2>...</child2> <child3>...</child3> <childgroup>17</childgroup> <setstate>init</setstate> <child1>...</child1> <child2>...</child2> <setstate>process</setstate> <child2>...</child2> <child3>...</child3> <childgroup>18</childgroup> <childgroup>19</childgroup> <setstate>init</setstate> <child1>...</child1> <child2>...</child2> <setstate>process</setstate> <child2>...</child2> <child3>...</child3> <childgroup>20</childgroup> </root> 

It produces:

 <childgroup no="16"> <state name="init"> <child1>...</child1> <child2>...</child2> </state> <state name="process"> <child2>...</child2> <child3>...</child3> </state> </childgroup> <childgroup no="17"> <state name="init"> <child1>...</child1> <child2>...</child2> </state> <state name="process"> <child2>...</child2> <child3>...</child3> </state> </childgroup> <childgroup no="18"/> <childgroup no="19"> <state name="init"> <child1>...</child1> <child2>...</child2> </state> <state name="process"> <child2>...</child2> <child3>...</child3> </state> </childgroup> <childgroup no="20"/> 
+1
source

You must specify your variable, then you can copy it, for example.

 <xsl:variable name="rtf1"> <xsl:apply-templates mode="numb"/> </xsl:variable> <xsl:template match="/"> <xsl:copy-of select="$rtf1"/> </xsl:template> 

With XSLT 1.0, if you do not want to copy the contents of a variable with a copy and instead want to process it using application templates, you will need an extension function such as exsl: node-set , for example

 <xsl:variable name="rtf1"> <xsl:apply-templates mode="numb"/> </xsl:variable> <xsl:template match="/"> <xsl:apply-templates select="exsl:node-set($rtf1)/node()"/> </xsl:template> 
0
source

All Articles