393 votes

XPath contains(text(), 'some string') ne fonctionne pas lorsqu'il est utilisé avec un nœud comportant plus d'un sous-nœud Texte

J'ai un petit problème avec XPath contains avec dom4j ...

Disons que mon XML est

<Home>
    <Addr>
        <Street>ABC</Street>
        <Number>5</Number>
        <Comment>BLAH BLAH BLAH <br/><br/>ABC</Comment>
    </Addr>
</Home>

Disons que je veux trouver tous les noeuds qui ont ABC dans le texte donné à l'élément racine...

Donc le XPath que j'aurais besoin d'écrire serait

//*[contains(text(),'ABC')]

Cependant, ce n'est pas ce que renvoie dom4j ..... Est-ce un problème de dom4j ou une mauvaise compréhension du fonctionnement de XPath, puisque cette requête ne renvoie que l'élément Street et non l'élément Comment élément ?

Le DOM fait le Comment élément un élément composite avec quatre balises deux

[Text = 'XYZ'][BR][BR][Text = 'ABC'] 

Je suppose que la requête devrait toujours renvoyer l'élément puisqu'elle devrait trouver l'élément et exécuter contains sur lui, mais ce n'est pas le cas...

La requête suivante renvoie l'élément, mais elle renvoie bien plus que l'élément - elle renvoie également les éléments parents, ce qui n'est pas souhaitable pour le problème.

//*[contains(text(),'ABC')]

Est-ce que quelqu'un connaît la requête XPath qui renverrait seulement les éléments <Street/> y <Comment/> ?

0 votes

D'après ce que je sais, //*[contains(text(),'ABC')] ne renvoie que le <Street> élément. Elle ne renvoie pas d'ancêtres de <Street> o <Comment> .

982voto

Ken Bloom Points 27197

Le site <Comment> contient deux nœuds de texte et deux <br> comme enfants.

Votre expression xpath était

//*[contains(text(),'ABC')]

Pour décomposer cela,

  1. * est un sélecteur qui correspond à n'importe quel élément (i.e. balise) -- il retourne un ensemble de noeuds.
  2. Le site [] sont une condition qui opère sur chaque nœud individuel dans cet ensemble de nœuds. Il y a correspondance si l'un des nœuds individuels sur lequel il opère correspond aux conditions entre les crochets.
  3. text() est un sélecteur qui correspond à tous les noeuds de texte qui sont enfants du noeud de contexte -- il retourne un ensemble de noeuds.
  4. contains est une fonction qui opère sur une chaîne de caractères. Si on lui passe un ensemble de noeuds, l'ensemble de noeuds est converti en chaîne de caractères en retournant la valeur de la chaîne de caractères du nœud de l'ensemble de nœuds qui est le premier dans l'ordre du document. . Par conséquent, il ne peut correspondre qu'au premier nœud de texte dans votre fichier de texte. <Comment> élément -- à savoir BLAH BLAH BLAH . Puisque cela ne correspond pas, vous n'obtiendrez pas une <Comment> dans vos résultats.

Vous devez changer cela en

//*[text()[contains(.,'ABC')]]
  1. * est un sélecteur qui correspond à n'importe quel élément (i.e. balise) -- il retourne un ensemble de noeuds.
  2. L'extérieur [] sont une condition qui opère sur chaque nœud individuel dans cet ensemble de nœuds -- ici, elle opère sur chaque élément du document.
  3. text() est un sélecteur qui correspond à tous les noeuds de texte qui sont enfants du noeud de contexte -- il retourne un ensemble de noeuds.
  4. L'intérieur [] sont une condition qui opère sur chaque nœud de cet ensemble de nœuds -- ici chaque nœud de texte individuel. Chaque nœud de texte individuel est le point de départ de n'importe quel chemin dans les parenthèses, et peut également être mentionné explicitement comme . entre les parenthèses. Il correspond si l'un des nœuds individuels sur lequel il opère correspond aux conditions entre parenthèses.
  5. contains est une fonction qui opère sur une chaîne de caractères. Ici, on lui passe un nœud de texte individuel ( . ). Puisqu'on lui transmet le deuxième nœud de texte dans le fichier <Comment> individuellement, il verra la balise 'ABC' et être capable de la faire correspondre.

