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

У меня небольшая проблема с 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/>?

9 ответов

Решение

<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' строка и быть в состоянии соответствовать.

Документ 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 для этого узла истинно, узел включается в новый набор узлов; в противном случае он не включается.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Современный ответ, который охватывает поведение XPath 1.0 и XPath 2.0+...

Этот XPath,

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

ведет себя по-разному с XPath 1.0 и более поздними версиями XPath (2.0+).

Обычное поведение

  • //*выделяет все элементы в документе.
  • []фильтрует эти элементы в соответствии с выраженным в них предикатом.
  • внутри предиката отфильтрует эти элементы до тех, для которых подстрока является подстрокой в ​​строке .

Поведение XPath 1.0

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

Онлайн-пример XPath 1.0 показывает, что выбран только один.

Поведение XPath 2.0+

  • Это ошибка звонить contains(string, substring)с последовательностью из более чем одного элемента в качестве первого аргумента.
  • Это исправило нелогичное поведение, описанное выше в XPath 1.0.

Онлайн-пример XPath 2.0 показывает типичное сообщение об ошибке из-за ошибки преобразования, характерной для XPath 2.0+.

Общие решения

  1. Если вы хотите включить элементы-предки, проверьте строковое значение элемента как единую строку, а не отдельные строковые значения дочерних текстовых узлов, этот XPath,

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

    выбирает ваши целевые и элементы, а также их Addrа также Homeэлементы-предки, потому что они тоже имеют подстроки своих строковых значений.

    В онлайн-примере также показаны предки.

  2. Если вы хотите исключить элементы-предки, этот XPath,

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

    выбирает только ваш целевой объект и поскольку только те элементы имеют дочерние текстовые узлы, строковые значения которых содержат 'ABC'подстрока. Это будет справедливо для всех версий XPath.

    Онлайн-пример показывает только Streetа также Commentвыбирается.

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

//*[text()='ABC'] 

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

<street>ABC</street>
<comment>BLAH BLAH BLAH <br><br>ABC</comment>

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

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

Вот альтернативный способ сопоставления узлов, содержащих заданную текстовую строку. Сначала запросите сам текстовый узел, затем получите родительский:

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

Для меня это легко читать и понимать.

Это лучший ответ на тематический вопрос:

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

Пример:примерный случай

Xpath, чтобы получить bon dua madam

      //h3[text()='Contact Information']/parent::div/following-sibling::div/p[text()[contains(.,'bon dua madam')]]/text()[contains(.,'bon dua madam')]

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

//a[contains(text(),'JB-')]
Другие вопросы по тегам