XPath содержит (text (), 'некоторая строка') не работает при использовании с узлом с более чем одним текстовым подузлом

259

У меня небольшая проблема с Xpath содержит с dom4j ...

Допустим, мой XML

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

Допустим, я хочу найти все узлы, которые имеют ABC в тексте, учитывая корневой элемент ...

Так что xpath, который мне нужно было бы написать, был бы

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

Однако это не то, что Dom4j возвращает .... это проблема dom4j или мое понимание того, как работает xpath. поскольку этот запрос возвращает только элемент Street, а не элемент Comment.

DOM делает элемент Comment составным элементом с четырьмя тегами два

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

Я бы предположил, что запрос все еще должен возвращать элемент, поскольку он должен найти элемент и выполнить на нем содержится, но это не ... ...

следующий запрос возвращает элемент, но он возвращает гораздо больше, чем просто элемент, он также возвращает родительские элементы ... что нежелательно для проблемы ...

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

Кто-нибудь знает запрос xpath, который будет возвращать только элементы <Street/>и <Comment/>?

Майк Милкин
источник
Насколько я могу сказать, //*[contains(text(),'ABC')]возвращает только <Street>элемент. Это не возвращает никаких предков <Street>или <Comment>.
Кен Блум

Ответы:

707

<Comment>Тег содержит два текстовых узлов и два <br>узла , как дети.

Ваше выражение xpath было

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

Чтобы сломать это,

  1. * является селектором, который соответствует любому элементу (то есть тегу) - он возвращает набор узлов.
  2. Это []условие, которое действует на каждый отдельный узел в этом наборе узлов. Он соответствует, если какой-либо из отдельных узлов, с которыми он работает, соответствует условиям внутри скобок.
  3. text()является селектором, который соответствует всем текстовым узлам, которые являются дочерними элементами контекстного узла - он возвращает набор узлов.
  4. containsэто функция, которая работает со строкой Если передан набор узлов, набор узлов преобразуется в строку, возвращая строковое значение узла в наборе узлов, который находится первым в порядке документов . Следовательно, он может соответствовать только первому текстовому узлу в вашем <Comment>элементе, а именно BLAH BLAH BLAH. Так как это не соответствует, вы не получите <Comment>в ваших результатах.

Вы должны изменить это на

//*[text()[contains(.,'ABC')]]
  1. * является селектором, который соответствует любому элементу (то есть тегу) - он возвращает набор узлов.
  2. Внешние []являются условными, которые действуют на каждый отдельный узел в этом наборе узлов - здесь он действует на каждый элемент в документе.
  3. text() это селектором, который соответствует всем текстовым узлам, которые являются дочерними элементами контекстного узла - он возвращает набор узлов.
  4. Внутренний [] являются условными, которые действуют на каждый узел в этом наборе узлов - здесь каждый отдельный текстовый узел. Каждый отдельный текстовый узел является отправной точкой для любого пути в скобках, и также может быть явно указан как .внутри скобок. Он соответствует, если какой-либо из отдельных узлов, с которыми он работает, соответствует условиям внутри скобок.
  5. containsэто функция, которая работает со строкой Здесь передается отдельный текстовый узел ( .). Поскольку ему передается второй текстовый узел в <Comment>теге индивидуально, он увидит 'ABC'строку и сможет соответствовать ей.
Кен Блум
источник
1
Удивительно, я немного новичок в xpath, так что позвольте мне получить это, text () - это функция, которая принимает выражение, содержащее (., 'ABC'), есть ли шанс, что вы можете объяснить, поэтому я не делаю такого рода опять глупости;)
Майк Милкин
28
Я отредактировал свой ответ, чтобы дать длинное объяснение. Я сам не очень разбираюсь в XPath - я просто немного экспериментировал, пока не наткнулся на эту комбинацию. Получив рабочую комбинацию, я догадался, что происходит, и посмотрел в стандарте XPath, чтобы подтвердить, что я думаю, и написать объяснение.
Кен Блум
2
Как бы вы сделали этот поиск без учета регистра?
Зак
@ Зак: Пожалуйста, сделайте это новым вопросом.
user1129682
1
Я знаю, что это старая ветка, но кто-нибудь может прокомментировать, если есть принципиальное различие, желательно с некоторыми простыми контрольными случаями между ответом, данным Кеном Блумом, и //*[contains(., 'ABC')]. Я всегда использовал шаблон, предложенный Майком Милкиным, думая, что он более уместен, но просто делать containsто, что мне нужно в текущем контексте, на самом деле кажется тем, чего я хочу чаще.
Knickum
7

