XML – ćwiczenia 13: Transformery i StAX

Wszystkie przykładowe programy:

API Javy (pakiety javax.xml.*):

Java API for XML Processing

Standard JAXP opisuje podstawowe wsparcie dla przetwarzania XML w Javie, jest częścią platformy Java Standard Edition.

Standard określa klasy i interfejsy, z których bezpośrednio powinni korzystać programiści. Standard pozwala na używanie różnych implementacji poszczególnych składników bez jawnego używania klas implementacyjnych (odpowiada za to tzw. plugability layer). To, jaka implementacja zostanie wybrana, zależy od konfiguracji środowiska (klasy dostępne w ścieżce CLASSPATH oraz własności systemowe).

JAXP zawiera następujące składniki:

Transformacje

W pakiecie javax.xml.transform oraz podpakietach zawarte są klasy i interfejsy stanowiące wsparcie dla przekształceń dokumentów.

Obiekt typu Transformer odpowiada definicji przekształcenia, na przykład arkuszowi XSLT. Wywołanie metody transform tego obiektu powoduje przekształcenie dokumentu odczytanego z podanego źródła i zapisanie wyniku przekształcenia do podanego wyjścia.

Zarówno wejściem jak i wyjściem przekształcenia może być jedna z poniższych reprezentacji dokumentu:

Obecnie można używać przekształceń identycznościowych lub stworzyć obiekt typu Transformer na podstawie arkusza XSLT. Standard JAXP 1.4 odnosi się do XSLT w wersji 1.0, ale, jak dowodzą doświadczenia, implementacja Saxon pozwala na wykonywanie transformacji XSLT 2.0 w sposób zgodny z JAXP (wystarczy do CLASSPATH dodać plik saxon9.jar).

Przekształcenia identycznościowe mogą być wykorzystane do zmiany sposobu reprezentacji dokumentu, np. zbudowania drzewa DOM ze strumienia zdarzeń SAX, serializacji strumienia zdarzeń SAX lub drzewa DOM (bez użycia Load and Save).

Możliwość serializacji strumienia zdarzeń SAX pozwala na wykorzystanie filtrów SAX do zmiany dokumentów XML bez ich wczytywania do pamięci (co byłoby konieczne przy wykorzystaniu DOM lub JAXB).

Przykład 1.

Plik: Transformer1.java.

To przykład podstawowego wykorzystania transformera do wykonania przekształcenia XSLT.

Przykład 2.

Plik: Transformer2.java.

W tym przykładzie źródłem jest strumień zdarzeń SAX, bezpośrednio z parsera.

Przykład 3.

Plik: Transformer3.java.

W tym przykładzie zdarzenia SAX płyną z parsera, poprzez filtr, do transformera serializującego zdarzenia do pliku. W ten sposób dokument jest modyfikowany bez wczytywania go w całości do pamięci.

Zadanie 1.

  1. Napisz (lub wykorzystaj napisany tydzień temu) filtr SAX, który przepuszcza tylko grupy o atrybucie ważne równym tak i ich zawartość (podelementy i tekst), natomiast zatrzymuje nieważne grupy i ich zawartość. Chodzi o dokumenty zgodne ze schematem, oto przykładowy dokument.
  2. Połącz parser, filrt i transformer identycznościowy w taki sposób, aby program filtrował podany plik i wynik zapisywał w drugim pliku.

Filtr SAX wykonujący przekształcenie

Klasa SAXTransformerFactory, będąca podklasą TransformerFactory, umożliwia tworzenie, poza normalnymi Transformer-ami, filtrów SAX realizujących przekształcenie.

Przykład 4.

SAXTransformerFactory tf = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
XMLFilter f = tf.newXMLFilter(new StreamSource("arkusz.xsl"));

Bierny transformer dla SAX – TransfomerHandler

W przypadku strumienia zdarzeń SAX może zaistnieć potrzeba „biernej” obsługi zdarzeń i przetwarzania ich zgodnie z arkuszem XSLT lub zamiany ich na inny rodzaj reprezentacji dokumentu XML. Zwykły Transformer inicjuje przetwarzanie, a co za tym idzie (w przypadku SAX) także parsowanie.

Obiekt typu TransfomerHandler może być używany w roli ContentHandlera, który pozostaje podłączony do parsera (lub filtra) SAX i po prostu obsługuje zdarzenia wtedy, gdy ktoś inny uruchomi parsowanie. Za pomocą setResult ustala się cel przekształcenia.

Do tworzenia obiektów typu TransfomerHandler oraz innych użytecznych w przypadku SAX służy klasa SAXTransformerFactory.

Zadanie 2.

Napisz program używający filtra grup inaczej, korzystając z TransfomerHandler.

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

Niedawno 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, nadaje się więc do takich zastosowań jak aplikacje J2ME.
  • za pomocą 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.

Pliki: LiczbyStax1.java, LiczbyStax2.java.

Przykład o sumowaniu liczb w StAX, dwie wersje: z kursorem i ze zdarzeniami.

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

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

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 4. (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,
  3. można teraz spróbować obsłużyć całe pliki ods (czyli zipy zawierający m.in. content.xml).

Valid XHTML 1.1Valid CSS