views:

47

answers:

2

Hi, I have a xml as below that I'd like to copy n times while incrementing one of its element and one of its attribute.

XML input:

<Person position=1>
<name>John</name>
<number>1</number>
<number>1</number>
</Person>

and I'd like something like below with the number of increment to be a variable.

XML output:

<Person position=1>
<name>John</name>
<number>1</number>
</Person>
<Person position=2>
<name>John</name>
<number>2</number>
</Person>
....
<Person position=n>
<name>John</name>
<number>n</number>
</Person>

Any clue

A: 

This one is pretty easy:

<xsl:template match="/">
  <xsl:variable name="n">20</xsl:variable>
  <xsl:call-template name="loop">
    <xsl:with-param name="n" select="$n"/>
    <xsl:with-param name="counter" select="1"/>
  </xsl:call-template>
</xsl:template>
<xsl:template name="loop">
  <xsl:param name="n"/>
  <xsl:param name="counter"/>
  <xsl:if test="$counter &lt;= $n">
    <Person position="{$counter}">
      <xsl:copy-of select="//name"/>
      <number>
        <xsl:value-of select="$counter"/>
      </number>
    </Person>
    <xsl:call-template name="loop">
      <xsl:with-param name="n" select="$n"/>
      <xsl:with-param name="counter" select="$counter+1"/>
    </xsl:call-template>
  </xsl:if>
</xsl:template>

Set the variable n to however many iterations you need.

Welbog
You could skip one parameter by counting backwards and swapping the instructions inside the call template.
0xA3
+3  A: 

I. XSLT 1.0 solution:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pTimes" select="5"/>

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

 <xsl:template match="/*">
  <xsl:call-template name="applyNTimes">
    <xsl:with-param name="pTimes" select="$pTimes"/>
    <xsl:with-param name="pPosition" select="1"/>
  </xsl:call-template>
 </xsl:template>

 <xsl:template name="applyNTimes">
  <xsl:param name="pTimes" select="0"/>
  <xsl:param name="pPosition" select="1"/>

  <xsl:if test="$pTimes > 0">
   <xsl:apply-templates select="*">
    <xsl:with-param name="pPosition" select="$pPosition"/>
   </xsl:apply-templates>

   <xsl:call-template name="applyNTimes">
    <xsl:with-param name="pTimes" select="$pTimes -1"/>
    <xsl:with-param name="pPosition" select="$pPosition+1"/>
   </xsl:call-template>
  </xsl:if>
 </xsl:template>

 <xsl:template match="Person">
  <xsl:param name="pPosition" select="1"/>

  <Person position="{$pPosition}">
   <xsl:apply-templates>
     <xsl:with-param name="pPosition" select="$pPosition"/>
   </xsl:apply-templates>
  </Person>
 </xsl:template>

 <xsl:template match="number">
  <xsl:param name="pPosition" select="1"/>

  <number><xsl:value-of select="$pPosition"/></number>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the following XML document:

<t>
    <Person position="1">
        <name>John</name>
        <number>1</number>
    </Person>
</t>

the wanted result is produced:

<Person position="1">
   <name>John</name>
   <number>1</number>
</Person>
<Person position="2">
   <name>John</name>
   <number>2</number>
</Person>
<Person position="3">
   <name>John</name>
   <number>3</number>
</Person>
<Person position="4">
   <name>John</name>
   <number>4</number>
</Person>
<Person position="5">
   <name>John</name>
   <number>5</number>
</Person>

II. XSLT 2.0 solution:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 exclude-result-prefixes="xs"
 >
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:param name="pNumTimes" as="xs:integer" select="5"/>

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

 <xsl:template match="/">
   <xsl:variable name="vDoc" select="/"/>

     <xsl:for-each select="1 to $pNumTimes">
       <xsl:apply-templates select="$vDoc/*/*">
        <xsl:with-param name="pPosition" select="."/>
       </xsl:apply-templates>
     </xsl:for-each>
 </xsl:template>

 <xsl:template match="Person">
  <xsl:param name="pPosition" select="1"/>

  <Person position="{$pPosition}">
   <xsl:apply-templates>
     <xsl:with-param name="pPosition" select="$pPosition"/>
   </xsl:apply-templates>
  </Person>
 </xsl:template>

 <xsl:template match="number">
  <xsl:param name="pPosition" select="1"/>

  <number><xsl:value-of select="$pPosition"/></number>
 </xsl:template>
 </xsl:stylesheet>

