Java + DOM: как установить базовое пространство имен (уже созданного) документа?
Я имею дело с уже созданным объектом Document.
Я должен уметь устанавливать базовое пространство имен (имя атрибута "xmlns" ) на определенное значение.
Мой ввод DOM и что-то вроде:
<root>...some content...</root>
Мне нужен DOM, который выглядит примерно так:
<root xmlns="myNamespace">...some content...</root>
Что это. Легко, не так ли? Неправильно! Не с DOM!
Я пробовал следующее:
1) Использование doc.getDocumentElement(). setAttribute ( "xmlns", "myNamespace" )
Я получаю документ с пустым xmlns (он работает с любым именем другого атрибута!)
<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(), но, возможно, это только я.
Теперь переименуйте корень node:
output.renameNode(output.getDocumentElement(),"myNamespace",
output.getDocumentElement().getTagName());
Теперь не , что просто?;)
Теперь я получаю:
<root xmlns="myNamespace">
<someElement xmlns=""/>
<someOtherElement xmlns=""/>
</root>
Итак (как мы все ожидали, правильно?), это переименовывает пространство имен только из корня node.
Проклятие, DOM!
Есть ли способ сделать это рекурсивно (без написания собственного рекурсивного метода)?
Пожалуйста, помогите;)
Пожалуйста, не советуйте мне делать какое-то причудливое обходное решение, например, преобразование DOM в
что-то еще, измените там пространство имен и преобразуйте его обратно.
Мне нужен DOM, потому что это самый быстрый стандартный способ манипулирования XML.
Примечание. Я использую последний JDK.
ИЗМЕНИТЬ
Удалены неверные предположения из вопроса, связанного с префиксом пространства имен.
Ответы
Ответ 1
У меня была такая же проблема сегодня. Я закончил использование частей @ivan_ivanovich_ivanoff answer, но удалил рекурсию и исправил некоторые ошибки.
Очень важно:, если старое пространство имен 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<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);
}
}
}
Ответ 2
В дополнение к настройке префикса вы также должны объявить свое пространство имен.
[EDIT] Если вы посмотрите в пакет org.w3c.dom
, вы заметите, что поддержка пространств имен отсутствует, за исключением того, что вы можете создать документ node с URI пространства имен:
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");
В стандартном W3C DOM API для Java 5 (и выше) невозможно изменить пространство имен node.
Но API W3C DOM - это всего лишь пара интерфейсов. Итак, что вы должны попробовать, это посмотреть на реализацию (то есть на фактический класс вашего экземпляра документа), применить его к реальному типу. Этот тип должен иметь дополнительные методы, и если вам повезет, вы можете использовать их для изменения пространства имен.
Ответ 3
Ну, здесь идет рекурсивное "решение":
(Я все еще надеюсь, что кто-то может найти лучший способ сделать это)
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);
}
}
Кажется, работает, хотя я не знаю, правильно ли переименовать только тип node
ELEMENT_NODE или если другие типы node должны быть переименованы.
Ответ 4
мы можем изменить пространство имен xml с помощью парсера sax, попробуйте
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();
}
}
}
}
Ответ 5
Для меня работала небольшая вариация оригинального сообщения Ивана: установка атрибута в документе node.
xslRoot.setAttribute("xmlns:fo", "http://www.w3.org/1999/XSL/Format");
где
- xslRoot - это элемент document/root/ node,
- fo - это идентификатор пространства имен
Надеюсь, что это поможет кому-то!
Майк Уоттс
Ответ 6
Если вы в порядке с использованием классов Xerces, вы можете создать DOMParser, который заменяет URI атрибутов и элементов вашими установленными URI:
import org.apache.xerces.parsers.DOMParser;
public static class MyDOMParser extends DOMParser {
private Map<String, String> 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();
Ответ 7
Скажем, у вас есть экземпляр документа.
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());
}
}
Это должно сделать.
Ответ 8
Я решил использовать org.jdom.Element:
Java:
import org.jdom.Element;
...
Element kml = new Element("kml", "http://www.opengis.net/kml/2.2");
XML:
<kml xmlns="http://www.opengis.net/kml/2.2">;
...
</kml>