Richa Bajaj
Richa Bajaj

Reputation: 21

how to merge two siblings using xslt

I need to merge two siblings

<table>
    <tbody>
    <tr>
    <td> table data</td>
    </tr>
    </tbody>
    <tbody>
    <tr>
    <td> table data</td>
    </tr>
    </tbody>
    </table>

Expected output:

<table>
<tbody>
<tr>
<td> table data</td>
</tr>
<tr><td> table data</td>
</tr>
</tbody>
</table>

My xslt code is :

<xsl:template match="/">
   <xsl:for-each-group select="*" group-adjacent="boolean(self::tbody)">
         <tbody>
    <xsl:value-of select="."/>
         </tbody>
         </xsl:for-each-group>
    </xsl:template>

which is not giving the correct output. Could you please suggest

Upvotes: 0

Views: 256

Answers (2)

Tim C
Tim C

Reputation: 70618

There are a number of issues with your current XSLT

  1. Your template matches the document node, which in your case is the parent of the table element, not the table element itself. As you are trying to group on child tbody elements, your template should match the table element
  2. xsl:value-of returns just the text value of a node. You should be using xsl:copy-of here (or xsl:apply-templates in conjunction with the identity template). You should also select all items in the group, not just the current item.
  3. You haven't accounted for what happens for nodes under table other than tbody

So, your XSLT should look like this....

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:output method="html" indent="yes" />
  <xsl:strip-space elements="*" />

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

  <xsl:template match="table">
    <table>
      <xsl:for-each-group select="*" group-adjacent="boolean(self::tbody)">
        <xsl:choose>
          <xsl:when test="current-grouping-key()">
            <tbody>
              <xsl:apply-templates select="current-group()/*"/>
            </tbody>
          </xsl:when>
          <xsl:otherwise>
            <xsl:apply-templates select="current-group()" />
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each-group>
    </table>
  </xsl:template>
</xsl:stylesheet>

Note, if you are using XSLT 3.0, you can replace the identity template with this:

<xsl:mode on-no-match="shallow-copy"/>

On the other hand, if you are actually only to use XSLT 1.0, then you need to do Muenchian Grouping. That means defining a key like so:

<xsl:key name="table" match="table/*" use="boolean(self::tbody)" />

Then, instead of using xsl:for-each-group, do this (although this will group all tbody elements, not just adjacent ones)

<xsl:for-each select="*[generate-id() = generate-id(key('table', boolean(self::tbody))[1])]">

Try this XSLT instead

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

  <xsl:key name="table" match="table/*" use="boolean(self::tbody)" />

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

  <xsl:template match="table">
    <table>
      <xsl:for-each select="*[generate-id() = generate-id(key('table', boolean(self::tbody))[1])]">
        <xsl:choose>
          <xsl:when test="self::tbody">
            <tbody>
              <xsl:apply-templates select="key('table', true())/*"/>
            </tbody>
          </xsl:when>
          <xsl:otherwise>
            <xsl:apply-templates select="key('table', false())" />
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each>
    </table>
  </xsl:template>
</xsl:stylesheet>

Of course, after all this, Michael.Hor257k's answer is much simpler in this case. (Although it is definitely worth reading up on Muenchian Grouping if indeed you are stuck with XSLT 1.0).

Upvotes: 1

michael.hor257k
michael.hor257k

Reputation: 116993

Your example could be handled simply by:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/table">
    <xsl:copy>
        <tbody>
            <xsl:copy-of select="tbody/*"/>
        </tbody>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Upvotes: 0

Related Questions