Java+DOM:如何设置(已创建的)文档的基本命名空间?

发布于 2024-08-05 15:39:04 字数 1730 浏览 2 评论 0原文

我正在处理一个已创建 Document 对象。 我必须能够将其基本命名空间(属性名称“xmlns”)设置为特定值。 我的输入是 DOM,类似于:

<root>...some content...</root>

我需要的是 DOM,类似于:

<root xmlns="myNamespace">...some content...</root>

就是这样。很容易,不是吗? 错了!不适用于 DOM!

我尝试过以下操作:

1)使用 doc.getDocumentElement().setAttribute("xmlns","myNamespace")

我得到一个带有空 xmlns 的文档(它适用于 any其他属性名称!)

<root xmlns="">...</root>

2)使用renameNode(...)

首先克隆文档:

Document input = /*that external Document whose namespace I want to alter*/;

DocumentBuilderFactory BUILDER_FACTORY_NS = DocumentBuilderFactory.newInstance();
BUILDER_FACTORY_NS.setNamespaceAware(true);
Document output = BUILDER_NS.newDocument();
output.appendChild(output.importNode(input.getDocumentElement(), true));

我真的缺少document.clone(),但也许只是我。

现在重命名根节点

output.renameNode(output.getDocumentElement(),"myNamespace",
    output.getDocumentElement().getTagName());

现在不是很简单吗? ;)

我现在得到的是:

<root xmlns="myNamespace">
    <someElement xmlns=""/>
    <someOtherElement xmlns=""/>
</root>

所以(正如我们所有人所期望的,对吧?),这仅重命名根节点的命名空间

诅咒你,DOM!

有什么方法可以递归地执行此操作(无需编写自己的递归方法)?

请帮忙;)

请不要建议我做一些花哨的解决方法,例如将 DOM 转换为 其他的,改变那里的命名空间,然后将其转换回来。 我需要 DOM,因为它是操作 XML 的最快标准方法。

注意:我使用的是最新的 JDK。

编辑
从问题中删除了与命名空间前缀有关的错误假设。

I am dealing with an already created Document object.
I have to be able to set it's base namespace (attribute name "xmlns") to certain value.
My input is DOM and is something like:

<root>...some content...</root>

What I need is DOM which is something like:

<root xmlns="myNamespace">...some content...</root>

That's it. Easy, isn't it? Wrong! Not with DOM!

I have tried the following:

1) Using doc.getDocumentElement().setAttribute("xmlns","myNamespace")

I get a document with empty xmlns (it works on any other attribute name!)

<root xmlns="">...</root>

2) Using renameNode(...)

First clone the document:

Document input = /*that external Document whose namespace I want to alter*/;

DocumentBuilderFactory BUILDER_FACTORY_NS = DocumentBuilderFactory.newInstance();
BUILDER_FACTORY_NS.setNamespaceAware(true);
Document output = BUILDER_NS.newDocument();
output.appendChild(output.importNode(input.getDocumentElement(), true));

I'm really missing document.clone(), but perhaps it's just me.

Now rename the root node:

output.renameNode(output.getDocumentElement(),"myNamespace",
    output.getDocumentElement().getTagName());

Now isn't that straightforward? ;)

What I get now is:

<root xmlns="myNamespace">
    <someElement xmlns=""/>
    <someOtherElement xmlns=""/>
</root>

So (as all of us have expected, right?), this renames the namespace only of the the root node.

Curse you, DOM!

Is there any way to do this recursively (without writing an own recursive method)?

Please help ;)

Please don't advice me to do some fancy workaround, such as transforming DOM to
something else, alter the namespace there, and transform it back.
I need DOM because it's the fastest standard way to manipulate XML.

Note: I'm using the latest JDK.

EDIT
Removed wrong assumptions from the question, which had to do with namespace prefix.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

何必那么矫情 2024-08-12 15:39:05

好吧,这是递归“解决方案”:
(我仍然希望有人能找到更好的方法来做到这一点)

