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 иEB элементы s, которые являются дочерними элементами элементов?

По сути, что-то вроде:

a/b/(c|d|e)

В моей ситуации, вместо того , чтобы просто a/b/, запрос , приводя к выбору тех C, D, Eузлы на самом деле довольно сложный , поэтому я хотел бы избежать этого:

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

Это возможно?

nickf
источник

Ответы:

208

Один правильный ответ :

/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
'or' не работает с for-each, вам нужно будет использовать вертикальную линию вместо '|'
Guasqueño
8
@ Guasqueño or- это логический оператор, он работает с двумя логическими значениями. XPath , объединение оператор |действует на двух наборах узлов. Они совершенно разные, и для каждого из них есть свои варианты использования. Использование | может решить исходную проблему, но в результате становится более длинным, сложным и сложным для понимания выражения XPath. Более простое выражение в этом ответе, в котором используется orоператор, создает желаемый набор узлов и может быть указано в атрибуте «select» <xsl:for-each>операции XSLT. Просто попробуйте.
Dimitre Novatchev
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']

Вопреки антагонистическому мнению Димитра, вышесказанное не является неправильным в вакууме, где OP не указал взаимодействие с пространствами имен. self::Ось имен ограничительным, local-name()нет. Если намерение OP заключается в захвате c|d|eнезависимо от пространства имен (что, я бы предположил, является даже вероятным сценарием, учитывая природу проблемы OR), то это «еще один ответ, который все еще имеет некоторые положительные голоса», что неверно.

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

annakata
источник
3
Говоря здесь как третья сторона - лично я считаю предложение Димитра лучшей практикой, за исключением случаев, когда у пользователя есть явная (и веская) причина заботиться о имени тега, не имеющем отношения к пространству имен; если бы кто-нибудь сделал это с документом, который я смешивал с содержимым с другим пространством имен (предположительно предназначенным для чтения другой цепочкой инструментов), я бы счел его поведение очень неуместным. Тем не менее, аргумент, как вы предполагаете, немного неуместен.
Чарльз Даффи
4
именно то, что я искал. Пространства имен XML в том виде, в каком они используются в реальной жизни, представляют собой ужасный беспорядок. Из-за отсутствия возможности указать что-то вроде / a / b / ( : c | : d | * e) ваше решение именно то, что нужно. Пуристы могут спорить сколько угодно, но пользователям все равно, что приложение ломается, потому что все, что сгенерировало их входной файл, испортило пространства имен. Они просто хотят, чтобы это работало.
Ghostrider
7
У меня есть лишь смутное представление, в чем разница между этими двумя ответами, и никто не потрудился объяснить. Что означает "ограничение пространства имен"? Если я использую local-name(), означает ли это, что теги будут соответствовать любому пространству имен? Если я использую self::, с каким пространством имен оно должно совпадать? Как бы мне соответствовать только OhMy:c?
meustrus 09
15

А почему бы и нет a/b/(c|d|e)? Я только что попробовал использовать библиотеку Saxon XML (красиво обернутую добротой Clojure), и, похоже, она работает. abc.xml- это документ, описанный OP.

(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, и на самом деле есть больше элементов под B, которые я не хочу выбирать. Я обновлю свой пример, чтобы было понятнее.
nickf 06
Ну, в таком случае у аннаката есть решение.
Calvin