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

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

...some content... 

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

 ...some content... 

而已。 容易,不是吗? 错误! 没有DOM!

我尝试过以下方法:

1)使用doc.getDocumentElement()。setAttribute(“xmlns”,“myNamespace”)

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

 ... 

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()); 

现在不是那么直截了当吗? ;)

我现在得到的是:

     

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

诅咒你,DOM!

有没有办法以递归方式执行此操作(无需编写自己的递归方法)?

请帮忙 ;)

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

注意:我正在使用最新的JDK。

编辑
从问题中删除了错误的假设,这与假名前缀有关

我今天遇到了同样的问题。 我最终使用@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, Value> translations = new HashMap, Value>(); public XmlNamespaceTranslator addTranslation(String fromNamespaceURI, String toNamespaceURI) { Key key = new Key(fromNamespaceURI); Value value = new Value(toNamespaceURI); this.translations.put(key, value); return this; } public void translateNamespaces(Document xmlDoc) { Stack nodes = new Stack(); nodes.push(xmlDoc.getDocumentElement()); while (!nodes.isEmpty()) { Node node = nodes.pop(); switch (node.getNodeType()) { case Node.ATTRIBUTE_NODE: case Node.ELEMENT_NODE: Value value = this.translations.get(new Key(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 { 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 extends Holder { public Key(T value) { super(value); } } private static class Value extends Holder { public Value(T value) { super(value); } } } 

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

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

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

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

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

Ivan的原始post稍有不同,对我有用:在文档节点上设置属性。

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

哪里

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

希望有人帮助!

迈克沃茨

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

 import org.apache.xerces.parsers.DOMParser; public static class MyDOMParser extends DOMParser { private Map 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(); 

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

应该这样做。

我使用org.jdom.Element解决了:

Java的:

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

XML:

 ; ...