2
votes

After a day's research into XSLT, I am admitting defeat!

This is my input:

<div class="a" >
  <div class="b">b1</div>
  <div class="c">b1c1</div>
  <div class="d">b1d1</div>
  <div class="d">b1d2</div>
  <div class="b">b2</div>
  <div class="c">b2c1</div>
  <div class="d">b2d1</div>
  <div class="d">b2d2</div>
  <div class="d">b2d3</div>
  <div class="b">b3</div>
  <div class="c">b3c1</div>
  <div class="d">b3d1</div>
</div>

And this is the output I would like to get:

<div class="a" >
  <div class="b">b1
    <div class="c">b1c1</div>
    <div class="d">b1d1</div>
    <div class="d">b1d2</div>
  </div>
  <div class="b">b2
    <div class="c">b2c1</div>
    <div class="d">b2d1</div>
    <div class="d">b2d2</div>
    <div class="d">b2d3</div>
  </div>
  <div class="b">b3
    <div class="c">b3c1</div>
    <div class="d">b3d1</div>
  </div>
</div>

This is the xslt that I am using:

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

 <!-- Identity template, copies everything as is -->
 <xsl:template match="@*|node()">
  <xsl:copy>
   <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
 </xsl:template>

 <!-- Override for target element -->
 <xsl:template match="div[@class='a']">
  <!-- Copy the element -->
  <xsl:copy>
   <!-- And everything inside it -->
   <xsl:copy-of select="@*|node()"/>
   <!-- Move nodes -->
   <xsl:apply-templates select="div[@class='c']"/>
   <xsl:apply-templates select="div[@class='d']"/>
  </xsl:copy>
 </xsl:template>

</xsl:stylesheet>

But it's giving me the wrong output:

<div class="a">
  <div class="b">b1</div>
  <div class="c">b1c1</div>
  <div class="d">b1d1</div>
  <div class="d">b1d2</div>
  <div class="b">b2</div>
  <div class="c">b2c1</div>
  <div class="d">b2d1</div>
  <div class="d">b2d2</div>
  <div class="d">b2d3</div>
  <div class="b">b3</div>
  <div class="c">b3c1</div>
  <div class="d">b3d1</div>
  <div class="c">b1c1</div>
  <div class="c">b2c1</div>
  <div class="c">b3c1</div>
  <div class="d">b1d1</div>
  <div class="d">b1d2</div>
  <div class="d">b2d1</div>
  <div class="d">b2d2</div>
  <div class="d">b2d3</div>
  <div class="d">b3d1</div>
</div>

I understand why it's giving me this output, but I cannot find a way to modify it and get the correct output.

Thank you in advance.

PullingHair

1

1 Answers

1
votes

It looks like you are grouping the "c" and "d" classes by the first preceding "b" class. To do this in XSLT 1.0, you could define a key to capture this grouping.

<xsl:key name="b" match="div[@class!='b']" use="generate-id(preceding-sibling::div[@class='b'][1])" />

So, rather than selecting all child nodes in the template that matches the "a" class, you just select the "b" ones

<xsl:apply-templates select="div[@class='b']" />

Then, in the template that matches the "b" class, you can use the key to get the associated "c" and "d" elements

<xsl:template match="div[@class='b']">
 <xsl:copy>
  <xsl:copy-of select="@*" />
  <xsl:apply-templates select="key('b', generate-id())" />
 </xsl:copy>
</xsl:template>

Try this XSLT

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

 <xsl:key name="b" match="div[@class!='b']" use="generate-id(preceding-sibling::div[@class='b'][1])" />

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

 <xsl:template match="div[@class='a']">
  <xsl:copy>
   <xsl:apply-templates select="@*|div[@class='b']" />
  </xsl:copy>
 </xsl:template>

 <xsl:template match="div[@class='b']">
  <xsl:copy>
   <xsl:apply-templates select="@*|node()" />
   <xsl:apply-templates select="key('b', generate-id())" />
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

In XSLT 2.0, you can make use of xsl:for-each-group instead

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <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="div[@class='a']">
  <xsl:copy>
   <xsl:apply-templates select="@*" />
   <xsl:for-each-group select="div" group-starting-with="div[@class='b']">
     <xsl:copy>
      <xsl:apply-templates select="@*|node()" />
      <xsl:apply-templates select="current-group()[position() > 1]" />
     </xsl:copy>
    </xsl:for-each-group>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>