public static void renameNamespaceRecursive(Document doc, Node node,
        String namespace) {

    if (node.getNodeType() == Node.ELEMENT_NODE) {
        System.out.println("renaming type: " + node.getClass()
            + ", name: " + node.getNodeName());
        doc.renameNode(node, namespace, node.getNodeName());
    }

    NodeList list = node.getChildNodes();
    for (int i = 0; i < list.getLength(); ++i) {
        renameNamespaceRecursive(doc, list.item(i), namespace);
    }
}

似乎可行,尽管我不知道仅重命名节点类型是否正确
ELEMENT_NODE,或者是否必须重命名其他节点类型。

Well, here goes the recursive "solution":
(I still hope that someone might find a better way to do this)

public static void renameNamespaceRecursive(Document doc, Node node,
        String namespace) {

    if (node.getNodeType() == Node.ELEMENT_NODE) {
        System.out.println("renaming type: " + node.getClass()
            + ", name: " + node.getNodeName());
        doc.renameNode(node, namespace, node.getNodeName());
    }

    NodeList list = node.getChildNodes();
    for (int i = 0; i < list.getLength(); ++i) {
        renameNamespaceRecursive(doc, list.item(i), namespace);
    }
}

Seems to work, although I don't know if it's correct to rename only the node type
ELEMENT_NODE
, or if other node types must be renamed.

倾听心声的旋律 2024-08-12 15:39:05

我们可以使用 sax 解析器更改 xml 命名空间,试试这个

import java.util.ListIterator;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.dom4j.Visitor;
import org.dom4j.VisitorSupport;
import org.dom4j.io.SAXReader;

public class VisitorExample {

  public static void main(String[] args) throws Exception {
    Document doc = new SAXReader().read("test.xml");
    Namespace oldNs = Namespace.get("oldNamespace");
    Namespace newNs = Namespace.get("newPrefix", "newNamespace");
    Visitor visitor = new NamespaceChangingVisitor(oldNs, newNs);
    doc.accept(visitor);
    System.out.println(doc.asXML());
  }
}

class NamespaceChangingVisitor extends VisitorSupport {
  private Namespace from;
  private Namespace to;

  public NamespaceChangingVisitor(Namespace from, Namespace to) {
    this.from = from;
    this.to = to;
  }

  public void visit(Element node) {
    Namespace ns = node.getNamespace();

    if (ns.getURI().equals(from.getURI())) {
      QName newQName = new QName(node.getName(), to);
      node.setQName(newQName);
    }

    ListIterator namespaces = node.additionalNamespaces().listIterator();
    while (namespaces.hasNext()) {
      Namespace additionalNamespace = (Namespace) namespaces.next();
      if (additionalNamespace.getURI().equals(from.getURI())) {
        namespaces.remove();
      }
    }
  }

}

we can change the xml namespace using sax parser, try this

import java.util.ListIterator;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.dom4j.Visitor;
import org.dom4j.VisitorSupport;
import org.dom4j.io.SAXReader;

public class VisitorExample {

  public static void main(String[] args) throws Exception {
    Document doc = new SAXReader().read("test.xml");
    Namespace oldNs = Namespace.get("oldNamespace");
    Namespace newNs = Namespace.get("newPrefix", "newNamespace");
    Visitor visitor = new NamespaceChangingVisitor(oldNs, newNs);
    doc.accept(visitor);
    System.out.println(doc.asXML());
  }
}

class NamespaceChangingVisitor extends VisitorSupport {
  private Namespace from;
  private Namespace to;

  public NamespaceChangingVisitor(Namespace from, Namespace to) {
    this.from = from;
    this.to = to;
  }

  public void visit(Element node) {
    Namespace ns = node.getNamespace();

    if (ns.getURI().equals(from.getURI())) {
      QName newQName = new QName(node.getName(), to);
      node.setQName(newQName);
    }

    ListIterator namespaces = node.additionalNamespaces().listIterator();
    while (namespaces.hasNext()) {
      Namespace additionalNamespace = (Namespace) namespaces.next();
      if (additionalNamespace.getURI().equals(from.getURI())) {
        namespaces.remove();
      }
    }
  }

}
红衣飘飘貌似仙 2024-08-12 15:39:05

