默认Transformer与XSLT恒等转换行为差异及前缀保留实现问询
原始XML内容:
<?xml version="1.0"?> <x:books xmlns:x="urn:collection" xmlns:p="urn:property"> <book id="bk001"> <p:author>Writer</p:author> <p:title>The First Book</p:title> <p:genre>Fiction</p:genre> </book> </x:books>
使用非命名空间感知的DOM解析器加载该XML:
DocumentBuilderFactory factory = DocumentBuilderFactory.newDefaultInstance(); // JDK默认实现 factory.setNamespaceAware(false); DocumentBuilder builder = factory.newDocumentBuilder(); Document dom = builder.parse(new InputSource(new StringReader(xmlString)));
此时用默认恒等转换器转换回字符串,结果和原XML一致,能保留x:和p:前缀:
TransformerFactory factory = TransformerFactory.newDefaultInstance(); // JDK默认实现 Transformer tr0 = factory.newTransformer(); Writer w0 = new StringWriter(); tr0.transform(new DOMSource(dom), new StreamResult(w0));
但用标准XSLT 1.0恒等转换时:
XSLT代码:
<transform version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform"> <output method="xml" encoding="UTF-8" indent="no" omit-xml-declaration="no" /> <template match="*|@*|node()"> <copy><apply-templates select="*|@*|node()" /></copy> </template> </transform>
Java调用代码:
Transformer tr1 = factory.newTransformer(new StreamSource(new StringReader(xsltString))); Writer w1 = new StringWriter(); tr1.transform(new DOMSource(dom), new StreamResult(w1));
得到的XML会丢失所有命名空间前缀:
<?xml version="1.0" encoding="UTF-8"?><books xmlns:p="urn:property" xmlns:x="urn:collection"> <book id="bk001"> <author>Writer</author> <title>The First Book</title> <genre>Fiction</genre> </book> </books>
核心需求:无法控制DOM的创建方式(可能是命名空间感知或非感知,后者居多),如何编写XSLT 1.0转换以始终保留所有XML命名空间前缀?(恒等转换仅为示例,后续需扩展复杂逻辑)
是否可以通过设置TransformerFactory属性,或修改XSLT的match="..."属性实现?
1. 问题根源
当DOM由非命名空间感知解析器生成时,元素的前缀会被当作标签名的一部分(比如<x:books>的节点名是x:books,而非带命名空间URI的标准节点)。标准XSLT的copy指令会按命名空间规则处理节点,导致前缀被自动剥离。
2. 适配非命名空间DOM的XSLT写法
要保留原始前缀,需直接复制节点的完整名称(包含前缀),替代标准copy的命名空间处理逻辑。调整后的XSLT恒等转换如下:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" encoding="UTF-8" indent="no" omit-xml-declaration="no" /> <!-- 匹配元素节点,复制含前缀的完整节点名 --> <xsl:template match="*"> <xsl:element name="{name()}"> <!-- 复制所有属性 --> <xsl:apply-templates select="@*"/> <!-- 复制子节点 --> <xsl:apply-templates select="node()"/> </xsl:element> </xsl:template> <!-- 匹配属性节点,复制含前缀的完整属性名 --> <xsl:template match="@*"> <xsl:attribute name="{name()}"> <xsl:value-of select="."/> </xsl:attribute> </xsl:template> <!-- 匹配文本、注释、处理指令节点,直接复制 --> <xsl:template match="text()|comment()|processing-instruction()"> <xsl:copy/> </xsl:template> </xsl:stylesheet>
这里用{name()}获取节点的完整名称(包含前缀),手动创建元素和属性,避免标准copy指令的命名空间解析逻辑,确保前缀被保留。
3. 兼容命名空间感知DOM
如果DOM是命名空间感知的,上述XSLT依然有效:name()会返回前缀:本地名的格式,xsl:element会自动复用已有的命名空间声明,不会丢失前缀。
4. TransformerFactory属性补充(仅对命名空间感知DOM生效)
对于JDK默认的XSLT处理器(Xalan),可以设置以下属性强制保留前缀,但仅适用于命名空间感知的DOM,对非命名空间DOM仍需依赖上述XSLT修改:
TransformerFactory factory = TransformerFactory.newDefaultInstance(); // 强制保留原始前缀(仅对命名空间感知DOM生效) factory.setAttribute("http://xml.apache.org/xalan/features/preserve-namespace-prefixes", true);
- 针对非命名空间感知生成的DOM:必须修改XSLT,用
name()获取完整节点名并手动创建元素/属性,替代标准copy指令的命名空间处理。 - 针对命名空间感知生成的DOM:可以结合TransformerFactory属性+标准XSLT,或直接使用上述修改后的XSLT(兼容两种场景)。
内容的提问来源于stack exchange,提问作者ManSamVampire

