如何在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;apos;'"/> <xsl:param name="by" select="'&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;apos;')"> <xsl:value-of select="replace(string-join(text(), ''), '&amp;apos;', '&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>
问题分析
- 两个方案都错误地以实体
&apos;作为匹配目标,但XML文本中的单引号在XSLT处理时是实际的'字符,并非实体形式,因此无法匹配到任何需要替换的内容。 - 方案二存在逻辑错误:既输出替换后的文本又重复复制节点,导致结果混乱;同时未区分缩写和表达式的单引号,不符合需求。
正确解决方案
方案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(., '(?<![a-zA-Z])''|''(?![a-zA-Z])', '&quot;')"/> </xsl:template> </xsl:stylesheet>
- 正则说明:
(?<![a-zA-Z])'匹配前面非字母的单引号(如to 'insurance中的'),'(?![a-zA-Z])匹配后面非字母的单引号(如programs' as中的'),这两类单引号会被替换为";而像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="'''"/> <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>&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>
- 逻辑说明:递归遍历文本每个字符,遇到单引号时检查其前后字符是否为字母,只要有一个非字母就替换为
",否则保留原单引号。
预期输出
<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>
内容的提问来源于stack exchange,提问作者Smile
相关产品推荐
相关产品推荐

