YGREG.COM -
XPath czyli jak znaleźć igłę w stogu siana
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".
Nie jest to kompletna lista funkcji - opisy pozostałych funkcji można znaleźć w specyfikacji XPath ( (http://www.w3c.org/TR/xpath) http://www.w3c.org/TR/xpath).
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) 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) http://www.ygreg.com/forum
Grzegorz 'Ygreg' Plebański
ygreg@ygreg.com
http://www.ygreg.com
http://www.ygreg.com