Ivan 的原始帖子的一个细微变化对我有用:在文档节点上设置属性。

xslRoot.setAttribute("xmlns:fo", "http://www.w3.org/1999/XSL/Format");

其中

  • xslRoot 是文档/根元素/节点,
  • fo 是命名空间 ID

希望对某人有所帮助!

迈克·沃茨

A slight variation of Ivan's original post worked for me: setting the attribute on the document node.

xslRoot.setAttribute("xmlns:fo", "http://www.w3.org/1999/XSL/Format");

where

  • xslRoot is the document/root element/node,
  • fo is the namespace ID

Hope that helps someone!

Mike Watts

谁人与我共长歌 2024-08-12 15:39:05

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

在其他地方,您可以解析

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

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:

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 the

DOMParse p = new MyDOMParser(...);
p.parse(new InputSource(inputStream));
Document doc = p.getDocument();
指尖凝香 2024-08-12 15:39:05

假设您已经有了 Document 实例..

import org.dom4j.*;

{

    static final String         YOUR_NAMESPACE_PREFIX =   "PREFIX"; 
    static final String         YOUR_NAMESPACE_URI    =   "URI"; 

    Document document = ...

    //now get the root element
    Element element = document.getRootElement();
    renameNamespaceRecursive(element);
    ...

    //End of this method
}

//the recursive method for the operation
void renameNamespaceRecursive(Element element) {
    element.setQName(new QName(element.getName(), DocumentHelper.createNamespace(YOUR_NAMESPACE_PREFIX, YOUR_NAMESPACE_URI)));
    for (Iterator i  = element.elementIterator(); i.hasNext();) {
        renameNamespaceRecursive((Element)i.next());
    }
}

应该可以。

Let's say you've got your Document instance..

import org.dom4j.*;

{

    static final String         YOUR_NAMESPACE_PREFIX =   "PREFIX"; 
    static final String         YOUR_NAMESPACE_URI    =   "URI"; 

    Document document = ...

    //now get the root element
    Element element = document.getRootElement();
    renameNamespaceRecursive(element);
    ...

    //End of this method
}

//the recursive method for the operation
void renameNamespaceRecursive(Element element) {
    element.setQName(new QName(element.getName(), DocumentHelper.createNamespace(YOUR_NAMESPACE_PREFIX, YOUR_NAMESPACE_URI)));
    for (Iterator i  = element.elementIterator(); i.hasNext();) {
        renameNamespaceRecursive((Element)i.next());
    }
}

That should do.

宛菡 2024-08-12 15:39:05

我使用 org.jdom.Element:

Java:

import org.jdom.Element;
...
Element kml = new Element("kml", "http://www.opengis.net/kml/2.2");

XML 解决了:

<kml xmlns="http://www.opengis.net/kml/2.2">; 
...
</kml>

I solved using org.jdom.Element:

Java:

import org.jdom.Element;
...
Element kml = new Element("kml", "http://www.opengis.net/kml/2.2");

XML:

<kml xmlns="http://www.opengis.net/kml/2.2">; 
...
</kml>
晚雾 2024-08-12 15:39:04

我今天也遇到了同样的问题。我最终使用了 @ivan_ivanovich_ivanoff 答案,但删除了递归并修复了一些错误。

非常重要:如果旧命名空间为 null,您必须添加两个翻译,一个从 null 到新的 namespaceURI,然后另一个从 "" 到您的新 namespaceURI。发生这种情况是因为第一次调用 renameNode 会将具有 null namespaceURI 的现有节点更改为 xmlns=""

使用示例:

Document xmlDoc = ...;

new XmlNamespaceTranslator()
    .addTranslation(null, "new_ns")
    .addTranslation("", "new_ns")
    .translateNamespaces(xmlDoc);

// xmlDoc will have nodes with namespace null or "" changed to "new_ns"

完整源代码如下:

public  class XmlNamespaceTranslator {

    private Map<Key<String>, Value<String>> translations = new HashMap<Key<String>, Value<String>>();

