XPath enthält (text (), 'some string') funktioniert nicht, wenn es mit einem Knoten mit mehr als einem Text-Unterknoten verwendet wird

259

Ich habe ein kleines Problem mit Xpath enthält mit dom4j ...

Nehmen wir an, mein XML ist

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

Nehmen wir an, ich möchte alle Knoten mit ABC im Text finden, wenn das Stammelement angegeben ist ...

Der xpath, den ich schreiben müsste, wäre also

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

Dies ist jedoch nicht das, was Dom4j zurückgibt. Ist dies ein dom4j-Problem oder mein Verständnis, wie xpath funktioniert? da diese Abfrage nur das Straßenelement und nicht das Kommentarelement zurückgibt.

Das DOM macht das Kommentarelement zu einem zusammengesetzten Element mit vier Tags und zwei

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

Ich würde annehmen, dass die Abfrage das Element immer noch zurückgeben sollte, da es das Element finden und ausführen sollte, aber es nicht ... ...

Die folgende Abfrage gibt das Element zurück, aber es gibt weit mehr als nur das Element zurück, es gibt auch die übergeordneten Elemente zurück ... was für das Problem unerwünscht ist ...

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

Kennt jemand die xpath-Abfrage, die nur die Elemente <Street/>und zurückgeben würde <Comment/>?

Mike Milkin
quelle
Soweit ich das beurteilen kann, wird //*[contains(text(),'ABC')]nur das <Street>Element zurückgegeben. Es werden keine Vorfahren von <Street>oder zurückgegeben <Comment>.
Ken Bloom

Antworten:

707

Das <Comment>Tag enthält zwei Textknoten und zwei <br>Knoten als untergeordnete Knoten.

Ihr xpath-Ausdruck war

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

Um dies zu brechen,

  1. * ist ein Selektor, der mit einem beliebigen Element (dh einem Tag) übereinstimmt - er gibt eine Knotenmenge zurück.
  2. Dies []sind Bedingungen, die für jeden einzelnen Knoten in diesem Knotensatz gelten. Es stimmt überein, ob einer der einzelnen Knoten, an denen es arbeitet, mit den Bedingungen in den Klammern übereinstimmt.
  3. text()ist ein Selektor , der mit allen Textknoten übereinstimmt, die untergeordnete Elemente des Kontextknotens sind. Er gibt einen Knotensatz zurück.
  4. containsist eine Funktion, die mit einer Zeichenfolge arbeitet. Wenn ein Knotensatz übergeben wird, wird der Knotensatz in eine Zeichenfolge konvertiert, indem der Zeichenfolgenwert des Knotens in dem Knotensatz zurückgegeben wird, der zuerst in der Dokumentreihenfolge steht . Daher kann es nur mit dem ersten Textknoten in Ihrem <Comment>Element übereinstimmen - nämlich BLAH BLAH BLAH. Da dies nicht übereinstimmt, erhalten Sie keine <Comment>Ergebnisse.

Sie müssen dies ändern in

//*[text()[contains(.,'ABC')]]
  1. * ist ein Selektor, der mit einem beliebigen Element (dh einem Tag) übereinstimmt - er gibt eine Knotenmenge zurück.
  2. Das Äußere []ist eine Bedingung, die für jeden einzelnen Knoten in diesem Knotensatz gilt - hier für jedes Element im Dokument.
  3. text()ist ein Selektor , der mit allen Textknoten übereinstimmt, die untergeordnete Elemente des Kontextknotens sind. Er gibt einen Knotensatz zurück.
  4. Die inneren []sind eine Bedingung, die für jeden Knoten in diesem Knotensatz gilt - hier für jeden einzelnen Textknoten. Jeder einzelne Textknoten ist der Ausgangspunkt für einen beliebigen Pfad in den Klammern und kann auch explizit als .innerhalb der Klammern bezeichnet werden. Es stimmt überein, ob einer der einzelnen Knoten, an denen es arbeitet, mit den Bedingungen in den Klammern übereinstimmt.
  5. containsist eine Funktion, die mit einer Zeichenfolge arbeitet. Hier wird ein einzelner Textknoten ( .) übergeben. Da der zweite Textknoten im <Comment>Tag einzeln übergeben wird, wird die 'ABC'Zeichenfolge angezeigt und kann mit ihr übereinstimmen.
Ken Bloom
quelle
1
Genial, ich bin ein bisschen wie ein xpath noob, also lass mich das verstehen. Text () ist eine Funktion, die den Ausdruck enthält, der (., 'ABC') enthält. Gibt es eine Chance, die du erklären kannst, damit ich das nicht irgendwie mache? wieder dummes Zeug;)
Mike Milkin
28
Ich habe meine Antwort bearbeitet, um eine lange Erklärung zu liefern. Ich weiß selbst nicht so viel über XPath - ich habe nur ein bisschen experimentiert, bis ich auf diese Kombination gestoßen bin. Sobald ich eine funktionierende Kombination hatte, machte ich eine Vermutung, was los war, und sah im XPath-Standard nach, um zu bestätigen, was ich dachte, und schrieb die Erklärung.
Ken Bloom
2
Wie würden Sie eine Suche ohne Berücksichtigung der Groß- und Kleinschreibung durchführen?
Zack
@Zack: Bitte machen Sie dies eine neue Frage.
user1129682
1
Ich weiß, dass dies ein alter Thread ist, aber kann jemand einen Kommentar abgeben, wenn es einen grundlegenden Unterschied gibt, vorzugsweise mit einigen einfachen Testfällen zwischen der Antwort von Ken Bloom und //*[contains(., 'ABC')]. Ich hatte immer das von Mike Milkin gegebene Muster verwendet und dachte, es sei angemessener, aber nur containsim aktuellen Kontext zu arbeiten, scheint tatsächlich das zu sein, was ich öfter möchte.
Knickum
7

