XSLT merge rows from adjacent HTML tables, of same

2019-08-29 10:45发布

问题:

I am new to XSLT and I have the requirement of bringing all rows of adjacent HTML tables, having the same structure, in a single table, simply by appending all rows of following tables in the first table encountered:

Input:

<div>
<span class="title">sample</span>
<br/>
    some text node1
    <br/>
    some text node2
<p/>
<table class="class1">
   <tbody>
     <tr>
       <td>L1:</td>
       <td>C1</td>
     </tr>
   </tbody>
 </table>
 <p/>
 <span class="section">Section1</span>
<p/>
<table class="class1">
   <tbody>
     <tr>
       <td>L2:</td>
       <td>C2</td>
     </tr>
   </tbody>
 </table>
 <table class="class1">
   <tbody>
     <tr>
       <td>L3:</td>
       <td>C3</td>
     </tr>
   </tbody>
 </table>
    <table class="class1">
   <tbody>
     <tr>
       <td>L4:</td>
       <td>C4</td>
     </tr>
   </tbody>
</table>
    <span class="section">Section2</span>
<p/>
    <table class="class1">
   <tbody>
     <tr>
       <td>L5:</td>
       <td>C5</td>
     </tr>
   </tbody>
</table>
    <table class="class2">
   <tbody>
     <tr>
       <td>L6:</td>
      <td>C6</td>
     </tr>
  </tbody>
</table>
</div>

The resulting output, should merge adjacent tables with @class="class1".

Output:

<div>
<span class="title">sample</span>
<br/>
    some text node1
    <br/>
    some text node2
<p/>
<table class="class1">
   <tbody>
     <tr>
       <td>L1:</td>
       <td>C1</td>
     </tr>
   </tbody>
 </table>
<p/>
<span class="section">Section1</span>
<p/>
<table class="class1">
   <tbody>
     <tr>
       <td>L2:</td>
       <td>C2</td>
     </tr>
     <tr>
      <td>L3:</td>
       <td>C3</td>
    </tr>
     <tr>
       <td>L4:</td>
      <td>C4</td>
     </tr>
  </tbody>
</table>
    <span class="section">Section1</span>
<p/>
<table class="class1">
   <tbody>
     <tr>
       <td>L5:</td>
       <td>C5</td>
     </tr>
   </tbody>
</table>
    <table class="class2">
  <tbody>
    <tr>
      <td>L6:</td>
      <td>C6</td>
    </tr>
  </tbody>
</table>
</div>

Any ideas how this can be realized? I have been trying to do this using group-adjacent, but I'm not getting the intended result, and I don't know what's missing me.

- Edit:

here is my attempt (NOT WORKING PROPERLY).

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

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

<xsl:template match="table[@class='class1']">
<xsl:choose>
<xsl:when test="not(preceding-sibling::*[1][self::table])">
<xsl:copy>
 <xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
  <xsl:choose>
    <xsl:when test="preceding-sibling::*[1]/@class='class1'" />
    <xsl:when test="following-sibling::*[1]/@class='class1'" >
         <table class="class1">
           <tbody>
           <xsl:apply-templates select="tbody/*"/>
           <xsl:for-each select="following-sibling::*">
            <xsl:if test="self::table[@class='class1']">
              <xsl:apply-templates select="tbody/*"/>
             </xsl:if> 
           </xsl:for-each>
           </tbody>
          </table>
    </xsl:when>
    <xsl:otherwise>
      <xsl:copy>
       <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
    </xsl:otherwise>
   </xsl:choose>
  </xsl:otherwise>
 </xsl:choose>

NOTE If possible help me do that both in XSLT1.0, enhancing my attempt, and/or XSLT2.0.

回答1:

Using XSLT 2.0:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

<xsl:output indent="yes"/>

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

<xsl:template match="body | div"><!-- need to list other container elements here -->
  <xsl:copy>
    <xsl:for-each-group select="node() except text()[not(normalize-space())]" group-adjacent="boolean(self::table[@class = 'class1'])">
      <xsl:choose>
        <xsl:when test="current-grouping-key()">
          <table class="{@class}">
            <xsl:apply-templates select="thead"/>
            <tbody>
              <xsl:apply-templates select="current-group()/tbody/tr"/>
            </tbody>
          </table>
        </xsl:when>
        <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each-group>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

This is a correction of the first version which should ensure that all text nodes containing more than white space are also copied. But it assumes that between those tables you want to group there is solely white space.



回答2:

Another version using XSLT 2.0

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

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

    <xsl:template match="div">
        <xsl:for-each-group select="*" group-adjacent="boolean(self::table[@class='class1'])">
            <xsl:choose>
                <xsl:when test="current-group()[@class='class1'] and current-group()[2]">
                    <table class="{@class}">
                        <tbody>
                            <xsl:apply-templates select="current-group()/descendant::tr"/>
                        </tbody>
                    </table>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:for-each-group select="current-group()" group-adjacent="boolean(self::table[@class='class2'])">
                        <xsl:choose>
                            <xsl:when test="current-group()[@class='class2'] and current-group()[2]">
                                <table class="{@class}">
                                    <tbody>
                                        <xsl:apply-templates select="current-group()/descendant::tr"/>
                                    </tbody>
                                </table>
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:copy-of select="current-group()"/>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:for-each-group>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each-group>
    </xsl:template>
</xsl:stylesheet>


回答3:

not to disappoint you this time, there was a small change done:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

<xsl:output method="xml" indent="yes"/>

<xsl:key name="key" match="table[@class = following::*[1][local-name() = 'table']/@class or @class = preceding-sibling::*[1][local-name() = 'table']/@class]" use="@class"/>

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

<xsl:template match="table[not(@class = following::*[1][local-name() = 'table']/@class or @class = preceding-sibling::*[1][local-name() = 'table']/@class)]">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
</xsl:template>
<xsl:template match="table[@class = following::*[1][local-name() = 'table']/@class or @class = preceding-sibling::*[1][local-name() = 'table']/@class]"/>

<xsl:template match="table[generate-id() = generate-id(key('key', @class)[1])]">
    <table class="{@class}">
        <xsl:copy-of select="key('key', @class)/tbody"/>
    </table>
</xsl:template>

</xsl:stylesheet>