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

如何在XSLT中替换特定单引号:区分单词内与表达式场景

XML单引号替换解决方案(保留缩写形式)

需求

替换XML文本中用于包裹表达式的单引号为",但必须保留单词内的缩写单引号(如's、'm、've这类形式)。

示例XML

<para ampexmnem="dpa2">
<paratext>The Secretary, in consultation with the Secretary of Health and Human Services, shall, with respect to any items described in this subsection which are to be included in a taxpayer's return of tax, develop language for such items which is as simple and clear as possible (such as referring to 'insurance affordability programs' as 'free or low-cost health insurance').</paratext>
</para>

尝试过的无效方案

方案一

<xsl:template name="replace-quotes">
        <xsl:param name="text"/>
        <xsl:param name="replace" select="'&amp;amp;apos;'"/>
        <xsl:param name="by" select="'&amp;amp;quot;'"/>
        <xsl:choose>
            <xsl:when test="contains($text, $replace)">
                <xsl:value-of select="substring-before($text, $replace)"/>
                <xsl:value-of select="$by"/>
                <xsl:call-template name="replace-quotes">
                    <xsl:with-param name="text" select="substring-after($text, $replace)"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$text"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <xsl:template match="text()">
        <xsl:call-template name="replace-quotes">
            <xsl:with-param name="text" select="."/>
        </xsl:call-template>
    </xsl:template>

方案二

<xsl:template match="@* | node()">
        <xsl:choose>
            <xsl:when test="contains(string-join(text(), ''), '&amp;amp;apos;')">
                <xsl:value-of select="replace(string-join(text(), ''), '&amp;amp;apos;', '&amp;amp;quot;')"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:copy>
                    <xsl:apply-templates select="@* | node()"/>
                </xsl:copy>
            </xsl:otherwise>
        </xsl:choose>
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

问题分析

  1. 两个方案都错误地以实体&amp;apos;作为匹配目标,但XML文本中的单引号在XSLT处理时是实际的'字符,并非实体形式,因此无法匹配到任何需要替换的内容。
  2. 方案二存在逻辑错误:既输出替换后的文本又重复复制节点,导致结果混乱;同时未区分缩写和表达式的单引号,不符合需求。

正确解决方案

方案1:XSLT 2.0及以上版本(推荐)

利用正则表达式精准匹配需要替换的单引号:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <!-- 身份模板:复制所有节点和属性 -->
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

    <!-- 处理文本节点,替换目标单引号 -->
    <xsl:template match="text()">
        <!-- 匹配规则:前面不是字母的单引号,或后面不是字母的单引号 -->
        <xsl:value-of select="replace(., '(?&lt;![a-zA-Z])''|''(?![a-zA-Z])', '&amp;quot;')"/>
    </xsl:template>
</xsl:stylesheet>
  • 正则说明:(?<![a-zA-Z])'匹配前面非字母的单引号(如to 'insurance中的'),'(?![a-zA-Z])匹配后面非字母的单引号(如programs' as中的'),这两类单引号会被替换为&quot;;而像taxpayer's这类前后均为字母的缩写单引号会被保留。

方案2:XSLT 1.0版本(无正则支持)

通过递归逐个字符检查实现需求:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <!-- 身份模板:复制所有节点和属性 -->
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

    <!-- 处理文本节点,调用递归替换模板 -->
    <xsl:template match="text()">
        <xsl:call-template name="replace-target-quotes">
            <xsl:with-param name="text" select="."/>
        </xsl:call-template>
    </xsl:template>

    <!-- 递归替换模板 -->
    <xsl:template name="replace-target-quotes">
        <xsl:param name="text"/>
        <xsl:param name="quote" select="'&apos;'"/>
        <xsl:if test="$text != ''">
            <xsl:choose>
                <!-- 检查当前字符是否为单引号 -->
                <xsl:when test="substring($text, 1, 1) = $quote">
                    <xsl:variable name="prev-char" select="substring($text, 0, 1)"/>
                    <xsl:variable name="next-char" select="substring($text, 2, 1)"/>
                    <!-- 若前后字符不全是字母,则替换单引号 -->
                    <xsl:choose>
                        <xsl:when test="not(translate($prev-char, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', '') = '') 
                                        or not(translate($next-char, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', '') = '')">
                            <xsl:text>&amp;quot;</xsl:text>
                        </xsl:when>
                        <xsl:otherwise>
                            <xsl:value-of select="$quote"/>
                        </xsl:otherwise>
                    </xsl:choose>
                    <!-- 处理剩余文本 -->
                    <xsl:call-template name="replace-target-quotes">
                        <xsl:with-param name="text" select="substring($text, 2)"/>
                    </xsl:call-template>
                </xsl:when>
                <xsl:otherwise>
                    <!-- 输出当前字符,继续处理剩余文本 -->
                    <xsl:value-of select="substring($text, 1, 1)"/>
                    <xsl:call-template name="replace-target-quotes">
                        <xsl:with-param name="text" select="substring($text, 2)"/>
                    </xsl:call-template>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>
  • 逻辑说明:递归遍历文本每个字符,遇到单引号时检查其前后字符是否为字母,只要有一个非字母就替换为&quot;,否则保留原单引号。

预期输出

<para ampexmnem="dpa2">
<paratext>The Secretary, in consultation with the Secretary of Health and Human Services, shall, with respect to any items described in this subsection which are to be included in a taxpayer's return of tax, develop language for such items which is as simple and clear as possible (such as referring to &quot;insurance affordability programs&quot; as &quot;free or low-cost health insurance&quot;).</paratext>
</para>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 17:05:56