Generar / obtener xpath del nodo XML java

Me interesa el consejo / código de pseudocódigo / explicación en lugar de la implementación real .

  • Me gustaría ir a través del documento xml, todos sus nodos
  • Comprueba el nodo por la existencia de atributos

Caso si el nodo no tiene atributo, get/generate String with value of its xpath
Caso si el nodo tiene atributos, itere a través de la lista de atributos y cree xpath para cada atributo, incluido el nodo también.

¿Un consejo? Con suerte, proporcionará información útil

EDITAR:

La razón para hacer esto es … Estoy escribiendo pruebas automatizadas en jmeter, por lo que para cada solicitud necesito verificar que la solicitud realmente hizo su trabajo, así que estoy afirmando los resultados al obtener valores de nodos con xpath. (Información adicional – irrelevante)

Cuando la solicitud es pequeña, no es un problema crear aseveraciones a mano, pero para las más grandes es realmente un dolor en el … (información extra – irrelevante)

BOUNTY:

Estoy buscando el enfoque de Java

Gol

Mi objective es lograr lo siguiente de este archivo ex xml:

  one two three four  five   

para producir lo siguiente:

 //root[1]/elemA[1]='one' //root[1]/elemA[2]='two' //root[1]/elemA[2][@attribute1='first'] //root[1]/elemA[2][@attribute2='second'] //root[1]/elemB[1]='three' //root[1]/elemA[3]='four' //root[1]/elemC[1]/elemB[1]='five' 

Explicado:

  • Si el valor / texto del nodo no es nulo / cero, obtenga xpath, add = ‘nodevalue’ para fines de aserción
  • Si el nodo tiene atributos create assert para ellos también

ACTUALIZACIÓN DE BOUNTY:

Encontré este ejemplo, no produce los resultados correctos, pero estoy buscando algo como esto:

http://www.coderanch.com/how-to/java/SAXCreateXPath

Actualización :

@ c0mrade ha actualizado su pregunta. Aquí hay una solución:

Esta transformación XSLT :

    '     
              
   

cuando se aplica en el documento XML proporcionado :

  one two three four  five   

produce exactamente el resultado deseado y correcto :

 /root/elemA='one' /root/elemA[2]='two' /root/elemA[2][@attribute1='first'] /root/elemA[2][@attribute2='second'] /root/elemB='three' /root/elemA[3]='four' /root/elemC/elemB='five' 

Cuando se aplica al documento recién proporcionado por @ c0mrade :

   89734   

nuevamente se produce el resultado correcto :

 /root/elemX='89734' /root/elemX[@serial='kefw90234kf2esda9231'] 

