XPath - co, proč a hlavně jak?
Potom, co jsem na nedávné Poslední Sobotě mluvil o Matcheru, se zájem o tento nástroj znatelně zvýšil. Lidé začali posílat pull-requesty, bugreporty a maily s otázkami jak se dá udělat to či ono. Na všechny jsem rád odpověděl, protože dotazy byly buď triviální a tudíž jednoduché na vytřešení nebo zajímavé oříšky, které musely být vyřešeny. Jedno měly však společné: V naprosté většině řešení nemělo nic společného s Matcherem, ale ve správném použití XPath, na němž je nástroj založený. Právě z toho důvodu jsem napsal následující letmý úvod do XPath, který ukáže vlastnosti nejčastěji používané při extrakci dat z HTML.
XPath plní stejný účel jako CSS selektory: Z HTML stromu vybere uzly, které mě zajímají a se kterými bych chtěl dál pracovat - ostylovat v případě CSS nebo z nich extrahovat data v případě Matcheru. Ve srovnání se současnou verzí CSS selektorů je však XPath expresivnější a místy má odlišnou sémantiku.
Všechno bude nejlépe patrné z ukázek.
/html - Vybere element html, který je kořenovým elementem stromu.
/html/body - Odpovídá všem elementům body, které jsou přímými potomky kořenového elementu html`.
//h2 - Všechny elementy h2, které se v dokumentu vyskytují, včetně vlastních podstromů. Pokud jeden h2 kdekoli v sobě obsahuje jiný element h2, výsledkem budou všechny tyto elementy.
//div/span//a - Selektory je možno libovolně řetězit a tento najde všechny a jako potomky span, které jsou přímými potomky libovolného divu.
./div nebo div - Vybere všechny elementy div které jsou přímými potomky právě aktivního elementu. Jde o relativní polohu a ne o polohu fixovanou ke kořenu dokumentu.
.//div - To samé jako předchozí výraz, ale připouští i nepřímé potomky.
div[@class="asdf"] - div, jehož atribut class je "asdf". XPath nepodporuje CSS třídy a k obsahu atributů přistupuje jako ke stringům a proto, tento výraz není stejný jako css selektor div.asdf. XPath podmínce @class="asdf" vyhoví jen ty elementy jejichž atribut class je jen a pouze string "asdf". Na toto je si třeba dávat pozor, protože jde o největší odchylku od chování, které by čekal člověk odkojený CSS selektory.
div[@class != "asdf"] nebo div[not(@class = "asdf")] - Odpovídá divům jejichž třída není "asdf".
div[contains(@class, "asdf")] - divy jejichž atribut class obsahuje string "asdf". Například: Třída "xasdfy" také vyhovuje tomuto predikátu.
div[starts-with(@class, "asdf")] - divy jejichž atribut class začíná na "asdf".
div[contains(concat(" ", normalize-space(@class), " "), " asdf ")] - Tento výraz má stejný význam jako CSS selektor div.asdf (tedy kromě toho, že CSS nepřihlíží v velikosti písmen a XPath ano). Naštěstí takové konstrukce nejsou skoro nikdy potřeba, protože většinou stačí jednoduché @class="asdf" nebo funkce contains().
div[@class] - divy, které mají nějaký atribut class
div[span] - divy, které mají jako přímého potomka span
div[.//span] - divy, které obsahují span
div[span[@class]] - divy, které mají přímého potomka span, který má nastavenou libovolnou třídu
div[@class and span] - divy, které mají nastaven atribut class a zároveň obsahují span jako potomka. Stejně jako and se dá použít i logické or nebo funkce not().
div[1] - První div v pořadí v jakém se vyskytuje v dokumentu.
div[last()] - Poslední div.
div[position() mod 2 = 0] - Každý sudý div.
Na pořadí predikátů v hranatých závorkách záleží:
div[@class="asdf"][1] - Vybere všechny divy s třídou "asdf" a z nich pak vybere ten první. Pokud existují nějaké divy s touto třídou, výsledkem bude vždycky první z nich.
div[1][@class="asdf"] - Vybere první div a ten ve výsledné množině ponechá jen pokud má třídu "asdf". Když existují divy s touto třídou, ale první ji nemá, výsledkem je prázdná množina.
div/text()[1] - Z divu vybere první kus textu, který není obsažen v žádném jiném elementu.
preceding-sibling::h2 - Vybere elementy h2, které předcházejí aktivnímu elementu a zároveň jsou jeho sourozenci (tj. jsou přímými potomky stejného elementu jako aktivní element).
a[@class="active"]/following-sibling::a[1] - Vybere první odkaz, který následuje (a je sourozenec) po odkazu s třídou "active".
Dále k tématu: