web-dev-qa-db-fra.com

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

Je suis intéressé par des conseils /pseudocode code/explication plutôt que la mise en œuvre réelle.

  • Je voudrais parcourir un document xml, tous ses nœuds
  • Vérifier l'existence d'un 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, parcourez la liste des attributs et créez xpath pour chaque attribut, y compris le nœud.

Un conseil? J'espère que vous fournirez des informations utiles

MODIFIER:

La raison de cette opération est ... J'écris des tests automatisés dans jmeter, donc pour chaque demande, je dois vérifier que la demande a réellement fait son travail, donc j'affirme les résultats en obtenant les valeurs des nœuds avec xpath. (Informations supplémentaires - non pertinentes)

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

BOUNTY:

Je recherche Java

Objectif

Mon objectif est de réaliser 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 nul/zéro, obtenez xpath, add = 'nodevalue' à des fins d'assertion
  • Si le nœud a des attributs, créez également une assertion pour eux

MISE À JOUR BOUNTY:

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

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

36
ant

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 voulu 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 tout élément de ce type, s'il n'a pas d'éléments enfants, tous ses éléments ancêtres ou autonomes sont traités dans un mode spécifique, nommé 'path'. Puis le "='theValue'" une partie est sortie, puis un caractère NL.

  • Tous les attributs de l'élément correspondant sont ensuite traités.

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

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

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

Notez:

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

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


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

Voici une solution XSLT 1.0 pure:

Vous trouverez ci-dessous un exemple de document xml et une feuille de style qui prend un paramètre d'ensemble 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']
41
Dimitre Novatchev

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'
14
bdoughan

Avec jOOX (un jquery API port vers Java, clause de non-responsabilité - je travaille pour l'entreprise 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 toutefois mis en œuvre dans un avenir proche.

12
Lukas Eder
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 aucun élément de contenu mixte. 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, il est facile à implémenter. Le contenu mixte, en revanche, rendrait l'utilisation de xpath très fastidieuse, car vous devriez pouvoir traiter individuellement le deuxième, le troisième et ainsi de suite le nœud de texte dans un élément.

3
biziclop
  1. utilisez w3c.dom
  2. descendre récursivement
  3. pour chaque nœud, il est facile d'obtenir son xpath: soit en le stockant comme tableau/liste pendant # 2, soit via une fonction qui monte récursivement jusqu'à ce que le parent soit nul, puis inverse le tableau/liste des nœuds rencontrés.

quelque chose comme ca.

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

2
Osw

J'ai fait exactement la même chose la semaine dernière pour le traitement de mon xml au format compatible solr.

Puisque vous vouliez un pseudo code: c'est comme ça que j'ai fait ça.

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

1_ Initialiser un objet de noeud personnalisé: NodeObjectVO {String nodeName, String path, List attr, NodeObjectVO parent, List child}

2_ Créer une liste vide

3_ Créez une représentation dom de xml et parcourez le nœud. Pour chaque nœud, obtenez les informations correspondantes. Toutes les informations comme Node nom, noms d'attribut et 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 à partir de NodeElement. (While (node.Parent! = Null) {path + = node.parent.nodeName}.

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

5_ Dans la méthode setter de setAttributes (List), je vais ajouter le chemin de l'objet avec tous les attributs disponibles. (un chemin avec tous les attributs disponibles. Pas une liste de chemins avec chaque combinaison possible d'attributs. Vous voudrez peut-être faire autrement.)

6_ Ajoutez le NodeObjectVO à la liste.

7_ Nous avons maintenant une liste plate (non hiérarchique) d'objets personnalisés Node, qui contiennent toutes les informations dont j'ai besoin.

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

1
uncaught_exceptions

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

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

xpath vers le 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 le deuxième de tous les éléments avec le nom el". L'élément something n'affecte donc 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. En pratique, j'ai accompli cela en parcourant l'arbre de manière récursive et en me souvenant des informations sur les éléments et leurs index en cours de route.
La création de xpath faisant référence à un attribut spécifique de l'élément consistait alors simplement à ajouter '/ @ attrName' au xpath de l'élément.

1
alpha-mouse

J'ai écrit une méthode pour retourner le chemin absolu d'un élément dans la bibliothèque Practical XML . 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)); 

Ainsi, vous pouvez récapituler 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 basées sur XPath de cette même bibliothèque.

1
kdgregory