Re: [xsl] To control node duplication

Subject: Re: [xsl] To control node duplication
From: George Cristian Bina <george@xxxxxxxxxxxxx>
Date: Wed, 14 Jan 2009 15:19:09 +0200
Hi Nicola,

Yes, it works also without the copy mode. It is just a logical separation of the code, the templates in the copy mode have a clear function, they copy the content and append some value to the elem1 text.
If you have a more complex input, for instance if you have elem1 elements in some other parts of your file where you do not want to have the text changed then using a mode for these templates becomes necessary.


Best Regards,
George
--
George Cristian Bina
<oXygen/> XML Editor, Schema Editor and XSLT Editor/Debugger
http://www.oxygenxml.com

nick public wrote:
Hi George.
Thank you very much for your precious help.

I analyzed your implementation and I realized that the trick to
bypass, in this case, the reset variable limitation is to recall the
processLevel template
passing it a new instance of $states shifting each time the first char
by one to right. Since the processLevel just take the first char by
$states, I have obtained my scope to provide each time a different
value of $states. Great! :-))

 <xsl:template name="processLevel">
   <xsl:param name="states"/>
   <xsl:if test="string-length($states)>0">
     <xsl:apply-templates mode="copy">
       <xsl:with-param name="state" select="substring($states, 1, 1)"/>
     </xsl:apply-templates>
     <xsl:call-template name="processLevel">
       <xsl:with-param name="states" select="substring($states, 2,
string-length($states))"/>
     </xsl:call-template>
   </xsl:if>
 </xsl:template>

The way to exit by the recorsion is the empty $states <xsl:if test="string-length($states)>0">

The thing that I have not understood is the attribute mode="copy" that
you use in the template statements calling and called. Seems that
works without it too.

Really, thank you very much, George. You have solved my problem and I
have learned something by you about XSLT Language.

Ciao. Nicola



2009/1/13 George Cristian Bina <george@xxxxxxxxxxxxx>:
For XSLT 1.0 you can replace the for each with a recursive template as
below:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
 <xsl:template match="root">
   <xsl:copy><xsl:apply-templates/></xsl:copy>
 </xsl:template>

 <xsl:template match="level">
   <xsl:variable name="level" select="."/>
   <xsl:call-template name="processLevel">
     <xsl:with-param name="states" select="states"/>
   </xsl:call-template>
 </xsl:template>

 <xsl:template name="processLevel">
   <xsl:param name="states"/>
   <xsl:if test="string-length($states)>0">
     <xsl:apply-templates mode="copy">
       <xsl:with-param name="state" select="substring($states, 1, 1)"/>
     </xsl:apply-templates>
     <xsl:call-template name="processLevel">
       <xsl:with-param name="states" select="substring($states, 2,
string-length($states))"/>
     </xsl:call-template>
   </xsl:if>
 </xsl:template>

 <xsl:template match="node() | @*" mode="copy">
   <xsl:param name="state"/>
   <xsl:copy>
     <xsl:apply-templates select="node() | @*" mode="copy">
       <xsl:with-param name="state" select="$state"/>
     </xsl:apply-templates>
   </xsl:copy>
 </xsl:template>

 <xsl:template match="elem1/text()" mode="copy">
   <xsl:param name="state"/>
   <xsl:value-of select="."/>
   <xsl:text>_</xsl:text>
   <xsl:value-of select="$state"/>
 </xsl:template>
</xsl:stylesheet>

Best Regards,
George
--
George Cristian Bina
<oXygen/> XML Editor, Schema Editor and XSLT Editor/Debugger
http://www.oxygenxml.com

nick public wrote:
Hi George,
I appreciate very much your help.
Unfortunately, the middleware that I'm using and in which the XSLT
have to run, provide just XSLT version 1.0.
Is it possible to adapt your solution for XSLT 1.0?

I tried to think to an alternative way but it seems a lot of complicated.
I could create three different versions of my script; each version is
specialized to work with a single status:
1) script_4.xslt - evaluates the presence of status 4: if yes copies
<level> and modifies <elem1> with 4 status
2) script_5.xslt - evaluates the presence of status 5: if yes,
appendes to the previous a new <level> copy with <elem1> modified with
5 status. If 5 status doesn't exist, the output provided has to be
that of script_4.xslt
3) script_6.xslt - evaluates the presence of status 6: if yes,
appendes to the previous a new <level> copy with <elem1> modified with
6 status. If 6 status doesn't exist, the output provided has to be
that of script_5.xslt

At the end, I could concatenate the three scripts in a job and should
obtain (I hope) the same result.

