Tutorial :Group List of nodes into Tree of nodes with XSL



Question:

I'm parsing a huge word file with Test descriptions, and have a problem of scope of nodes. Word basically creates a list of paragraphs and I want to group them into a parent node. So for each node 'A' I want to group all the following nodes up to the next node 'A' into 'A'.

How can this be done with XSL?

Example: I've gotten to :

<A/>  <ab/>  <ac/>  <A/>  <ab/>  <ac/>  

But need:

<A>  <ab/>  <ac/>  </A>  <A>  <ab/>  <ac/>  </A>  

Thank you!


Solution:1

If you mean to match all the nodes following <A>, but come before the next <A>, I think you can use something like this:

<xsl:template match="A">    <xsl:copy>      <!-- start of range -->      <xsl:variable name="start" select="count(preceding-sibling::*) + 1" />      <!-- end of range -->      <xsl:variable name="stop">        <xsl:choose>          <!-- either just before the next A node -->          <xsl:when test="following-sibling::A">            <xsl:value-of select="count(following-sibling::A[1]/preceding-sibling::*) + 1" />          </xsl:when>          <!-- or all the rest -->          <xsl:otherwise>            <xsl:value-of select="count(../*) + 1" />          </xsl:otherwise>        </xsl:choose>      </xsl:variable>        <!-- this for debugging only -->      <xsl:attribute name="range">        <xsl:value-of select="concat($start + 1, '-', $stop - 1)" />      </xsl:attribute>        <!-- copy all nodes in the calculated range -->      <xsl:for-each select="../*[position() &gt; $start and position() &lt; $stop]">        <xsl:copy-of select="." />      </xsl:for-each>    </xsl:copy>  </xsl:template>  

For your input:

<root>    <A />    <ab />    <ac />    <A />    <ab />    <ac />  </root>  

I get (I left the "range" attribute in to make the calculations visible):

<A range="2-3">    <ab />    <ac />  </A>  <A range="5-6">    <ab />    <ac />  </A>  


Solution:2

There is a simple and very powerful solution using keys.

This transformation:

<xsl:stylesheet version="1.0"   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">     <xsl:output omit-xml-declaration="yes" indent="yes"/>     <xsl:key name="kFollowing" match="*[not(self::A)]"    use="generate-id(preceding-sibling::A[1])"/>        <xsl:template match="/*">       <t>         <xsl:apply-templates select="A"/>       </t>      </xsl:template>        <xsl:template match="A">       <A>         <xsl:copy-of select=            "key('kFollowing',generate-id())"/>       </A>      </xsl:template>  </xsl:stylesheet>  

when applied on the original XML document:

<t>      <A/>      <ab/>      <ac/>      <A/>      <ab/>      <ac/>  </t>  

produces the wanted result:

<t>     <A>        <ab/>        <ac/>     </A>     <A>        <ab/>        <ac/>     </A>  </t>  

Do note how the definition of the <xsl:key>, combined with the use of the key() function makes most easy and natural collecting all sibling elements between two neighboring <A/> elements.


Solution:3

XSLT 2.0 solution:

<xsl:for-each-group select="*" group-starting-with="A">    <xsl:element name="{name(current-group()[1])}">      <xsl:copy-of select="current-group()[position() gt 1]"/>      </xsl:element>  </xsl:for-each-group>  

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »