tags:

views:

54

answers:

2

I'm using XSLT 1.0 to transform some XML documents. I have a problem to do one thing, in that I would need some help please:

I have a set of items in my XML document something like :

<item1>...</item1>  
<item2>...</item2>  
<!-- ... -->  
<itemN>...</itemN>

I want to create something like this based on delta (items per page), for example delta is three:

<page>  
  <item1>...</item1>  
  <item2>...</item2>  
  <item3>...</item3>  
</page>  
<page>  
  <item4>...</item4>  
  <item5>...</item5>    
  <item6>...</item6>  
</page>

So, basically to create a XML structure in XSLT that will put fixed number of items per page.

If tried something like this (in order to use node-set):

<xsl:variable name="all_items_per_delta">  
  <xsl:for-each select="item">    
    <!-- if position is one or if mod of 3 then insert a page node, 
         so for first contract and for 3th, 6th, 9th... item -->
    <xsl:if test="position() = 1">  
      <page>  
    </xsl:if>  
    <!-- in the meantime for every contract insert the contract node -->
    <item>
      <!-- ... -->
    </item> 
    <!-- if position is one or if mod of 3 then insert a page node, 
         so for first item and for 3th, 6th, 9th... item--> 
    <xsl:if test="( (position() mod 3) = 0) ) and ( position() != last() )"> 
      </page>
      <page>
    </xsl:if>    
    <!-- if the position is last just end the page element -->
    <xsl:if test="position() = last()">  `  
      </page>
    </xsl:if>
  </xsl:for-each>
</xsl:variable>

But this doesn't work because the xslt parser says

'Expected end of tag 'page'

and this is for the first <page> tag I want to add. It seems that I have to end the page tab </page> in the same line. With <xslt:if> it doesn't work unfortunately.

Please advise, how can I make this kind of tree fragment structure in order to later extract the node-set using EXSL extension. Thank you.

+2  A: 

The reason why it doesn't work is because XSLT must itself be well-formed XML; and e.g. this:

<xsl:if test="...">
   </page>
</xsl:if>

isn't well-formed XML. Simply put, you can't have an opening tag inside one construct, and closing one inside another.

Now as to how to handle this properly. One simple way is to take every _n_th element in the outer loop, and generate <page> element for it; then put that element, and the following n-1 elements, inside that <page>:

<xsl:for-each select="item[position() mod $n = 0]">
   <page>
       <xsl:copy-of select=". | following-sibling::item[position() &lt; $n)]">
   </page>
</xsl:for-each>

Alternatively, you might want to use Muenchean method, grouping items by position() mod $n.

Pavel Minaev
+1, though I would express it as `following-sibling::item[position() <= $n]`
Tomalak
Actually it seems we're both wrong and it should be `< $n` - my code pproduced $n-1 items (self, and $n-2 following), while yours would produce $n+1 (self, and $n following).
Pavel Minaev
Ah, well. :) You are right, of course.
Tomalak
A: 

This requires an approach to grouping in XSLT. XSLT 1.0 provides little support and it needed the genius of the Munchean method to solve it. XSLT 2.0 has more support (but not all environments, e.g. some MS environments) support XSLT2.0.

Some resources (from helpfule XML experts) are:

http://www.jenitennison.com/xslt/grouping/

http://www.dpawson.co.uk/xsl/sect2/N4486.html

http://www.xml.com/pub/a/2003/11/05/tr.html

peter.murray.rust