XSLT转换中如何忽略引用的DTD?解决模板匹配异常
问题描述
在Oxygen中使用Saxon运行XSLT 3.0转换时遇到以下问题:
初始XSLT模板(匹配所有元素*):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:math="http://www.w3.org/2005/xpath-functions/math" exclude-result-prefixes="xs math" expand-text="yes" version="3.0"> <xsl:output indent="yes" method="xml" omit-xml-declaration="no" encoding="utf-8"/> <xsl:template match="/"> <xsl:text>
</xsl:text> <xsl:apply-templates select="*"/> </xsl:template> <xsl:template match="*"> <!-- On Martin's suggestion I should use node-name instead of name, so I have changed this, but the result is the same. --> <xsl:value-of select="node-name()"/><xsl:text>
</xsl:text> <xsl:apply-templates select="*"/> </xsl:template> </xsl:stylesheet>
针对带DTD声明的XML文件:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE ddn PUBLIC "-//S1000D//DTD Data Dispatch Note 20050501//EN//XML" "http://www.s1000d.org/s1000d_2-2/xml_dtd/ddn/dtd/ddn.dtd"> <ddn> <ddnc> <modelic>ABC</modelic> <sendid>AASSD</sendid> <recvid>VVBBN</recvid> <diyear>2024</diyear> <seqnum>00001</seqnum> </ddnc> </ddn>
转换输出正常,能列出所有元素名称:
<?xml version="1.0" encoding="utf-8"?> ddn ddnc modelic sendid recvid diyear seqnum
但将模板改为仅匹配ddn元素后:
<xsl:template match="ddn"> <xsl:value-of select="node-name()"/><xsl:text>
</xsl:text> <xsl:apply-templates select="*"/> </xsl:template>
输出异常,未打印ddn及子元素名称,仅输出文本内容:
<?xml version="1.0" encoding="utf-8"?> ABC AASSD VVBBN 2024 0000
移除XML的DOCTYPE声明后,输出恢复正常;改用match="*:ddn"匹配模板时,输出也恢复正常。
原因分析
问题核心在于DTD将ddn等元素定义到了某个命名空间中,尽管XML文件中没有显式使用命名空间前缀,但解析器加载DTD后,会将元素绑定到对应的命名空间。此时:
match="ddn"仅匹配无命名空间的ddn元素,无法命中实际处于命名空间中的目标元素,因此会触发XSLT的默认模板,直接输出元素的文本内容。match="*:ddn"是匹配任意命名空间下的ddn元素,因此能正确命中目标元素,执行自定义模板逻辑。
解决方案
方案1:让Saxon忽略DTD加载
通过禁用DTD加载,让解析器不处理DTD,元素将保持无命名空间状态,此时match="ddn"即可正常匹配:
- 在Oxygen中:打开转换场景配置,找到Saxon的Parser Options,勾选
Disable DTD loading(或类似选项,不同版本表述可能略有差异)。 - 命令行运行Saxon时:添加参数
-dtd:off,示例命令:
java -jar saxon-he-xxx.jar -s:input.xml -xsl:stylesheet.xsl -o:output.xml -dtd:off
方案2:调整XSLT的命名空间匹配规则
如果需要保留DTD处理,可通过以下方式适配命名空间:
- 明确绑定命名空间前缀:先确定DTD定义的命名空间URI(可通过
node-name(/*)在XSLT中输出查看),然后在XSLT中声明对应前缀并匹配:<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:math="http://www.w3.org/2005/xpath-functions/math" xmlns:s1000d="http://www.s1000d.org/s1000d_2-2" <!-- 替换为实际命名空间URI --> exclude-result-prefixes="xs math s1000d" expand-text="yes" version="3.0"> <!-- ... 其他代码 ... --> <xsl:template match="s1000d:ddn"> <!-- 用前缀匹配目标命名空间的元素 --> <xsl:value-of select="node-name()"/><xsl:text>
</xsl:text> <xsl:apply-templates select="*"/> </xsl:template> </xsl:stylesheet> - 通用匹配任意命名空间:继续使用
match="*:ddn",该方式无需关心具体命名空间URI,可匹配所有命名空间下的ddn元素。
内容的提问来源于stack exchange,提问作者Zug_Bug
相关产品推荐
相关产品推荐

