2009-09-14 10 views
8

Wir verwenden dom4j 1.6.1, um XML von irgendwo kommend zu analysieren. Manchmal haben die Balise den Namespace (zB:) und manchmal nicht(). Und es ist ein Aufruf von Element.selectSingleNode (String s) fehlgeschlagen.Saubere Namespace-Behandlung mit dom4j

Vorerst haben wir 3 Lösungen, und wir sind mit ihnen nicht zufrieden

1 - alle Namespace-Vorkommen entfernen, bevor irgendetwas mit dem XML-Dokument

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

2 tun - Entfernen Namespace, kurz bevor ein immer Knoten von

Element.remove(Namespace ns) 

Aufruf Aber es funktioniert nur für einen Knoten und der ersten Ebene des Kindes

3 - Clutter den Code von

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

Also ... was denken Sie? Hexe eins ist die weniger schlimm? Hast du andere Lösung vorzuschlagen?

Antwort

1

Option 1 ist gefährlich, da Sie die Präfixe für einen bestimmten Namespace nicht garantieren können, ohne das Dokument vorher zu analysieren, und weil Sie am Ende mit der Namespace-Kollision enden können. Wenn Sie ein Dokument konsumieren und nichts ausgeben, ist es in Abhängigkeit von der Quelle des Dokuments möglicherweise in Ordnung, aber sonst werden nur zu viele Informationen verloren.

Option 2 könnte rekursiv angewendet werden, aber die viele der gleichen Probleme wie Option 1.

Option 3 klingt wie der beste Ansatz bekam, sondern als Unordnung Code, eine statische Methode machen, die beide Prüfungen tut eher als die gleiche if-Anweisung in Ihre Codebase zu setzen.

Der beste Ansatz ist, wer auch immer Ihnen das schlechte XML sendet, um es zu beheben. Natürlich wirft dies die Frage auf, ob es tatsächlich gebrochen ist. Insbesondere erhalten Sie XML, wenn der Standardnamespace als X definiert ist, und dann erhält ein Namespace, der auch X darstellt, das Präfix "es"? Wenn das der Fall ist, dann ist das XML wohlgeformt und Sie brauchen nur Code, der das Präfix nicht versteht, aber immer noch einen qualifizierten Namen verwendet, um das Element zu holen. Ich kenne Dom4j nicht genug, um zu wissen, ob die Erstellung eines Namespace mit einem Null-Präfix dazu führt, dass alle Elemente mit einem passenden URI übereinstimmen oder nur diejenigen ohne Präfix, aber es lohnt sich zu experimentieren.

+0

ich werde versuchen, das Dokument über Namespace mit null Präfix graben. Danke trotzdem. Über die Quelle der XML-Datei: theire ist nicht so, dass sie nichts ändern. Aber die Datei mit oder ohne Namespace ist gültig. Mit den Dateien erstellen wir Objekte, die wir in unserem System verwenden. Aber wir haben nie etwas "geschrieben". (Wir haben nicht Recht, die XML-Datei zu ändern) –

4

Folgendes ist ein Code, den ich gefunden habe und jetzt benutze. Es könnte nützlich sein, wenn Sie nach einem generischen Weg suchen, alle Namespaces aus einem dom4j-Dokument zu entfernen.

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); 
      }    
     } 
    } 

Hoffe das ist nützlich für jemanden, der es braucht!

+0

konnte diesen Code nicht funktionieren. Ich habe XML mit Namespaces-Beispiel von w3schools verwendet, aber es ist wie dom4j erkennt die Namespaces nicht. Das erste if (root.getNamespace()! = Namespace.NO_NAMESPACE) wird als wahr ausgewertet, und selbst wenn ich das if entferne, tut es immer noch nichts. – Dan

+0

Hallo Dan, Dies entfernt die Namespaces aus dem Dokument. Wahrscheinlich sind Sie daran interessiert, die Präfixe ebenfalls zu entfernen. – Abhishek

+0

Sorry, aus Versehen habe ich gespeichert, bevor ich fertig habe, was ich schreiben wollte! Dan, diese Funktion entfernt die Namespaces aus dem Dokument. Ich habe das mit dem 5. Beispiel aus den w3schools probiert. Sie können dies überprüfen, indem Sie einen xpath wie "// table" erstellen. Führen Sie diesen xpath im Dokument vor und nach dem Aufruf der Funktion removeNamespaces aus, und Sie werden sehen, dass der letztere die Knoten für Sie findet. Was genau versuchst du zu tun? Ich bezweifle, wenn Sie mehr daran interessiert sind, nur die Präfixe zu entfernen, zum Beispiel (h: Tabelle -> Tabelle)? Lass es mich wissen, wenn ich dir helfen kann! – Abhishek

5

Ich wollte alle Namespace-Informationen (Deklaration und Tag) entfernen, um die Xpath-Auswertung zu erleichtern. Ich schließe mit dieser Lösung:

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

wo die NameSpaceCleaner ein dom4j Besucher ist:

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); 
     } 
     } 
} 
+0

Namespace.detach() scheint nichts zu tun, zumindest in meinem Dokument die Namespace-Instanzen hatten Null-Eltern und Null-Dokument-Eigenschaften, verhindert die Trennung von der Arbeit. Ich musste das übergeordnete Element verwenden, um das seltsame redundante Element loszuwerden (alle Elemente haben eine QName-Eigenschaft, die tatsächlich verwendet wird) Element Namespace child-nodes. Das war mit dom4j-1.6.1. –

+0

Funktioniert perfekt für mich! –

+0

Achtung. Wenn Sie den Quellcode von reader.read() aufrufen, werden Sie feststellen, dass der XML-Inhalt mit der Einstellung namesapce aware auf true (hardcoded dom4j 1.6) analysiert wird. – artificerpi

0

Als Abhishek, musste ich den Namespace von XML abzustreifen XPath-Abfragen in Systemtests Skripte zu vereinfachen.(Die XML zunächst XSD validiert)

Hier sind die Probleme, die ich gegenüber:

  1. Ich musste tief strukturierte XML verarbeiten, die den Stapel eine Tendenz der Sprengung hatte.
  2. Bei komplexem XML, aus einem Grund, den ich nicht vollständig untersucht habe, funktionierte das Entfernen aller Namespaces nur zuverlässig, wenn zuerst die DOM-Baumtiefe durchlaufen wurde. So dass der Besucher ausgeschlossen, oder sich die Liste der Knoten mit document.selectNodes("//*")

landete ich mit den folgenden (nicht das eleganteste, aber wenn das jemand Problemlösung helfen kann ...):

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); 
    } 
} 
tatsächlich funktioniert
0

Dieser Code:

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(); 
    } 
}