Explicación

  • Solo los elementos que no tienen elementos secundarios, o tienen atributos, se emparejan y procesan.

  • Para cualquier elemento de este tipo, si no tiene elementos hijos, todos sus antecesores, o los elementos propios se procesan en un modo específico, llamado 'path' . Luego se "='theValue'" parte "='theValue'" y luego un carácter NL.

  • Todos los atributos del elemento coincidente se procesan .

  • Entonces, finalmente, las plantillas se aplican a todos los elementos de los niños .

  • Procesar un elemento en el modo 'path' es simple : se muestran el carácter A / y el nombre del elemento. Luego, si hay hermanos anteriores con el mismo nombre, se genera una parte “[numPrecSiblings + 1]`.

  • El procesamiento de atributos es simple : Primero todos los elementos ancestor-or-self:: de su elemento primario se procesan en modo 'path' , luego se emite la parte [attrName = attrValue], seguido de un carácter NL.

Nota :

  • Los nombres que están en un espacio de nombres se muestran sin ningún problema y en su forma legible inicial.

  • Para ayudar a la legibilidad, nunca se muestra un índice de [1] .


A continuación está mi respuesta inicial (puede ser ignorada)

Aquí hay una solución XSLT 1.0 pura :

A continuación se muestra un documento xml de muestra y una hoja de estilo que toma un parámetro de conjunto de nodos y produce una expresión XPath válida para cada nodo miembro.

stylesheet (buildPath.xsl):


         /                                                        '         
      

xml source (buildPath.xml):


   textA  < ?myProc ?> xxxxxxxx       yyyyyyy  < ?myProc2 ?>    

Resultado :

 /root/nodeA[2]/nodeB[2]/namespace::*[local-name() = 'myNamespace'] /root/nodeA[2]/nodeB[2]/nodeC/namespace::*[local-name() = 'myNamespace'] 

Así es como se puede hacer esto con SAX:

 import java.util.HashMap; import java.util.Map; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; public class FragmentContentHandler extends DefaultHandler { private String xPath = "/"; private XMLReader xmlReader; private FragmentContentHandler parent; private StringBuilder characters = new StringBuilder(); private Map elementNameCount = new HashMap(); public FragmentContentHandler(XMLReader xmlReader) { this.xmlReader = xmlReader; } private FragmentContentHandler(String xPath, XMLReader xmlReader, FragmentContentHandler parent) { this(xmlReader); this.xPath = xPath; this.parent = parent; } @Override public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { Integer count = elementNameCount.get(qName); if(null == count) { count = 1; } else { count++; } elementNameCount.put(qName, count); String childXPath = xPath + "/" + qName + "[" + count + "]"; int attsLength = atts.getLength(); for(int x=0; x 0) { System.out.println(xPath + "='" + characters.toString() + "'"); } xmlReader.setContentHandler(parent); } @Override public void characters(char[] ch, int start, int length) throws SAXException { characters.append(ch, start, length); } } 

Se puede probar con:

 import java.io.FileInputStream; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; public class Demo { public static void main(String[] args) throws Exception { SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); XMLReader xr = sp.getXMLReader(); xr.setContentHandler(new FragmentContentHandler(xr)); xr.parse(new InputSource(new FileInputStream("input.xml"))); } } 

Esto producirá el resultado deseado:

 //root[1]/elemA[1]='one' //root[1]/elemA[2][@attribute1='first] //root[1]/elemA[2][@attribute2='second] //root[1]/elemA[2]='two' //root[1]/elemB[1]='three' //root[1]/elemA[3]='four' //root[1]/elemC[1]/elemB[1]='five' 

Con jOOX (un puerto API de jquery para Java, exención de responsabilidad – yo trabajo para la compañía detrás de la biblioteca), casi puede lograr lo que quiere en una sola statement:

 // I'm assuming this: import static org.joox.JOOX.$; // And then... List coolList = $(document).xpath("//*[not(*)]").map( context -> $(context).xpath() + "='" + $(context).text() + "'" ); 

Si el documento es su documento de muestra:

  one two three four  five   

Esto producirá

 /root[1]/elemA[1]='one' /root[1]/elemA[2]='two' /root[1]/elemB[1]='three' /root[1]/elemA[3]='four' /root[1]/elemC[1]/elemB[1]='five' 

Por “casi”, me refiero a que jOOX no (todavía) admite atributos de correspondencia / mapeo. Por lo tanto, sus atributos no producirán ningún resultado. Sin embargo, esto se implementará en el futuro cercano.

 private static void buildEntryList( List entries, String parentXPath, Element parent ) { NamedNodeMap attrs = parent.getAttributes(); for( int i = 0; i < attrs.getLength(); i++ ) { Attr attr = (Attr)attrs.item( i ); //TODO: escape attr value entries.add( parentXPath+"[@"+attr.getName()+"='"+attr.getValue()+"']"); } HashMap nameMap = new HashMap(); NodeList children = parent.getChildNodes(); for( int i = 0; i < children.getLength(); i++ ) { Node child = children.item( i ); if( child instanceof Text ) { //TODO: escape child value entries.add( parentXPath+"='"+((Text)child).getData()+"'" ); } else if( child instanceof Element ) { String childName = child.getNodeName(); Integer nameCount = nameMap.get( childName ); nameCount = nameCount == null ? 1 : nameCount + 1; nameMap.put( child.getNodeName(), nameCount ); buildEntryList( entries, parentXPath+"/"+childName+"["+nameCount+"]", (Element)child); } } } public static List getEntryList( Document doc ) { ArrayList entries = new ArrayList(); Element root = doc.getDocumentElement(); buildEntryList(entries, "/"+root.getNodeName()+"[1]", root ); return entries; } 

Este código funciona con dos suposiciones: no está utilizando espacios de nombres y no hay elementos de contenido mixto. La limitación del espacio de nombres no es grave, pero haría que tu expresión XPath sea mucho más difícil de leer, ya que cada elemento sería algo así como *:[namespace-uri()=''][] , pero de lo contrario es fácil de implementar. El contenido mixto, por otro lado, haría que el uso de xpath fuera muy tedioso, ya que tendría que poder direccionar individualmente el segundo, el tercero y así sucesivamente el nodo de texto dentro de un elemento.

  1. usa w3c.dom
  2. ir recursivamente abajo
  3. para cada nodo hay una manera fácil de obtener su xpath: ya sea almacenándolo como array / list mientras que # 2, o mediante una función que va recursivamente hasta que parent sea nulo, luego invierte el array / lista de nodos encontrados.

algo como eso.

UPD: y concatenar la lista final para obtener xpath final. no creas que los atributos serán un problema.

He hecho una tarea similar una vez. La idea principal utilizada fue que puede usar índices del elemento en xpath. Por ejemplo, en el siguiente xml

      

xpath a la segunda será /root[1]/el[2] (los índices xpath están basados ​​en 1). Esto se lee como “tomar la primera raíz, luego tomar la segunda de todos los elementos con el nombre el”. Entonces el elemento something no afecta la indexación de los elementos el . Entonces, en teoría, puedes crear un xpath para cada elemento específico en tu xml. En la práctica, he logrado esto recorriendo el árbol de forma recurrente y recordando información sobre los elementos y sus índices a lo largo del camino.
La creación de xpath haciendo referencia al atributo específico del elemento simplemente agregaba ‘/ @ attrName’ al elemento xpath.

He escrito un método para devolver la ruta absoluta de un elemento en la biblioteca práctica de XML . Para darle una idea de cómo funciona, aquí hay un extracto de una de las pruebas unitarias :

 assertEquals("/root/wargle[2]/zargle", DomUtil.getAbsolutePath(child3a)); 

Por lo tanto, podría recurse a través del documento, aplicar sus pruebas y utilizar esto para devolver el XPath. O, lo que probablemente sea mejor, es que podría usar las afirmaciones basadas en XPath de esa misma biblioteca.

Hice exactamente lo mismo la semana pasada para procesar mi formato compatible con xml a solr.

Como querías un pseudo código: así es como lo logré.

// Puede omitir la referencia a padre e hijo.

1_ Inicializar un objeto de nodo personalizado: NodeObjectVO {String nodeName, String path, List attr, NodeObjectVO parent, List child}

2_ Crea una lista vacía

3_ Crea una representación dom de xml e itera por el nodo. Para cada nodo, obtenga la información correspondiente. Toda la información como el nombre del nodo, los nombres de los atributos y el valor deberían estar fácilmente disponibles desde el objeto dom. (Debe verificar el NodeType dom, el código debe ignorar las instrucciones de procesamiento y los nodos de texto sin formato).

// advertencia de Bloat Code. 4_ La única parte difícil es obtener ruta. Creé un método de utilidad iterativo para obtener la cadena xpath de NodeElement. (While (node.Parent! = Null) {path + = node.parent.nodeName}.

(También puede lograr esto al mantener una variable de ruta global, que realiza un seguimiento de la ruta principal para cada iteración).

5_ En el método setter de setAttributes (List), anexaré la ruta del objeto con todos los atributos disponibles. (una ruta con todos los atributos disponibles. No una lista de rutas con cada posible combinación de atributos. Es posible que desee hacer otra cosa).

6_ Agregue el NodeObjectVO a la lista.

7_ Ahora tenemos una lista plana (no jerárquica) de objetos de nodo personalizados, que tienen toda la información que necesito.

(Nota: como mencioné, mantengo la relación padre-hijo, probablemente debería omitir esa parte. Existe la posibilidad de que el código se hinche, especialmente mientras getparentpath. Para xml pequeño esto no fue un problema, pero esto es una preocupación para xml grande) .