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:
; ...