Чистая обработка пространства имен с помощью dom4j - PullRequest
8 голосов
/ 14 сентября 2009

Мы используем dom4j 1.6.1, чтобы анализировать поступление XML откуда-то. Иногда в balise упоминается пространство имен (например:), а иногда нет (). И выполнить вызов Element.selectSingleNode (String s) не удалось.

На данный момент у нас есть 3 решения, и мы не довольны ими

1 - удалить все вхождения пространства имен, прежде чем делать что-либо с документом xml

xml = xml .replaceAll("xmlns=\"[^\"]*\"","");
xml = xml .replaceAll("ds:","");
xml = xml .replaceAll("etm:","");
[...] // and so on for each kind of namespace

2 - Удалить пространство имен непосредственно перед получением узла Позвонив

Element.remove(Namespace ns)

Но это работает только для узла и первого уровня потомка

3 - загромождать код

node = rootElement.selectSingleNode(NameWithoutNameSpace)
if ( node == null )
    node = rootElement.selectSingleNode(NameWithNameSpace)

Так ... что ты думаешь? Ведьма тем меньше хуже? Вы предлагаете другое решение?

Ответы [ 5 ]

5 голосов
/ 18 августа 2011

Я хотел удалить любую информацию о пространстве имен (объявление и тег), чтобы упростить оценку xpath. Я в конечном итоге с этим решением:

String xml = ...
SAXReader reader = new SAXReader();
Document document = reader.read(new ByteArrayInputStream(xml.getBytes()));
document.accept(new NameSpaceCleaner());
return document.asXML();

где NameSpaceCleaner является посетителем dom4j:

private static final class NameSpaceCleaner extends VisitorSupport {
    public void visit(Document document) {
        ((DefaultElement) document.getRootElement())
                .setNamespace(Namespace.NO_NAMESPACE);
        document.getRootElement().additionalNamespaces().clear();
    }
    public void visit(Namespace namespace) {
        namespace.detach();
    }
    public void visit(Attribute node) {
       if (node.toString().contains("xmlns")
        || node.toString().contains("xsi:")) {
        node.detach();
      }
    }

    public void visit(Element node) {
        if (node instanceof DefaultElement) {
        ((DefaultElement) node).setNamespace(Namespace.NO_NAMESPACE);
        }
         }
 }
4 голосов
/ 26 августа 2010

Ниже приведен код, который я нашел и теперь использую. Может быть полезно, если вы ищете универсальный способ, удалить все пространства имен из документа dom4j.

    public static void removeAllNamespaces(Document doc) {
        Element root = doc.getRootElement();
        if (root.getNamespace() !=
                Namespace.NO_NAMESPACE) {            
                removeNamespaces(root.content());
        }
    }

    public static void unfixNamespaces(Document doc, Namespace original) {
        Element root = doc.getRootElement();
        if (original != null) {
            setNamespaces(root.content(), original);
        }
    }

    public static void setNamespace(Element elem, Namespace ns) {

        elem.setQName(QName.get(elem.getName(), ns,
                elem.getQualifiedName()));
    }

    /**
     *Recursively removes the namespace of the element and all its
    children: sets to Namespace.NO_NAMESPACE
     */
    public static void removeNamespaces(Element elem) {
        setNamespaces(elem, Namespace.NO_NAMESPACE);
    }

    /**
     *Recursively removes the namespace of the list and all its
    children: sets to Namespace.NO_NAMESPACE
     */
    public static void removeNamespaces(List l) {
        setNamespaces(l, Namespace.NO_NAMESPACE);
    }

    /**
     *Recursively sets the namespace of the element and all its children.
     */
    public static void setNamespaces(Element elem, Namespace ns) {
        setNamespace(elem, ns);
        setNamespaces(elem.content(), ns);
    }

    /**
     *Recursively sets the namespace of the List and all children if the
    current namespace is match
     */
    public static void setNamespaces(List l, Namespace ns) {
        Node n = null;
        for (int i = 0; i < l.size(); i++) {
            n = (Node) l.get(i);

            if (n.getNodeType() == Node.ATTRIBUTE_NODE) {
                ((Attribute) n).setNamespace(ns);
            }
            if (n.getNodeType() == Node.ELEMENT_NODE) {
                setNamespaces((Element) n, ns);
            }            
        }
    }

Надеюсь, это полезно для тех, кому это нужно!

1 голос
/ 14 сентября 2009

Опция 1 опасна, потому что вы не можете гарантировать префиксы для данного пространства имен без предварительного анализа документа, и потому что вы можете столкнуться с конфликтом пространства имен. Если вы используете документ и ничего не выводите, это может быть нормально, в зависимости от источника документа, но в противном случае он просто теряет слишком много информации.

Вариант 2 может быть применен рекурсивно, но он имеет много тех же проблем, что и вариант 1.

Вариант 3 звучит как лучший подход, но вместо того, чтобы загромождать ваш код, создайте статический метод, который выполняет обе проверки, вместо того, чтобы помещать один и тот же оператор if в вашу кодовую базу.

Лучший подход - заставить того, кто отправляет вам плохой XML, исправить это. Конечно, напрашивается вопрос, действительно ли он сломан. В частности, вы получаете XML, где пространство имен по умолчанию определяется как X, а затем пространству имен, также представляющему X, присваивается префикс «es»? Если это так, то XML правильно сформирован, и вам просто нужен код, который не зависит от префикса, но все еще использует квалифицированное имя для извлечения элемента. Я недостаточно знаком с Dom4j, чтобы знать, приведет ли создание пространства имен с нулевым префиксом к совпадению всех элементов с совпадающим URI или только тех, которые не имеют префикса, но с ним стоит поэкспериментировать.

0 голосов
/ 15 ноября 2014

Этот код действительно работает:

public void visit(Document document) {
    ((DefaultElement) document.getRootElement())
            .setNamespace(Namespace.NO_NAMESPACE);
    document.getRootElement().additionalNamespaces().clear();
}

public void visit(Namespace namespace) {
    if (namespace.getParent() != null) {
        namespace.getParent().remove(namespace);
    }
}

public void visit(Attribute node) {
    if (node.toString().contains("xmlns")
            || node.toString().contains("xsi:")) {
        node.getParent().remove(node);
    }
}

public void visit(Element node) {
    if (node instanceof DefaultElement) {
        ((DefaultElement) node).setNamespace(Namespace.NO_NAMESPACE);
        node.additionalNamespaces().clear();
    }
}
0 голосов
/ 23 марта 2013

Как и Абхишек, мне нужно было убрать пространство имен из XML, чтобы упростить запросы XPath в сценариях тестирования системы. (XML сначала проверен XSD)

Вот проблемы, с которыми я столкнулся:

  1. Мне нужно было обработать глубоко структурированный XML, который имел тенденцию взрывать стек.
  2. В большинстве сложных XML, по причине, которую я не исследовал полностью, удаление всех пространств имен работало надежно только при первом обходе глубины дерева DOM. Чтобы исключить посетителя или получить список узлов с document.selectNodes("//*")

Я получил следующее (не самое элегантное, но если это поможет решить чью-то проблему ...):

public static String normaliseXml(final String message) {
    org.dom4j.Document document;
    document = DocumentHelper.parseText(message);

    Queue stack = new LinkedList();

    Object current = document.getRootElement();

    while (current != null) {
        if (current instanceof Element) {
            Element element = (Element) current;

            Iterator iterator = element.elementIterator();

            if (iterator.hasNext()) {
                stack.offer(element);
                current = iterator;
            } else {
                stripNamespace(element);

                current = stack.poll();
            }
        } else {
            Iterator iterator = (Iterator) current;

            if (iterator.hasNext()) {
                stack.offer(iterator);
                current = iterator.next();
            } else {
                current = stack.poll();

                if (current instanceof Element) {
                    stripNamespace((Element) current);

                    current = stack.poll();
                }
            }
        }
    }

    return document.asXML();
}

private static void stripNamespace(Element element) {
    QName name = new QName(element.getName(), Namespace.NO_NAMESPACE, element.getName());
    element.setQName(name);

    for (Object o : element.attributes()) {
        Attribute attribute = (Attribute) o;

        QName attributeName = new QName(attribute.getName(), Namespace.NO_NAMESPACE, attribute.getName());
        String attributeValue = attribute.getValue();

        element.remove(attribute);

        element.addAttribute(attributeName, attributeValue);
    }

    for (Object o : element.declaredNamespaces()) {
        Namespace namespace = (Namespace) o;
        element.remove(namespace);
    }
}
...