2009-09-14 21 views
8

Estamos usando dom4j 1.6.1, para analizar el XML proveniente de algún lugar. En algún momento, la baliza tiene mención del espacio de nombres (por ejemplo:) y alguna vez no(). Y hace que la llamada de Element.selectSingleNode (String s) falle.Limpiar el manejo del espacio de nombres con dom4j

Por ahora tenemos 3 soluciones, y no estamos contentos con ellos

1 - Eliminar todo ocurrencia espacio de nombres antes de hacer nada con el documento XML

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

2 - Eliminar espacio de nombres justo antes de recibir una al llamar nodo

Element.remove(Namespace ns) 

Pero es sólo funciona para un nodo y el primer nivel de niño

3 - El desorden del código por

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

Así que ... ¿qué te parece? ¿Una bruja es la menos peor? ¿Tienes alguna otra solución para proponer?

Respuesta

1

La opción 1 es peligrosa porque no puede garantizar los prefijos para un espacio de nombres determinado sin analizar previamente el documento, y porque puede terminar con una colisión del espacio de nombres. Si está consumiendo un documento y no está produciendo nada, podría estar bien, dependiendo de la fuente del documento, pero de lo contrario solo pierde demasiada información.

Opción 2 se podría aplicar de forma recursiva, pero su conseguido muchos de los mismos problemas que la opción 1.

Opción 3 suena como el mejor enfoque, pero en lugar de desorden de su código, hacer un método estático que hace dos cheques en vez que poner la misma instrucción if en su base de código.

El mejor enfoque es conseguir que quien te está enviando el XML incorrecto lo arregle. Por supuesto, esto plantea la pregunta si realmente está roto. Específicamente, ¿obtiene XML donde el espacio de nombre predeterminado se define como X y luego un espacio de nombres que también representa X tiene un prefijo de 'es'? Si este es el caso, entonces el XML está bien formado y solo necesita un código que sea independiente del prefijo, pero que todavía use un nombre calificado para recuperar el elemento. No estoy lo suficientemente familiarizado con Dom4j para saber si la creación de un espacio de nombres con un prefijo nulo hará que coincida con todos los elementos con un URI coincidente o solo aquellos sin prefijo, pero vale la pena experimentar con él.

+0

voy a tratar de cavar el documento sobre el espacio de nombres con el prefijo nulo. Gracias de cualquier manera. Acerca del origen del archivo XML: no hay manera de que cambien nada. Pero el archivo con o sin espacio de nombres es válido. Con los archivos, construimos objetos, que usamos en nuestro sistema. Pero nunca "escribimos" algo. (no tenemos derecho a modificar el archivo xml) –

4

El siguiente es un código que encontré y ahora uso. Podría ser útil, si busca una forma genérica, eliminar todos los espacios de nombres de un documento 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); 
      }    
     } 
    } 

Espero que esto es útil para alguien que lo necesita!

+0

no pudo hacer que este código funcione. Utilicé xml con muestras de espacios de nombres de w3schools, pero es como que dom4j no reconoce los espacios de nombres. El primer if (root.getNamespace()! = Namespace.NO_NAMESPACE) se evalúa como verdadero, e incluso si elimino el if, aún no hace nada. – Dan

+0

Hola, Dan, esto elimina los espacios de nombres del documento. Probablemente también le interese eliminar los prefijos. – Abhishek

+0

Lo siento, por error lo guardé antes de completar lo que quería escribir! Dan, esta función elimina los espacios de nombres del documento. Intenté esto con el quinto ejemplo de w3schools. Puede verificar esto creando un xpath como "// tabla". Ejecute este xpath en el documento antes y después de llamar a la función removeNamespaces, y verá que este último encontrará los nodos por usted. Qué estás tratando de hacer exactamente ? Dudo si está más interesado en simplemente eliminar los prefijos, por ejemplo, (h: tabla -> tabla)? ¡Avíseme si puedo ayudarlo! – Abhishek

5

Quería eliminar cualquier información de espacio de nombres (declaración y etiqueta) para facilitar la evaluación de xpath. Termino con esta solución:

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

donde el NameSpaceCleaner es un visitante 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() no parece hacer nada, al menos en mi documento las instancias de Namespace tenían padres nulos y propiedades de documento nulo, lo que evitaba que el desconectado funcionara. Tuve que usar el elemento padre para deshacerme de la extraña redundante (todos los elementos tienen una propiedad QName que en realidad se usa) Espacio de nombres de elementos child-nodes. Esto fue con dom4j-1.6.1. –

+0

¡Funciona perfecto para mí! –

+0

Atención. Si va al código fuente de reader.read(), encontrará que analizará el contenido xml con la configuración namesapce aware en true (hard4ded dom4j 1.6). – artificerpi

0

Como Abhishek, que tenía que quitar el espacio de nombres de XML para simplificar las consultas XPath en scripts de pruebas del sistema.(El XML se valida primera XSD)

Éstos son los problemas que enfrenté:

  1. que necesitaba para procesar XML profundamente estructurado que tenía una tendencia de la voladura de la pila.
  2. En la mayoría de los XML complejos, por una razón que no investigué completamente, eliminar todos los espacios de nombres solo funcionó de manera confiable al recorrer primero la profundidad del árbol DOM. Por lo que excluye el visitante, o conseguir la lista de nodos con document.selectNodes("//*")

que terminó con la siguiente (no el más elegante, pero si que puede ayudar a resolver problemas de alguien ...):

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

Este código funciona realmente:

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