XML – ćwiczenia 7: XPath

Standardy

Na tych i kilku kolejnych zajęciach będziemy poznawać standardy związane z przekształcaniem i odpytywaniem dokumentów XML: XSLT, XPath i XQuery. Standardy te są ze sobą silnie związane.

Generalnie istnieją dwie wersje tych standardów.

  1. Starsza i prostsza:

  2. Nowsza i bogatsza (ciągle rekomendacje kandydujące):

Chociaż nowsze wersje nie są jeszcze zatwierdzone i nie są wspierane przez wiele narzędzi, my skoncentrujemy się już na nich.

Model danych

Model danych opisany poniżej i w tej rekomendacji jest wspólny dla XPath 2.0, XQuery 1.0 i XSLT 2.0.

Sekwencje

Każde wyrażenie XPath ewaluuje się do sekwencji składającej się z zera lub większej liczby elementów (items). Elementem sekwencji może być węzeł lub wartość atomowa. Np. wyrażenie (1, 2.0, "C") wylicza się do trzyelementowej sekwencji wartości atomowych (każda innego typu).

W XPath sekwencje jednoelementowe są utożsamiane z wartościami, które zawierają. Dzięki temu np. wartość wyrażenia 2 + 2 można uznać za sekwencję jednoelementową z jedną wartością atomową 4.

Zagnieżdżone sekwencje są automatycznie spłaszczane, np. (1, (2, 3)) jest równe (1, 2, 3).

Wartości atomowe

Wartość atomowa posiada typ prosty. W modelu danych XPath występują wszystkie typy proste predefiniowane w XML Schema, kilka dodatkowych typów predefiniowanych oraz potencjalnie dowolne typy proste zdefiniowane w schematach jako zawężenia (nie unie ani listy).

Hierarchia typów w modelu danych XPath.

Węzły

Występują następujące rodzaje (kinds) węzłów:

  • dokument (korzeń dokumentu),
  • element,
  • atrybut,
  • węzeł tekstowy,
  • instrukcja przetwarzania,
  • komentarz,
  • węzeł przestrzeni nazw.

Węzły mogą pochodzić z dokumentów lub być konstruowane (np. w XSLT). Każdy węzeł należy do jakiegoś drzewa. Dwa węzły są rozróżnialne nawet jeśli zawierają te same dane (tak jak obiekty w programowaniu). Atrybuty ani węzły przestrzeni nazw nie są dziećmi elementów.

W modelu XPath nie mogą występować sąsiadujące ze sobą ani puste węzły tekstowe. Nie ma też w ogóle węzłów z warstwy fizycznej dokumentu: referencji do encji, referencji do znaków, sekcji CDATA. Dokument widziany przez XPath jest w tym sensie "znormalizowany".

Model obliczeń

Kontekst

Wyrażenie XPath jest obliczane w kontekście, na który składają się m.in. dokument, węzeł bieżący, środowiska zmiennych i funkcji.

Błędy

XPath jest typowany dynamicznie, dlatego błędy typowania zgłaszane są dopiero w trakcie obliczania wyrażenia. W przypadku leniwych obliczeń (obowiązkowe w if, opcjonalne w some i every) potencjalne błędy mogą nie zostać wykryte.

Wyrażenia XPath

Najczęściej używanymi wyrażeniami XPath są ścieżki. Jednak język ten pozwala na zapisywanie także wyrażeń arytmetycznych, logicznych i innych. Wyrażenia takie mogą być także zawarte w ścieżkach, dlatego poznamy je stopniowo, ze ścieżkami na końcu.

W poniższych przykładach zakładamy, że prefiks xs odnosi się do przestrzeni nazw http://www.w3.org/2001/XMLSchema a fn do http://www.w3.org/2005/xpath-functions.

Zadanie 1.

Otwórz w XML Spy dokument. Wybierz z menu lub paska narzędzi ewaluację XPath. Wybierz wersję XPath 2.0. Wpisuj poznawane po kolei konstrukcje i obserwuj wyniki.