    public XmlNamespaceTranslator addTranslation(String fromNamespaceURI, String toNamespaceURI) {
        Key<String> key = new Key<String>(fromNamespaceURI);
        Value<String> value = new Value<String>(toNamespaceURI);

        this.translations.put(key, value);

        return this;
    }

    public void translateNamespaces(Document xmlDoc) {
        Stack<Node> nodes = new Stack<Node>();
        nodes.push(xmlDoc.getDocumentElement());

        while (!nodes.isEmpty()) {
            Node node = nodes.pop();
            switch (node.getNodeType()) {
            case Node.ATTRIBUTE_NODE:
            case Node.ELEMENT_NODE:
                Value<String> value = this.translations.get(new Key<String>(node.getNamespaceURI()));
                if (value != null) {
                    // the reassignment to node is very important. as per javadoc renameNode will
                    // try to modify node (first parameter) in place. If that is not possible it
                    // will replace that node for a new created one and return it to the caller.
                    // if we did not reassign node we will get no childs in the loop below.
                    node = xmlDoc.renameNode(node, value.getValue(), node.getNodeName());
                }
                break;
            }

            // for attributes of this node
            NamedNodeMap attributes = node.getAttributes();
            if (!(attributes == null || attributes.getLength() == 0)) {
                for (int i = 0, count = attributes.getLength(); i < count; ++i) {
                    Node attribute = attributes.item(i);
                    if (attribute != null) {
                        nodes.push(attribute);
                    }
                }
            }

            // for child nodes of this node
            NodeList childNodes = node.getChildNodes();
            if (!(childNodes == null || childNodes.getLength() == 0)) {
                for (int i = 0, count = childNodes.getLength(); i < count; ++i) {
                    Node childNode = childNodes.item(i);
                    if (childNode != null) {
                        nodes.push(childNode);
                    }
                }
            }
        }
    }

    // these will allow null values to be stored on a map so that we can distinguish
    // from values being on the map or not. map implementation returns null if the there
    // is no map element with a given key. If the value is null there is no way to
    // distinguish from value not being on the map or value being null. these classes
    // remove ambiguity.
    private static class Holder<T> {

        protected final T value;

        public Holder(T value) {
            this.value = value;
        }

        public T getValue() {
            return value;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((value == null) ? 0 : value.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Holder<?> other = (Holder<?>) obj;
            if (value == null) {
                if (other.value != null)
                    return false;
            } else if (!value.equals(other.value))
                return false;
            return true;
        }

    }

    private static class Key<T> extends Holder<T> {

        public Key(T value) {
            super(value);
        }

    }

    private static class Value<T> extends Holder<T> {

        public Value(T value) {
            super(value);
        }

    }
}

I had the very same problem today. I ended up using parts of @ivan_ivanovich_ivanoff answer but removed the recursion and fixed some bugs.

Very important: if old namespace is null you must add two translations, one from null to your new namespaceURI and another from "" to your new namespaceURI. This happens because the first call to renameNode will change existing nodes that have a null namespaceURI to xmlns="".

Example of usage:

Document xmlDoc = ...;

new XmlNamespaceTranslator()
    .addTranslation(null, "new_ns")
    .addTranslation("", "new_ns")
    .translateNamespaces(xmlDoc);

// xmlDoc will have nodes with namespace null or "" changed to "new_ns"

Full source code follows:

public  class XmlNamespaceTranslator {

    private Map<Key<String>, Value<String>> translations = new HashMap<Key<String>, Value<String>>();

    public XmlNamespaceTranslator addTranslation(String fromNamespaceURI, String toNamespaceURI) {
        Key<String> key = new Key<String>(fromNamespaceURI);
        Value<String> value = new Value<String>(toNamespaceURI);

        this.translations.put(key, value);

        return this;
    }

