Java+DOM:如何设置(已创建)文档的基本命名空间?
我正在处理一个已经创建的 Document 对象.我必须能够将它的基本命名空间(属性名称xmlns")设置为某个值.我的输入是 DOM,类似于:
I am dealing with an already created Document object. I have to be able to set it's base namespace (attribute name "xmlns") to certain value. My input is DOM and is something like:
<root>...some content...</root>
我需要的是 DOM,它类似于:
What I need is DOM which is something like:
<root xmlns="myNamespace">...some content...</root>
就是这样.容易,不是吗?错了!不是 DOM!
That's it. Easy, isn't it? Wrong! Not with DOM!
我得到一个带有空 xmlns 的文档(它适用于 any 其他属性名称!)
I get a document with empty xmlns (it works on any other attribute name!)
<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!
有没有办法递归地做到这一点(无需编写自己的递归方法)?
Is there any way to do this recursively (without writing an own recursive method)?
请不要建议我做一些花哨的解决方法,例如将 DOM 转换为别的东西,改变那里的命名空间,并将其转换回来.我需要 DOM,因为它是操作 XML 的最快标准方法.
Please don't advice me to do some fancy workaround, such as transforming DOM to something else, alter the namespace there, and transform it back. I need DOM because it's the fastest standard way to manipulate XML.
注意:我使用的是最新的 JDK.
Note: I'm using the latest JDK.
编辑
从问题中删除了与命名空间前缀有关的错误假设.
推荐答案
我今天也遇到了同样的问题.我最终使用了部分 @ivan_ivanovich_ivanoff 回答但删除了递归并修复了一些错误.
I had the very same problem today. I ended up using parts of @ivan_ivanovich_ivanoff answer but removed the recursion and fixed some bugs.
非常重要:如果旧命名空间是 null
,您必须添加两个翻译,一个从 null
到新的 namespaceURI
和另一个从 ""
到您的新 namespaceURI
.发生这种情况是因为第一次调用 renameNode
会将具有 null
namespaceURI
的现有节点更改为 xmlns=""
.
Very important: if old namespace is null
you must add two translations, one from null
to your new namespaceURI
and another from ""
to your new namespaceURI
. This happens because the first call to renameNode
will change existing nodes that have a 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"
完整源代码如下:
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);
}
}
}
相关文章