1 votes

Génial, je suis un peu un noob de xpath, alors laissez-moi comprendre, text() est une fonction qui prend l'expression contains(.,'ABC'), Y a-t-il une chance que vous puissiez expliquer afin que je ne fasse pas ce genre de choses stupides à nouveau ;)

41 votes

J'ai modifié ma réponse pour fournir une longue explication. Je ne connais pas vraiment XPath moi-même - j'ai juste expérimenté un peu jusqu'à ce que je tombe sur cette combinaison. Une fois que j'ai eu une combinaison qui fonctionnait, j'ai deviné ce qui se passait et j'ai regardé dans le fichier Norme XPath pour confirmer ce que je pensais et écrire l'explication.

4 votes

Comment faire pour que cette recherche soit insensible à la casse ?

24voto

x-yuri Points 616

Le document XML :

<Home>
    <Addr>
        <Street>ABC</Street>
        <Number>5</Number>
        <Comment>BLAH BLAH BLAH <br/><br/>ABC</Comment>
    </Addr>
</Home>

L'expression XPath :

//*[contains(text(), 'ABC')]

//* correspond à n'importe quel élément descendant de la Nœud racine . C'est-à-dire tout élément sauf le nœud racine.

[...] est un prédicat il filtre l'ensemble des nœuds. Il renvoie les nœuds pour lesquels ... es true :

Un prédicat filtre un ensemble de nœuds [...] pour produire un nouvel ensemble de nœuds. Pour chaque noeud de l'ensemble de noeuds à filtrer, l'expression PredicateExpr est évaluée [...] ; si l'expression PredicateExpr est vraie pour ce noeud, celui-ci est inclus dans le nouvel ensemble de noeuds ; sinon, il n'est pas inclus.

contains('haystack', 'needle') renvoie à true si haystack contient needle :

Fonction : boolean contains(string, string)

La fonction contains renvoie vrai si la chaîne du premier argument contient la chaîne du second argument, et sinon renvoie faux.

Pero contains() prend une chaîne de caractères comme premier paramètre. Et on lui passe des noeuds. Pour gérer cela, chaque nœud ou ensemble de nœuds passé comme premier paramètre est converti à une chaîne de caractères par le string() fonction :

Un argument est converti en type chaîne de caractères comme si on appelait la fonction chaîne de caractères.

string() les fonctions de retour string-value de le premier nœud :

Un ensemble de nœuds est converti en une chaîne de caractères en retournant la valeur de la chaîne de caractères du nœud de l'ensemble de nœuds qui est le premier dans l'ordre du document. Si l'ensemble de nœuds est vide, une chaîne vide est retournée.

string-value d'un nœud d'élément :

La valeur de chaîne d'un nœud d'élément est la concaténation des valeurs de chaîne de tous les nœuds de texte descendants du nœud d'élément dans l'ordre du document.

string-value d'un nœud de texte :

La valeur de la chaîne d'un nœud de texte est la donnée du caractère.

Donc, en gros string-value est tout le texte contenu dans un nœud (concaténation de tous les nœuds de texte descendants).

text() est un test de nœud qui correspond à tout nœud de texte :

Le test de nœud text() est vrai pour tout nœud de texte. Par exemple, child::text() sélectionnera les noeuds de texte enfants du noeud de contexte.

Ceci étant dit, //*[contains(text(), 'ABC')] correspond à tout élément (sauf le nœud Root) dont le premier nœud de texte contient ABC . Puisque text() renvoie un ensemble de nœuds qui contient tous les nœuds de texte enfants du nœud de contexte (par rapport auquel une expression est évaluée). Mais contains() ne prend que le premier. Ainsi, pour le document ci-dessus, le chemin correspond au Street élément.

