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

如何用XSLT 2/3将注释转换为包裹式输出元素?

解决HTML注释区块转XML元素的XSLT分组问题

问题背景

需要将非规范HTML转换为XML,以便通过XSLT批量处理数千份文档生成指定格式JSON。现有HTML使用<!-- TAG_NAME -->与<!-- /TAG_NAME -->形式的注释标记内容区块,目标是将这类注释替换为对应XML元素,包裹两个注释之间的所有内容。当前尝试的XSLT代码仅能匹配结束注释前的第一个同级节点,无法正确分组包裹所有中间节点,需修正分组逻辑。

待处理HTML示例

<!-- IMAGECOUNT -->
      <table width="100%">
         <tr>
            <td colspan="1" rowspan="1">
               <strong>
2 Images
</strong>
            </td>
         </tr>
      </table>
      <!-- /IMAGECOUNT -->
      <p>
         <strong>
<!-- SECTION -->
The section 
<!-- /SECTION -->
<!-- COUNTRY -->
The country
<!-- /COUNTRY -->


</strong>
      </p>
      <!-- DATE -->
      <p>
         <font size="-1">
            <b>Date Posted: 09-Dec-2019</b>
         </font>
      </p>
      <!-- /DATE -->
      <!-- TEXT -->
      <center>
         <p>
            <font size="-1">
Just some text
</font>
         </p>
      </center>
      <!-- /TEXT -->
      <!-- TOP THUMBNAILS -->
      <table class="tabletopbottom" width="100%">
         <tr>
            <td colspan="1" rowspan="1">
               <img src="images/s1353556.jpg" alt="Cat"/>
               <img src="images/s1164352.jpg" alt="Dog"/>
            </td>
         </tr>
      </table>
      <!-- /TOP THUMBNAILS -->

尝试的XSLT代码(存在问题)

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

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

  <!-- Match opening comments and create elements -->
  <xsl:template match="comment()" priority="10">
    <xsl:variable name="commentContent" select="normalize-space(.)"/>
    <xsl:message>Processing comment: <xsl:value-of select="$commentContent"/></xsl:message>
    <xsl:choose>
      <xsl:when test="starts-with($commentContent, '/')">
        <xsl:message>   Ignoring closing comment: <xsl:value-of select="$commentContent"/></xsl:message>
      </xsl:when>
      <xsl:otherwise>
        <xsl:message>Creating element for comment: <xsl:value-of select="$commentContent"/></xsl:message>
        <xsl:element name="{replace($commentContent, ' ', '')}">
                      <xsl:apply-templates select="following-sibling::node()[1][not(self::comment())]"/>
        </xsl:element>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

问题分析

原代码仅通过following-sibling::node()[1]选择当前注释后的第一个非注释节点,无法覆盖到结束注释前的所有节点。同时,未处理结束注释的跳过逻辑,会导致结束注释被保留或重复处理。

解决方案

使用XSLT 3.0的for-each-group分组功能,通过group-starting-with匹配开头注释,group-ending-with匹配对应的结束注释,将两个注释之间的所有节点作为一组,再用对应名称的XML元素包裹该组内容。

修正后的XSLT代码

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

  <!-- Identity transform: 默认复制所有节点,后续模板覆盖特定情况 -->
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

  <!-- 对同级节点按注释区块分组处理 -->
  <xsl:template match="*">
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
      <xsl:for-each-group select="node()" 
                          group-starting-with="comment()[normalize-space(.) != '' and not(starts-with(normalize-space(.), '/'))]">
        <!-- 处理分组:如果是注释开头的组 -->
        <xsl:choose>
          <xsl:when test="self::comment()">
            <xsl:variable name="tagName" select="normalize-space(.)"/>
            <xsl:variable name="closingTag" select="concat('/', $tagName)"/>
            <!-- 检查是否有对应的结束注释 -->
            <xsl:if test="current-group()[last()][self::comment() and normalize-space(.) = $closingTag]">
              <!-- 创建替换的XML元素,移除标签名中的空格 -->
              <xsl:element name="{replace($tagName, ' ', '')}">
                <!-- 处理组内除开头和结尾注释外的所有节点 -->
                <xsl:apply-templates select="current-group()[position() > 1 and position() < last()]"/>
              </xsl:element>
            </xsl:if>
            <!-- 无对应结束注释的情况:直接复制开头注释及后续节点 -->
            <xsl:if test="not(current-group()[last()][self::comment() and normalize-space(.) = $closingTag])">
              <xsl:apply-templates select="current-group()"/>
            </xsl:if>
          </xsl:when>
          <!-- 非注释开头的组:直接复制所有节点 -->
          <xsl:otherwise>
            <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>

  <!-- 单独匹配结束注释,直接跳过不输出 -->
  <xsl:template match="comment()[starts-with(normalize-space(.), '/')]"/>
</xsl:stylesheet>

代码说明

  1. 分组逻辑:通过for-each-group将节点按开头注释分组,每组从<!-- TAG -->开始,到对应的<!-- /TAG -->结束。
  2. 元素创建:提取注释中的标签名,替换空格后作为XML元素名,包裹组内除首尾注释外的所有节点。
  3. 异常处理:如果开头注释没有对应的结束注释,会按原结构保留节点,避免数据丢失。
  4. 结束注释处理:单独匹配结束注释,直接跳过不输出,避免残留无用注释。

注意事项

  • 确保使用支持XSLT 3.0的处理器(如Saxon HE/PE/EE)。
  • 需保证HTML中的注释区块无嵌套结构,否则分组逻辑可能失效。
  • 标签名中的空格会被移除(如TOP THUMBNAILS转为TOPTHUMBNAILS),若需保留空格可调整replace逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 16:25:58