[contains(text(),'')]возвращает только true или false. Это не вернет никаких результатов элемента.

Ratna
источник
это не сработает, если у меня будет "или", как мы можем обрезать?
Шариф
contains(text(),'JB-')это не работа! conatainsпринимает две строки в качестве аргумента - contains(**string**, **string**)! text () не строка , это функция!
AtachiShadow
6

XML-документ:

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

Выражение XPath:

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

//*соответствует любому потомок элемента из корневого узла . То есть любой элемент, кроме корневого узла.

[...]является предикатом , он фильтрует набор узлов. Возвращает узлы, для которых ...есть true:

Предикат фильтрует набор узлов [...], чтобы создать новый набор узлов. Для каждого узла в наборе узлов, подлежащем фильтрации, оценивается PredicateExpr [...]; если PredicateExpr для данного узла оценивается как true, узел включается в новый набор узлов; в противном случае, это не входит.

contains('haystack', 'needle')возвращает, trueесли haystack содержит needle :

Функция: логическое содержит (строка, строка)

Функция contains возвращает true, если первая строка аргумента содержит вторую строку аргумента, а в противном случае возвращает false.

Но contains()принимает строку в качестве первого параметра. И это пройденные узлы. Чтобы справиться с этим, каждый узел или набор узлов, переданный в качестве первого параметра, преобразуется в строку string()функцией:

Аргумент преобразуется в тип string как будто путем вызова строковой функции.

string()функция возвращает string-valueиз первого узла :

Набор узлов преобразуется в строку, возвращая строковое значение узла в наборе узлов, который находится первым в порядке документа. Если набор узлов пуст, возвращается пустая строка.

string-valueиз узла элемента :

Строковое значение узла элемента - это объединение строковых значений всех потомков текстового узла узла элемента в порядке документа.

string-valueиз текстового узла :

Строковое значение текстового узла - это символьные данные.

Таким образом, в основном string-valueэто весь текст, который содержится в узле (объединение всех дочерних текстовых узлов).

text() это тест узла, который соответствует любому текстовому узлу:

Тест узла text () имеет значение true для любого текстового узла. Например, child :: text () выберет дочерние текстовые узлы узла контекста.

Сказав это, //*[contains(text(), 'ABC')]соответствует любому элементу (кроме корневого узла), первый текстовый узел которого содержит ABC. Since text()возвращает набор узлов, который содержит все дочерние текстовые узлы узла контекста (относительно которого вычисляется выражение). Но contains()занимает только первое. Таким образом, для документа выше путь соответствует Streetэлементу.

Следующее выражение //*[text()[contains(., 'ABC')]]соответствует любому элементу (кроме корневого узла), который имеет хотя бы один дочерний текстовый узел, который содержит ABC. .представляет узел контекста. В данном случае это дочерний текстовый узел любого элемента, кроме корневого узла. Таким образом, для документа выше путь соответствует Street, иComment элементы.

Теперь, тогда, //*[contains(., 'ABC')]соответствует любому элементу (кроме корневого узла), который содержит ABC(в конкатенации нисходящих текстовых узлов). Для документа выше , что он соответствует Home, с Addr, в Street, и Commentэлементы. По существу, //*[contains(., 'BLAH ABC')]соответствует Home, то Addrи Commentэлементы.

х-юри
источник
0

Это заняло у меня немного времени, но, наконец, понял. Пользовательский xpath, содержащий текст ниже, работал для меня идеально.

//a[contains(text(),'JB-')]
zagoo2000
источник
2
contains(text(),'JB-')это не работа! conatainsпринимает две строки в качестве аргумента - contains(**string**, **string**)! text () не строка , это функция!
AtachiShadow
0

Принятый ответ также вернет все родительские узлы. Чтобы получить только фактические узлы с ABC, даже если строка после
:

//*[text()[contains(.,'ABC')]]/text()[contains(.,"ABC")]
Роджер Вечана
источник
0
//*[text()='ABC'] 

возвращается

<street>ABC</street>
<comment>BLAH BLAH BLAH <br><br>ABC</comment>
user3520544
источник
3
При добавлении ответа на вопрос девятилетней давности с пятью существующими ответами очень важно указать, к какому уникальному новому аспекту вопроса относится ваш ответ.
Джейсон Аллер
Ответ, который я разместил, был очень простым. Мысль о том, как делиться, может помочь начинающим, таким как я
user3520544