funkcionálně.cz

Přední český blog o funkcionálním programování, kde se o funkcionálním programování nepíše
««« »»»

XPath - co, proč a hlavně jak?

2. 1. 2015

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:

PHP DOM, SimpleXML a Matcher

@kaja47, kaja47@k47.cz, deadbeef.k47.cz, starší články