You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用XSLT 1.0对HTML中同class的同级元素分组?

XSLT 1.0 同级连续元素分组问题解决建议

问题需求

使用XSLT 1.0处理HTML,将连续同级的div.case1/div.case2元素分组到<box>容器中,同时将每个div的内容转换为<p>标签,其他元素保持原样输出。

输入HTML

<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <div>
      <p>A</p>
      <div class="case1">0</div>
      <div class="case1">1</div>
      <div class="case1">2</div>
      <p>B</p>
      <p>C</p>
      <div class="case2">3</div>
      <div class="case2">4</div>
      <p>D</p>
      <p>E</p>
      <div class="case1">5</div>
      <div class="case1">6</div>
    </div>
  </body>
</html>

期望输出

<?xml version="1.0"?>
<html>
  <head>
    <meta charset="utf-8"/>
  </head>
  <body>
    <div>
      <p>A</p>
      <box class="case1">
        <p>0</p>
        <p>1</p>
        <p>2</p>
      </box>
      <p>B</p>
      <p>C</p>
      <box class="case2">
        <p>3</p>
        <p>4</p>
      </box>
      <p>D</p>
      <p>E</p>
      <box class="case1">
        <p>5</p>
        <p>6</p>
      </box>
    </div>
  </body>
</html>

用户尝试的XSLT代码及问题

用户编写的XSLT模板中,mode="inbox"未生效,分组后的<box>内容为空,仅最后一个空模板<xsl:template match="div[@class='case1']" />生效:

<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>

  <!-- For the case of "case1". -->
  <xsl:template match="div[@class='case1'][not(preceding-sibling::*[1][@class='case1'])]">
    <box>
      <xsl:apply-templates select="." mode="inbox" />
      <xsl:apply-templates select="following-sibling::div[@class='case1'][preceding-sibling::*[not(self::div[@class='case1'])][1]/following-sibling::div[@class='case1'][1] = current()]" mode="inbox" />
    </box>
  </xsl:template>
  <xsl:template match="div[@class='case1']" mode="inbox">
    <p><xsl:apply-templates select="node()" /></p>
  </xsl:template>
  <xsl:template match="div[@class='case1']" />
</xsl:stylesheet>
<!-- For case2, similarly -->

问题根源:

  • 后续兄弟节点的选择逻辑过于复杂且存在错误,无法正确选中连续的同classdiv元素;
  • 分开处理case1和case2导致代码冗余,且空模板会阻止非分组触发节点的正确处理。

解决方案:Muenchian分组法

XSLT 1.0中最可靠的分组方式是Muenchian分组,通过定义key将连续同类型元素归为一组,统一处理所有case类div:

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

  <!-- 定义分组key:以最近的非case元素ID + class作为分组标识 -->
  <xsl:key 
    name="case-divs" 
    match="div[starts-with(@class, 'case')]" 
    use="concat(
          generate-id(preceding-sibling::*[not(starts-with(@class, 'case'))][1]), 
          '|', 
          @class
        )" 
  />

  <!-- 身份模板:复制所有未被特殊模板匹配的节点 -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <!-- 匹配每组的第一个case div,生成box容器 -->
  <xsl:template match="div[starts-with(@class, 'case')][count(. | key('case-divs', concat(generate-id(preceding-sibling::*[not(starts-with(@class, 'case'))][1]), '|', @class))[1]) = 1]">
    <box class="{@class}">
      <!-- 处理当前组内所有case div,使用wrap模式转换为p标签 -->
      <xsl:apply-templates 
        select="key('case-divs', concat(generate-id(preceding-sibling::*[not(starts-with(@class, 'case'))][1]), '|', @class))" 
        mode="wrap" 
      />
    </box>
  </xsl:template>

  <!-- wrap模式:将case div转换为p标签 -->
  <xsl:template match="div[starts-with(@class, 'case')]" mode="wrap">
    <p>
      <xsl:apply-templates/>
    </p>
  </xsl:template>

  <!-- 默认模式下,忽略非分组首节点的case div,避免重复输出 -->
  <xsl:template match="div[starts-with(@class, 'case')]"/>
</xsl:stylesheet>

方案说明

  1. 分组Key:通过preceding-sibling::*[not(starts-with(@class, 'case'))][1]定位每个case div之前最近的非case元素,结合class生成唯一分组标识,确保连续同class的div被归为同一组;
  2. 分组触发模板:仅匹配每组的第一个case div,生成<box>容器并遍历组内所有元素;
  3. 模式转换:使用mode="wrap"将组内每个div转换为<p>标签,避免与默认模板冲突;
  4. 空模板:默认模式下忽略非分组首节点的case div,防止重复输出。

内容的提问来源于stack exchange,提问作者msnrhnd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 22:57:11