java 如何更改 DOM 中每个节点上的命名空间?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1316791/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 16:06:40  来源:igfitidea点击:

How can I change the namespace on every node in a DOM?

javaxmldom

提问by Chris R

How can I, given a w3c DOM (Java's default implementation, specifically) change the namespace of every element/attribute/node in that DOM? Efficiently, preferably. The DOM doesn't seem to have a setNamespaceURI method on it, which is inconvenient.

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

I've tried XSL approaches, but they've failed to work in the JAXP transformers (although they work all right in Saxon9B, which I can't use for various other reasons).

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

Basically, I need a pure core java solution that will allow me to take one document and change its namespace.

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

采纳答案by Esko

Based on my hugely biased opinion what you want will be a huge pain in the ass. I can see the typecasting hell and numerous recursive for loops needed to do this reliably already! Ahh, Java's default implementation, how I hate your NPE:s at internals, the reversed logic, the extra steps needed for simple operations!

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

So yes, my suggestion would be recursive for loops with typecasting for every single possible node type, based on my very personal experience the Java's default implementation sucks that badly.

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

回答by bobince

This is not efficient on a namespace-aware DOM. You would have to use the DOM Level 3 Core method Document.renameNode(javadoc) on every descendant Element whose namespace you wanted to change. (You wouldn't normally need to change so many Attr nodes, because the namespace of an Attr node with no prefix is always null, rather than the Element's namespace.)

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

If all you want to do is substitute one namespace for another, it might be quicker to use a namespace-unaware DOM, and simply change the xmlns attribute in question. You should be able to get a namespace-unaware DOM by setting the DOMConfiguration‘namespaces' parameter to false, but I've not tried this in Java and it's the sort of obscure little thing DOM imps would get wrong.

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

回答by Suraj

If intent is to just change name space, then just use some stream editor to change NS mapping to URL.

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

A Namspace is more or less a binding between namespace prefix and a URI. In order to quickly change namespace, just change the mapping:

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

Before: xmlns:myNS="my-namespace-uri"

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

After: xmlns:myNS="my-new-namespace-uri"

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

Basically changing mapping is sufficient, if intent is simply to change the namespace. Moreover if XML Document has default namespace, then changing the default namespace URL value would change namespace for whole of the document.

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

Before: xmlns="my-namespace-uri"

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

After: xmlns="my-new-namespace-uri"

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

回答by McDowell

How can I, given a w3c DOM (Java's default implementation, specifically) change the namespace of every element/attribute/node in that DOM? Efficiently, preferably.

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

I don't think there is an efficient solution that is also robust. You can't just rename something on the root element. Consider these documents:

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

Doc1

文档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>

Doc2

文档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>

Doc3

文档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>

These three documents are equivalent in a namespace-aware DOM. You could run the same namespaced XPath queriesagainst any of them.

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

Since the DOM allows you to specify exactly how nodes should be namespaced, there is no catch-all, one-step call to change a namespace. You need to walk the DOM, taking into consideration not only prefix and URI values, but their scopeat any given time.

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

This XSLT can be used with a Transformerto change elements namespaced as urn:fleetto be namespaced as urn:new:

此 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>

Caveats: further tweaking would be required to handle namespaced attributes; dangling urn:fleetdeclarations can be left behind, which is messy, but largely inconsequential; probably other stuff I haven't thought of.

警告:需要进一步调整来处理命名空间属性;urn:fleet可能会留下悬空声明,这很混乱,但在很大程度上无关紧要;可能还有其他我没有想到的东西。

回答by Simon Kissane

This code given a DOM Document will return a new DOM Document in which a given set of namespace URI translations have been applied (uriMap). The keys must be the URIs in the source document, the values the replacement URIs in the destination document. Unknown namespace URIs pass through unchanged. It knows to change the value of xmlns:* attributes, but will not change other attributes that might happen to have namespace URIs as their values (e.g. XSD targetNamespace)

给定 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;
}

回答by Shay

You may copy your DOM tree to another tree and make some tweaks during process. For example, using org.apache.xml.utils.DOMBuilder as the implementation of ContentHandler, you may override methods in such way:

您可以将 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 will handle all dirty work during copying leaving to you only namespace replacement logic.

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

回答by Kevin Krouse

If you are ok with using the Xerces classes, you can create a DOMParser that replaces the URI of attributes and elements with your fixed up URIs:

如果您可以使用 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);
    }       
}

The elsewhere, you can parse into a DOM Document:

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

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

回答by Kevin Krouse

The namespace is changed on every element without a defined namespace prefix by applying a targetnamespace attribute to your root element. Doing this will also require that you then alter each of your elements with a namespace prefix. You can make this prefix change manually or write some script logic to walk your DOM tree to applying it only where necessary.

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

Here is more reading about the targetnamespace attribute and the nonamespaceschema attribute:

以下是有关 targetnamespace 属性和 nonamespaceschema 属性的更多信息:

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

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