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

基于前置元素分组的XSLT转换问题及解决方案

问题描述

环境:使用Apache FOP 2.8,搭配SaxonHE12。

XML输入

<viewport>
    <id name="123"/>
    <font family="sans-serif" style="normal" weight="400" variant="normal" size="10000" color="#000000"/>
    <text>foo 1</text>
    <id name="#321"/>
    <text>foo 2</text>
    <id name="123"/>
    <text>foo 3</text>
    <text>foo 4</text>
    <text>foo 5</text>
    <id name="456"/>
    <text>foo 6</text>
    <text>foo 7</text>
    <id name="789"/>
    <text>foo 8</text>
    <text>foo 9</text>
    <text>foo 10</text>
</viewport>

转换规则

  • 所有前置存在**无前缀#**的id元素的text,归属于该id标识的文本段;
  • 若text的直接前置id带#号,则该text需额外保留此id属性。

示例:foo 1-5归属于id为123的文本段,foo 2同时需保留#321的id属性。

期望输出HTML

<div id="123">
    <span>foo 1</span>
    <span id="#321">foo 2</span>
    <span>foo 3</span>
    <span>foo 4</span>
    <span>foo 5</span>
</div>
<div id="456">
    <span>foo 6</span>
    <span>foo 7</span>
</div>
<div id="789">
    <span>foo 8</span>
    <span>foo 9</span>
    <span>foo 10</span>
</div>

尝试的XSLT及问题

最初编写的XSLT如下,存在text模板被多次调用、生成重复内容的问题,调整viewport的apply-templates选择器后仍未解决重复id的问题:

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:fn="http://www.w3.org/2005/xpath-functions">

  <xsl:template match="*|/">
    <xsl:apply-templates select="./*"/>
  </xsl:template>
              
  <xsl:template match="viewport">
      <xsl:apply-templates select="./*"/>
  </xsl:template>
  
  <xsl:template match="id">
    <xsl:variable name="name"><xsl:value-of select="@name" /></xsl:variable>
    <xsl:if test="not(fn:starts-with(@name, '#'))">
      <div class="element-container" id="{$name}">
        <!-- all following text elements that have an id element with the current id -->
        <xsl:apply-templates select="following::text[preceding::id[not(fn:starts-with($name, '#'))]/@name = $name]"/>
      </div>
    </xsl:if>
  </xsl:template>
  
  <xsl:template match="text">
    <xsl:variable name="special-id"><xsl:value-of select="preceding-sibling::id[1][fn:starts-with(@name, '#')]/@name"/></xsl:variable>
        <span>
          <xsl:if test="$special-id != ''">
            <xsl:attribute name="id"><xsl:value-of select="$special-id"/></xsl:attribute>
          </xsl:if>
          <xsl:value-of select="text()"/>
        </span>
  </xsl:template>

</xsl:stylesheet>

可行解决方案

使用xsl:for-each-group按文本所属的无前缀id分组,解决重复渲染问题,最终XSLT如下:

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:fn="http://www.w3.org/2005/xpath-functions">

  <xsl:template match="*|/">
    <xsl:apply-templates select="./*"/>
  </xsl:template>
              
  <xsl:template match="viewport">
    <xsl:for-each-group select="./*" group-by="if(preceding-sibling::id[not(fn:starts-with(@name, '#'))][1]/@name != '') then (preceding-sibling::id[not(fn:starts-with(@name, '#'))][1]/@name) else ''">
      <xsl:choose>
        <xsl:when test="current-grouping-key() != ''">
          <div class="report-element" id="{current-grouping-key()}">
            <xsl:apply-templates select="current-group()"/>
          </div>
        </xsl:when>
        <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each-group>
  </xsl:template>

  <xsl:template match="text">
    <xsl:variable name="special-id"><xsl:value-of select="preceding-sibling::id[1][fn:starts-with(@name, '#')]/@name"/></xsl:variable>
        <span>
          <xsl:if test="$special-id != ''">
            <xsl:attribute name="id"><xsl:value-of select="$special-id"/></xsl:attribute>
          </xsl:if>
          <xsl:value-of select="text()"/>
        </span>
  </xsl:template>

</xsl:stylesheet>

说明:该方案通过分组将同属一个无前缀id的元素归为一组,仅渲染一次;同时保留了对带#前缀id的处理逻辑,确保对应text元素带上特殊id属性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 23:37:01