You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JDK17下更新XML并保存后命名空间前缀丢失问题

解决JDK 17中XML更新后丢失命名空间前缀的问题

问题原因

  1. XML解析器未启用命名空间感知:默认配置下,DocumentBuilderFactory不会处理命名空间,导致节点的前缀与命名空间URI的关联关系丢失,后续输出时无法正确还原前缀。
  2. 非规范的节点查询方式:使用getElementsByTagName("foo:Config")这种基于完整标签名(含前缀)的字符串匹配,而非基于命名空间URI+本地名的标准查询,容易引发前缀处理异常。
  3. XML输出组件的默认行为:XML处理器默认会选择最优的命名空间前缀(甚至省略前缀,只要URI正确),但业务场景需要严格保留原前缀。

解决方案

1. 开启XML解析器的命名空间支持

修改getDocument方法,启用命名空间感知:

public static Document getDocument(final Path file) {
    final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
    // 启用命名空间支持,这是处理带命名空间XML的核心配置
    docBuilderFactory.setNamespaceAware(true);
    DocumentBuilder docBuilder = null;
    Document doc = null;
    try {
        docBuilder = docBuilderFactory.newDocumentBuilder();
        doc = docBuilder.parse(file.toFile());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return doc;
}

2. 使用标准的命名空间节点查询方法

替换原有的getElementsByTagName为getElementsByTagNameNS,基于固定的命名空间URI和本地名查询节点:

public static void main(String[] args) {
    Path xml = Paths.get("user.xml").normalize();
    Document doc = getDocument(xml);
    // 用命名空间URI和本地名查询根节点,不依赖前缀字符串
    NodeList sslConfig = doc.getElementsByTagNameNS("http://cc.bb.aa/def", "Config");
    System.out.println("sslConfig= "+sslConfig);
    Element sslConfigElement = (Element) sslConfig.item(0);
    // 同样查询子节点
    NodeList nodeList = sslConfigElement.getElementsByTagNameNS("http://cc.bb.aa/def", "isEnabled");
    if ("true" != null && !nodeList.item(0).getTextContent().equals("true")) {
        nodeList.item(0).setTextContent("true");
    }
    try {
        saveDocToFile(doc, xml);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

3. 调整XML输出配置以保留前缀

提供两种可选方案,根据需求选择:

方案A:修改XSLT模板确保前缀保留

更新saveDocToFile中的XSLT,同时添加必要的输出属性:

public static void saveDocToFile(final Document doc, final Path file) {
    try {
        String xslt = """
            <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
                <xsl:output method="xml" indent="yes" encoding="UTF-8" omit-xml-declaration="no"/>
                <xsl:strip-space elements="*"/>
                
                <!-- 复制所有节点、属性及关联的命名空间信息 -->
                <xsl:template match="@*|node()">
                    <xsl:copy>
                        <xsl:apply-templates select="@*|node()"/>
                    </xsl:copy>
                </xsl:template>
            </xsl:stylesheet>
            """;
        Transformer transformer = TransformerFactory.newInstance().newTransformer(new StreamSource(new StringReader(xslt)));
        
        // 还原原XML的standalone属性
        transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
        // 设置缩进格式
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
        
        if (doc.getDoctype() != null) {
            String systemValue = doc.getDoctype().getSystemId();
            transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, systemValue);
        }
        transformer.transform(new DOMSource(doc), new StreamResult(file.toFile()));
    } catch (TransformerFactoryConfigurationError | TransformerException e) {
        e.printStackTrace();
    }
}

方案B:直接使用DOM输出配置(无需XSLT)

如果不需要XSLT的格式化逻辑,可简化为直接配置Transformer:

public static void saveDocToFile(final Document doc, final Path file) {
    try {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        // 基础输出配置
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
        // 设置缩进量
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
        
        if (doc.getDoctype() != null) {
            String systemValue = doc.getDoctype().getSystemId();
            transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, systemValue);
        }
        // 确保DOM源启用命名空间感知
        DOMSource source = new DOMSource(doc);
        source.setNamespaceAware(true);
        transformer.transform(source, new StreamResult(file.toFile()));
    } catch (TransformerFactoryConfigurationError | TransformerException e) {
        e.printStackTrace();
    }
}

验证结果

修改后运行代码,输出XML将保留原有的foo:前缀:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo:Config xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:foo="http://cc.bb.aa/def">
    <foo:isEnabled>true</foo:isEnabled>
</foo:Config>

关键注意事项

  • 命名空间感知是基础:必须开启NamespaceAware,否则DOM无法正确处理命名空间URI与前缀的绑定关系。
  • 优先使用URI查询节点:getElementsByTagNameNS基于固定的命名空间URI查询,避免因前缀变化导致的查询失败。
  • 输出属性适配:部分XML处理器(如Xalan)需要额外配置indent-amount等属性,确保格式和前缀保留符合预期。

内容的提问来源于stack exchange,提问作者TheUser

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 19:09:51