Opinion about?

Thanks a lot.
Ciao. Nicola


2009/1/13 George Cristian Bina <george@xxxxxxxxxxxxx>:
In XSLT 2.0 you can use something like this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
 <xsl:template match="root">
  <xsl:copy><xsl:apply-templates/></xsl:copy>
 </xsl:template>

 <xsl:template match="level">
  <xsl:variable name="level" select="."/>
  <xsl:for-each select="string-to-codepoints(states)">
    <xsl:apply-templates select="$level" mode="copy">
      <xsl:with-param name="state" select="."/>
    </xsl:apply-templates>
  </xsl:for-each>
 </xsl:template>

 <xsl:template match="node() | @*" mode="copy">
  <xsl:param name="state"/>
  <xsl:copy>
    <xsl:apply-templates select="node() | @*" mode="copy">
      <xsl:with-param name="state" select="$state"/>
    </xsl:apply-templates>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="elem1/text()" mode="copy">
  <xsl:param name="state"/>
  <xsl:value-of select="."/>
  <xsl:text>_</xsl:text>
  <xsl:value-of select="codepoints-to-string($state)"/>
 </xsl:template>
</xsl:stylesheet>

on your sample input it gives

<root>
  <level>
      <states>456</states>
      <start>
          <elem1>element_4</elem1>
      </start>
  </level><level>
      <states>456</states>
      <start>
          <elem1>element_5</elem1>
      </start>
  </level><level>
      <states>456</states>
      <start>
          <elem1>element_6</elem1>
      </start>
  </level>
</root>

Best Regards,
George
--
George Cristian Bina
<oXygen/> XML Editor, Schema Editor and XSLT Editor/Debugger
http://www.oxygenxml.com

nick public wrote:
Hi people.
I have the following need.
I have to duplicate a sub-tree basing of an element <states> contained
into the source XML.

<root>
 <level>
  <states>456</states>
  <start>
    <elem1>element</elem1>
  </start>
 </level>
</root>


For each <level> copy, the <elem1> element have to contain the corresponding states byte. In this case I've create a XML target containing 3 <level> element and for each copy, the <elem1> element contains the 4, 5 and 6 status.

The target for the example have to be like this:

<root>
 <level>
  <control>456</control>
  <start>
    <elem1>element_4</elem1>
  </start>
 </level>
 <level>
  <control>456</control>
  <start>
    <elem1>element_5</elem1>
  </start>
 </level>
 <level>
  <control>456</control>
  <start>
    <elem1>element_6</elem1>
  </start>
 </level>
</root>

Now, I learned thanks to you how to copy the sub-tree. I haven't
problem with string processing.
My problem is to control the copy.
I need something like this (please, don't become frightened: it's the
minimal code and is commented)

<?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" indent="yes"/>

 <!-- Load in a variable the states to manage -->
 <xsl:variable name='states' select ='root/level/states'/>

 <xsl:template match="level">
  <!-- Predisposes to manage all the three states
       passing as parameter each status to check in
       front to the states variable -->

  <!-- Invokes copyNode for the status 4.
       In copyNodes, if 4 is contained in states
       variable the level element will be copied
       otherwise no -->
  <xsl:call-template name='copyNode'>
    <xsl:with-param name='status' select='4'/>
  </xsl:call-template>

  <xsl:call-template name='copyNode'>
    <xsl:with-param name='status' select='5'/>
  </xsl:call-template>

  <xsl:call-template name='copyNode'>
    <xsl:with-param name='status' select='6'/>
  </xsl:call-template>
 </xsl:template>

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

 <xsl:template name="copyNode">
  <xsl:param name='status'/>

  <!-- Checks if the current $status (4, 5 or 6)
       is contained in the $states.
       If yes, can copies the level element -->
  <xsl:if test='contains($states,$status)'>

    <xsl:choose>
      <!-- :-(( THIS when SEEMS NEVER PERFORMED :-(( -->
      <xsl:when test="self::elem1">
        <elem1>
          <!-- The elem1 text value have to be manipulated by $status
-->
          <xsl:value-of select ='concat(substring(.,1,5),$status)'/>
        </elem1>
      </xsl:when>

      <xsl:otherwise>
        <xsl:copy-of select="."/>
      </xsl:otherwise>
    </xsl:choose>

   </xsl:if>
 </xsl:template>

</xsl:stylesheet>

The critical point is that the  <xsl:when test="self::elem1">  is
never reached and then I cannot operate the tranformation.


Could you help me URGENTLY? Thanks a lot in advance.

Ciao. Nicola

Current Thread