Подтвердить что ты не робот

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.

ИЗМЕНИТЬ
Удалены неверные предположения из вопроса, связанного с префиксом пространства имен.

4b9b3361

Ответ 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>