XML – ćwiczenia 12: SAX

Wszystkie przykładowe programy:

SAX – Simple API for XML

SAX jest to interfejs programistyczny oparty o zdarzeniowy model dokumentu. Może być zaimplementowany w obiektowych językach programowania, a interfejsy zbliżone do SAX można spotkać także w językach pozwalających na przekazywanie referencji do funkcji (np. C). My zajmiemy się standardową implementacją w Javie.

Cechy charakterystyczne SAX:

Referencje

API Javy:

Zobacz także:

Parsowanie dokumentu i odczyt danych

Przykład 1.

Pliki: SaxSimplePrinter, InfoHandler.

Program parsuje podany dokument i wypisuje na wyjście informacje o węzłach (niektórych rodzajów). Klasa InfoHandler jest realizacją interfejsu ContentHandler, jej obiekty obsługują zdarzenia SAX.

Przykład 2. Przykład z wykładu

Plik: StaffSAX.java.

Program liczy sumę zarobków osób z podanego stanowiska.

Programista podaje swój kod tworząc klasę implementującą interfejs ContentHandler. Parserowi wskazuje się obiekt, który ma obsługiwać zdarzenia, a podczas parsowania metody tego obiektu wywoływane są w odpowiedzi na zdarzenia takie jak początek elementu czy węzeł tekstowy.

Więcej zdarzeń można obsługiwać, jeśli zaimplementuje się także interfejsy ErrorHandler (obsługa błędów, m.in. walidacji), LexicalHandler (obsługa encji, sekcji CDATA, komentarzy), DeclHandler (obsługa DTD). Szczegóły w API.

Zadanie 1.

Zadanie 1. Odczyt zawartości

Napisz program czytający dokument taki jak sklep.xml i dla podanego w parametrze id kategorii wypisujący nazwę i cenę najtańszego towaru w tej kategorii.


Write a program reading documents such as sklep.xml which will take a category id as an argument and print: the name (nazwa) and the price (cena) of the cheapest article (towar) in the given category.

Obsługa przestrzeni nazw

Podobnie jak w DOM, w SAX możemy przetwarzać dokument uwzględniając lub nie przestrzenie nazw.

Jeśli nie chcemy uwzględniać przestrzeni nazw, powinniśmy:

  • w fabryce ustawić setNamespaceAware(false) (tak naprawdę to jest domyślna wartość),
  • parsować dokument parserem uzyskanym z takiej fabryki,
  • odczytywać nazwy z parametru qName w metodzie startElement i metodą getQName dla atrybutów.

Jeśli chcemy uwzględniać przestrzenie nazw, powinniśmy:

  • w fabryce ustawić setNamespaceAware(true),
  • parsować dokument parserem uzyskanym z takiej fabryki,
  • odczytywać nazwy z parametrów uri i localName w metodzie startElement oraz metodami getURI i getLocalName dla atrybutów. W pewnych sytuacjach użyteczne mogą być też nazwy kwalifikowane (z ewentualnym prefiksem), ale to para (uri, localName) decyduje o znaczeniu elementu bądź atrybutu.

Filtry SAX

Filtr to obiekt klasy implementującej interfejs XMLFilter (można rozszerzać standardową klasę XMLFilterImpl). Obiekt taki można umieścić między parserem a ContentHandler-em, z punktu widzenia „handlera” działa on jak parser (można wywołać na nim metodę parse, on wywołuje metody „handlera”), natomiast z punktu widzenia parsera zachowuje się on jak „handler” – parser wywołuje metody filtra.

Filtry można łączyć w łańcuchy (metoda setParent), każdy filtr może filtrować lub modyfikować zdarzenia, a także wykonywać pewne czynności / obliczenia. Dzięki temu podczas jednego parsowania dokumentu można wykonać wiele czynności.

Przykład 3. Łączenie parsera i handlera poprzez filtr

XMLReader parser = XMLReaderFactory.getXMLReader();
ContentHandler handler = new MyHandler();
XMLFilter filtr = new MyFilter();
filtr.setParent(parser);
filtr.setContentHandler(handler);
filtr.parse();

Przykład 4. FiltrySAX

Plik: FiltrySAX.

Prosty przykład użycia filtra SAX. Filtr przepuszcza tylko niepuste węzły tekstowe i zmienia wielkość liter w nazwach elementów.

Zadanie 3.

Dotyczy przykładu sklep.

Napisz filtr SAX przepuszczający tylko kategorie i towary o podanym id-kategorii (i ich zawartość).

Napisz ContentHandler liczący średnią cenę towaru. Połącz go z filtrem tak, aby program zliczał średnią cenę towaru w wybranej kategorii.

Parsowanie strumieniowe

Parsowanie strumieniowe to sposób czytania dokumentu XML, w którym użytkownik parsera „prosi” parser o kolejne zdarzenie, o wczytanie kolejnego fragmentu dokumentu.

Daje to bardziej intuicjny przepływ sterowania niż w SAX, gdzie to parser „zarzuca” użytkownika zdarzeniami, które ten musi obsłużyć.