XML Spy "nie zna" prefiksów xs i fn. Aby mógł z nich korzystać, można zadeklarować je w dokumencie, na którym pracujemy. Oto wersja z zadeklarowanymi prefiksami.

Zmienne

$foo jest odwołaniem do zmiennej o nazwie foo. Zmienna musi być dostępna w kontekście w momencie ewaluacji wyrażenia.

Literały

Poprawnymi wyrażeniami XPath są literały różnych typów.

Typ wartościPrzykładowe literały
string'12.5' "He said, ""I don't like it."""
integer123 5
decimal12.3 5.0 .23
double125E2 1.13e-8

Konstruktory i rzutowanie typów

Wartości wielu (wszystkich?) typów prostych (wbudowanych oraz zdefiniowanych w schematach) można uzyskać za pomocą konstruktorów. Oto przykłady:

  • xs:date("2001-08-25")
  • xs:decimal(125E2)
  • xs:double(3)
  • xs:dayTimeDuration("PT5H")
  • xs:float("NaN")
  • xs:double("INF")
  • usa:zipcode("12345"), o ile taki typ jest zdefiniowany

To samo można uzyskać dzięki rzutowaniu typów:

  • "2001-08-25" cast as xs:date
  • ...

Konstruktor sekwencji

Sekwencje można jawnie tworzyć poprzez wyliczenie ich elementów lub podanie przedziałów (dla typu xs:integer). Całą sekwencję obejmuje się nawiasami, a poszczególne elementy i przedziały rozdziela przecinkami.