    public void translateNamespaces(Document xmlDoc) {
        Stack<Node> nodes = new Stack<Node>();
        nodes.push(xmlDoc.getDocumentElement());

        while (!nodes.isEmpty()) {
            Node node = nodes.pop();
            switch (node.getNodeType()) {
            case Node.ATTRIBUTE_NODE:
            case Node.ELEMENT_NODE:
                Value<String> value = this.translations.get(new Key<String>(node.getNamespaceURI()));
                if (value != null) {
                    // the reassignment to node is very important. as per javadoc renameNode will
                    // try to modify node (first parameter) in place. If that is not possible it
                    // will replace that node for a new created one and return it to the caller.
                    // if we did not reassign node we will get no childs in the loop below.
                    node = xmlDoc.renameNode(node, value.getValue(), node.getNodeName());
                }
                break;
            }

            // for attributes of this node
            NamedNodeMap attributes = node.getAttributes();
            if (!(attributes == null || attributes.getLength() == 0)) {
                for (int i = 0, count = attributes.getLength(); i < count; ++i) {
                    Node attribute = attributes.item(i);
                    if (attribute != null) {
                        nodes.push(attribute);
                    }
                }
            }

            // for child nodes of this node
            NodeList childNodes = node.getChildNodes();
            if (!(childNodes == null || childNodes.getLength() == 0)) {
                for (int i = 0, count = childNodes.getLength(); i < count; ++i) {
                    Node childNode = childNodes.item(i);
                    if (childNode != null) {
                        nodes.push(childNode);
                    }
                }
            }
        }
    }

    // these will allow null values to be stored on a map so that we can distinguish
    // from values being on the map or not. map implementation returns null if the there
    // is no map element with a given key. If the value is null there is no way to
    // distinguish from value not being on the map or value being null. these classes
    // remove ambiguity.
    private static class Holder<T> {

        protected final T value;

        public Holder(T value) {
            this.value = value;
        }

        public T getValue() {
            return value;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((value == null) ? 0 : value.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Holder<?> other = (Holder<?>) obj;
            if (value == null) {
                if (other.value != null)
                    return false;
            } else if (!value.equals(other.value))
                return false;
            return true;
        }

    }

    private static class Key<T> extends Holder<T> {

        public Key(T value) {
            super(value);
        }

    }

    private static class Value<T> extends Holder<T> {

        public Value(T value) {
            super(value);
        }

    }
}
醉殇 2024-08-12 15:39:04

除了设置前缀之外,您还必须在某处声明您的命名空间。

[编辑] 如果您查看包 org.w3c.dom,您会发现除了可以使用名称空间 URI 创建文档节点之外,不支持任何名称空间:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
DOMImplementation DOMImplementation = builder.getDOMImplementation();
Document doc = DOMImplementation.createDocument(
    "http://www.somecompany.com/2005/xyz", // namespace
    "root",
    null /*DocumentType*/);

Element root = doc.getDocumentElement();
root.setPrefix("xyz");
root.setAttribute(
    "xmlns:xyz",
    "http://www.somecompany.com/2005/xyz");

使用标准Java 5(及更高版本)的 W3C DOM API,无法修改节点的名称空间。

但 W3C DOM API 只是几个接口。因此,您应该尝试的是查看实现(即文档实例的实际类),将其转换为真实类型。这种类型应该有额外的方法,如果幸运的话,您可以使用这些方法来修改命名空间。

In addition to setting the prefix, you must also declare your namespace somewhere.

[EDIT] If you look into the package org.w3c.dom, you'll notice that there is no support for namespaces whatsoever except that you can create a Document node with a namespace URI:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
DOMImplementation DOMImplementation = builder.getDOMImplementation();
Document doc = DOMImplementation.createDocument(
    "http://www.somecompany.com/2005/xyz", // namespace
    "root",
    null /*DocumentType*/);

Element root = doc.getDocumentElement();
root.setPrefix("xyz");
root.setAttribute(
    "xmlns:xyz",
    "http://www.somecompany.com/2005/xyz");

With the standard W3C DOM API of Java 5 (and up), it's not possible to modify the namespace of a node.

But the W3C DOM API is just a couple of interfaces. So what you should try is to look at the implementation (i.e. the actual class of your document instance), cast it to the real type. This type should have additional methods and if you're lucky, you can use those to modify the namespace.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文