XPath untuk memilih beberapa tag

132

Dengan format data yang disederhanakan ini:

<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>

Bagaimana Anda memilih semua elemen C, Ddan elemen Eyang merupakan anak-anak Belemen?

Pada dasarnya, sesuatu seperti:

a/b/(c|d|e)

Dalam situasi saya sendiri, bukan hanya a/b/, query yang mengarah ke memilih mereka C, D, Enode sebenarnya cukup kompleks jadi saya ingin menghindari melakukan hal ini:

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

Apakah ini mungkin?

nickf
sumber

Jawaban:

207

Satu jawaban yang benar adalah :

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

Perhatikan bahwa ini

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

keduanya terlalu panjang dan salah . Ekspresi XPath ini akan memilih node seperti:

OhMy:c

NotWanted:d 

QuiteDifferent:e
Dimitre Novatchev
sumber
2
'atau' tidak berfungsi untuk masing-masing, Anda harus menggunakan garis vertikal sebagai gantinya '|'
Guasqueño
8
@ Guasqueño, oradalah operator logis - beroperasi pada dua nilai Boolean. Operator serikat XPath |beroperasi pada dua set node. Ini sangat berbeda dan ada kasus penggunaan khusus untuk masing-masing. Menggunakan | dapat memecahkan masalah asli, tetapi menghasilkan lebih lama dan lebih kompleks dan menantang untuk memahami ekspresi XPath. Ekspresi yang lebih sederhana dalam jawaban ini, yang menggunakan oroperator menghasilkan set simpul yang diinginkan dan dapat ditentukan dalam atribut "pilih" dari <xsl:for-each>operasi XSLT. Cobalah.
Dimitre Novatchev
4
@ JonathanBenn, Siapa pun yang "tidak peduli dengan ruang nama" sebenarnya tidak peduli dengan XML, dan tidak menggunakan XML. Penggunaan local-name()hanya benar jika kita ingin memilih semua elemen dengan nama lokal itu, terlepas dari namespace tempat elemen tersebut berada. Ini adalah kasus yang sangat jarang - pada umumnya orang peduli tentang perbedaan antara: kitchen:tabledan sql:table, atau di antara architecture:column, sql:column, array:column,military:column
Dimitre Novatchev
2
@ DimitreNovatchev Anda membuat poin yang bagus. Saya menggunakan XPath untuk inspeksi HTML, yang merupakan kasus tepi di mana namespace tidak begitu penting ...
Jonathan Benn
2
Itu super. Di mana Anda datang dengan itu?
Keith Tyler
46

Anda bisa menghindari pengulangan dengan tes atribut sebagai gantinya:

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

Bertentangan dengan pendapat antagonis Dimitre, hal di atas tidak salah dalam ruang hampa di mana OP belum menentukan interaksi dengan ruang nama. The self::sumbu namespace membatasi, local-name()tidak. Jika niat OP adalah untuk menangkap c|d|eterlepas dari namespace (yang saya sarankan bahkan skenario yang mungkin diberikan sifat ATAU dari masalah) maka itu adalah "jawaban lain yang masih memiliki beberapa suara positif" yang tidak benar.

Anda tidak dapat menjadi pasti tanpa definisi, meskipun saya cukup senang menghapus jawaban saya sebagai benar-benar salah jika OP mengklarifikasi pertanyaannya sehingga saya salah.

annakata
sumber
3
Berbicara sebagai pihak ke-3 di sini - secara pribadi, saya menemukan saran Dimitre untuk menjadi praktik yang lebih baik kecuali dalam kasus di mana pengguna memiliki alasan eksplisit (dan bagus) untuk peduli tentang nama tag yang tidak relevan dengan namespace; jika ada yang melakukan ini terhadap dokumen yang saya campur dalam konten dengan nama yang berbeda (mungkin dimaksudkan untuk dibaca oleh rantai alat yang berbeda), saya akan menganggap perilaku mereka sangat tidak pantas. Yang mengatakan, argumennya - seperti yang Anda sarankan - sedikit tidak pantas.
Charles Duffy
4
persis apa yang saya cari. Ruang nama XML cara mereka digunakan dalam kehidupan nyata adalah kekacauan yang tidak suci. Karena tidak bisa menentukan sesuatu seperti / a / b / ( : c | : d | * e) solusi Anda persis seperti yang dibutuhkan. Purist dapat berdebat semua yang mereka inginkan tetapi pengguna tidak peduli bahwa aplikasi rusak karena apa pun yang dihasilkan file input mereka mengacaukan ruang nama. Mereka hanya ingin itu berhasil.
Ghostrider
7
Saya hanya memiliki gagasan yang tidak jelas apa perbedaan antara dua jawaban ini dan tidak ada yang mau menjelaskan. Apa yang dimaksud dengan "namespace restriktif"? Jika saya menggunakan local-name(), apakah itu berarti itu akan cocok dengan tag dengan namespace apa pun? Jika saya menggunakan self::, namespace apa yang harus cocok? Bagaimana saya hanya cocok OhMy:c?
meustrus
15

Mengapa tidak a/b/(c|d|e)? Saya baru saja mencoba dengan perpustakaan XML Saxon (dibungkus dengan baik dengan beberapa kebaikan Clojure), dan tampaknya berhasil. abc.xmladalah dokumen yang dijelaskan oleh 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>>)
Pavel Repin
sumber
8
Ya, tapi itu XPath 2.0
Ini bekerja dengan baik untuk saya. Tampaknya XPath 2.0 adalah default untuk penguraian HTML dalam lxml pada Python 2.
Martin Burch
-1

Tidak yakin apakah ini membantu, tetapi dengan XSL, saya akan melakukan sesuatu seperti:

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

dan bukankah XPath ini akan memilih semua anak dari simpul B:

a/b/*
Calvin
sumber
Terima kasih Calvin, tapi saya tidak menggunakan XSL, dan sebenarnya ada lebih banyak elemen di bawah B yang tidak ingin saya pilih. Saya akan memperbarui contoh saya menjadi lebih jelas.
nickf
Oh, nah dalam kasus itu annakata tampaknya punya solusinya.
Calvin