BETA
Aby się zalogować, najpiew wybierz portal.
Aby się zarejestrować, najpiew wybierz portal.
Podaj słowa kluczowe
Słowa kluczowe muszą mieć co najmniej 3 sąsiadujące znaki alfanumeryczne
Pole zawiera niedozwolone znaki

Baza wiedzy











XPath, czyli kto pyta, nie błądzi

19-12-2003 19:33 | zooly

XML jest coraz bardziej popularnym formatem zapisu dokumentów, komunikacji z serwisami internetowymi itd. Napotkamy jednak na problem próbując znaleźć cokolwiek w pliku o tym formacie. W przypadku implementacji DOM operacja przeszukiwania drzewa w celu znalezienia dowolnego elementu jest do zrealizowania, jednakże wymaga ona sporego nakładu pracy i nie zawsze musi być efektywna. Co będzie, jeśli zechcemy znaleźć zbiór elementów, które spełniają więcej kryteriów (np. przeszukiwanie bazy biblioteki w celu znalezienia książek o programowaniu w C#, mających 4 lub 5 autorów, które zostały wydane w 2003 roku)?

Odpowiednim narzędziem do tego zadania jest XPath.

XPath jest językiem stworzonym do jednoznacznego identyfikowania części dokumentu Xml. Podobnie jak Xml, jest zatwierdzony przez konsorcjum W3C, więc jego stosowanie i zasady działania są bardzo dobrze udokumentowane. To tyle, jeśli chodzi o książkowe definicje. Artykuł dotyczy języka XPath pomimo, że istnieją już definicje języków XPath 2.0 i XQL – Xml Query Language (na razie nie zaimplementowane w Visual Studio .NET). Posiadają one niemal identyczną składnię i jeśli nauczycie się XPath’a to nauka ta nie pójdzie na marne.

Najważniejsze zastosowanie XPath’a to transformacje XSL, które umożliwiają szybką i łatwą transformację dokumentu XML na np. tabelę w serwisie internetowym. Dodatkowo w ADO.NET mamy możliwość synchronizacji DataSeta z klasą XmlDocument i dzięki temu możemy stosować do niego zapytania XPath. Artykuł ten potraktujcie więc jako konieczny wstęp, niezbędny dla każdego, kto chce szybko tworzyć efektowne serwisy WWW i bazy danych.

Oto podstawowe cechy języka XPath:

  • traktuje przeszukiwany dokument jak drzewo – wyróżnia węzeł główny, atrybuty, komentarze, instrukcje przetwarzania i inne.

  • zawsze zwraca (zaznacza) w wyniku listę węzłów, które odpowiadają zadanym kryteriom poszukiwania (to bardzo ważne: nigdy nie dostaniemy np. wartości boolowskiej czy stringu)

  • umożliwia łączenie w jeden zestaw elementy zebrane w ciągu paru zapytań (ale tylko takie, które zostały znalezione względem tego samego węzła drzewa)

W dalszej części tekstu znajdziecie opisy i przykłady najważniejszych funkcji i operatorów XPath’a. Do artykułu dołączyłem kod prostego programu, za pomocą którego będziecie mogli ćwiczyć składnię tego języka i zapisywać otrzymane wyniki (oczywiście do plików XML). Otwarty plik jest trzymany w obiekcie doc (klasy XmlDocument, czyli implementacji DOM) a większość funkcji użytych do wstawiania węzłów znajdziecie w XmlEdytorze (tam zostały dokładniej opisane).

W klasie XmlNode znajdziemy dwie metody, za pomocą których będziemy mogli zadawać pytania: SelectNodes i SelectSingleNode (różnica między nimi jest taka, że druga metoda zawsze zwraca tylko pierwszy znaleziony element). W związku z tym wybierzmy metodę pierwszą. Domyślnie wszelkie pytania są zadawane poprzez węzeł kontekstowy doc (czyli nasz dokument), dalsze informacje znajdziecie w rozdziale Osie i konteksty.

Podstawowe operatory (plik samochody-katalog.xml)

„/” – operator ten wybiera synów bieżącego węzła. Dla zapytania Samochody/Samochód otrzymujemy listę wszystkich samochodów w naszym pliku. Inaczej mówiąc operator ten oznacza „szukaj na niższym poziomie”. Natomiast, jeśli wpiszemy tylko „/” to otrzymamy rodzica danego węzła.

„*”–  jak się domyślacie znak ten jest odpowiedzialny za wybór wszystkich elementów bez względu na nazwę. Jako zapytanie wpiszcie Samochody/*. Otrzymaliśmy 22 elementy (poprzednio 21). Na samym dole drzewa znajdziecie element Samochód. Czemu nie został znaleziony wcześniej? Otóż w zapytaniach dużą rolę gra wielkość znaków. Stringi natomiast możecie wstawiać między znakami ‘string’ lub ”string”.

„@” – ten operator oznacza atrybut. Podobnie jak w XmlDocumencie, atrybuty w XPath nie są stawiane na równi z innymi węzłami. Aby znaleźć wszystkie atrybuty silników naszych samochodów, wpiszmy Samochody/Samochód/Silnik/@*

„.” – oznacza bieżący kontekst, czyli element, przez który przechodzi XPath. Dla zapytania:

./* otrzymamy węzeł zawierający listę wszystkich samochodów (jeśli odwołujemy się tym operatorem na początku zapytania, to oznacza to, że węzłem kontekstowym jest węzeł od którego zaczynamy poszukiwania).

„[]” – ten operator ma dwie funkcje: może służyć jako indeks, czyli dla zapytania: Samochody/Samochód[1] zwróci nam pierwszy samochód w pliku; operator ten może także służyć jako filtr. Np. dla wyrażenia Samochody/Samochód[Kolor=czerwony] otrzymamy w odpowiedzi listę samochodów o kolorze czerwonym.

W nawiasach możemy także umieszczać nazwę węzła, wtedy uzyskamy listę wszystkich elementów, które takowy węzeł posiadają. Musicie pamiętać, że XPath numeruje elementy od 1, jeśli podacie 0 ( lub wartości ujemne) to po prostu wynikiem będzie pusta lista znalezionych węzłów. Dodatkowo nie musicie się martwić o odstępy między operatorami, gdyż XPath’owi jest wszystko jedno czy wstawiamy odstępy, czy też nie.

„//” – operator ten pozwala na szukanie węzłów na dowolnej głębokości. Przydatny, kiedy mamy węzły o danej nazwie na różnych poziomach drzewa dokumentu. W innych przypadkach nie jest przydatny, gdyż z wyników takiego zapytania: .//* niewiele się dowiemy (po prostu otrzymamy wszystkie elementy potomne węzła kontekstowego. Aby zobaczyć jak działa ten operator, otwórzcie plik abstrakcja.xml i zadajcie zapytanie: *//A/text() otrzymamy wtedy elementy A z różnych poziomów dokumentu.

„( )”– operator grupowania, jest bardzo pomocny , kiedy chcemy ustalić co i w jakiej kolejności ma być znalezione. Rozważcie te dwa przykłady:

root/poziom1a//A[3] – trzeci element A z poziomu 1a

(root/poziom1a//A)[3] – drugi element A z poziomu 3a

Jak widzicie operator ten czasami się przydaje.

Filtry

Powyższe operatory można łączyć i tworzyć bardziej skomplikowane zapytania. Oto kilka przykładów:

Samochody/Samochód[Opis] – zwraca te samochody, które posiadają element Opis.

Samochody/Samochód[Kolor=czerwony] – dzięki temu wiemy, jakie samochody mają kolor czerwony.

Samochody/Samochód[Kolor="czerwony"][2] – przekazuje drugi element z listy uzyskanej w punkcie poprzednim.

Podobnie jak w C# możecie w filtrze zastosować operatory and, or, not, operatory relacji:

Samochody/Samochód[Kolor=czerwony and Silnik/@moc>150] – i już wiemy czym można zaszaleć :)

Samochody/Samochód[(Kolor=zielony or Kolor=niebieski) and Cena<100] – za pomocą tego zapytania dostaniemy „wariant ekonomiczny”, czyli coś zielonego lub niebieskiego przy „niskiej” cenie.

Samochody/Samochód[(Kolor=zielony or Kolor=niebieski) and not(Cena<100)] – dowiemy się, które samochody o kolorze zielonym lub niebieskim przeznaczone są dla zamożniejszych klientów naszej wypożyczalni.

„|” – ten operator łączy wyniki kilku zapytań w jeden zestaw węzłów. Np.za pomocą następującego: root//A | root//B otrzymamy wszystkie elementy A i B z pliku abstrakcja.xml. Przy czym oba zapytania muszą dotyczyć jednego węzła kontekstowego (w naszym przypadku roota).

Funkcje dodatkowe

Mamy także do dyspozycji funkcje takie jak:

position() – stosowana w filtrach:

Samochody/Samochód[position()=1] równoważne jest Samochody/Samochód[1]

last() – zwraca numer ostatniego elementu:

Samochody/Samochód[last()-1] – i otrzymujemy przedostatni samochód z pliku

text() – zwraca true jeśli węzeł jest węzłem tekstowym. Np:

Samochody/Samochód//text() daje nam wszystkie tekstowe węzły naszego dokumentu.”

Do dyspozycji mamy także kilka innych funkcji, jednakże nie są one często używane, dlatego pomijam ich opis. Zainteresowanych zapraszam do MSDN’a.

node() – odpowiednik ‘*’ w zapytaniu

comment() – umożliwia wyświetlenie tylko komentarzy

processing-instruction() – znajduje instrukcje XSL

XPath i przestrzenie nazw

Za pomocą XPath’a możemy dokonać przeszukania dokumentu używając przestrzeni nazw. Może być to jednak uciążliwe, gdyż metoda SelectNodes( string zapytanie ) przestaje wystarczać i musimy użyć: XmlNodeList SelectNodes(string zapytanie, XmlNamespaceManager); Czym jest drugi parametr? Otóż w implementacji MSDOM aby znaleźć węzeł za pomocą przestrzeni nazw trzeba najpierw znać jej prefix i jej nazwę. Zatem żeby zaznaczyć w pliku przestrzenie.xml elementy z przestrzeni o prefiksie „mi” należałoby wstawić następujący kod:

XmlDocument doc = new XmlDocument();
string prefix;
string nS;
//...otwarcie pliku, pobranie zapytania...
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace( prefix, nS );
XmlNodeList listaw = doc.SelectNodes( zapytanie, nsmgr );

Obejście tego problemu jest następujące: przestrzenie nazw wewnątrz implementacji DOM są traktowane jak zwykłe atrybuty. Podczas wstawiania węzłów do klasy TreeView, przy wczytywaniu dokumentu z pliku sprawdzamy, czy dany atrybut rozpoczyna się tekstem ‘xmlns:’. Jeśli tak to jest to definicja przestrzeni nazw i wstawiamy ją do tablicy nStbl. Prefix natomiast wyciągamy z zapytania użytkownika (w funkcji Szukaj() ). Teraz znając już prefix i przestrzeń nazw dodajemy je do XmlNamespaceManager’a i wykonujemy przeszukanie dokumentu. Procedura ta jest dość czasochłonna, dlatego też jeśli chcecie pobawić się znajdowaniem elementów z danej przestrzeni nazw musicie zaznaczyć odpowiedni CheckBox.

Osie i konteksty

Jak zapewne zauważyliście za pomocą menu „węzeł kontekstowy” możecie w załączonym programie zmieniać węzeł, do którego będziemy stosować nasze zapytania. Dla węzła doc i roota otrzymamy różne wyniki przy przetwarzaniu wyrażenia ‘.’ (czyli znajdowania bieżącego węzła kontekstowego). Dla doc’a otrzymamy element ‘document’, który odpowiada zawartości całego pliku, zaś dla węzła głównego (roota) otrzymamy główny element „Samochody”. Dla roota, aby znaleźć wszystkie elementy o nazwie „Samochód”, możemy pominąć element „Samochody/” i podać tylko tekst „Samochód”. Uważam, że lepszym wyborem jest przeszukiwanie względem roota (ponieważ można pominąć nazwę elementu głównego w zapytaniu; nie zrobiłem tego wcześniej, żeby nie wprowadzić zamieszania, ale teraz wiecie już jak można by zmienić poprzednie zapytania), ale pozostawiam Wam wybór. Dodatkowo możecie zaznaczyć dowolny węzeł drzewa dokumentu i przeprowadzić dla niego odpowiednie testy.

Teraz coś dla wytrwałych: osie dokumentu i sposoby odwoływania się do nich.

Operatory, które teraz poznacie pozwalają na wyznaczenie relacji węzła kontekstowego wobec innych węzłów w drzewie dokumentu XML. W poniższych przykładach będziemy pracować z plikiem osie.xml (oczywiście jest dołączony do artykułu). Wybierzcie jako węzeł kontekstowy węzeł o nazwie „Ja”. Oto kilka przykładów:

ancestor::* – umożliwia nam wyświetlenie rodzica węzła kontekstowego, dalej rodzica-rodzica (czyli dziada naszego węzła), pradziada itd. aż do roota.

ancestor-or-self::* – różni się tym od poprzedniego, że wyświetla także węzeł kontekstowy

descendant::* – wyświetla nam węzeł potomny, później węzły-wnuki. Podobnie jak w poprzednim przypadku istnieje wersja descendant-or-self.

parent::* – zwraca rodzica węzła kontekstowego

child::* – zwraca potomka węzła kontekstowego

self::* – i otrzymujemy węzeł o nazwie „Ja”

following::* – zaznacza węzły znajdujące się bezpośrednio po naszym węźle

following-sibling::* – zwraca węzły znajdujące się po naszym węźle, ale tylko w tej samej gałęzi drzewa

preceding::* – wyświetla węzły znajdujące się przed naszym węzłem

preceding-sibling::* – analogicznie do following-sibling

Dodatkowo mamy instrukcję: attribute::* – pozwala wyświetlać tylko atrybuty (ale chyba lepiej używać jej skróconej formy – @, omówionej wcześniej).

Wszystkie operatory dotyczące osi można swobodnie łączyć z funkcjami i operatorami opisanymi we wcześniejszej części artykułu.

Dziękuję Wam za poświęcenie uwagi artykułowi, zachęcam do eksperymentowania zapytaniami XPath i rozszerzania wiedzy o XML. Czekam na wszelkie sugestie, opinie i komentarze.

 

Załączniki:

Podobne artykuły

Komentarze 2

DRECH
DRECH
1 pkt.
Nowicjusz
21-01-2010
oceń pozytywnie 0
"(root/poziom1a//A)[3] – drugi element A z poziomu 3a" - to jakby trochę nie jasne (dla mnie)
jacek.helka897
jacek.helka897
0 pkt.
Nowicjusz
21-01-2010
oceń pozytywnie 0
Bardzo kiepska polszczyzna, temat mało twórczy.
pkt.

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.