使用XSL检查特定节点内容 满足条件时修改MathML转义字符
XSLT转义MathML字符方案
核心错误原因
- 语法报错原因:
xsl:otherwise仅能作为xsl:choose的直接子元素存在,不能直接放在xsl:for-each节点下。 - 逻辑缺陷:你编写的匹配根节点
/的模板覆盖了初始的身份复制模板,导致原文档结构无法完整输出;同时XPath大小写敏感,你写的select="Text"无法匹配到文本节点,文本节点的正确选择写法是text()。 - 实现思路问题:不需要用
xsl:for-each手动遍历mtext节点,XSLT的模板匹配机制会自动命中目标节点,配合身份模板可以完整保留原文档所有结构、属性、非目标节点内容。
可直接运行的实现代码
以下代码兼容XSLT 1.0,可直接在C#的XslCompiledTransform中使用,实现三个核心需求:
- 仅对
<mtext>节点内的{、}、[、]字符添加反斜杠转义 - 完整保留MathML原结构、所有属性、其他节点内容不变
- 保留
 实体不被转换为普通空格
<?xml version='1.0' encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://www.w3.org/1998/Math/MathML"> <xsl:output method="xml" indent="yes" encoding="UTF-8"/> <!-- 身份模板:复制所有未被特殊匹配的节点、属性 --> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*" /> </xsl:copy> </xsl:template> <!-- 匹配mtext节点,处理内部文本转义 --> <xsl:template match="m:mtext"> <xsl:copy> <xsl:apply-templates select="@*"/> <xsl:call-template name="multi-replace"> <xsl:with-param name="text" select="text()"/> </xsl:call-template> </xsl:copy> </xsl:template> <!-- 多字符递归替换模板 --> <xsl:template name="multi-replace"> <xsl:param name="text"/> <xsl:choose> <!-- 优先保留 实体 --> <xsl:when test="contains($text, ' ')"> <xsl:value-of select="substring-before($text, ' ')"/> <xsl:text disable-output-escaping="yes">&#160;</xsl:text> <xsl:call-template name="multi-replace"> <xsl:with-param name="text" select="substring-after($text, ' ')"/> </xsl:call-template> </xsl:when> <!-- 依次转义四个括号字符 --> <xsl:when test="contains($text, '{')"> <xsl:value-of select="substring-before($text, '{')"/> <xsl:text>\{</xsl:text> <xsl:call-template name="multi-replace"> <xsl:with-param name="text" select="substring-after($text, '{')"/> </xsl:call-template> </xsl:when> <xsl:when test="contains($text, '}')"> <xsl:value-of select="substring-before($text, '}')"/> <xsl:text>\}</xsl:text> <xsl:call-template name="multi-replace"> <xsl:with-param name="text" select="substring-after($text, '}')"/> </xsl:call-template> </xsl:when> <xsl:when test="contains($text, '[')"> <xsl:value-of select="substring-before($text, '[')"/> <xsl:text>\[</xsl:text> <xsl:call-template name="multi-replace"> <xsl:with-param name="text" select="substring-after($text, '[')"/> </xsl:call-template> </xsl:when> <xsl:when test="contains($text, ']')"> <xsl:value-of select="substring-before($text, ']')"/> <xsl:text>\]</xsl:text> <xsl:call-template name="multi-replace"> <xsl:with-param name="text" select="substring-after($text, ']')"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$text"/> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet>
关键说明
- 代码中声明了MathML的默认命名空间前缀,直接匹配
m:mtext比用local-name()='mtext'更严谨,不会误处理其他命名空间下的同名节点。  保留逻辑通过disable-output-escaping实现:XML解析阶段 会被转换为Unicode非断空格字符(U+00A0),输出时手动写回转义实体并禁止输出转义,即可保留原实体格式。- 递归替换逻辑在同一个模板内完成所有字符的判断替换,不需要多次嵌套调用单字符替换模板,执行效率更高。如果后续需要新增转义字符,只需要在
xsl:choose节点内新增对应的判断分支即可。 - 你之前尝试用
translate()函数失败是符合预期的,该函数仅支持单字符到单字符的映射,无法实现单字符替换为双字符的需求。
内容的提问来源于stack exchange,提问作者nyghtrunner
相关产品推荐
相关产品推荐

