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

如何用递归实现XSLT中字符串字符与<app>节点的双重遍历替换

问题描述

需求:将<p>元素中的字符串里的指定字符,替换为对应<lem>包含该字符的<app>节点。要求每个<app>仅对应文本中的一个字符,按顺序替换,每个<app>顶部只有一个<lem>,下方可包含任意其他节点。

本人刚接触XSLT,受Java/MATLAB的循环遍历思维(比如i=1:n、j=1:m)限制,写不出合适的递归实现。现有代码因为对每个<app>遍历整个文本导致输出错误,求正确的递归实现方法。

示例输入

<div>
            <p>SOMEWONDERFULOLDTEXT</p>
            <app>
               <lem>O</lem>
               <rdg>Ø</rdg>
            </app>
            <app>
               <lem>W</lem>
               <rdg>V</rdg>
            </app>
            <app>
               <lem>O</lem>
               <rdg>Ö</rdg>
            </app>
            <app>
               <lem>E</lem>
               <rdg>Ë</rdg>
               <rdg>ę</rdg>
            </app>
         </div>

现有错误XSLT代码

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:tei="http://www.tei-c.org/ns/1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs tei" version="3.0">

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

    <!-- now build the apparatus -->
    <xsl:template match="tei:div">
        <xsl:param name="thisBlock" select="./tei:p/node()"/>
        <xsl:for-each select="tei:app">
            <xsl:variable name="thisApp" select="."/>
            <xsl:for-each
                select="tokenize(replace(replace($thisBlock, '(.)', '$1\n'), '\n$', ''), '\n')">
                <xsl:choose>
                    <xsl:when test="$thisApp/tei:lem/text() = .">
                    <xsl:copy-of select="$thisApp"></xsl:copy-of>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:apply-templates></xsl:apply-templates>
                </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

期望输出

<div>
            <p>S<app>
               <lem>O</lem>
               <rdg>Ø</rdg>
            </app>ME<app>
               <lem>W</lem>
               <rdg>V</rdg>
            </app><app>
               <lem>O</lem>
               <rdg>Ö</rdg>
            </app>ND<app>
               <lem>E</lem>
               <rdg>Ë</rdg>
               <rdg>ę</rdg>
            </app>RFULOLDTEXT</p>
         </div>
解决方案

核心思路是递归遍历文本字符和<app>序列,按顺序匹配替换,避免原代码中重复遍历文本的问题。以下是XSLT 3.0的实现:

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

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

    <!-- 处理div节点:准备文本和app序列,调用递归模板 -->
    <xsl:template match="div">
        <xsl:copy>
            <xsl:variable name="text" select="p/text()"/>
            <xsl:variable name="apps" select="app"/>
            <p>
                <!-- 调用递归模板,传入剩余文本、剩余app序列 -->
                <xsl:call-template name="replace-chars">
                    <xsl:with-param name="remaining-text" select="$text"/>
                    <xsl:with-param name="remaining-apps" select="$apps"/>
                </xsl:call-template>
            </p>
        </xsl:copy>
    </xsl:template>

    <!-- 递归替换模板 -->
    <xsl:template name="replace-chars">
        <xsl:param name="remaining-text"/>
        <xsl:param name="remaining-apps"/>

        <!-- 终止条件:文本为空或无剩余app时退出 -->
        <xsl:if test="$remaining-text ne ''">
            <xsl:variable name="first-char" select="substring($remaining-text, 1, 1)"/>
            <xsl:choose>
                <!-- 当前字符匹配第一个app的lem时,替换为该app,递归处理剩余文本和剩余app -->
                <xsl:when test="$remaining-apps and $remaining-apps[1]/lem = $first-char">
                    <xsl:copy-of select="$remaining-apps[1]"/>
                    <xsl:call-template name="replace-chars">
                        <xsl:with-param name="remaining-text" select="substring($remaining-text, 2)"/>
                        <xsl:with-param name="remaining-apps" select="$remaining-apps[position() > 1]"/>
                    </xsl:call-template>
                </xsl:when>
                <!-- 不匹配时,输出当前字符,递归处理剩余文本和原app序列 -->
                <xsl:otherwise>
                    <xsl:value-of select="$first-char"/>
                    <xsl:call-template name="replace-chars">
                        <xsl:with-param name="remaining-text" select="substring($remaining-text, 2)"/>
                        <xsl:with-param name="remaining-apps" select="$remaining-apps"/>
                    </xsl:call-template>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

代码说明

  1. 身份模板:保留XML结构中不需要修改的部分,是XSLT的基础模板。
  2. div处理模板:提取<p>的文本内容和所有<app>节点,调用递归模板开始替换流程。
  3. 递归替换模板:
    • 每次取文本的第一个字符和第一个<app>的<lem>内容对比。
    • 匹配时输出该<app>,并递归处理剩余文本和剩余的<app>序列(去掉已使用的第一个)。
    • 不匹配时输出当前字符,递归处理剩余文本和原<app>序列。
    • 当文本为空或没有剩余<app>时,递归终止。

这种方式严格按顺序逐个匹配替换,符合XSLT的声明式思维,而非命令式循环逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 00:06:25