問題描述
我正在處理一個已經創建的 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);
}
}
}
這篇關于Java+DOM:如何設置(已創建)文檔的基本命名空間?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!