III. Using a DVD-style recursion to avoid stack overflow on large N (XSLT 1.0)

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pTimes" select="5"/>

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

 <xsl:template match="/*">
  <xsl:call-template name="applyNTimes">
    <xsl:with-param name="pTimes" select="$pTimes"/>
    <xsl:with-param name="pPosition" select="1"/>
  </xsl:call-template>
 </xsl:template>

 <xsl:template name="applyNTimes">
  <xsl:param name="pTimes" select="0"/>
  <xsl:param name="pPosition" select="1"/>

  <xsl:if test="$pTimes > 0">
   <xsl:choose>
     <xsl:when test="$pTimes = 1">
           <xsl:apply-templates select="*">
            <xsl:with-param name="pPosition" select="$pPosition"/>
           </xsl:apply-templates>
     </xsl:when>
     <xsl:otherwise>
       <xsl:variable name="vHalf" select="floor($pTimes div 2)"/>

       <xsl:call-template name="applyNTimes">
        <xsl:with-param name="pTimes" select="$vHalf"/>
        <xsl:with-param name="pPosition" select="$pPosition"/>
       </xsl:call-template>

       <xsl:call-template name="applyNTimes">
        <xsl:with-param name="pTimes" select="$pTimes - $vHalf"/>
        <xsl:with-param name="pPosition" select="$pPosition + $vHalf"/>
       </xsl:call-template>
     </xsl:otherwise>
   </xsl:choose>
  </xsl:if>
 </xsl:template>

 <xsl:template match="Person">
  <xsl:param name="pPosition" select="1"/>

  <Person position="{$pPosition}">
   <xsl:apply-templates>
     <xsl:with-param name="pPosition" select="$pPosition"/>
   </xsl:apply-templates>
  </Person>
 </xsl:template>

 <xsl:template match="number">
  <xsl:param name="pPosition" select="1"/>

  <number><xsl:value-of select="$pPosition"/></number>
 </xsl:template>
</xsl:stylesheet>

The problem with deep recursion is the exhaustion of the call stack resulting in stack overflow. On different systems this happens with different levels of nesting, but this is typical at around N = 1000.

The above transformation practically has no such problem. The maximum recursion depth with DVC is log2(N), which means that if we need to repeat the action 1000000 (1M) times, the maximum recursion depth is only 19.

IV. Finally, how to avoid recursion at all (XSLT 1.0)

For known, not too big N, the following non-recursive technique works well:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pTimes" select="5"/>

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

 <xsl:template match="/*">
  <xsl:call-template name="applyNTimes">
    <xsl:with-param name="pTimes" select="$pTimes"/>
  </xsl:call-template>
 </xsl:template>

 <xsl:template name="applyNTimes">
  <xsl:param name="pTimes" select="0"/>

 <xsl:variable name="vCurrent" select="."/>

  <xsl:if test="$pTimes > 0">
   <xsl:for-each select=
     "document('')//node() | document('')//@* | document('')//namespace::*">
    <xsl:if test="not( position() > $pTimes )">
        <xsl:apply-templates select="$vCurrent/*">
         <xsl:with-param name="pPosition" select="position()"/>
        </xsl:apply-templates>
    </xsl:if>
   </xsl:for-each>
  </xsl:if>
 </xsl:template>

 <xsl:template match="Person">
  <xsl:param name="pPosition" select="1"/>

  <Person position="{$pPosition}">
   <xsl:apply-templates>
     <xsl:with-param name="pPosition" select="$pPosition"/>
   </xsl:apply-templates>
  </Person>
 </xsl:template>

 <xsl:template match="number">
  <xsl:param name="pPosition" select="1"/>

  <number><xsl:value-of select="$pPosition"/></number>
 </xsl:template>
</xsl:stylesheet>
Dimitre Novatchev
Just out of curiosity: What does DVC (or DVD?) stand for?
0xA3
+1 for the "clever hack" that the non-recursive XSLT 1.0 solution is. I've always been reluctant of applying this technique because it can fail for numbers greater than the available nodes in the document and thus does not really feel "clean" to me. PS: I've had the exact same solution as your I.) ready but you were faster. ;)
Tomalak
@0xA3: DVC = DiVide and Conquer :)
Dimitre Novatchev
@Tomalak: The non-recursive solution is known as the "Piez method".
Dimitre Novatchev
There must be a name for everything I guess. :-)
Tomalak