【问题标题】:Java+DOM: How do I set the base namespace of an (already created) Document?Java+DOM:如何设置(已创建)文档的基本命名空间?
【发布时间】:2010-12-02 07:43:11
【问题描述】:

我正在处理一个已经创建的 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。

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

【问题讨论】:

  • 更改节点的命名空间就像写一篇文章,设置一个语言属性并期望它被翻译。你必须翻译这些词。因此,“干净的方法”是使用递归函数在第二个文档中重新创建节点。

标签: java xml dom namespaces document


【解决方案1】:

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

[编辑] 如果您查看包 org.w3c.dom,您会注意到不支持任何名称空间,除非您可以使用名称空间 URI 创建一个 Document 节点:

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 只是几个接口。因此,您应该尝试查看实现(即文档实例的实际类),将其转换为真实类型。这种类型应该有额外的方法,如果你幸运的话,你可以使用它们来修改命名空间。

【讨论】:

  • 好,在哪里? Document.declareNamespaceSomewhere(...) 不存在 ;)
  • 正确,这不是 W3C 的 DOM API 的一部分。查看我的编辑以寻找出路。
  • 谢谢。这里的问题是:我按照您描述的方式创建了一个新文档,with 一个根元素(没有它不起作用,感谢 DOM)。所以现在我必须将现有文档的子节点复制到新文档,对吗?确保完整副本就足够了吗?
  • 要复制节点,您必须先将它们从源文档中删除,然后再将它们添加到目标文档中。这样,一切都应该被保留。如果你省略删除,你会得到“节点已经有一个父节点”的错误。
  • 你说得对:命名空间已经够难的了,W3C API 肯定无助于让它们变得更简单。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    我今天遇到了同样的问题。我最终使用了@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);
            }
    
        }
    }
    

    【讨论】:

    • 迭代由document.getElementsByTagName("*")返回的NodeList而不是遍历DOM树更容易。
    【解决方案4】:

    我们可以使用 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();
          }
        }
      }
    
    }
    

    【讨论】:

    • 此解决方案仅适用于 dom4j 文档,不适用于 w3c 文档。
    【解决方案5】:

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

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

    在哪里

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

    希望对某人有所帮助!

    迈克·沃茨

    【讨论】:

    • 设置“xmlns”属性实际上并没有改变命名空间。尝试调用getNamespaceURI() 方法
    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

      假设你有你的 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());
          }
      }
      

      应该可以。

      【讨论】:

        【解决方案8】:

        我使用 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>
        

        【讨论】:

        • 问题是关于一个已经创建的 DOM 文档,所以这种方法行不通。 JDOM 不是 DOM。
        猜你喜欢
        • 2022-07-10
        • 2012-05-22
        • 2012-06-23
        • 2010-10-06
        • 2010-12-15
        • 2011-09-16
        • 1970-01-01
        • 1970-01-01
        • 2011-02-28
        相关资源
        最近更新 更多