L'expression suivante //*[text()[contains(., 'ABC')]] correspond à tout élément (à l'exception du nœud racine), qui a au moins un nœud de texte enfant, qui contient ABC . . représente le nœud de contexte. Dans ce cas, c'est un nœud de texte enfant de n'importe quel élément sauf le nœud Root. Ainsi, pour le document ci-dessus, le chemin correspond à l'élément Street et le Comment éléments.

Maintenant, alors, //*[contains(., 'ABC')] correspond à tout élément (sauf le nœud racine) qui contient ABC (dans la concaténation des nœuds de texte descendants). Pour le document ci-dessus, il correspond au Home le Addr le Street et le Comment éléments. En tant que tel, //*[contains(., 'BLAH ABC')] correspond au Home le Addr et le Comment éléments.

19voto

kjhughes Points 5581

Réponse moderne qui couvre le comportement de XPath 1.0 vs XPath 2.0+ ...

Cette XPath,

//*[contains(text(),'ABC')]

se comporte différemment avec XPath 1.0 et les versions ultérieures de XPath (2.0+).

Comportement commun

  • //* sélectionne tous les éléments d'un document.
  • [] filtre ces éléments en fonction du prédicat qui y est exprimé.
  • contains(string, substring) dans le prédicat filtrera ces éléments pour ceux pour lesquels sous-chaîne est une sous-chaîne dans chaîne de caractères .

Comportement de XPath 1.0

  • contains(string, substring) convertira un ensemble de nœuds à un chaîne de caractères en prenant la valeur de la chaîne de caractères de l'élément premier nœud de l'ensemble de nœuds .
  • Pour //*[contains(text(),'ABC')] cet ensemble de nœuds sera constitué de tous les nœuds de texte enfants de chaque élément du document.
  • Desde seul le premier enfant du nœud de texte est utilisé l'attente que tous les nœuds de texte enfants soient testés pour les éléments suivants 'ABC' le confinement de la sous-chaîne est violé.
  • Cela conduit à des résultats contre-intuitifs pour quiconque n'est pas familier avec les règles de conversion ci-dessus.

Exemple en ligne de XPath 1.0 montre qu'un seul 'ABC' est sélectionné.

Comportement d'XPath 2.0+.

  • C'est une erreur d'appeler contains(string, substring) avec une séquence de plus d'un élément comme premier argument.
  • Cela a permis de corriger le comportement contre-intuitif décrit ci-dessus dans XPath 1.0.

Exemple en ligne de XPath 2.0 montre un message d'erreur typique dû à l'erreur de conversion particulière à XPath 2.0+.

Solutions communes

  1. Si vous souhaitez inclure des éléments ancêtres, effectuez un test par rapport à l'élément valeur de la chaîne d'un élément en tant que chaîne unique, plutôt que les valeurs de chaîne individuelles des nœuds de texte enfants, cette XPath,

    //*[contains(.,'ABC')]

    sélectionne votre cible Street y Comment ainsi que leurs Addr y Home des éléments ancêtres, car ceux-ci ont aussi 'ABC' en tant que sous-chaînes de leurs valeurs de chaîne.

    Exemple en ligne montre que les ancêtres sont également sélectionnés.

  2. Si vous souhaitez exclure les éléments ancêtres, ce XPath,

    //*[text()[contains(.,'ABC')]]

    ne sélectionne que vos cibles Street y Comment parce que seuls ces éléments ont des enfants nœuds de texte dont les valeurs de chaîne contiennent l'élément 'ABC' sous-chaîne. Cela sera vrai pour toutes les versions de XPath

    Exemple en ligne ne montre que Street y Comment en cours de sélection.

8voto

Ratna Points 87

[contains(text(),'')] ne renvoie que true ou false. Il ne renvoie aucun résultat d'élément.

5voto

Roger Veciana Points 354
La réponse acceptée renverra également tous les nœuds parents. Pour obtenir seulement les noeuds actuels avec ABC même si la chaîne est après

//*[text()[contains(.,'ABC')]]/text()[contains(.,"ABC")]

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