40 votes

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

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

  • Je voudrais aller à la fosse du document xml, l'ensemble de ses nœuds
  • Vérifiez que le nœud de l'attribut de l'existence

Cas si le nœud n'a pas d'attribut, get/generate String with value of its xpath
Cas si le nœud n'ont attributs, itérer creux liste d'attributs et de créer xpath pour chaque attribut, y compris le nœud.

Parole de conseils? J'espère que vous apporter des éléments utiles intel

EDIT:

La raison pour cela est .. je suis en train d'écrire des tests automatisés dans jmeter, donc pour chaque demande, j'ai besoin de vérifier que la demande a effectivement fait son travail, donc je suis en affirmant résultats en obtenant les valeurs des nœuds xpath.(plus d'info pertinente)

Lorsque la demande est faible sa pas de problème pour créer affirme par la main, mais pour les grandes ses vraiment une douleur dans le .. (plus d'info pertinente)

BOUNTY :

Je suis à la recherche pour java approche

Objectif

Mon objectif est de parvenir à la suite de cette ex fichier 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 les éléments suivants :

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

A expliqué :

  • Si la valeur du nœud/texte n'est pas null/zero, obtenir xpath , ajoutez = 'nodevalue' pour l'affirmation but
  • Si le nœud a les attributs de créer affirmer pour eux aussi

BOUNTY MISE À JOUR :

J'ai trouvé cet exemple, il ne produit pas les résultats corrects , mais je suis à la recherche de quelque chose comme ceci:

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

BOUNTY MISE À JOUR II:

En regardant Dimitre Novatchev réponse, la sortie est correcte pour le cas prévu à ma question, cependant, j'ai découvert un cas que je n'ai pas mentionné dans ma question et sa xpath n'est pas affichée le cas est :

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

Ce produit :

//root/elemX/id='89734'

Au lieu de cela :

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

45voto

Dimitre Novatchev Points 147842

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:

<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 voulait, résultat 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é à la nouvelle-document fourni par @c0mrade:

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

de nouveau le résultat correct est produit:

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

Explication:

  • Seuls les éléments qui n'ont pas d'enfants, ou avoir des attributs sont appariés et traitées.

  • Pour cet élément, si ce n'est pas avoir des enfants-les éléments de l'ensemble de ses ancêtres, ou de soi que les éléments sont traités dans un mode spécifique, nommé 'path'. Puis l' "='theValue'" de la partie est de sortie et ensuite un caractère NL.

  • Tous les attributs de la correspondance de l'élément sont ensuite traitées.

  • Puis enfin, les modèles sont appliqués à tous les enfants-éléments.

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

  • Le traitement des attributs est simple: d'Abord tous ancestor-or-self:: des éléments de son parent sont traitées en 'path' mode, la [attrName=attrValue] la partie est de sortie, suivi par un caractère NL.

Note:

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

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


Ci-dessous est ma première réponse (peut être ignoré)

Ici est un pur XSLT 1.0 solution:

Ci-dessous est un exemple de document xml et une feuille de style qui prend un node-set de paramètre et produit une expression XPath valide pour tous les membres-nœud.

la 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']

18voto

Blaise Doughan Points 75613

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'
 

13voto

Lukas Eder Points 48046

Avec jOOX (un jquery API port de Java, que j'ai développé), vous pouvez presque atteindre ce que vous voulez en une seule instruction:

List<String> list = $(document).xpath("//*[not(*)]").map(new Mapper<String>() {
  public String map(Context context) {
    return $(context).xpath() + "='" + $(context).text() + "'";
  }
}});

Si le document est à votre document:

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

Ceci va produire

/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 n'a pas (encore) de soutien de contrepartie/mappage d'attributs. Par conséquent, vos attributs ne produit aucune sortie. Ce sera mis en œuvre dans un proche avenir, si.

4voto

biziclop Points 21446
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 les espaces de noms et il n'y a pas mélangé des éléments de contenu. La limitation de l'espace de noms n'est pas grave, mais ce serait faire de votre expression XPath beaucoup plus difficile à lire, comme chaque élément serait quelque chose comme *:<name>[namespace-uri()='<nsuri>'][<index>], mais sinon il est facile à mettre en œuvre. Contenu mixte, d'autre part rendre l'utilisation de xpath très fastidieux, car vous devriez être en mesure d'évaluer individuellement les deuxième, troisième et ainsi de suite nœud de texte au sein d'un élément.

2voto

Osw Points 1762
  1. utiliser w3c.dom
  2. descendre récursivement
  3. Pour chaque nœud, il existe un moyen simple d’obtenir son chemin xpath: soit en le stockant sous la forme tableau / liste sous # 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 pense pas que les attributs seront un problème.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X