XPath, czyli jak znaleźć igłę w stogu siana
Wstęp
XPath jest językiem pozwalającym na wyszukiwanie i wskazywanie elementów dokumentu
XML. Za pomocą wyrażenia XPath można np. wybrać z dokumentu wszystkie
elementy o określonej nazwie. Oczywiście jest to najprostszy przykład
- możliwości XPath są znacznie większe, co postaram się pokazać
w tym artykule.
Podstawy
Zacznijmy od najprostszego przykładu. Mamy dokument XML zawierający listę pracowników jakiejś
fikcyjnej firmy:
| | <?xml version="1.0" encoding="ISO-8859-2"?>
<pracownicy>
<pracownik>
<imie>Jan</imie>
<nazwisko>Kowalski</nazwisko>
</pracownik>
<pracownik>
<imie>Bogdan</imie>
<nazwisko>Nowak</nazwisko>
</pracownik>
</pracownicy> |
|
Załóżmy, że chcemy wybrać wszystkich pracowników. Można to zrobić
za pomocą wyrażenia:
Wyrażenie to zaznaczy następujące elementy:
| | <?xml version="1.0" encoding="ISO-8859-2"?>
<pracownicy>
<pracownik>
<imie>Jan</imie>
<nazwisko>Kowalski</nazwisko>
</pracownik>
<pracownik>
<imie>Bogdan</imie>
<nazwisko>Nowak</nazwisko>
</pracownik>
</pracownicy> |
|
Jak widać do wyrażenia pasują 2 elementy i obydwa zostały zaznaczone.
Aby określić o który z nich nam chodzi, możemy podać jego numer
w nawiasie kwadratowym:
Nie podaje numeru przy elemencie pracownicy, ponieważ mamy tylko jeden element o tej nazwie. Do tego wyrażenia pasuje
już tylko jeden element:
| | <?xml version="1.0" encoding="ISO-8859-2"?>
<pracownicy>
<pracownik>
<imie>Jan</imie>
<nazwisko>Kowalski</nazwisko>
</pracownik>
<pracownik>
<imie>Bogdan</imie>
<nazwisko>Nowak</nazwisko>
</pracownik>
</pracownicy> |
|
Można też użyć funkcji last(), oznaczającej ostatni element:
| | /pracownicy/pracownik[last()-1] |
|
Efekt będzie taki sam jak poprzednio.
Możemy też wybrać pracownika według nazwiska:
| | /pracownicy/pracownik[nazwisko="Kowalski"] |
|
Efekt znów będzie ten sam.
Element o dowolnej nazwie
Nazwę elementu możemy zastąpić znakiem * oznaczającym element o dowolnej
nazwie. Przykładowo aby pobrać z naszego dokumentu wszystkie dane
pracownika możemy napisać:
| | /pracownicy/pracownik[1]/* |
|
Do tego wyrażenia będą pasować następujące elementy:
| | <?xml version="1.0" encoding="ISO-8859-2"?>
<pracownicy>
<pracownik>
<imie>Jan</imie>
<nazwisko>Kowalski</nazwisko>
</pracownik>
<pracownik>
<imie>Bogdan</imie>
<nazwisko>Nowak</nazwisko>
</pracownik>
</pracownicy> |
|
Gwiazdka może oczywiście zostać użyta w dowolnym miejscu wyrażenia. Jako
przykładu użyje innego dokumentu, który lepiej pokaże o co chodzi.
Do wyrażenia:
będą pasować elementy:
| | <?xml version="1.0" encoding="ISO-8859-2"?>
<aaa>
<aaa>
<aaa />
</aaa>
<bbb>
<aaa />
</bbb>
</aaa> |
|
Nie jest natomiast prawidłowe wyrażenie:
Wybieranie wszystkich elementów o określonej nazwie
Aby wybrać z dokumentu wszystkie elementy o podanej nazwie, niezależnie gdzie
się one znajdują należy zacząć wyrażenie od //. Przykładowo
do wyrażenia
będą pasować elementy:
| | <?xml version="1.0" encoding="ISO-8859-2"?>
<aaa>
<aaa>
<aaa />
</aaa>
<bbb>
<aaa />
</bbb>
</aaa> |
|
Można też napisać:
Do tego wyrażenia będą pasować wszystkie elementy bbb będące potomkami elementu
aaa.
Znaków // można użyć nie tylko na początku wyrażenia. Prawidłowym
wyrażeniem jest także:
Będą do niego pasować następujące elementy:
| | <?xml version="1.0" encoding="ISO-8859-2"?>
<aaa>
<aaa>
<aaa />
</aaa>
<bbb>
<aaa />
</bbb>
</aaa> |
|
Wyrażenie //* oznacza więc wszystkie elementy dokumentu.
Atrybuty
Wróćmy do naszej listy pracowników z pierwszego przykładu. Tym razem
każdemu pracownikowi naszej firmy damy plakietkę z numerem identyfikacyjnym:
| | <?xml version="1.0" encoding="ISO-8859-2"?>
<pracownicy>
<pracownik id="1">
<imie>Jan</imie>
<nazwisko>Kowalski</nazwisko>
</pracownik>
<pracownik id="2">
<imie>Bogdan</imie>
<nazwisko>Nowak</nazwisko>
</pracownik>
</pracownicy> |
|
Chcemy teraz zaznaczyć pracownika o określonym id. Aby zaznaczyć, że nie chodzi nam
o nazwę pod-elementu, ale jego atrybut używamy znaku @:
| | /pracownicy/pracownik[@id="2"] |
|
Wybrany zostanie oczywiście Jan Kowalski.
Załóżmy teraz, że zatrudniliśmy nowego pracownika, który nie dostał
jeszcze plakietki z identyfikatorem:
| | <?xml version="1.0" encoding="ISO-8859-2"?>
<pracownicy>
<pracownik id="1">
<imie>Jan</imie>
<nazwisko>Kowalski</nazwisko>
</pracownik>
<pracownik id="2">
<imie>Bogdan</imie>
<nazwisko>Nowak</nazwisko>
</pracownik>
<pracownik>
<imie>Tadeusz</imie>
<nazwisko>Malinowski</nazwisko>
</pracownik>
</pracownicy> |
|
Pracownik odpowiedzialny za wydawanie plakietek będzie pewnie chciał wiedzieć, którzy pracownicy dostali już id,
a którzy nie. Aby wybrać wszystkich pracowników mających identyfikator,
może użyć wyrażenia:
| | /pracownicy/pracownik[@id] |
|
Aby z kolei sprawdzić którzy pracownicy nie mają jeszcze identyfikatora należy użyć
wyrażenia:
| | /pracownicy/pracownik[not(@id)] |
|
Nazwę atrybutu tak samo jak nazwę elementu możemy zastąpić znakiem
*, np. wyrażenie:
Oznacza wszystkie elementy o nazwie pracownik posiadające dowolne atrybuty.
Funkcje
XPath udostępnia wiele funkcji, których możemy użyć do odnalezienia interesujących
nas elementów. Na razie była mowa tylko o jednej z niech - last().
Teraz przyjrzyjmy się pozostałym.
Funkcja count sprawdza, ile pod-elementów (lub atrybutów) o podanej nazwie posiada element. Przykładowo,
aby zaznaczyć wszystkie elementy aaa posiadające 2 pod-elementy
o nazwie bbb należy użyć wyrażenia:
Aby wybrać wszystkie elementy posiadające więcej niż 2 atrybuty można
użyć wyrażenia:
Funkcja name zwraca nazwę elementu. Wyrażenie
można więc zapisać także jako
Funkcja position zwraca numer elementu. Wyrażenie
można więc zastąpić wyrazeniem:
| | /pracownicy/pracownik[position()=2] |
|
Funkcja contains sprawdza, czy tekst podany jako pierwszy argument
zawiera tekst z argumentu drugiego. Można więc np. wybrać wszystkie elementy, których nazwy zawierają
tekst aa:
| | //*[contains(name(), "aa")] |
|
Funkcja starts-with działa podobnie do contains, z tą różnicą, że sprawdza czy pierwszy
argument zaczyna się od drugiego.
Funkcja substring(tekst, start, długość) zwraca fragment parametru tekst zaczynający się od znaku
o numerze start i długości długość. Znaki numerowane są od
1 (nie od 0 jak ma to miejsce w wielu językach programowania).
Funkcja substring-before zwraca część tekstu od początku do wystąpienia podanego ciągu. Przykładowo
substring-before("aaabbb", "bbb") zwróci "aaa".
Funkcja substring-after działa analogicznie do substring-before, z tym że zwraca część tekstu
znajdującą się za podanym ciągiem.
Funkcja string-length zwraca długość podanego ciągu. Aby wybrać z naszej bazy pracowników
osoby których imię jest krótsze niż 5 znaków można więc napisać:
| | //pracownik[string-length(imie)<5] |
|
Funkcja normalize-space usuwa białe znaki (spacje, tabulacje, nowe linie itp.) z początku i końca
podanego tekstu.
Funkcja translate(tekst, znaki1, znaki2) zamienia znaki z parametru
znaki1 na odpowiadające im znaki z parametru znaki2. Nie jest to wyszukiwanie i zamiana
tekstu - zamieniane są pojedyńcze znaki. Jeśli znak z paramatru
znaki1 nie ma odpowiednika w znaki2 (znaki1 jest dłuższe niż znaki2)
wystąpienia tego znaku są usuwane. Przykładowo translate("cbcada",
"abcd", "ABC") zwróci "CBCAA".
Osie (Axes)
Wyrazenie XPath może zawierać jeszcze jeden element - określenie osi. Domyślną
osią jest oś child. Zawiera ona "dzieci" aktualnego elementu. Pełna
postać wyrażenia
wygląda następująco:
| | /child::pracownicy/child::pracownik |
|
Oś descendant zawiera wszystkie elementy potomne aktualnego elementu.
Do wyrażenia:
| | /pracownicy/descendant::* |
|
będą pasować elementy:
| | <?xml version="1.0" encoding="ISO-8859-2"?>
<pracownicy>
<pracownik id="1">
<imie>Jan</imie>
<nazwisko>Kowalski</nazwisko>
</pracownik>
<pracownik id="2">
<imie>Bogdan</imie>
<nazwisko>Nowak</nazwisko>
</pracownik>
</pracownicy> |
|
Oś parent zawiera rodzica aktualnego elementu.
Oś ancestor zawiera wszystkich przodków elementu.
Oś following-sibling zawiera część "rodzeństwa" elementu znajdującą
się za nim. Dla wyrażenia /pracownicy/pracownik[1]/following-sibling::*
będzie to następujący element:
| | <?xml version="1.0" encoding="ISO-8859-2"?>
<pracownicy>
<pracownik id="1">
<imie>Jan</imie>
<nazwisko>Kowalski</nazwisko>
</pracownik>
<pracownik id="2">
<imie>Bogdan</imie>
<nazwisko>Nowak</nazwisko>
</pracownik>
</pracownicy> |
|
Oś preceding-sibling zawiera część "rodzeństwa" elementu znajdującą
się przed nim.
Oś following zawiera wszystkie elementy znajdujące się za aktualnym
elementem.
Oś preceding zawiera wszystkie elementy znajdujące się przed aktualnym
elementem.
Oś attribute zawiera atrybuty elementu. Dla wyrażenia //pracownik/attribute::*
będą to:
| | <?xml version="1.0" encoding="ISO-8859-2"?>
<pracownicy>
<pracownik id="1">
<imie>Jan</imie>
<nazwisko>Kowalski</nazwisko>
</pracownik>
<pracownik id="2">
<imie>Bogdan</imie>
<nazwisko>Nowak</nazwisko>
</pracownik>
</pracownicy> |
|
Oś namespace zawiera węzły przestrzeni nazw.
Oś self zawiera tylko aktualny element.
Oś descendant-or-self to połączenie osi descendant i self.
Oś ancestor-or-self to połączenie osi ancestor i self.
Zakończenie
Ten artykuł nie zawiera oczywiście wszystkich informacji na temat języka
XPath. Więcej można się dowiedzieć z oficjalnej specyfikacji
XPath, dostępnej pod adresem http://www.w3c.org/TR/xpath - ściągnięcie tego dokumentu polecam każdemu kto chce korzystać
z XPath. W razie jakichkolwiek pytań zapraszam na forum: http://www.ygreg.com/forum
|