2009-09-14 11 views
8

Nous utilisons dom4j 1.6.1, pour analyser XML à partir de quelque part. Parfois, les balises mentionnent l'espace de noms (par exemple:) et parfois non(). Et c'est appel de Element.selectSingleNode (String s) échoue.Nettoyer l'espace de noms avec dom4j

Pour l'instant nous avons 3 solutions, et nous ne sommes pas heureux avec eux

1 - Retirez tout occurence namespace avant de faire quoi que ce soit avec le document xml

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

2 - Retirer le namespace juste avant d'obtenir un noeud En appelant

Element.remove(Namespace ns) 

Mais il ne fonctionne que pour un nœud et le premier niveau de l'enfant

3 - Clutter le code par

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

Alors ... que pensez-vous? La sorcière est la moins pire? Avez-vous d'autres solutions à proposer?

Répondre

1

L'option 1 est dangereuse car vous ne pouvez pas garantir les préfixes pour un espace de noms donné sans pré-analyser le document et parce que vous pouvez vous retrouver avec une collision d'espace de noms. Si vous consommez un document et ne produisez rien, cela peut être correct, selon la source du document, mais sinon il perd trop d'informations.

Option 2 pourrait être appliquée de manière récursive mais son a beaucoup des mêmes problèmes que l'option 1.

Option 3 sonne comme la meilleure approche, mais plutôt que l'encombrement de votre code, faire une méthode statique qui fait les deux contrôles plutôt que de mettre la même déclaration si partout dans votre code.

La meilleure approche consiste à demander à quiconque vous envoie le code XML incorrect de le réparer. Bien sûr, cela soulève la question est-il réellement brisé. Plus précisément, obtenez-vous XML où l'espace de noms par défaut est défini en tant que X et qu'un espace de nom représentant également X reçoit un préfixe de 'es'? Si tel est le cas, le XML est bien formé et vous n'avez besoin que d'un code indépendant du préfixe, mais qui utilise toujours un nom qualifié pour récupérer l'élément. Je ne suis pas assez familier avec Dom4j pour savoir si la création d'un Namespace avec un préfixe nul le fera correspondre à tous les éléments avec un URI correspondant ou seulement à ceux qui n'ont pas de préfixe, mais qui vaut la peine d'être expérimenté.

+0

Je vais essayer de creuser le doc à propos de l'espace de noms avec préfixe nul. Merci quand même. A propos de la source du fichier XML: theire n'est pas moyen de changer quoi que ce soit. Mais le fichier avec ou sans espace de noms est valide. Avec les fichiers, nous construisons des objets que nous utilisons dans notre système. Mais nous ne "écrivons" jamais quelque chose. (nous n'avons pas le droit de modifier le fichier xml) –

4

Voici un code que j'ai trouvé et que j'utilise maintenant. Peut-être utile, si vous cherchez un moyen générique, de supprimer tous les espaces de noms d'un document 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); 
      }    
     } 
    } 

Espérons que cela soit utile pour quelqu'un qui en a besoin!

+0

ne pouvait pas faire fonctionner ce code. J'ai utilisé l'exemple xml avec namespaces de w3schools, mais c'est comme si dom4j ne reconnaissait pas les espaces de noms. Le premier if (root.getNamespace()! = Namespace.NO_NAMESPACE) évalue à vrai, et même si je supprime le si, il ne fait toujours rien. – Dan

+0

Bonjour Dan, Cela supprime les espaces de noms du document. Vous êtes probablement intéressé à supprimer les préfixes également. – Abhishek

+0

Désolé, par erreur j'ai sauvé avant de terminer ce que je voulais écrire! Dan, Cette fonction supprime les espaces de noms du document. J'ai essayé ceci w/le 5ème exemple du w3schools. Vous pouvez le vérifier en créant un xpath comme "// table". Exécutez ce xpath sur le document avant et après l'appel de la fonction removeNamespaces, et vous verrez que le dernier trouvera les nœuds pour vous. Qu'est-ce que vous essayez de faire exactement ? Je doute que vous soyez plus intéressé par la suppression des préfixes, par exemple (h: table -> table)? Faites-moi savoir si je peux être de toute aide! – Abhishek

5

Je voulais supprimer toutes les informations d'espace de nom (déclaration et balise) pour faciliter l'évaluation de xpath. Je termine avec cette solution:

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

où le NameSpaceCleaner est un visiteur 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); 
     } 
     } 
} 
+0

Namespace.detach() ne semble pas faire quoi que ce soit, au moins dans mon Document les instances de l'espace de noms avaient des parents nuls et des propriétés de document nul, empêchant le détachement de fonctionner. J'ai dû utiliser l'élément parent pour se débarrasser de l'étrange redondant (tous les éléments ont une propriété QName qui est réellement utilisée) Element Namespace child-nodes. C'était avec dom4j-1.6.1. –

+0

Fonctionne parfaitement pour moi! –

+0

Attention. Si vous allez au code source de reader.read(), vous trouverez qu'il va analyser le contenu xml avec le paramètre namesapce aware à true (hard4cod dom4j 1.6). – artificerpi

0

Comme Abhishek, je devais enlever l'espace de noms XML pour simplifier les requêtes XPath dans les scripts de test du système.(Le XML est d'abord validé XSD)

Voici les problèmes que j'ai rencontrés:

  1. je avais besoin de traiter XML profondément structuré qui a eu tendance à faire sauter la pile.
  2. Sur le XML le plus complexe, pour une raison que je n'ai pas étudiée complètement, l'effacement de tous les espaces de noms ne fonctionnait que de manière fiable lorsque l'on traversait la profondeur de l'arborescence DOM en premier. Alors, qui excluait le visiteur, ou d'obtenir la liste des noeuds avec document.selectNodes("//*")

J'ai fini avec les éléments suivants (pas le plus élégant, mais si cela peut aider à résoudre le problème de quelqu'un ...):

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

Ce code fonctionne réellement:

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