【问题标题】:How can I change the namespace on every node in a DOM?如何更改 DOM 中每个节点的命名空间?
【发布时间】:2026-02-18 10:05:02
【问题描述】:

如果给定 w3c DOM(具体来说是 Java 的默认实现),我该如何更改该 DOM 中每个元素/属性/节点的命名空间?有效,最好。 DOM 上好像没有 setNamespaceURI 方法,很不方便。

我尝试过 XSL 方法,但它们无法在 JAXP 转换器中工作(尽管它们在 Saxon9B 中工作正常,但由于其他各种原因我无法使用)。

基本上,我需要一个纯核心 java 解决方案,它允许我获取一个文档并更改其命名空间。

【问题讨论】:

标签: java xml dom


【解决方案1】:

根据我非常有偏见的观点,你想要的将是一个巨大的痛苦。我可以看到已经可靠地执行此操作所需的类型转换地狱和大量递归 for 循环!啊,Java的默认实现,我多么讨厌你的NPE:内部结构,颠倒的逻辑,简单操作所需的额外步骤!

所以是的,我的建议是递归循环,对每个可能的节点类型进行类型转换,根据我的个人经验,Java 的默认实现非常糟糕。

【讨论】:

  • 我认为这里的其他一些解决方案更优雅,但这是我最终做的:)
【解决方案2】:

这在命名空间感知的 DOM 上效率不高。您必须在要更改其名称空间的每个后代元素上使用 DOM 3 级核心方法 Document.renameNode (javadoc)。 (通常不需要更改这么多 Attr 节点,因为没有前缀的 Attr 节点的命名空间始终为 null,而不是 Element 的命名空间。)

如果您只想用一个命名空间替换另一个命名空间,使用不知道命名空间的 DOM 可能会更快,只需更改相关的 xmlns 属性即可。您应该能够通过将 DOMConfiguration 'namespaces' 参数设置为 false 来获得一个不知道命名空间的 DOM,但是我没有在 Java 中尝试过这个,这是 DOM imps 会出错的那种不起眼的小东西。

