XPath для вибору декількох тегів


132

Враховуючи цей спрощений формат даних:

<a>
    <b>
        <c>C1</c>
        <d>D1</d>
        <e>E1</e>
        <f>don't select this one</f>
    </b>
    <b>
        <c>C2</c>
        <d>D2</d>
        <e>E1</e>
        <g>don't select me</g>
    </b>
    <c>not this one</c>
    <d>nor this one</d>
    <e>definitely not this one</e>
</a>

Як би ви вибрали всі Cs, Ds і Es, які є дітьми Bелементів?

В основному, щось на кшталт:

a/b/(c|d|e)

У моїй власній ситуації замість просто a/b/запиту, що веде до вибору цих C,D , Eвузли насправді досить складний , тому я хотів би уникнути цього:

a/b/c|a/b/d|a/b/e

Чи можливо це?

Відповіді:


207

Одна правильна відповідь :

/a/b/*[self::c or self::d or self::e]

Зверніть увагу, що це

a/b/*[local-name()='c' or local-name()='d' or local-name()='e']

є і занадто довгим, і неправильним . Цей вираз XPath вибере вузли типу:

OhMy:c

NotWanted:d 

QuiteDifferent:e

2
'або' не працює для кожного, вам потрібно буде використовувати вертикальну лінію замість '|'
Гуаскеньо

8
@ Гуаскеньо, orє логічним оператором - він працює на двох булевих значеннях. XPath , об'єднання оператор |діє на двох наборах вузлів. Вони досить різні, і для кожного з них є конкретні випадки використання. Використання | може вирішити оригінальну проблему, але це призводить до більш тривалого і складного і складного для розуміння виразу XPath. Простіший вираз у цій відповіді, який використовує orоператор, створює шуканий набір вузлів і може бути визначений в атрибуті "select" <xsl:for-each>операції XSLT. Просто спробуйте.
Димитрій Новатчев

4
@JonathanBenn, той, хто "не піклується про простори імен", насправді не хвилює XML і не використовує XML. Використання local-name()правильного лише в тому випадку, якщо ми хочемо вибрати всі елементи з цим локальним іменем, незалежно від простору імен, в якому знаходиться цей елемент. Це дуже рідкісний випадок - загалом люди дбають про відмінності між: kitchen:tableта sql:tableабо між architecture:column, sql:column, array:column,military:column
Dimitre Novatchev

3
@DimitreNovatchev ви добре зазначаєте. Я використовую XPath для перевірки HTML, що є кращим випадком, коли простір імен не так важливий ...
Джонатан Бенн,

2
Це супер. Де ти це придумав?
Кіт Тайлер

46

Ви можете уникнути повторення замість тесту на атрибут:

a/b/*[local-name()='c' or local-name()='d' or local-name()='e']

Всупереч антагоністичній думці Дімтера, вищезазначене не є помилковим у вакуумі, де ОП не вказала взаємодію з просторами імен. self::Ось імен обмежувальним, local-name()немає. Якщо наміром ОП є захопленняc|d|e незалежно від простору імен (що я вважаю, що це навіть ймовірний сценарій, враховуючи характер проблеми АБО), то це "ще одна відповідь, яка все ще має позитивні голоси", що невірно.

Ви не можете бути остаточним без визначення, хоча я дуже радий видалити свою відповідь як справді неправильну, якщо ОП роз'яснить його питання таким чином, що я невірний.


3
Виступаючи тут як третя сторона - особисто я вважаю, що пропозиція Димитра є найкращою практикою, за винятком випадків, коли у користувача є явні (і хороші) причини для того, щоб піклуватися про тег імені, що не стосується простору імен; якщо хтось зробив це проти документа, який я змішував у вмісті, розміщеному по-різному, іменами (імовірно, призначеним для читання іншим ланцюжком інструментів), я вважав би їхню поведінку дуже недоречною. Однак, аргумент - як ви пропонуєте - трохи непереборний.
Чарльз Даффі

4
саме те, що я шукав. Простіри імен XML, як вони використовуються в реальному житті, - це нечесний безлад. Через відсутність можливості вказати щось на кшталт / a / b / ( : c | : d | * e) ваше рішення саме те, що потрібно. Пуристи можуть заперечувати все, що хочуть, але користувачів не хвилює, що програма не працює, тому що все, що генерує їх вхідний файл, накрутив простори імен. Вони просто хочуть, щоб це спрацювало.
Ghostrider

7
Я маю лише неясне уявлення про те, яка різниця була б між цими двома відповідями, і ніхто не намагався пояснити. Що означає "обмежувальний простір імен"? Якщо я використовую local-name(), чи означає це, що він відповідатиме тегам з будь-яким простором імен? Якщо я використовую self::, який простір імен він повинен відповідати? Як би я відповідав лише OhMy:c?
meustrus

15

Чому ні a/b/(c|d|e)? Я щойно спробував із Saxon XML-бібліотекою (добре завернута з користю Clojure), і, здається, працює. abc.xmlє документом, описаним ОП.

(require '[saxon :as xml])
(def abc-doc (xml/compile-xml (slurp "abc.xml")))
(xml/query "a/b/(c|d|e)" abc-doc)
=> (#<XdmNode <c>C1</c>>
    #<XdmNode <d>D1</d>>
    #<XdmNode <e>E1</e>>
    #<XdmNode <c>C2</c>>
    #<XdmNode <d>D2</d>>
    #<XdmNode <e>E1</e>>)

8
Так, але це XPath 2.0

Це добре спрацювало для мене. Здається, XPath 2.0 є типовим для розбору HTML в lxml на Python 2.
Мартін Берч

-1

Не впевнений, чи допоможе це, але з XSL я б зробив щось на кшталт:

<xsl:for-each select="a/b">
    <xsl:value-of select="c"/>
    <xsl:value-of select="d"/>
    <xsl:value-of select="e"/>
</xsl:for-each>

і цей XPath не вибере всіх дітей із B вузлів:

a/b/*

Дякую Келвін, але я не використовую XSL, а під Б фактично є більше елементів, які я не хочу вибирати. Я оновлю свій приклад, щоб було зрозуміліше.
nickf

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