Générer / obtenir xpath à partir du nœud XML java


Je suis intéressé par le conseil/ pseudocodecode/explication plutôt que l'implémentation réelle.

  • Je voudrais aller à travers le document xml, tous ses nœuds
  • Vérifiez l'existence de l'attribut dans le nœud

Cas si le nœud n'a pas d'attribut, get/generate String with value of its xpath
Cas si le nœud a des attributs, itérez la liste des attributs à travers et créez xpath pour chaque attribut, y compris le nœud.

Un conseil? Espérons que vous fournirez quelques intel utiles

MODIFIER:

Raison de faire cela est .. J'écris des tests automatisés dans jmeter, donc pour chaque demande, je dois vérifier que cette demande a réellement fait son travail, donc j'affirme des résultats en obtenant des valeurs de nœuds avec xpath.(info supplémentaire-non pertinent)

Lorsque la demande est petite, ce n'est pas un problème de créer des assertions à la main, mais pour les plus grandes, c'est vraiment une douleur dans le .. (info supplémentaire-non pertinent)

BOUNTY:

Je cherche approche java

Objectif

Mon objectif est d'atteindre ce qui suit à partir de ce fichier 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>

Pour produire ce qui suit:

//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'

Expliqué :

  • Si la valeur/le texte du nœud n'est pas null / zero, obtenez xpath, ajoutez = 'nodevalue' à des fins d'assertion
  • Si le nœud a des attributs, créez une assert pour eux aussi

MISE À JOUR BOUNTY:

J'ai trouvé cet exemple, il ne produit pas les résultats corrects, mais je cherche quelque chose comme ceci:

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

Author: Dimitre Novatchev, 2011-01-20

8 answers

Mise à Jour:

@c0mrade a mis à jour sa question. Voici une solution:

Cette transformation 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>&#xA;</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>&#xA;</xsl:text>
    </xsl:template>
</xsl:stylesheet>

Lorsqu'il est appliqué sur le document XML fourni:

<root>
    <elemA>one</elemA>
    <elemA attribute1='first' attribute2='second'>two</elemA>
    <elemB>three</elemB>
    <elemA>four</elemA>
    <elemC>
        <elemB>five</elemB>
    </elemC>
</root>

Produit exactement le résultat souhaité et correct:

/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'

Lorsqu'il est appliqué au document nouvellement fourni par @c0mrade:

<root>
    <elemX serial="kefw90234kf2esda9231">
        <id>89734</id>
    </elemX>
</root>

Encore une fois le résultat correct est produit:

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

Explication:

  • Seuls les éléments qui n'ont pas d'éléments enfants ou qui ont des attributs sont mis en correspondance et traités.

  • Pour un tel élément, s'il n'a pas d'éléments enfants, tous ses éléments ancêtres ou auto sont traités dans un mode spécifique, nommé 'path'. Ensuite, la partie "='theValue'" est sortie, puis un caractère NL.

  • Tous les attributs de l'élément sont appariés puis traité.

  • Enfin, les modèles sont appliqués à tous les éléments enfants.

  • Le traitement d'un élément en mode 'path' est simple: Un caractère / et le nom de l'élément sont en sortie. Ensuite, s'il y a des frères et sœurs précédents avec le même nom, une partie "[numPrecSiblings+1] " est sortie.

  • Le traitement des attributs est simple : Tout d'abord, tous les éléments ancestor-or-self:: de son parent sont traités dans 'path' mode, puis la partie [attrName=attrValue] est sortie, suivie d'un caractère NL.

Remarque :

  • Les noms qui se trouvent dans un espace de noms sont affichés sans aucun problème et sous leur forme lisible initiale.

  • Pour faciliter la lisibilité, un indice de [1] n'est jamais affiché.


Voici ma réponse initiale (peut être ignorée)

Voici une solution XSLT 1.0 pure:

Ci-dessous est un exemple de document XML et une feuille de style qui prend un paramètre de jeu de nœuds et produit une expression XPath valide pour chaque nœud membre.

Feuille de style (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>&#xA;</xsl:text>
  </xsl:for-each>
 </xsl:variable>
 <xsl:value-of select="msxsl:node-set($theResult)"/>
</xsl:template>
</xsl:stylesheet>

Source Xml (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 -->

Résultat:

/root/nodeA[2]/nodeB[2]/namespace::*[local-name() = 'myNamespace']
/root/nodeA[2]/nodeB[2]/nodeC/namespace::*[local-name() =
'myNamespace']
 40
Author: Dimitre Novatchev, 2011-01-24 13:54:32

Voici comment cela peut être fait avec 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);
    }

}

, Il peut être testé avec:

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

Cela produira la sortie souhaitée:

//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'
 12
Author: Blaise Doughan, 2011-01-24 15:04:47

Avec jOOX (un jquery API port vers Java , avertissement-Je travaille pour la société derrière la bibliothèque), vous pouvez presque réaliser ce que vous voulez en une seule déclaration:

// I'm assuming this:
import static org.joox.JOOX.$;

// And then...
List<String> coolList = $(document).xpath("//*[not(*)]").map(
    context -> $(context).xpath() + "='" + $(context).text() + "'"
);

Si le document est votre exemple de document:

<root>
    <elemA>one</elemA>
    <elemA attribute1='first' attribute2='second'>two</elemA>
    <elemB>three</elemB>
    <elemA>four</elemA>
    <elemC>
        <elemB>five</elemB>
    </elemC>
</root>

Cela produira

/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'

Par "presque", je veux dire que jOOX ne prend pas (encore) en charge les attributs de correspondance/mappage. Par conséquent, vos attributs ne produiront aucune sortie. Cela sera mis en œuvre dans un proche avenir, cependant.

 12
Author: Lukas Eder, 2015-12-01 18:44:59
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;
}

Ce code fonctionne avec deux hypothèses: vous n'utilisez pas d'espaces de noms et il n'y a pas d'éléments de contenu mixtes. La limitation de l'espace de noms n'est pas sérieuse, mais cela rendrait votre expression XPath beaucoup plus difficile à lire, car chaque élément serait quelque chose comme *:<name>[namespace-uri()='<nsuri>'][<index>], mais sinon c'est facile à implémenter. Le contenu mixte, d'autre part, rendrait l'utilisation de xpath très fastidieuse, car vous devez être capable d'adresser individuellement le deuxième, le troisième et ainsi de suite nœud de texte dans un élément.

 3
Author: biziclop, 2011-01-23 17:20:39
  1. utiliser w3c. dom
  2. descendre récursivement
  3. pour chaque nœud, il existe un moyen facile d'obtenir son xpath: soit en le stockant sous forme de tableau/liste pendant #2, soit via une fonction qui monte récursivement jusqu'à ce que le parent soit null, puis inverse le tableau/liste des nœuds rencontrés.

Quelque chose comme ça.

UPD: et concaténer la liste finale afin d'obtenir xpath final. ne pensez pas que les attributs seront un problème.

 2
Author: Osw, 2011-01-20 11:55:31

J'ai fait une tâche similaire une fois. L'idée principale utilisée était que vous pouvez utiliser des index de l'élément dans le xpath. Par exemple dans le xml suivant

<root>
    <el />
    <something />
    <el />
</root>

Xpath au second <el/> sera /root[1]/el[2] (les index xpath sont basés sur 1). Cela se lit comme "prenez la première racine, puis prenez la seconde de tous les éléments avec le nom el". Donc l'élément something n'affecte pas l'indexation des éléments el. Vous pouvez donc en théorie créer un xpath pour chaque élément spécifique de votre xml. Dans la pratique, j'ai accompli ceci en marchant l'arbre de manière récurrente et en se souvenant des informations sur les éléments et leurs index le long du chemin.
Créer xpath référençant un attribut spécifique de l'élément, c'était simplement ajouter '/@attrName' au xpath de l'élément.

 1
Author: alpha-mouse, 2011-01-20 12:49:26

J'ai écrit une méthode retourne le chemin absolu d'un élément dans le Pratiques XML bibliothèque. Pour vous donner une idée de son fonctionnement, voici un extrait de l'un des tests unitaires :

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

Vous pouvez donc récurser le document, appliquer vos tests et l'utiliser pour renvoyer le XPath. Ou, ce qui est probablement mieux, c'est que vous pouvez utiliser les assertions XPath de cette même bibliothèque.

 1
Author: kdgregory, 2011-01-20 12:53:49

J'ai fait exactement la même chose la semaine dernière pour traiter mon XML au format conforme solr.

Puisque vous vouliez un pseudo code: Voici comment j'ai accompli cela.

// Vous pouvez ignorer la référence au parent et à l'enfant.

1_ Initialise un objet node personnalisé: NodeObjectVO {String nodeName ,String path, List attr, NodeObjectVO parent, List child}

2_ Créer une liste vide

3_ Créer une représentation dom de xml et itérer thro le nœud. Pour chaque nœud, obtenez les informations correspondantes. Toutes les informations telles que le nom du nœud, les noms d'attribut et la valeur doivent être facilement disponibles à partir de l'objet dom. (Vous devez vérifier le dom NodeType, le code doit ignorer les instructions de traitement et les nœuds de texte brut.)

// Avertissement de ballonnement de code. 4_ La seule partie délicate est get path. J'ai créé une méthode utilitaire itérative pour obtenir la chaîne xpath de NodeElement. (While(nœud.Parent != null) {chemin+ = nœud.parent.nodeName}.

(Vous pouvez également y parvenir en maintien d'une variable de chemin global, qui garde une trace du chemin parent pour chaque itération.)

5_ Dans la méthode setter de setAttributes (List), j'ajouterai le chemin de l'objet avec tous les attributs disponibles. (un chemin avec tous les attributs disponibles. Pas une liste de chemin avec chaque combinaison possible d'attributs. Vous voulez peut-être faire someother façon. )

6_ Ajouter le NodeObjectVO à la liste.

7_ Maintenant, nous avons une liste plate (pas hiérarchique) d'objets Node personnalisés, qui avoir toutes les informations dont j'ai besoin.

(Note: Comme je l'ai mentionné, je maintiens une relation parent-enfant, vous devriez probablement ignorer cette partie. Il y a une possibilité de ballonnements de code, en particulier pendant getparentpath. Pour les petits xml, ce n'était pas un problème, mais c'est une préoccupation pour les grands xml).

 1
Author: uncaught_exceptions, 2011-01-23 15:50:32