使用XSLT 1.0移除XML未使用命名空间并保留已用命名空间
移除XML中未使用的命名空间(XSLT 1.0实现)
需求说明
加载XML文件时自动移除其中未使用的命名空间,环境要求为XSLT 1.0(Java)。
示例输入XML
<root xmlns:ns1="some_namespace" xmlns:foo="unused_namespace"> <ns1:a>bla</ns1:a> <c xmlns="std_namespace"> <d>text</d> </c> <ns2:e xmlns:ns2="another_ns"> <ns2:f>aaa</ns2:f> </ns2:e> </root>
期望转换结果
<root xmlns:ns1="some_namespace"> <ns1:a>bla</ns1:a> <c xmlns="std_namespace"> <d>text</d> </c> <ns2:e xmlns:ns2="another_ns"> <ns2:f>aaa</ns2:f> </ns2:e> </root>
当前尝试的XSLT及问题
参考相关代码修改后,虽保留了默认命名空间,但已使用的带前缀命名空间被错误移除,转换结果丢失了所有命名空间前缀。
修改后的XSLT代码:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:strip-space elements="*"/> <xsl:template match="node()|@*" priority="-2"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="*"> <xsl:element name="{name()}" namespace="{namespace-uri()}"> <xsl:variable name="vtheElem" select="."/> <xsl:for-each select="namespace::*"> <xsl:variable name="vPrefix" select="name()"/> <xsl:if test=" ($vPrefix = '') or ($vtheElem/descendant::* [(namespace-uri()=current() and substring-before(name(),':') = $vPrefix) or @*[substring-before(name(),':') = $vPrefix] ]) "> <xsl:copy-of select="."/> </xsl:if> </xsl:for-each> <xsl:apply-templates select="node()|@*"/> </xsl:element> </xsl:template> </xsl:stylesheet>
错误转换结果:
<?xml version="1.0" encoding="UTF-8"?><root> <a>bla</a> <c xmlns="std_namespace"> <d>text</d> </c> <e> <f>aaa</f> </e> </root>
解决方案
问题核心在于命名空间使用判断逻辑不完整,未覆盖当前节点本身的命名空间使用场景,且前缀与URI的关联判断不够严谨。修改后的XSLT如下:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:strip-space elements="*"/> <!-- 通用复制模板,优先级最低 --> <xsl:template match="node()|@*" priority="-2"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <!-- 元素处理模板 --> <xsl:template match="*"> <xsl:element name="{name()}" namespace="{namespace-uri()}"> <xsl:variable name="currentElem" select="."/> <!-- 遍历所有命名空间节点 --> <xsl:for-each select="namespace::*"> <xsl:variable name="nsPrefix" select="name()"/> <xsl:variable name="nsUri" select="."/> <!-- 判断命名空间是否被使用: 1. 是默认命名空间(前缀为空) 2. 当前节点使用了该前缀+URI 3. 后代节点使用了该前缀+URI 4. 当前节点或后代节点的属性使用了该前缀 --> <xsl:if test=" $nsPrefix = '' or (substring-before($currentElem/name(), ':') = $nsPrefix and $currentElem/namespace-uri() = $nsUri) or ($currentElem//*[ substring-before(name(), ':') = $nsPrefix and namespace-uri() = $nsUri ]) or ($currentElem//@*[substring-before(name(), ':') = $nsPrefix]) "> <xsl:copy-of select="."/> </xsl:if> </xsl:for-each> <xsl:apply-templates select="node()|@*"/> </xsl:element> </xsl:template> </xsl:stylesheet>
修改说明
- 补充了当前节点本身的命名空间使用判断,避免遗漏当前节点的前缀命名空间
- 明确前缀与URI的对应关系,确保只有匹配的使用场景才保留命名空间
- 拆分判断条件,覆盖元素、后代元素、属性的命名空间使用场景,逻辑更清晰
内容的提问来源于stack exchange,提问作者mamayr
相关产品推荐
相关产品推荐