Przykłady:

  • () – sekwencja pusta
  • ("ala") – singleton równoważny wartości atomowej "ala"
  • (1, 2,3, 4 ,6, 7, -3)
  • (1 to 4, 6 to 7, -3)
  • ("ala", 20.5, 2 + 5, //obiekt/opis)
  • (10, (1, 2), (), (3, 4)) – jest równe (10, 1, 2, 3, 4)

Operatory arytmetyczne i logiczne

Wyrażeniami XPath są "normalne" wyrażenia arytmetyczne z operatorami + - * div idiv mod.

Dodawanie i odejmowanie działają także na typach daty i czasu (tzn. do daty można dodać duration itp.).

Poza operatorami arytmetycznymi, są dostępne także logiczne or i and.

Przykłady:

  • 2 + 2
  • 12.5 div 3
  • 13 idiv 3
  • 14 mod 3 = 2 and 3 > 2

Istnieją także specjalne operatory dla sekwencji węzłów. Wyniki tych operatorów to sekwencje bez powtórzeń, uporządkowane według porządku dokumentu (nawet jeśli argumenty tego nie spełniały):

  • union lub | – suma zbiorów
  • intersect – przecięcie zbiorów
  • except – różnica zbiorów

Porównania

Do porównywania wartości atomowych służą operatory porównania atomowego: eq, ne, lt, le, gt i ge.

Argumenty są rzutowane sa najwęższy wspólny typ, a jeśli takiego nie ma lub jest nim xs:untypedAtomic, rzutowane na xs:string. Dla pewnych kombinacji typów niektóre operatory mogą nie być zdefiniowane, wówczas pojawia się błąd.

Do porównywania sekwencji służą operatory porównania ogólnego: = != < <= > >=. Porównanie dla dwóch sekwencji L i R zwraca prawdę wtedy i tylko wtedy, gdy dla chociaż jednej pary: l (elementu L) oraz r (elementu R) zachodzi porównanie atomowe odpowiadające zadanemu porównaniu ogólnemu.

Poza tym istnieją operatory porównania specyficzne dla węzłów: is (identyczność węzłów), << i >> (porównywanie względem porządku dokumentu).

Przykłady (sprawdźcie czy odpowiedzi się zgadzają):

  • 5 gt 3 – prawda
  • (2) eq 2 – prawda
  • (3) eq 2 – fałsz
  • () eq 2 – pusta sekwencja
  • (2, 3) eq 2 – błąd
  • (2) = (2) – prawda
  • 2 = 2 – prawda
  • () = 2 – fałsz
  • (2, 3) = 2 – prawda
  • (1, 2) = (2, 3) – prawda
  • (2, 3) = (3, 4) – prawda
  • (1, 2) = (3, 4) – fałsz (= nie jest przechodnie)
  • (1, 2) != (1, 2) – prawda (_ != _ nie jest równoważne not(_ = _))
  • //obiekt[9]/opis/text() = //obiekt[10]/opis/text() – prawda
  • //obiekt[9]/opis/text() is //obiekt[10]/opis/text() – fałsz
  • //obiekt is //obiekt[1] – błąd (więcej niż jeden węzeł po lewej stronie)

Wywołania funkcji

W XPath można wywoływać funkcje. Wiele funkcji zdefiniowanych jest w tej rekomendacji. Funkcje te znajdują się w przestrzeni nazw http://www.w3.org/2005/xpath-functions. W XML Spy nie trzeba używać prefiksów.

Standardy XSLT i XQuery przewidują definiowanie własnych funkcji. Definiowanie własnych funkcji dostępne jest także poprzez specjalne interfejsy programistyczne (np. EXSLT).

Funkcja musi być dostępna w kontekście w momencie ewaluacji wyrażenia.

Przykłady:

  • fn:not(fn:false()) eq fn:true()
  • fn:concat("abc", "xyz")
  • fn:sum(//obiekt/@parzysty)
  • moje:moja_funkcja(12, //jakieś_elementy)

Wyrażenie for

Wyrażenie for $x in E1 return E2 może być obliczone w następujący (lub równoważny) sposób:

  1. Obliczamy wyrażenie E1, jego wynikiem jest sekwencja.
  2. Dla każdego elementu Vi obliczonej sekwencji wykonujemy ewaluację wyrażenia E2 w kontekście wzbogaconym o zmienną x, której przypisano wartość równą Vi.
  3. Wynikiem jest sekwencja złożona z wyników poszczególnych ewaluacji, następnie "spłaszczona".

W jednym forze można związać wiele zmiennych.

Przykłady:

  • for $n in (10, 20, 30.5) return $n + 5
  • for $x in //obiekt return fn:concat("Obiekt o nazwie: ", $x/@nazwa)
  • for $x in X, $y in Y return $x + $y

Wyrażenia warunkowe

Obliczenie wyrażenia if (W) then E1 else E2 sprowadza się do obliczenia W, a następnie, w zależności od wyniku, E1 lub E2 (leniwie).

Przykład:

  • for $x in //obiekt return if($x/@nazwa) then fn:concat("Obiekt ma nazwę: ", $x/@nazwa) else "Obiekt nie ma nazwy"

Kwantyfikatory

Wyrażenie some $var in E1 satisfies E2 zwraca prawdę gdy przynajmniej jeden element sekwencji obliczonej z E1 spełnia wyrażnie E2, tzn. E2 w kontekście wzbogaconym o zmienną var z wartością równą elementowi sekwencji wylicza się do prawdy.

Analogicznie every $var in E1 satisfies E2 zwraca prawdę gdy każdy element sekwencji obliczonej z E1 spełnia wyrażenie E2.

Obliczanie wyrażeń kwantyfikowanych może odbywać się w dowolnej kolejności, może być leniwe i może zostać przerwane po wystąpieniu dynamicznego błędu.

Przykłady:

  • some $x in (1,2,3) satisfies $x = 3 (prawda)
  • every $x in (1,2,3) satisfies $x = 3 (fałsz)
  • some $x in (//obiekt, 'aaa') satisfies $x is //obiekt[2] (prawda lub błąd w zależności od kierunku ewaluacji, spróbujcie zmienić kolejność w sekwencji)

Ścieżki XPath

To wyrażenia najbardziej charakterystyczne dla XPath. Służą do odczytywania (adresowania) fragmentów dokumentu.

Zasadnicza składnia

Ścieżka względna ma następującą postać:

krok/krok ...

gdzie krok to:

::test-węzłów [predykat] [predykat] ...

Ścieżka bezwzględna dodatkowo zaczyna się od / lub //.

Obliczenie wyrażenia zaczyna się od węzła konktekstowego (ścieżka względna) lub węzła dokumentu (ścieżka bezwzględna). W każdym kroku na wejściu ("z lewej strony") mamy jakąś sekwencję węzłów. Dla każdego węzła z tej sekwencji krok prowadzi do jakiejś sekwencji węzłów (np. dzieci). Wszystkie uzyskane w ten sposób węzły tworzą sekwencję, która jest wynikiem kroku i "przechodzi na prawą stronę".

Wynikiem całej ścieżki jest sekwencja węzłów, która jest wynikiem ostatniego kroku.

Oś wskazuje "kierunek", w którym podążamy w danym kroku (tak naprawdę określa jakąś sekwencję węzłów). Test węzłów pozwala na proste wybranie tylko niektórych węzłów z danej osi. Predykaty to dowolne wyrażenia dodatkowo filtrujące węzły.

Test węzłów

Zasadniczo są dwa rodzaje testów: po rodzaju węzła i po nazwie. Oto reprezentatywne przykłady:

  • node() – dowolne węzły,
  • element() – dowolne elementy,
  • text() – dowolne węzły tekstowe,
  • processing-instruction() – dowolne instrukcje przetwarzania,
  • comment() – dowolne komentarze,
  • attribute() – dowolne atrybuty,
  • element(obiekt) – elementy o nazwie obiekt,
  • element(*, TGość) – elementy o dowolnej nazwie, o typie TGość (ustalonym na podstawie załączonej schemy),
  • attribute(licznik, xs:integer) – atrybuty o nazwielicznik i typie xs:integer (ustalonym na podstawie załączonej schemy),
  • obiekt – węzły o nazwie obiekt w przestrzeni nazw o pustym identyfikatorze (albo domyślnej przestrzeni nazw?),
  • pre:obiekt – węzły o nazwie obiekt w przestrzeni nazw wskazanej prefiksem pre,
  • *:obiekt – węzły o lokalnej nazwie obiekt,
  • pre:* – węzły w przestrzeni nazw o prefiksie pre.

Osie

Każda oś wskazuje sekwencję (ew. pustą) węzłów. Kolejność węzłów w sekwencji jest zgodna lub przeciwna do ich wystąpienia w dokumencie (w zależności od tego czy oś jest w przód czy w tył).

  • child – zawiera dzieci węzła kontekstowego. Jedynie węzeł dokumentu i elementu mają dzieci (mogą to być elementy, instrukcje przetwarzania, komentarze, lub węzły tekstowe; dziećmi nie są atrybuty, przestrzenie nazw i węzły dokumentu).
  • descendant – potomkowie, domknięcie przechodnie osi child.
  • parent – rodzic węzła kontekstowego.
  • ancestor – domknięcie przechodnie osi parent (zawiera węzeł dokumentu chyba, że był to węzeł kontekstowy).
  • following-sibling – rodzeństwo, które zgodnie z porządkiem dokumentu występuje później (dla węzłów atrybutu i przestrzeni nazw ta oś jest pusta).
  • preceding-sibling – analogicznie.
  • following – wszyscy potomkowie węzła dokumentu nie będący potomkami węzła kontekstowego i w dokumencie występujący po nim.
  • preceding – analogicznie.
  • attribute – atrybuty węzła kontekstu.
  • self – węzeł kontekstu.
  • descendant-or-self – połączenie osi descendant i self.
  • ancestor-or-self – połączenie osi ancestor i self.
  • namespace – dostęp do węzła przestrzeni nazw. W XPath 2.0 jest deprecated. Zastępują ją funkcje fn:get-in-scope-prefixes i fn:get-namespace-uri-for-prefix.

Przykłady:

  • /child::lista/child::obiekt – sekwencja wszystkich elementów obiekt z dokumentu
  • child::obiekt – sekwencja wszystkich elementów obiekt, które są dziećmi "węzła konktekstowego", jest sens obliczać z elementu lista
  • attribute::* – wszystkie atrybuty węzła konktekstowego (sprawdź w obiekcie)
  • preceding-sibling::obiekt – sprawdź w obiekcie w środku dokumentu oraz w dzieciach obiektu
  • preceding::obiekt – sprawdź w obiekcie w środku dokumentu oraz w dzieciach obiektu
  • child::attribute() – czy są to atrybuty bieżącego węzła?
  • attribute::node() – a to?
  • /child::lista/child::obiekt/attribute::node()
  • /descendant-or-self::node()
  • /descendant-or-self::element()/attribute::attribute()

Składnia skrócona

Można stosować różne skróty notacyjne. Domyślną osią jest child, rozpoczęcie nazwy węzła od @ oznacza przejście na oś atrybutów.

// oznacza wszystkich potomków (nie tylko dzieci jak /), .. oznacza parent::node(), natomiast . (kropka) węzeł kontekstowy (self::node()).

Przykłady:

  • /lista/obiekt
  • obiekt
  • @*
  • //node()
  • //* (czym się różnią?)

Predykaty

Predykaty pozwalają na sprawdzanie własności, których nie da się wyrazić w samych testach węzłów. Predykat jest obliczany dla każdego węzła z sekwencji (wyniku wyrażenia z lewej strony predykatu), tzn. kolejne węzły są ustawiane jako węzły kontekstowe. Jeśli dla danego węzła test zachodzi, węzeł znajdzie się w wynikowej sekwencji („przejdzie na prawą stronę”). Jeśli test nie zachodzi, węzeł zostanie „zatrzymany”.

Predykatem może być dowolne wyrażenie XPath. Jeśli jego wynikiem jest wartość numeryczna, wówczas predykat zachodzi wtw gdy pozycja testowanego węzła jest równa tej liczbie. Jeśli jego wynikiem nie jest wartość numeryczna, wówczas wynik jest rzutowany na xs:boolean. W szczególności pusta sekwencja jest rzutowana na fałsz, a sekwencja z jakimkolwiek węzłem na początku na prawdę.

Przykłady:

  • //obiekt[2] – drugi obiekt
  • //obiekt[position() > 5] – obiekty o pozycji > 5
  • //obiekt[length()] – ostatni obiekt
  • //obiekt[@nazwa='drugi'] – obiekt z atrybutem nazwa równym drugi
  • //obiekt[@nazwa] – obiekt z jakimkolwiek atrybutem nazwa
  • //obiekt[@nazwa and position() = 6] – ...
  • //obiekt[@nazwa][position() = 6] – ...
  • //obiekt[@nazwa]/opis – ...

Zadanie 2.

Za pomocą XPath wybierz elementy dokumentu przyklad.xml, które zaznaczaliśmy za pomocą CSS na poprzednich zajęciach i trochę innych (dokument minimalnie się zmienił).

  1. wszystkie obiekty,
  2. wszystkie opisy,
  3. zagnieżdżone opisy,
  4. obiekt o id równym E4,
  5. obiekty parzyste,
  6. obiekty parzyste (ale bez pomocy atrybutu),
  7. obiekty bez nazwy,
  8. obiekty bez opisu,
  9. tylko drugie opisy obiektów,
  10. wyróżnienia w opisach zagnieżdżonych,
  11. wyróżnienia w opisach, ale nie w opisach zagnieżdżonych,
  12. dodatki znajdujące się za opisami,
  13. wyróżnienia w dodatkach znajdujących się za opisami,
  14. obiekty, których wszystkie dzieci posiadają podelement wyr,
  15. dla każdego obiektu, liczba jego dzieci (sekwencja).

Zobacz także


Valid XHTML 1.1Valid CSS