2

I have a xsl:template that is inserting an additional node into my original XML.

I would then like to use the following Template to reference that new node to assist in the continuation of the parsing of the source file.

My current method (second template) does not 'see' the newly inserted node from the first template. How would I approach this?

Many Thanks.

The below examples are extremely simplified to express what I am trying to achieve.

Starting XML:

<master> 
 <node>
  <node1>hi</node1>
  <node2>bye</node2>
 </node>
</master>

First Template:

<xsl:template match="master/node">
  <node>
  <xsl:apply-templates/>
  <node3>greetings</node3>
  </node>
</xsl:template>

Result XML 1:

<master> 
 <node>
  <node1>hi</node1>
  <node2>bye</node2>
  <node3>greetings<node3>
 </node>
</master>

Second Template:

<xsl:template match="master/node[node3='greetings']">
  <node>
   <newnode><xsl:value-of select="./node3"/>
  </node>
</xsl:template>

Expected Result:

<master> 
 <node>
  <newnode>greetings</newnode>
 </node>
</master>

XSL:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
<xsl:strip-space elements="*"/>

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

        <!-- first template -->
<xsl:template match="master/node">
  <node>
  <xsl:apply-templates/>
  <node3>greetings</node3>
  </node>
</xsl:template>

        <!-- second template -->
<xsl:template match="master/node[node3='greetings']">
  <node>
   <newnode><xsl:value-of select="./node3"/></newnode>
  </node>
</xsl:template>

user1540142
  • 185
  • 2
  • 15

3 Answers3

3

In XSLT 1.0 without extensions, only nodes in input documents can be matched by templates. To apply templates to an intermediate result, you can use the nodeset extension (widely implemented by XSLT 1.0 implementations) which allows templates to be applied to result-tree fragments. Or you can move to XSLT 2.0.

See Dimitre Novatchev's answer to a related question for more details on the nodeset extension.

Community
  • 1
  • 1
C. M. Sperberg-McQueen
  • 24,596
  • 5
  • 38
  • 65
1

Michael Sperberg-McQueen has given a precise explanation how to process RTFs (Result Tree Fragments) in XSLT 1.0.

Here is an example of a complete solution, using the EXSLT node-set() extension function:

<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="/">
  <xsl:variable name="vrtfPass1">
   <xsl:apply-templates/>
  </xsl:variable>
  <xsl:apply-templates select="ext:node-set($vrtfPass1)/*"/>
 </xsl:template>

 <xsl:template match="master/node">
  <node>
   <xsl:apply-templates/>
   <node3>greetings</node3>
  </node>
 </xsl:template>

 <xsl:template match="master/node[node3='greetings']" priority="2">
  <node>
   <newnode><xsl:value-of select="./node3"/></newnode>
  </node>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied to the provided XML document:

<master>
 <node>
  <node1>hi</node1>
  <node2>bye</node2>
 </node>
</master>

the wanted, correct result is produced:

<master>
   <node>
      <newnode>greetings</newnode>
   </node>
</master>

Do note that it is best to organize multi-pass processing in such a way, that all templates operating in Pass-N are in a separate and different mode than all templates operating in any other pass number. This is to avoid errors when the same template accidentally is selected for execution both in Pass-N and in Pass-M.

Using modes the above solution becomes:

<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="node()|@*" mode="pass2">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*" mode="pass2"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="/">
  <xsl:variable name="vrtfPass1">
   <xsl:apply-templates/>
  </xsl:variable>
  <xsl:apply-templates mode="pass2"
  select="ext:node-set($vrtfPass1)/*"/>
 </xsl:template>

 <xsl:template match="master/node">
  <node>
   <xsl:apply-templates/>
   <node3>greetings</node3>
  </node>
 </xsl:template>

 <xsl:template mode="pass2" match="master/node[node3='greetings']"
      priority="2">
  <node>
   <newnode><xsl:value-of select="./node3"/></newnode>
  </node>
 </xsl:template>
</xsl:stylesheet>
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • Thanks Dimitre,this works but I am failing to understand how the second template containing the variable is referenced in the following templates which otherwise appear identical except for the inclusion of a priority? and the additional namespace. Could you provide more detail please? – user1540142 Sep 11 '12 at 04:28
  • @user1540142, Please read this answer -- the topic is explained well there: http://stackoverflow.com/a/3881931/36305 – Dimitre Novatchev Sep 11 '12 at 04:35
  • Thanks Dimitre, The result is working for me but after reading the referenced material I still dont really understand how the variable is passed to a template that does not reference the variable. Ill keep trying to find more material to help me understand. Much Appreciated. – user1540142 Sep 11 '12 at 11:46
  • @user1540142, The variable is not "passed to a template". Instead, the template is *applied* to all nodes in the node-set that the variable contains. This is done with the instruction: `` – Dimitre Novatchev Sep 11 '12 at 12:07
  • Thanks Dimitre, How does this function work if I wanted to then perform another task using the results of, in this case, the master/node[node3='greetings' template? I tried to add another '' with a new ext:node-set variable name but this did not work. – user1540142 Sep 12 '12 at 23:54
  • @user1540142, You have a complete, working solution here. In case you have another problem, please, ask a new question. You need to put the templates for the second pass in their own mode -- please read about modes -- or otherwise they will be in the same (anonymous) mode as the templates for the first paths so there will be conflicts and either error messages or unexpected effects due to anwanted template being selected for execution. – Dimitre Novatchev Sep 13 '12 at 01:43
1

In XSLT 2.0, you just capture the output of the first template in a variable, and the value of the variable is a new XML document which can be processed just like a source document.

In XSLT 1.0, when you capture the output of a template in a variable, it is not a first-class document, but rather a "result tree fragment" which can only be processed in very limited ways. The exslt:node-set() extension converts it from a "result tree fragment" to a first-class document which can then be processed normally.

I hope that helps to summarise the very detailed information you have been given by others (though I do feel that these days, we should assume people are using XSLT 2.0 unless they tell us otherwise).

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • Thanks Michael, Appreciated the 'human readable' description. XSL is indeed tricky, just when you think youve got a handle on it.. bang... – user1540142 Sep 11 '12 at 11:49