【讨论】:

    【解决方案3】:

    如果意图只是更改名称空间,则只需使用一些流编辑器将 NS 映射更改为 URL。

    命名空间或多或少是命名空间前缀和 URI 之间的绑定。为了快速更改命名空间,只需更改映射即可:

    之前: xmlns:myNS="my-namespace-uri"

    之后: xmlns:myNS="my-new-namespace-uri"

    如果意图只是改变命名空间,基本上改变映射就足够了。此外,如果 XML 文档具有默认命名空间,则更改默认命名空间 URL 值将更改整个文档的命名空间。

    之前: xmlns="my-namespace-uri"

    之后: xmlns="my-new-namespace-uri"

    【讨论】:

    • 好主意,但此时需要重新序列化为字节流并重新解析,从性能角度来看这是不可接受的。我已经有一个 DOM,我必须使用它。
    【解决方案4】:

    通过将 targetnamespace 属性应用于根元素,可以在每个没有定义命名空间前缀的元素上更改命名空间。这样做还需要您使用命名空间前缀更改每个元素。您可以手动更改此前缀或编写一些脚本逻辑来遍历您的 DOM 树,以便仅在必要时应用它。

    这里有更多关于 targetnamespace 属性和 nonamespaceschema 属性的阅读:

    http://www.xml.com/pub/a/2000/11/29/schemas/part1.html?page=8 http://www.computerpoweruser.com/editorial/article.asp?article=articles%2Farchive%2Fc0407%2F48c07%2F48c07.asp

    【讨论】:

      【解决方案5】:

      如果给定 w3c DOM(具体来说是 Java 的默认实现),我该如何更改该 DOM 中每个元素/属性/节点的命名空间?高效,最好。

      我认为没有一个有效的解决方案也很强大。您不能只重命名根元素上的某些内容。考虑这些文件:

      文档1

      <?xml version="1.0" encoding="UTF-8"?>
      <root xmlns="urn:all" xmlns:f="urn:fleet" xmlns:m="urn:mission">
        <f:starfleet>
          <m:bold>
            <f:ship name="Enterprise" />
          </m:bold>
        </f:starfleet>
      </root>
      

      文档2

      <?xml version="1.0" encoding="UTF-8"?>
      <root xmlns="urn:all">
        <starfleet xmlns="urn:fleet">
          <bold xmlns="urn:mission">
            <ship xmlns="urn:fleet" name="Enterprise" />
          </bold>
        </starfleet>
      </root>
      

      文档3

      <?xml version="1.0" encoding="UTF-8"?>
      <r:root xmlns:r="urn:all">
        <r:starfleet xmlns:r="urn:fleet">
          <r:bold xmlns:r="urn:mission">
            <r:ship xmlns:r="urn:fleet" name="Enterprise" />
          </r:bold>
        </r:starfleet>
      </r:root>
      

      这三个文档在命名空间感知 DOM 中是等价的。您可以对其中任何一个运行相同的 namespaced XPath queries

      由于 DOM 允许您准确指定节点应如何命名,因此没有包罗万象的一步调用来更改命名空间。您需要遍历 DOM,不仅要考虑前缀和 URI 值,还要考虑它们在任何给定时间的 scope

      此 XSLT 可与 Transformer 一起使用,以将命名为 urn:fleet 的元素更改为命名为 urn:new

      <?xml version="1.0" encoding="UTF-8"?>
      <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:f="urn:fleet" version="1.0">
        <xsl:output method="xml" indent="yes" />
        <xsl:template match="*">
          <xsl:copy>
            <xsl:copy-of select="@*" />
            <xsl:apply-templates />
          </xsl:copy>
        </xsl:template>
        <xsl:template match="f:*">
          <xsl:variable name="var.foo" select="local-name()" />
          <xsl:element namespace="urn:new" name="{$var.foo}">
            <xsl:copy-of select="@*" />
            <xsl:apply-templates />
          </xsl:element>
        </xsl:template>
      </xsl:stylesheet>
      

      注意事项:需要进一步调整来处理命名空间属性;悬空urn:fleet 声明可以被抛在后面,这很混乱,但在很大程度上是无关紧要的;可能还有其他我没有想到的东西。

      【讨论】:

      • 那是我最初使用的,几乎是逐字逐句,但事实证明我们的客户对 XML 的使用是……原始的。或者愚蠢。你选。如果 XML 没有布局正确,他们的工具就会崩溃,看在上帝的份上。
      【解决方案6】:

      您可以将您的 DOM 树复制到另一棵树,并在此过程中进行一些调整。 例如,使用 org.apache.xml.utils.DOMBuilder 作为 ContentHandler 的实现,你可以这样重写方法:

      public void startElement(String ns, String localName, String name, Attributes atts) throws SAXException {
              super.startElement("new_namespace", localName, name, atts);
          }
      

      DOMBuilder 将在复制过程中处理所有脏活,只留下命名空间替换逻辑。

      【讨论】:

        【解决方案7】:

        如果您可以使用 Xerces 类,您可以创建一个 DOMParser,用您固定的 URI 替换属性和元素的 URI:

        import org.apache.xerces.parsers.DOMParser;
        
        public static class MyDOMParser extends DOMParser {
            private Map<String, String> fixupMap = ...;
        
            @Override
            protected Attr createAttrNode(QName attrQName)
            {
                if (fixupMap.containsKey(attrQName.uri))
                    attrQName.uri = fixupMap.get(attrQName.uri);
                return super.createAttrNode(attrQName);
            }
        
            @Override
            protected Element createElementNode(QName qName)
            {
                if (fixupMap.containsKey(qName.uri))
                    qName.uri = fixupMap.get(qName.uri);
                return super.createElementNode(qName);
            }       
        }
        

        在别处,你可以解析成一个DOM Document:

        DOMParse p = new MyDOMParser(...);
        p.parse(new InputSource(inputStream));
        Document doc = p.getDocument();
        

        【讨论】:

          【解决方案8】:

          给定 DOM 文档的这段代码将返回一个新的 DOM 文档,其中应用了一组给定的命名空间 URI 翻译 (uriMap)。键必须是源文档中的 URI,值必须是目标文档中的替换 URI。未知的命名空间 URI 原封不动地通过。它知道更改 xmlns:* 属性的值,但不会更改可能碰巧将命名空间 URI 作为其值的其他属性(例如 XSD targetNamespace)

          private static Node makeClone(Node kid, Node to, Map<String, String> uriMap) {
             Document doc = to.getNodeType() == Node.DOCUMENT_NODE ?
                     (Document) to :
                     to.getOwnerDocument();
             if (kid.getNodeType() == Node.ELEMENT_NODE) {
                String newURI =
                        uriMap.containsKey(kid.getNamespaceURI()) ?
                        uriMap.get(kid.getNamespaceURI()) :
                        kid.getNamespaceURI();
                Element clone = doc.createElementNS(newURI, kid.getNodeName());
                to.appendChild(clone);
                for (int i = 0; i < kid.getAttributes().getLength(); i++) {
                   Attr attr = (Attr) kid.getAttributes().item(i);
                   String newAttrURI =
                           uriMap.containsKey(attr.getNamespaceURI()) ?
                           uriMap.get(attr.getNamespaceURI()) :
                           attr.getNamespaceURI();
                   String newValue = attr.getValue();
                   if (attr.getNamespaceURI() != null &&
                           attr.getNamespaceURI().equals(
                           "http://www.w3.org/2000/xmlns/") &&
                           uriMap.containsKey(attr.getValue()))
                      newValue = uriMap.get(attr.getValue());
                   clone.setAttributeNS(newAttrURI, attr.getNodeName(), newValue);
                }
                return clone;
             }
             Node clone = kid.cloneNode(false);
             doc.adoptNode(clone);
             to.appendChild(clone);
             return clone;
          }
          
          private static void copyKidsChangingNS(Node from, Node to,
                  Map<String, String> uriMap) {
             NodeList kids = from.getChildNodes();
             for (int i = 0; i < kids.getLength(); i++) {
                Node kid = kids.item(i);
                Node clone = makeClone(kid, to, uriMap);
                copyKidsChangingNS(kid, clone, uriMap);
             }
          }
          
          public static Document changeDocNS(Document doc, Map<String, String> uriMap)
                  throws Exception {
             DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
             dbf.setNamespaceAware(true);
             DocumentBuilder db = dbf.newDocumentBuilder();
             Document newDoc = db.newDocument();
             copyKidsChangingNS(doc, newDoc, uriMap);
             return newDoc;
          }
          

          【讨论】: