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

Potom, co jsem na ne­dávné Po­slední Sobotě mluvilMatcheru, se zájem o tento ná­stroj zna­telně zvýšil. Lidé začali po­sí­lat pull-requesty, bu­gre­porty a maily s otáz­kami jak se dá udělat to či ono. Na všechny jsem rád od­po­vě­děl, pro­tože dotazy byly buď tri­vi­ální a tudíž jed­no­du­ché na vy­tře­šení nebo za­jí­mavé oříšky, které musely být vy­ře­šeny. Jedno měly však spo­lečné: V na­prosté vět­šině řešení nemělo nic spo­leč­ného s Matche­rem, ale ve správ­ném po­u­žití XPath, na němž je ná­stroj za­lo­žený. Právě z toho důvodu jsem napsal ná­sle­du­jící letmý úvod do XPath, který ukáže vlast­nosti nej­čas­těji po­u­ží­vané při ex­trakci dat z HTML.

XPath plní stejný účel jako CSS se­lek­tory: Z HTML stromu vybere uzly, které mě za­jí­mají a se kte­rými bych chtěl dál pra­co­vat – osty­lo­vat v pří­padě CSS nebo z nich ex­tra­ho­vat data v pří­padě Matcheru. Ve srov­nání se sou­čas­nou verzí CSS se­lek­torů je však XPath ex­pre­siv­nější a místy má od­liš­nou sé­man­tiku.

Všechno bude nej­lépe patrné z ukázek.

/html – Vybere ele­ment html, který je ko­ře­no­vým ele­men­tem stromu.

/html/body - Odpovídá všem elementům body, které jsou přímými potomky kořenového elementu html`.

//h2 – Všechny ele­menty h2, které se v do­ku­mentu vy­sky­tují, včetně vlast­ních pod­stromů. Pokud jeden h2 kde­koli v sobě ob­sa­huje jiný ele­ment h2, vý­sled­kem budou všechny tyto ele­menty.

//div/span//a – Se­lek­tory je možno li­bo­volně ře­tě­zit a tento najde všechny a jako po­tomky span, které jsou přímými po­tomky li­bo­vol­ného divu.

./div nebo div – Vybere všechny ele­menty div které jsou přímými po­tomky právě ak­tiv­ního ele­mentu. Jde o re­la­tivní polohu a ne o polohu fi­xo­va­nou ke kořenu do­ku­mentu.

.//div – To samé jako před­chozí výraz, ale při­pouští i ne­přímé po­tomky.

div[@class="asdf"]div, jehož atri­but class je „asdf“. XPath ne­pod­po­ruje CSS třídy a k obsahu atri­butů při­stu­puje jako ke strin­gům a proto, tento výraz není stejný jako css se­lek­tor div.asdf. XPath pod­mínce @class="asdf" vyhoví jen ty ele­menty je­jichž atri­but class je jen a pouze string „asdf“. Na toto je si třeba dávat pozor, pro­tože jde o nej­větší od­chylku od cho­vání, které by čekal člověk od­ko­jený CSS se­lek­tory.

div[@class != "asdf"] nebo div[not(@class = "asdf")] – Od­po­vídá divům je­jichž třída není „asdf“.

div[contains(@class, "asdf")] – divy je­jichž atri­but class ob­sa­huje string „asdf“. Na­pří­klad: Třída „xasdfy“ také vy­ho­vuje tomuto pre­di­kátu.

div[starts-with(@class, "asdf")] – divy je­jichž atri­but class začíná na „asdf“.

div[contains(concat(" ", normalize-space(@class), " "), " asdf ")] – Tento výraz má stejný význam jako CSS se­lek­tor div.asdf (tedy kromě toho, že CSS ne­při­hlíží v ve­li­kosti písmen a XPath ano). Na­štěstí takové kon­strukce nejsou skoro nikdy po­třeba, pro­tože vět­ši­nou stačí jed­no­du­ché @class="asdf" nebo funkce contains().

div[@class] – divy, které mají nějaký atri­but class

div[span] – divy, které mají jako pří­mého po­tomka span

div[.//span] – divy, které ob­sa­hují span

div[span[@class]] – divy, které mají pří­mého po­tomka span, který má na­sta­ve­nou li­bo­vol­nou třídu

div[@class and span] – divy, které mají na­sta­ven atri­but class a zá­ro­veň ob­sa­hují span jako po­tomka. Stejně jako and se dá použít i lo­gické or nebo funkce not().

div[1] – První div v pořadí v jakém se vy­sky­tuje v do­ku­mentu.

div[last()] – Po­slední div.

div[position() mod 2 = 0] – Každý sudý div.

Na pořadí pre­di­kátů v hra­na­tých zá­vor­kách záleží:

div[@class="asdf"][1] – Vybere všechny divy s třídou „asdf“ a z nich pak vybere ten první. Pokud exis­tují nějaké divy s touto třídou, vý­sled­kem bude vždycky první z nich.

div[1][@class="asdf"] – Vybere první div a ten ve vý­sledné mno­žině po­ne­chá jen pokud má třídu „asdf“. Když exis­tují divy s touto třídou, ale první ji nemá, vý­sled­kem je prázdná mno­žina.

div/text()[1] – Z divu vybere první kus textu, který není ob­sa­žen v žádném jiném ele­mentu.

preceding-sibling::h2 – Vybere ele­menty h2, které před­chá­zejí ak­tiv­nímu ele­mentu a zá­ro­veň jsou jeho sou­ro­zenci (tj. jsou přímými po­tomky stej­ného ele­mentu jako ak­tivní ele­ment).

a[@class="active"]/following-sibling::a[1] – Vybere první odkaz, který ná­sle­duje (a je sou­ro­ze­nec) po odkazu s třídou „active“.


Dále k tématu:

PHP DOM, Sim­pleXML a Matcher

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