Wstęp.
Dziś to co tygrysy lubią najbardziej. Czysta praktyka i (w miarę) konkretne przykłady. Będziesz mieć niebywałą okazję dostrzec, że XPath może służyć do rozwiązania kwestii, które nie śniły się filozofom. Od czego świat ma Ciebie!
Scena pierwsza: "Nieparzyste wiersze w tabeli pomijając nagłówek".
Nasze dane wejściowe:
<!DOCTYPE html>
<html>
<body>
<table>
<tr>
<th>Nagłówek</th>
</tr>
<tr>
<td>Wiersz 1</td>
</tr>
<tr>
<td>Wiersz 2</td>
</tr>
<tr>
<td>Wiersz 3</td>
</tr>
<tr>
<td>Wiersz 4</td>
</tr>
<tr>
<td>Wiersz 5</td>
</tr>
</table>
</body>
</html>
+
/* inicjalizację DOMDocument,
ładowanie kodu HTML,
inicjalizację DOMXPath
znajdziesz w pierwszej części "The tag is out there"
*/
$oddTrs = $xpath->query('//table//tr[position() mod 2=1 and position()>1]');
foreach($oddTrs as $tr){
echo $tr->C14N() . PHP_EOL;
}
Pamiętając, że XPath indeksuje elementy poczynając od 1, zakładamy warunek na ominięcie nagłówka (position()>1) oraz wybieramy wiersze o numerach nieparzystych. Nieparzystość dotyczy numeracji w strukturze DOM, nie numeracji/tekstów wewnątrz komórek.
Otrzymamy:
<tr><td>Wiersz 2</td></tr>
<tr><td>Wiersz 4</td></tr>
czyli wiersze 3 i 5 w strukturze DOM.
A co jesli dostaliśmy tabelę z zamówieniami od naszej znielubianej pani Gieni z działu zakupów, w której numeracja (w tabeli, nie w pani Gieni) jest wewnątrz komórek, i która to dodatkowo wcale nie jest uporządkowana i ciągła, czyli np.:
<!DOCTYPE html>
<html>
<body>
<table>
<tr>
<th>Nagłówek</th>
</tr>
<tr>
<td>Zamówienie 7</td>
</tr>
<tr>
<td>Zamówienie 3</td>
</tr>
<tr>
<td>Zamówienie 2</td>
</tr>
<tr>
<td>Zamówienie 8</td>
</tr>
<tr>
<td>Zamówienie 5</td>
</tr>
</table>
</body>
</html>
Teraz musimy się dostać do tekstu w komórce i wyciągnąć samą liczbę. Dla uproszczenia zakładamy, że liczba jest ciągiem kończącym zawartość komórki, czyli tekst istnieje tylko przed nią.
//table//tr/*[substring(text(),12) mod 2=1]
Otrzymamy tu wiersze:
<td>Zamówienie 7</td>
<td>Zamówienie 3</td>
<td>Zamówienie 5</td>
Ponieważ ciąg "Zamówienie" wraz ze spacją zawiera 11 znaków, to odcinamy wszystko od pozycji 12 do końca i używamy operatora mod (modulo) na tym co nam zostało (czyli na liczbie).
Gdybyśmy oprócz wierszy spełniających kryteria, chcieli wybrać również nagłówek, zapytanie będzie wyglądać tak:
//table//tr/*[substring(text(),12) mod 2=1 or name()="th"]
Użyliśmy tu funkcji name, która zwraca nazwę tagu (w postaci lowercase), łącząc ją poprzez operator or z poprzednim warunkiem.
Scena druga: "Pobranie wartości z wybranych komórek na podstawie komórki tego wiersza spełniającej kryterium".
login | saldo | rejestracja | punkty |
---|---|---|---|
marek | 100 | 2018-10-28 | 40 |
zdzisiek | -5 | 2018-10-25 | 130 |
heniek | -15 | 2018-10-27 | 8 |
janek | 0 | 2018-10-24 | 25 |
Interesuje nas pobranie wartości saldo oraz punkty dla użytkowników zdzisiek i janek.
$data = $xpath->query('//table//td[text()="zdzisiek" or text()="janek"]/../td[position()=2 or position()=4]');
foreach($data as $d){
echo $d->nodeValue . PHP_EOL;
}
Otrzymamy:
-5
130
0
25
Jak tu działa zapytanie XPath? Wyszukujemy komórki z tekstem o loginie zdzisiek lub janek, następnie dla każdej znalezionej komórki szukamy rodzica (..), a dla rodzica czyli TR wyszukujemy komórki o indeksie 2 lub 4 i drukujemy ich zawartość.
Scena trzecia: "Wybranie wierszy, których dwie komórki spełniają zadane, odrębne kryteria".
Wykorzystamy poprzednią tabelę i wyszukamy wiersze, w których saldo<0 i jednocześnie punkty>100.
Nie możemy zdefiniować XPath w postaci:
//table//tr/td[(position()=2 and text()<0) and (position()=4 and text()>100)]/..
bo komórka nie może spełniać jednocześnie dwóch kryteriów position()=2 i position()=4. Komórka jest na jednej, określonej pozycji.
Nie może być to również zapytanie:
//table//tr/td[(position()=2 and text()<0) or (position()=4 and text()>100)]/..
bowiem dostaniemy wiersze gdzie wartość saldo<0 lub ilość punktów>100.
Musimy najpierw pobrać wiersze, które spełniają jedno z kryteriów, a potem poddać je drugiemu:
//table//tr/td[(position()=2 and text()<0)]/../td[(position()=4 and text()>100)]
Wybieramy więc komórki z kolumny 2 spełniające pierwsze kryterium, wracam do ich rodzica (TR) i z niego wybieramy komórki z kolumny 4 spełniające drugie kryterium. Pewnie wyczuwasz tu analogię do sceny drugiej.
Zakończenie.
Te kilka rozdziałów to pomysły na dziś. Jeśli znalazłeś nierozwiązywalny problem związany ze skakaniem na drzewo DOM, pisz śmiało na e-mail, w komentarzach lub na fejsbuku.
Przydatne linki:
"The tag is out there", czyli DOMXPath S01E01
Klasa DOMDocument dla HTML5
Funkcja name()
Funkcja translate()
Funkcja normalize-space()
Metoda removeAttribute
Metoda removeChild