iva*_*off 18 java xml dom document namespaces
我正在处理一个已经创建的 Document对象.我必须能够将它的基本命名空间(属性名称"xmlns")设置为特定值.我的输入是DOM,类似于:
<root>...some content...</root>
Run Code Online (Sandbox Code Playgroud)
我需要的是DOM,它类似于:
<root xmlns="myNamespace">...some content...</root>
Run Code Online (Sandbox Code Playgroud)
而已.容易,不是吗?错误!没有DOM!
我得到一个空xmlns的文档(它适用于任何其他属性名称!)
<root xmlns="">...</root>
Run Code Online (Sandbox Code Playgroud)
首先克隆文档:
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));
Run Code Online (Sandbox Code Playgroud)
我真的很想念document.clone(),但也许只是我.
现在重命名根节点:
output.renameNode(output.getDocumentElement(),"myNamespace",
output.getDocumentElement().getTagName());
Run Code Online (Sandbox Code Playgroud)
现在不是那么简单吗?;)
我现在得到的是:
<root xmlns="myNamespace">
<someElement xmlns=""/>
<someOtherElement xmlns=""/>
</root>
Run Code Online (Sandbox Code Playgroud)
那么(正如我们所有人所期望的那样,对吧?),这只重命名根节点的命名空间.
诅咒你,DOM!
有没有办法以递归方式执行此操作(无需编写自己的递归方法)?
请不要建议我做一些花哨的解决方法,例如将DOM转换为其他内容,在那里更改命名空间,然后将其转换回来.我需要DOM,因为它是操作XML的最快标准方法.
注意:我正在使用最新的JDK.
编辑
从问题中删除了错误的假设,这与假名前缀有关.
Jor*_*ira 10
我今天遇到了同样的问题.我最终使用@ivan_ivanovich_ivanoff回答的部分,但删除了递归并修复了一些错误.
非常重要:如果旧的命名空间是null
必须添加两个翻译,一个从null
您的新namespaceURI
,另一个""
到您的新namespaceURI
.发生这种情况是因为第一次调用renameNode
将更改具有null
namespaceURI
to的现有节点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"
Run Code Online (Sandbox Code Playgroud)
完整源代码如下:
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
除了设置前缀之外,还必须在某处声明命名空间.
[编辑]如果你查看包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");
Run Code Online (Sandbox Code Playgroud)
使用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);
}
}
Run Code Online (Sandbox Code Playgroud)
似乎有效,尽管我不知道仅重命名节点类型 ELEMENT_NODE 是否正确,或者是否必须重命名其他节点类型。