Jednocześnie zachowane są największe zalety SAX: szybkość (brak zbędnych czynności) i możliwość przetwarzania dokumentów nie mieszczących się w pamięci.

Streaming API for XML (StAX)

Standard Streaming API for XML jest realizacją idei „parsowania strumieniowego” w Javie, został opracowany w ramach JSR 173, a implementacja parsera była znana także jako Sun Java Streaming XML Parser (SJSXP).

StAX został dołączony do wersji 1.4 standardu JAXP i włączony do platformy Java Standard Edition (do wersji 6).

StAX pozwala na czytanie oraz zapisywanie dokumentów XML. Ze standardu można korzystać zasadniczo na dwa sposoby:

  • za pomocą kursora – interfejsy ze Stream w nazwie. Niejawnym kursorem przechodzimy do kolejnych węzłów dokumentu, o tym w jakim węźle jesteśmy możemy się dowiedzieć metodą getEventType, a inne metody get... zwracają wartości (np. nazwę węzła, wartość węzła tekstowego / atrybutu). Ten sposób jest najbardziej efektywny, nie zużywa niepotrzebnych zasobów, nie tworzy obiektu dla każdego odwiedzanego węzła.
  • za pomocą obiektów–zdarzeń – interfejsy z Event w nazwie. Tutaj metodą nextEvent pobieramy kolejne zdarzenie – obiekt typu XMLEvent. Różne zdarzenia będą miały różne konkretne typy (zobacz pakiet javax.xml.stream.events). Ten sposób umożliwia bardziej „eleganckie” programowanie obiektowe w zastosowaniach gdzie oszczędność zasobów nie jest aż tak istotna.

Przykład 5. Przykład z wykładu

Plik: StaffStAX.java.

Program liczy sumę zarobków osób z podanego stanowiska.

Tworzenie wyniku w StAX

StAX pozwala także tworzyć dokumenty, służą do tego interfejsy XMLStreamWriter i XMLEventWriter.

Przykład 6. Tworzenie wyniku przy pomocy XMLStreamWriter

Źródło: Tutorial JEE

XMLOutputFactory output = XMLOutputFactory.newInstance();
XMLStreamWriter writer = output.createXMLStreamWriter( ... );
writer.writeStartDocument();
writer.setPrefix("c","http://c");
writer.setDefaultNamespace("http://c");
writer.writeStartElement("http://c","a");
writer.writeAttribute("b","blah");
writer.writeNamespace("c","http://c");
writer.writeDefaultNamespace("http://c");
writer.setPrefix("d","http://c");
writer.writeEmptyElement("http://c","d");
writer.writeAttribute("http://c","chris","fry");
writer.writeNamespace("d","http://c");
writer.writeCharacters("Jean Arp");
writer.writeEndElement();
writer.flush();

Program jest odpowiedzialny za przekazywanie zdarzeń w odpowiedniej kolejności, m.in. zamykanie otwartych elementów. Writer po otrzymaniu zdarzenia startElement czeka z wypisaniem znacznika otwierającego dopóki otrzymuje zdarzenia związane z atrybutami lub przestrzeniami nazw.

Interfejsu XMLEventWriter używa sie podobnie, z tym że konsumuje obiekty–zdarzenia (XMLEvent). To podejście jest wygodne szczególnie wtedy, gdy większość zapisywanych węzłów pochodzi z dokumentu czytanego na wejściu, a tylko niektóre węzły są pomijane lub tworzone od nowa (modyfikować istniejących obiektów XMLEvent akurat nie można). Wówczas wystarczy w pętli czytać zdarzenia z XMLEventReader-a i zapisywać do XMLEventWriter-a.

Filtry StAX

Interfejsy StreamFilter i EventFilter służą do implementacji filtrów, które w prosty sposób pozwalają przepuszczać tylko niektóre zdarzenia. XMLInputFactory pozwala tworzyć parsery od razu filtrujące zdarzenia.

Przykład 7.

Źródło: Tutorial JEE

public class MyStreamFilter
     implements javax.xml.stream.StreamFilter {
  public boolean accept(XMLStreamReader reader) {
    if (!reader.isStartElement() && !reader.isEndElement())
      return false;
    else
      return true;
  }
}
...
  FileInputStream fis = new FileInputStream(filename);
                
  XMLStreamReader xmlr = xmlif.createFilteredReader(
    xmlif.createXMLStreamReader(fis), new MyStreamFilter());
...

Zadanie 4.

Plik content.xml to dokument z zawartością arkusza Open Document (cały plik ods to zip). Napisz program, który czytając plik poprzez interfejs StAX (najlepiej EventReader):

  1. policzy liczbę wszystkich wierszy, wszystkich komórek i maksymalny numer kolumny,
  2. dla każdej komórki, w której występuje formuła, wypisze wiersz z tą formułą.

Zadanie 5. (opcjonalne)

Korzystając z EventReader i EventWriter napisz program, który:

  1. przepisuje zawartość content.xml,
  2. ale dla każdej komórki zawierającej liczbę, zwiększa jej wartość o 100.

Valid XHTML 1.1Valid CSS