YGREG.COM - XPath czyli jak znaleźć igłę w stogu siana 

29.07.2004
Nowa oferta hostingowa.

29.07.2004
Kurs XPath.

13.10.2002
Poprawiony problem z oznaczaniem przeczytanych wiadomości na forum.

13.06.2002
PHP z Zend Engine 2 już dostępne!

17.02.2002
Zmiany w organizacji serwisu.








Początek strony

(C)opyright 1997-2007
by Grzegorz Plebański
INDEX I ARTYKUŁY I SKRYPTY I DOWNLOAD

Dodaj stronę
do ulubionych

Artykuły i kursy
   PHP
   XML
   CGI
   HTML
   Inne

Skrypty PHP
   Komunikacja
   Księgi gości
   Statystyki
   Głosowania
   Bannery
   Inne
Skrypty CGI

Download



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:

   
/pracownicy/pracownik

        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:

   
/pracownicy/pracownik[1]

        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:

   
/aaa/*/aaa

        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:

   
/aaa/a*/b




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

   
//aaa

        będą pasować elementy:

   
<?xml version="1.0" encoding="ISO-8859-2"?>
<aaa>
   <aaa>
      <aaa />
   </aaa>
   <bbb>
      <aaa />
   </bbb>
</aaa>

        Można też napisać:

   
//aaa/bbb

        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:

   
/aaa//aaa

        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:

   
//pracownik[@*]

        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:

   
//aaa[count(bbb)=2]

        Aby wybrać wszystkie elementy posiadające więcej niż 2 atrybuty można użyć wyrażenia:

   
//*[count(@*)>2]

        Funkcja name zwraca nazwę elementu. Wyrażenie

   
//aaa

        można więc zapisać także jako

   
//*[name()="aaa"]

        Funkcja position zwraca numer elementu. Wyrażenie

   
/pracownicy/pracownik[2]

        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).



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

   
/pracownicy/pracownik

        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


Grzegorz 'Ygreg' Plebański
ygreg@ygreg.com
http://www.ygreg.com