ejemplos - Generar/obtener xpath del nodo XML java
contains xpath ejemplos (8)
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 objetivo es lograr lo siguiente de este archivo ex xml:
<root>
<elemA>one</elemA>
<elemA attribute1=''first'' attribute2=''second''>two</elemA>
<elemB>three</elemB>
<elemA>four</elemA>
<elemC>
<elemB>five</elemB>
</elemC>
</root>
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:
- usa w3c.dom
- ir recursivamente abajo
- 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.
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<String, Integer> elementNameCount = new HashMap<String, Integer>();
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<attsLength; x++) {
System.out.println(childXPath + "[@" + atts.getQName(x) + "=''" + atts.getValue(x) + '']'');
}
FragmentContentHandler child = new FragmentContentHandler(childXPath, xmlReader, this);
xmlReader.setContentHandler(child);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
String value = characters.toString().trim();
if(value.length() > 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 declaración:
// I''m assuming this:
import static org.joox.JOOX.$;
// And then...
List<String> coolList = $(document).xpath("//*[not(*)]").map(
context -> $(context).xpath() + "=''" + $(context).text() + "''"
);
Si el documento es su documento de muestra:
<root>
<elemA>one</elemA>
<elemA attribute1=''first'' attribute2=''second''>two</elemA>
<elemB>three</elemB>
<elemA>four</elemA>
<elemC>
<elemB>five</elemB>
</elemC>
</root>
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.
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.
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
<root>
<el />
<something />
<el />
</root>
xpath a la segunda <el/>
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.
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) .
Actualización :
@ c0mrade ha actualizado su pregunta. Aquí hay una solución:
Esta transformación XSLT :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vApos">''</xsl:variable>
<xsl:template match="*[@* or not(*)] ">
<xsl:if test="not(*)">
<xsl:apply-templates select="ancestor-or-self::*" mode="path"/>
<xsl:value-of select="concat(''='',$vApos,.,$vApos)"/>
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:apply-templates select="@*|*"/>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:value-of select="concat(''/'',name())"/>
<xsl:variable name="vnumPrecSiblings" select=
"count(preceding-sibling::*[name()=name(current())])"/>
<xsl:if test="$vnumPrecSiblings">
<xsl:value-of select="concat(''['', $vnumPrecSiblings +1, '']'')"/>
</xsl:if>
</xsl:template>
<xsl:template match="@*">
<xsl:apply-templates select="../ancestor-or-self::*" mode="path"/>
<xsl:value-of select="concat(''[@'',name(), ''='',$vApos,.,$vApos,'']'')"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
cuando se aplica en el documento XML proporcionado :
<root>
<elemA>one</elemA>
<elemA attribute1=''first'' attribute2=''second''>two</elemA>
<elemB>three</elemB>
<elemA>four</elemA>
<elemC>
<elemB>five</elemB>
</elemC>
</root>
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 :
<root>
<elemX serial="kefw90234kf2esda9231">
<id>89734</id>
</elemX>
</root>
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):
<xsl:stylesheet version=''1.0''
xmlns:xsl=''http://www.w3.org/1999/XSL/Transform''
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
>
<xsl:output method="text"/>
<xsl:variable name="theParmNodes" select="//namespace::*[local-name() =
''myNamespace'']"/>
<xsl:template match="/">
<xsl:variable name="theResult">
<xsl:for-each select="$theParmNodes">
<xsl:variable name="theNode" select="."/>
<xsl:for-each select="$theNode |
$theNode/ancestor-or-self::node()[..]">
<xsl:element name="slash">/</xsl:element>
<xsl:choose>
<xsl:when test="self::*">
<xsl:element name="nodeName">
<xsl:value-of select="name()"/>
<xsl:variable name="thisPosition"
select="count(preceding-sibling::*[name(current()) =
name()])"/>
<xsl:variable name="numFollowing"
select="count(following-sibling::*[name(current()) =
name()])"/>
<xsl:if test="$thisPosition + $numFollowing > 0">
<xsl:value-of select="concat(''['', $thisPosition +
1, '']'')"/>
</xsl:if>
</xsl:element>
</xsl:when>
<xsl:otherwise> <!-- This node is not an element -->
<xsl:choose>
<xsl:when test="count(. | ../@*) = count(../@*)">
<!-- Attribute -->
<xsl:element name="nodeName">
<xsl:value-of select="concat(''@'',name())"/>
</xsl:element>
</xsl:when>
<xsl:when test="self::text()"> <!-- Text -->
<xsl:element name="nodeName">
<xsl:value-of select="''text()''"/>
<xsl:variable name="thisPosition"
select="count(preceding-sibling::text())"/>
<xsl:variable name="numFollowing"
select="count(following-sibling::text())"/>
<xsl:if test="$thisPosition + $numFollowing > 0">
<xsl:value-of select="concat(''['', $thisPosition +
1, '']'')"/>
</xsl:if>
</xsl:element>
</xsl:when>
<xsl:when test="self::processing-instruction()">
<!-- Processing Instruction -->
<xsl:element name="nodeName">
<xsl:value-of select="''processing-instruction()''"/>
<xsl:variable name="thisPosition"
select="count(preceding-sibling::processing-instruction())"/>
<xsl:variable name="numFollowing"
select="count(following-sibling::processing-instruction())"/>
<xsl:if test="$thisPosition + $numFollowing > 0">
<xsl:value-of select="concat(''['', $thisPosition +
1, '']'')"/>
</xsl:if>
</xsl:element>
</xsl:when>
<xsl:when test="self::comment()"> <!-- Comment -->
<xsl:element name="nodeName">
<xsl:value-of select="''comment()''"/>
<xsl:variable name="thisPosition"
select="count(preceding-sibling::comment())"/>
<xsl:variable name="numFollowing"
select="count(following-sibling::comment())"/>
<xsl:if test="$thisPosition + $numFollowing > 0">
<xsl:value-of select="concat(''['', $thisPosition +
1, '']'')"/>
</xsl:if>
</xsl:element>
</xsl:when>
<!-- Namespace: -->
<xsl:when test="count(. | ../namespace::*) =
count(../namespace::*)">
<xsl:variable name="apos">''</xsl:variable>
<xsl:element name="nodeName">
<xsl:value-of select="concat(''namespace::*'',
''[local-name() = '', $apos, local-name(), $apos, '']'')"/>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="msxsl:node-set($theResult)"/>
</xsl:template>
</xsl:stylesheet>
xml source (buildPath.xml):
<!-- top level Comment -->
<root>
<nodeA>textA</nodeA>
<nodeA id="nodeA-2">
<?myProc ?>
xxxxxxxx
<nodeB/>
<nodeB xmlns:myNamespace="myTestNamespace">
<!-- Comment within /root/nodeA[2]/nodeB[2] -->
<nodeC/>
<!-- 2nd Comment within /root/nodeA[2]/nodeB[2] -->
</nodeB>
yyyyyyy
<nodeB/>
<?myProc2 ?>
</nodeA>
</root>
<!-- top level Comment -->
Resultado :
/root/nodeA[2]/nodeB[2]/namespace::*[local-name() = ''myNamespace'']
/root/nodeA[2]/nodeB[2]/nodeC/namespace::*[local-name() =
''myNamespace'']
private static void buildEntryList( List<String> 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<String, Integer> nameMap = new HashMap<String, Integer>();
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<String> getEntryList( Document doc ) {
ArrayList<String> entries = new ArrayList<String>();
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 *:<name>[namespace-uri()=''<nsuri>''][<index>]
, 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.