XPath містить (text (), 'деякий рядок') не працює, коли використовується з вузлом, що має більше одного підвузла Text


258

У мене є невелика проблема з 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. оскільки цей запит повертає лише елемент вулиці, а не елемент коментаря.

DOM робить елемент коментаря складовим елементом із чотирма тегами два

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

Я б припустив, що запит все-таки повинен повернути елемент, оскільки він повинен знайти елемент і запустити містить на ньому, але він не ... ...

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

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

Хтось знає запит xpath, який повертав би лише елементи <Street/>та <Comment/>?


Наскільки я можу сказати, //*[contains(text(),'ABC')]повертає лише <Street>елемент. Це не повертає жодних предків <Street>або <Comment>.
Кен Блум

Відповіді:


706

<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
Awesome im трохи трохи xpath noob, тому дозвольте мені це зрозуміти, текст () - це функція, яка приймає вираз містить (., 'ABC'). Чи є шанс ви можете пояснити, щоб я не робив цього роду дурні речі знову;)
Майк Мілкін

28
Я відредагував свою відповідь, щоб дати довге пояснення. Я насправді не знаю так багато про XPath - я просто трохи експериментував, поки не натрапив на цю комбінацію. Після того, як у мене була робоча комбінація, я здогадався, що відбувається, і переглянув стандарт XPath, щоб підтвердити те, що я вважав, що відбувається, і написати пояснення.
Кен Блум

2
Як би ви зробили цей випадок нечутливим до пошуку?
Зак

@Zack: Будь ласка, поставте це питання.
користувач1129682

1
Я знаю, що це стара тема, але хто-небудь може прокоментувати, якщо є принципова різниця, бажано, за допомогою декількох простих тестових випадків між відповіддю Кен Блум та //*[contains(., 'ABC')]. Я завжди використовував схему, яку дав Майк Мілкін, вважаючи, що це більш доречно, але просто робити containsв поточному контексті, здається, саме те, що мені хочеться частіше.
knickum

7

[contains(text(),'')]повертає лише істинне або хибне. Він не поверне жодних результатів елементів.


ця робота не буде, якщо у мене було "" чи "" як ми можемо обрізати?
shareef

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 :

Функція: булева містить (рядок, рядок)

Функція містить повертає істину, якщо перша рядок аргументу містить другу рядок аргументу, а в іншому випадку повертає значення false.

Але contains()приймає рядок як свій перший параметр. І це передані вузли. Щоб вирішити це, кожен вузол або набір вузлів, переданий як перший параметр, перетворюється на рядок string()функцією:

Аргумент перетворюється на рядок типу так, ніби викликує функцію string.

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

Набір вузлів перетворюється на рядок, повертаючи значення рядка вузла в наборі вузлів, який є першим у порядку документа. Якщо набір вузлів порожній, повертається порожня рядок.

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

Значення рядка елемента елемента - це конкатенація рядкових значень усіх текстових вузлів нащадків вузла елемента в порядку документа.

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

Рядок-значення текстового вузла - це символьні дані.

Отже, в основному string-valueце весь текст, який міститься у вузлі (об'єднання всіх текстових вузлів нащадків).

text() це тест вузла, який відповідає будь-якому текстовому вузлу:

Тестовий текст вузла () справедливий для будь-якого текстового вузла. Наприклад, дочір :: 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елементи.


0

Це зайняло мене трохи, але нарешті зрозуміло. Користувальницький xpath, який містить текст нижче, працював для мене ідеально.

//a[contains(text(),'JB-')]

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>

3
Додаючи відповідь на дев'ятирічне запитання із п’ятьма існуючими відповідями, дуже важливо вказати, який унікальний новий аспект питання ваші відповіді.
Джейсон Аллер

Відповідь, яку я опублікував, була дуже простою. Тож думав, як поділитися, що може допомогти таким початківцям, як я.
користувач3520544
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.