[contains(text(),'')]gibt nur true oder false zurück. Es werden keine Elementergebnisse zurückgegeben.

Ratna
quelle
Das wird nicht funktionieren, wenn ich '' oder '' hätte. Wie können wir trimmen?
Shareef
contains(text(),'JB-')ist keine Arbeit! conatainsnimmt zwei Zeichenfolgen als Argumente - contains(**string**, **string**)! text () ist kein String , ist eine Funktion!
AtachiShadow
6

Das XML-Dokument:

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

Der XPath-Ausdruck:

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

//*Entspricht einem beliebigen untergeordneten Element des Stammknotens . Das heißt, jedes Element außer dem Wurzelknoten.

[...]ist ein Prädikat , es filtert die Knotenmenge. Es gibt Knoten , für die ...ist true:

Ein Prädikat filtert eine [...] Knotenmenge, um eine neue Knotenmenge zu erzeugen. Für jeden zu filternden Knoten in der Knotengruppe wird der PredicateExpr [...] ausgewertet. Wenn PredicateExpr für diesen Knoten den Wert true ergibt, wird der Knoten in den neuen Knotensatz aufgenommen. Andernfalls ist es nicht enthalten.

contains('haystack', 'needle')gibt zurück, truewenn haystack enthält needle :

Funktion: Boolescher Wert enthält (String, String)

Die Funktion enthält gibt true zurück, wenn die erste Argumentzeichenfolge die zweite Argumentzeichenfolge enthält, andernfalls false.

Nimmt contains()aber einen String als ersten Parameter. Und es ist Knoten übergeben. Um damit umzugehen, wird jeder Knoten oder Knotensatz, der als erster Parameter übergeben wird , von der Funktion in einen String konvertiertstring() :

Ein Argument wird wie durch Aufrufen der Zeichenfolgenfunktion in den Typ string konvertiert.

string()Funktionsrückgabe string-valuedes ersten Knotens :

Ein Knotensatz wird in eine Zeichenfolge konvertiert, indem der Zeichenfolgenwert des Knotens in dem Knotensatz zurückgegeben wird, der zuerst in der Dokumentreihenfolge steht. Wenn der Knotensatz leer ist, wird eine leere Zeichenfolge zurückgegeben.

string-valueeines Elementknotens :

Der Zeichenfolgenwert eines Elementknotens ist die Verkettung der Zeichenfolgenwerte aller Textknotennachkommen des Elementknotens in Dokumentreihenfolge.

string-valueeines Textknotens :

Der Zeichenfolgenwert eines Textknotens sind die Zeichendaten.

Grundsätzlich string-valuehandelt es sich also um den gesamten Text, der in einem Knoten enthalten ist (Verkettung aller untergeordneten Textknoten).

text() ist ein Knotentest, der mit jedem Textknoten übereinstimmt:

Der Knotentesttext () gilt für jeden Textknoten. Beispielsweise wählt child :: text () die untergeordneten Textknoten des Kontextknotens aus.

Dies //*[contains(text(), 'ABC')]entspricht jedoch jedem Element (außer dem Wurzelknoten), dessen erster Textknoten enthält ABC. Since text()gibt einen Knotensatz zurück, der alle untergeordneten Textknoten des Kontextknotens enthält (relativ zu dem ein Ausdruck ausgewertet wird). Nimmt contains()aber nur den ersten. Für das Dokument oben entspricht der Pfad dem StreetElement.

Der folgende Ausdruck //*[text()[contains(., 'ABC')]]entspricht jedem Element (außer dem Stammknoten), das mindestens einen untergeordneten Textknoten enthält ABC. .repräsentiert den Kontextknoten. In diesem Fall handelt es sich um einen untergeordneten Textknoten eines Elements außer dem Stammknoten. Für das Dokument oben entspricht der Pfad also Streetden CommentElementen und.

Entspricht nun //*[contains(., 'ABC')]jedem Element (außer dem Stammknoten), das enthält ABC(in der Verkettung der untergeordneten Textknoten). Für das obige Dokument entspricht es den Home, den Addr, den Streetund den CommentElementen. Entspricht als solches //*[contains(., 'BLAH ABC')]den Home, den Addrund den CommentElementen.

x-yuri
quelle
0

Es hat eine Weile gedauert, aber ich habe es endlich herausgefunden. Benutzerdefinierter xpath, der unten Text enthält, hat für mich perfekt funktioniert.

//a[contains(text(),'JB-')]
zagoo2000
quelle
2
contains(text(),'JB-')ist keine Arbeit! conatainsnimmt zwei Zeichenfolgen als Argumente - contains(**string**, **string**)! text () ist kein String , ist eine Funktion!
AtachiShadow
0

Die akzeptierte Antwort gibt auch alle übergeordneten Knoten zurück. So erhalten Sie nur die tatsächlichen Knoten mit ABC, auch wenn die Zeichenfolge nach
:

//*[text()[contains(.,'ABC')]]/text()[contains(.,"ABC")]
Roger Veciana
quelle
0
//*[text()='ABC'] 

kehrt zurück

<street>ABC</street>
<comment>BLAH BLAH BLAH <br><br>ABC</comment>
user3520544
quelle
3
Wenn Sie eine Antwort auf eine neun Jahre alte Frage mit fünf vorhandenen Antworten hinzufügen, ist es sehr wichtig, darauf hinzuweisen, welchen einzigartigen neuen Aspekt der Frage Ihre Antwort anspricht.
Jason Aller
Die Antwort, die ich gepostet habe, war sehr einfach. Also dachte ich gerne teilen, was Anfängern wie mir helfen könnte.
user3520544