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:

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