首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >Java+DOM:如何设置(已经创建的)文档的基本名称空间?

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

Stack Overflow用户
提问于 2009-09-29 13:11:12
回答 8查看 28.8K关注 0票数 19

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

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

我需要的是DOM,它类似于:

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

就这样。很简单,不是吗?错了!不是用DOM!

我尝试过以下几种方法:

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

我得到了一个具有空xmlns的文档(它可以在任何属性名上工作!)

<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,因为它是操作XML的最快的标准方法。

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

编辑

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

EN

回答 8

Stack Overflow用户

发布于 2010-10-01 22:29:32

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

非常重要:如果旧的命名空间是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);
        }

    }
}
票数 11
EN

Stack Overflow用户

发布于 2009-09-29 13:16:54

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

编辑如果您查看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");

使用Java5(及更高版本)的标准W3C DOM,不可能修改节点的名称空间。

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

票数 8
EN

Stack Overflow用户

发布于 2009-09-29 14:21:59

好了,下面是递归的“解决方案”:

(我仍然希望有人能找到更好的方法来做到这一点)

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是否正确,或者是否必须重命名其他节点类型。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1492428

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档