r09-01, ## Documents ##, XML Vademecum profesjonalisty


Rozdział --> 9[Author:T] .
Java i SAX

Poprzedni rozdział w całości poświęciliśmy użyciu Javy do obsługi modelu DOM XML. Jednak dla wielu osób użycie DOM jest zbyt skomplikowane, a traktowanie dokumentu jako drzewa uważają za nadmiernie skomplikowane. Uważają, że to nie oni powinni przeszukiwać dokument, ale to cały dokument powinien zostać im udostępniony. Takie właśnie głosy stały się przyczynkiem powstania Prostego interfejsu API dla XML (SAX, Simple API for XML), któremu poświęcimy cały ten rozdział. SAX jest znacznie prostszy w użyciu w przypadku wielu, a być może nawet większości zastosowań XML.

Być może będziesz zaskoczony, jeśli się dowiesz, że myśl przewodnią twórców SAX-a wcielaliśmy w życie w całym poprzednim rozdziale. Jak pamiętasz, do każdego węzła drzewa DOM stosowaliśmy rekursywną metodę display, w tej metodzie używaliśmy z kolei instrukcji switch. Poszczególne frazy case instrukcji switch obsługiwały różne rodzaje węzłów:

public static void display(Node node, String indent)

{

if(node == null) {

return;

}

int type = node.getNodeType();

switch (type) {

case Node.DOCUMENT_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] +=

"<?xml version=\"1.0\" encoding=\"iso-8859-2\"?>";

numberDisplayLines++;

display(((Document)node).getDocumentElement(), "");

break;

}

case Node.ELEMENT_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += node.getNodeName();

int length = (node.getAttributes() != null) ?

node.getAttributes().getLength() : 0;

Attr attributes[] = new Attr[length];

for (int loopIndex = 0; loopIndex < length; loopIndex++){

attributes[loopIndex] =

(Attr)node.getAttributes().item(loopIndex);

}

}

.

.

.

Dalsze frazy case obsługiwały instrukcje przetwarzania i inne części dokumentu XML.

W zasadzie przetwarzanie dokumentów za pomocą SAX wygląda tak samo. Zamiast nawigować sami po dokumencie, korzystamy z dokumentu opisując sposób obsługi poszczególnych przypadków. SAX jest oparty na obsłudze zdarzeń, co oznacza, że kiedy parser SAX napotyka element, traktuje to jako zdarzenie i wywołuje fragment kodu obsługujący elementy; kiedy napotyka instrukcję przetwarzania, wywołuje znów inny fragment kodu, i tak dalej. W ten sposób nie trzeba samemu po dokumencie nawigować - to dokument „sam się przetwarza”, wystarczy tylko odpowiednio reagować. To, że znakomita większość poprzednich przykładów na takiej technice się opiera, wskazuje, jak użyteczna jest ta technika.

Używany przez nas w poprzednim rozdziale pakiet XML for Java --> firmy alphaWorks [Author:AJ] obsługuje także SAX. Oznacza to, że możemy użyć tych samych plików JAR, co w poprzednim rozdziale; wystarczy tylko do zmiennej CLASSPATH dopisać na końcu ścieżki do tych plików (pamiętaj, że wszystko ma być w jednym wierszu):

C:\SET CLASSPATH=%CLASSPATH%;C:\xmlparser\XML4J_3_0_1\xerces.jar;

C:\xmlparser\XML4J_3_0_1\xercesSamples.jar

Można też, tak jak poprzednio, użyć przełącznika -classpath:

%javac -classpath C:\xmlparser\XML4J_3_0_1\xerces.jar;

C:\xmlparser\XML4J_3_0_1\xercesSamples.jar browser.java

%java -classpath C:\xmlparser\XML4J_3_0_1\xerces.jar;

C:\xmlparser\XML4J_3_0_1\xercesSamples.jar browser

Praca z interfejsem SAX

Teraz przejdziemy do pierwszego przykładu zastosowania interfejsu SAX. Oczywiście, tak samo jak poprzednio, zaczniemy od zliczania wystąpień elementu KLIENT w dokumencie zamówienia.xml --> [Author:T] . Oto nasz plik roboczy XML:

<?xml version="1.0" encoding="iso-8859-2"?>

<DOKUMENT>

<KLIENT STATUS="Rzetelny kredytobiorca">

<IMIĘNAZWISKO>

<NAZWISKO>Smith</NAZWISKO>

<IMIĘ>Sam</IMIĘ>

</IMIĘNAZWISKO>

<DATA>15 października 2001</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>Pomidory</PRODUKT>

<ILOŚĆ>8</ILOŚĆ>

<CENA>5zł</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>Pomarańcze</PRODUKT>

<ILOŚĆ>24</ILOŚĆ>

<CENA>9.98zł</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

<KLIENT STATUS="Kredytobiorca niesolidny">

<IMIĘNAZWISKO>

<NAZWISKO>Jones</NAZWISKO>

<IMIĘ>Polly</IMIĘ>

</IMIĘNAZWISKO>

<DATA>20 października 2001</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>Chleb</PRODUKT>

<ILOŚĆ>12</ILOŚĆ>

<CENA>28.80zł</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>Jabłka</PRODUKT>

<ILOŚĆ>6</ILOŚĆ>

<CENA>6.00zł</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

<KLIENT STATUS="Rzetelny kredytobiorca">

<IMIĘNAZWISKO>

<NAZWISKO>Weber</NAZWISKO>

<IMIĘ>Bill</IMIĘ>

</IMIĘNAZWISKO>

<DATA>25 października 2001</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>Asparagus</PRODUKT>

<ILOŚĆ>12</ILOŚĆ>

<CENA>11.90zł</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>Sałata</PRODUKT>

<ILOŚĆ>6</ILOŚĆ>

<CENA>31.50zł</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

</DOKUMENT>

Nasz nowy program oprzemy na nowej klasie FirstParserSAX. Będziemy potrzebować obiektu tej klasy, aby przekazać go parserowi SAX, aby ten z kolei mógł wywoływać metody przekazanego obiektu przy napotykaniu elementów, początku dokumentu, końca dokumentu i tak dalej. Zaczniemy od utworzenia obiektu klasy FirstParserSAX o nazwie SAXHandler:

import org.xml.sax.*;

import org.apache.xerces.parsers.SAXParser;

public class FirstParserSAX

{

public static void main(String[] args)

{

FirstParserSAX SAXHandler = new FirstParserSAX();

.

.

.

}

}

Następnie tworzymy parser SAX, którego będziemy używać. Obiekt parsera jest obiektem klasy org.apache.xerces.parsers.SAXParser (tak jak w poprzednim rozdziale parser DOM był obiektem klasy org.apache.xerces.parsers.DOMParser). Aby użyć klasy SAXParser, importujemy odpowiednie klasy z pakietu org.xml.sax i możemy utworzyć nowy parser SAX o nazwie parser:

import org.xml.sax.*;

import org.apache.xerces.parsers.SAXParser;

public class FirstParserSAX

{

public static void main(String[] args)

{

FirstParserSAX SAXHandler = new FirstParserSAX();

SAXParser parser = new SAXParser();

.

.

.

}

}

Klasa SAXParser pochodzi z klasy XMLParser, która z kolei pochodzi z klasy java.lang.Object:

java.lang.Object

|

+--org.apache.xerces.framework.XMLParser

|

+--org.apache.xerces.parsers.SAXParser

Konstruktorem klasy SAXParser jest SAXParser(); metody klasy SAXParser zestawiono w tabeli 9.1. Konstruktorem klasy XMLParser jest protectedXMLParser().

Tabela 9.1.
Metody SAXParser

Metoda

Opis

void attlistDecl(int elementTypeIndex, int attrNameIndex, int attType, java.lang.String enumString, int attDefaultType, int attDefaultValue)

Metoda zwrotna deklaracji atrybutu.

void characters(char[] ch, int start, int length)

Metoda zwrotna znaków z tablicy znakowej.

void comment(int dataIndex)

Metoda zwrotna komentarzy.

void commentInDTD(int dataIndex)

Metoda zwrotna komentarzy występujących w DTD.

void elementDecl(int elementTypeIndex, XMLValidator.ContentSpec contentSpec)

Metoda zwrotna deklaracji elementu.

void endDATA()

Metoda zwrotna końca sekcji CDATA.

void endDocument()

Metoda zwrotna końca dokumentu.

void endDTD()

Wywoływana przy napotkaniu końca DTD.

void endElement(int elementTypeIndex)

Metoda zwrotna końca elementów.

void endEntityReference(int entityName, int entityType, int entityContext)

Metoda zwrotna końca odwołania do encji.

void endNameSpaceDeclScope(int prefix)

Metoda zwrotna końca zakresu deklaracji przestrzeni nazw.

void externalEntityDecl(int entityName, int publicId, int systemId)

Metoda zwrotna deklaracji parsowanej zewnętrznej encji parametrycznej.

void externalPEDecl(int entityName, int publicId, int systemId)

Metoda zwrotna deklaracji zewnętrznej encji parametrycznej.

ContentHandler getContentHandler()

Instrukcja pobierająca treść procedury obsługi zdarzenia.

protected DeclHandler getDeclHandler()

Instrukcja pobierająca procedurę obsługi zdarzenia deklaracji DTD.

DTDHandler getDTDHandler()

Instrukcja pobierająca bieżącą procedurę obsługi zdarzenia.

boolean getFeature(java.lang.String featureId)

Pobiera bieżący stan wskazanej cechy parsera.

java.lang.String[] getFeaturesRecognized()

Pobiera listę cech rozpoznawanych przez parser.

protected LexicalHandler getLexicalHandler()

Instrukcja pobierająca procedurę obsługi leksykalnej parsera.

protected boolean getNamespacePrefixes()

Instrukcja pobierająca wartość przedrostków przestrzeni nazw.

java.lang.String[] getPropertiesRecognized()

Pobiera listę właściwości rozpoznawanych przez parser.

java.lang.Object getProperty(java.lang.String propertyId)

Pobiera wartość właściwości parsera SAX2.

void ignorableWhitespace(int dataIndex)

Metoda zwrotna białych znaków, które można pominąć.

protected void init()

Inicjalizuje parser na stan sprzed parsowania.

void internalEntityDecl(int entityName, int entityValue)

Metoda zwrotna deklaracji ogólnej encji wewnętrznej.

void internalPEDecl(int entityName, int entityValue)

Metoda zwrotna deklaracji wewnętrznej encji parametrycznej.

void internalSubset(int internalSubset)

Obsługa internalSubsets z DOM Level 2.

void notationDecl(int notationName, int publicId, int systemId)

Metoda zwrotna deklaracji notacji.

void processingInstruction(int piTarget, int piData)

Metoda zwrotna instrukcji przetwarzania.

void processingInstructionInDTD(int piTarget, int piData)

Metoda zwrotna instrukcji przetwarzania występującej w DTD.

void setContentHandler(ContentHandler handler)

Instrukcja ustawiająca procedurę obsługi treści, co umożliwia aplikacji obsługę zdarzeń SAX.

protected void setDeclHandler(DeclHandler handler)

Instrukcja ustawiająca procedurę obsługi deklaracji DTD.

void setDocumentHandler(DocumentHandler handler)

Instrukcja ustawiająca procedurę obsługi dokumentu.

void setDTDHandler(DTDHandler handler)

Instrukcja ustawiająca procedurę obsługi DTD.

void setFeature(java.lang.String featureId, boolean state)

Ustawia stan dowolnej cechy parsera SAX2.

protected void setLexicalHandler(LexicalHandler handler)

Instrukcja ustawiająca procedurę obsługi leksykalnej.

protected void setNamespacePrefixes(boolean process)

Określa, jak parser zgłasza nazwy prefiksowane oraz czy zgłaszane są atrybuty xmlns.

void setProperty(java.lang.String propertyId, java.lang.Object value)

Ustawia wartość właściwości parsera SAX2.

void startCDATA()

Metoda zwrotna początku sekcji CDATA.

void startDocument(int versionIndex, int encodingIndex, int standAloneIndex)

Metoda zwrotna początku dokumentu.

void startDTD(int rootElementType, int publicId, int systemId)

Metoda zwrotna początku DTD.

void startElement(int elementType, XMLAttrList xmlAttrList, int attrListIndex)

Metoda zwrotna początku elementu.

void startEntityReference(int entityName, int entityType, int entityContext)

Metoda zwrotna początku odwołania do encji.

void startNamespaceDeclScope(int prefix, int uri)

Metoda zwrotna początku zakresu deklaracji przestrzeni nazw.

void unparsedEntityDecl(int entityName, int publicId, int systemId, int notationName)

Metoda zwrotna deklaracji encji nie parsowanej.

Tabela 9.2 jest dokładny powtórzeniem tabeli 8.2, wobec czego nie widzę potrzeby tutaj jej powtarzać z nowym numerem. Proponuję ją całkowicie pominąć, zamiast niej wstawić akapit podany poniżej. W związku z tym oczywiście przenumerowuję dalsze tabele tego rozdziału - jeden numer „w dół”.

Metody klasy XMLParser były już przedstawione w poprzednim rozdziale i nie będą tutaj powtarzane. W razie potrzeby zajrzyj do tabeli 8.2 z rozdziału 8.

Koniec proponowanego akapitu.

Mamy już zatem obiekt SAXParser, pozostaje tylko powiązać obiekt SAXHandler z tym obiektem, aby w przypadku wystąpienia poszczególnych zdarzeń parsowania - takich jak początek dokumentu, rozpoczęcie nowego elementu i tak dalej, wywoływane były metody obiektu SAXHandler. Parsery SAX wywołują wiele metod, na przykład dla elementów, instrukcji przetwarzania, deklaracji w DTD i tak dalej. Metody wywoływane przez parser SAX w celu poinformowania o wystąpieniu jakiejś sytuacji nazywamy metodami zwrotnymi, muszą one być w parserze SAX zarejestrowane.

Cztery podstawowe interfejsy SAX obsługują różne metody zwrotne:

Powyższe interfejsy zawierają wiele metod zwrotnych; jeśli chcesz używać danego interfejsu, musisz zaimplementować jego wszystkie metody. Praca z pakietem XML for Java jest pod tym względem łatwiejsza, gdyż tworzona jest klasa DefaultHandler zawierająca implementację domyślną wszystkich potrzebnych metod zwrotnych. Konstruktorem klasy DefaultHandler jest DefaultHandler(); metody tej klasy zestawiono w tabeli 9.2.

Tabela --> 9.2.[Author:T]
Metody DefaultHandler

Metoda

Opis

void characters(char[] ch, int start, int length)

Metoda zwrotna danych znakowych z elementu.

void endDocument()

Metoda zwrotna końca dokumentu.

void endElement(java.lang.String uri, java.lang.String localName, java.lang.String rawName)

Metoda zwrotna końca elementu.

void endPrefixMapping(java.lang.String prefix

Metoda zwrotna końca odwzorowania przestrzeni nazw.

void error(SAXParseException e)

Metoda zwrotna błędu parsera, który to błąd może zostać naprawiony.

void fatalError(SAXParseException e)

Metoda zwrotna błędu krytycznego parsowania XML.

void ignorableWhitespace(char[] ch, int start, int length)

Metoda zwrotna pomijalnych białych znaków w treści elementu.

void notationDecl(java.lang.String name, java.lang.String publicId, java.lang.String systemId)

Metoda zwrotna deklaracji notacji.

void processingInstruction(java.lang.String target, java.lang.String data)

Metoda zwrotna instrukcji przetwarzania.

InputSource resolveEntity(java.lang.String publicId, java.lang.String systemId)

Metoda zwrotna encji zewnętrznej.

void setDocumentLocator(Locator locator)

Ustawia obiekt klasy Locator dla zdarzeń dokumentu.

void skippedEntity(java.lang.String name)

Metoda zwrotna pominiętej encji.

void startDocument()

Metoda zwrotna początku dokumentu.

void startElement(java.lang.String uri, java.lang.String localName, java.lang.String rawName, Attributes attributes)

Metoda zwrotna początku elementu.

void startPrefixMapping(java.lang.String prefix, java.lang.String uri)

Metoda zwrotna początku odwzorowania przestrzeni nazw.

void unparsedEntityDecl(java.lang.String name, java.lang.String publicId, java.lang.String systemId, java.lang.String notationName)

Metoda zwrotna deklaracji encji nieparsowanej.

void warning(SAXParseException e)

Metoda zwrotna ostrzeżenia parsera.

Jeśli swój program oprzesz na interfejsie DefaultHandler, musisz zaimplementować tylko potrzebne Ci metody zwrotne, zatem główna klasa naszego programu, FirstParserSAX, pochodzić będzie od interfejsu DefaultHandler, co zaznacza się słowem kluczowym extends:

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class FirstParserSAX extends DefaultHandler

{

public static void main(String[] args)

{

FirstParserSAX SAXHandler = new FirstParserSAX();

SAXParser parser = new SAXParser();

.

.

.

}

}

Teraz możemy już naszą klasę FirstParserSAX zarejestrować w parserze SAX. Nie będziemy się martwić o obsługę DTD ani o rozwijanie encji zewnętrznych, ograniczymy się do obsługi treści dokumentu i błędów metodami setContentHandler i setErrorHandler:

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class FirstParserSAX extends DefaultHandler

{

public static void main(String[] args)

{

FirstParserSAX SAXHandler = new FirstParserSAX();

SAXParser parser = new SAXParser();

parser.setContentHandler(SAXHandler);

parser.setErrorHandler(SAXHandler);

.

.

.

}

}

W ten sposób zarejestrowaliśmy obiekt SAXHandler, który będzie teraz odbierał zdarzenia SAX treści dokumentu i błędów. Dodamy metody, które będą wywoływane po zakończeniu działania metody main.

Do parsowania dokumentu XML użyjemy metody parse obiektu parser. Umożliwimy użytkownikowi podanie nazwy parsowanego dokumentu w wierszu poleceń, jak args[0] (pamiętaj, że nie musi to być nazwa pliku lokalnego, może to być także adres URL dokumentu znajdującego się gdzieś w Internecie). Metoda parse może generować wyjątki, co oznacza, że trzeba ją zamknąć w bloku try i dopisać odpowiadający mu blok catch:

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class FirstParserSAX extends DefaultHandler

{

public static void main(String[] args)

{

try {

FirstParserSAX SAXHandler = new FirstParserSAX();

SAXParser parser = new SAXParser();

parser.setContentHandler(SAXHandler);

parser.setErrorHandler(SAXHandler);

parser.parse(args[0]);

}

catch (Exception e) {

e.printStackTrace(System.err);

}

}

}

W ten sposób przygotowaliśmy już metodę main, teraz trzeba dopisać metody wywoływane podczas parsowania dokumentu XML. Naszym celem jest określenie liczby wystąpień elementu KLIENT, wobec czego implementację metody startElement zaczniemy następująco:

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class FirstParserSAX extends DefaultHandler

{

public void startElement(String uri, String localName,

String rawName, Attributes attributes)

{

.

.

.

}

.

.

.

Metoda startElement wywoływana jest za każdym razem, kiedy parser SAX napotyka początek elementu, natomiast metoda endElement jest wywoływana, kiedy parser napotyka koniec elementu.

Zwróć uwagę na to, że metoda startElement przetwarza dwie nazwy elementu: localName i rawName. Podczas przetwarzania z przestrzeniami nazw używa się parametru localName, gdyż zawiera on nazwę samego elementu, bez przedrostka przestrzeni nazw. Parametr rawName zawiera pełną, kwalifikowaną nazwę elementu wraz z przedrostkiem.

Zamierzamy zliczać wystąpienia elementu KLIENT, wobec czego skorzystamy z parametru rawName - jeśli jego wartością będzie „KLIENT”, zwiększymy wartość zmiennej customerCount:

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class FirstParserSAX extends DefaultHandler

{

int customerCount = 0;

public void startElement(String uri, String localName,

String rawName, Attributes attributes)

{

if (rawName.equals("KLIENT")) {

customerCount++;

}

}

.

.

.

Skąd wiadomo, że dokument już się skończył i że nie ma już elementów KLIENT do zliczania? Używa się metody endDocument wywoływanej przy napotkaniu końca dokumentu, w tej metodzie wyświetlamy odpowiedź dla użytkownika:

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class FirstParserSAX extends DefaultHandler

{

int customerCount = 0;

public void startElement(String uri, String localName,

String rawName, Attributes attributes)

{

if (rawName.equals("KLIENT")) {

customerCount++;

}

}

public void endDocument()

{

System.out.println("Dokument zawiera " +

customerCount + " element. KLIENT.");

}

public static void main(String[] args)

{

try {

FirstParserSAX SAXHandler = new FirstParserSAX();

SAXParser parser = new SAXParser();

parser.setContentHandler(SAXHandler);

parser.setErrorHandler(SAXHandler);

parser.parse(args[0]);

}

catch (Exception e) {

e.printStackTrace(System.err);

}

}

}

Teraz już program można skompilować i uruchomić:

%java FirstParserSAX zamówienia.xml

Dokument zawiera 3 element. KLIENT.

I tak oto zaczęliśmy naszą pracę z pakietem SAX.

Wyświetlanie całego dokumentu

W następnym przykładzie, jak w rozdziale poprzednim, napiszemy program parsujący i wyświetlający cały dokument z wcięciami poszczególnych elementów, instrukcji przetwarzania i tak dalej, a także pokazujący atrybuty i ich wartości. Tym razem jednak zamiast metod DOM użyjemy metod SAX. Program nazwiemy IndentingParserSAX.java; jeśli przekażemy mu nazwę pliku zamówienia.xml, wyświetlony zostanie cały dokument z odpowiednimi wcięciami.

Zaczynamy od umożliwienia użytkownikowi wskazania dokumentu który ma być parsowany, następnie dokument parsujemy. Z metody main wywołamy nową metodę displayDocument, która wypełni tablicę displayStrings sformatowanymi wierszami dokumentu, następnie metoda main tę tablicę wyświetli:

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class IndentingParserSAX

{

public static void displayDocument(String uri)

{

.

.

.

}

public static void main(String[] args)

{

displayDocument(args[0]);

for(int index = 0; index < numberDisplayLines; index++){

System.out.println(displayStrings[index]);

}

}

}

W metodzie displayDocument utworzymy parser SAX i zarejestrujemy obiekt głównej klasy programu, aby SAX w odpowiedzi na zdarzenia wywoływał jego metody:

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class IndentingParserSAX

{

public static void displayDocument(String uri)

{

try {

IndentingParserSAX SAXHandler = new IndentingParserSAX();

SAXParser parser = new SAXParser();

parser.setContentHandler( --> (ContentHandler)[Author:T] SAXHandler);

parser.setErrorHandler((ErrorHandler) SAXHandler);

parser.parse(uri);

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

public static void main(String[] args)

{

displayDocument(args[0]);

for(int index = 0; index < numberDisplayLines; index++){

System.out.println(displayStrings[index]);

}

}

}

Teraz pozostało już tylko utworzyć różne metody wywoływane w odpowiedzi na zdarzenia generowane przez SAX, zaczniemy od początku dokumentu.

Obsługa początku dokumentu

Kiedy parser SAX napotyka początek parsowanego dokumentu, wywołuje metodę startDocument. Metoda ta nie otrzymuje żadnych parametrów, zatem wystarczy w niej wyświetlić deklarację XML. Tak jak w poprzednim rozdziale, wszystkie gotowe fragmenty dokumentu będziemy zapisywać w tablicy displayStrings obiektów String, a bieżącą pozycję będziemy zapisywać w zmiennej numberDisplayLines. Aktualne wcięcie będziemy zapisywać w obiekcie indent klasy String. Użycie tablicy napisów ułatwi przekształcenie programu tak, aby mógł wyświetlać dane w oknie Javy.

Oto kod obsługujący wyświetlenie deklaracji XML na początku dokumentu:

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class IndentingParserSAX

{

static String displayStrings[] = new String[1000];

static int numberDisplayLines = 0;

static String indent = "";

public static void displayDocument(String uri)

{

try {

IndentingParserSAX SAXHandler = new IndentingParserSAX();

SAXParser parser = new SAXParser();

parser.setContentHandler((ContentHandler) SAXHandler);

parser.setErrorHandler((ErrorHandler) SAXHandler);

parser.parse(uri);

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

public void startDocument()

{

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] +=

"<?xml version=\"1.0\" encoding=\"iso-8859-2\"?>";

numberDisplayLines++;

}

.

.

.

public static void main(String[] args)

{

displayDocument(args[0]);

for(int index = 0; index < numberDisplayLines; index++){

System.out.println(displayStrings[index]);

}

}

}

Następnie zajmiemy się instrukcjami przetwarzania.

Obsługa instrukcji przetwarzania

Obsługę instrukcji przetwarzania umożliwia metoda zwrotna processingInstruction. Wywoływana jest z dwoma parametrami: samą instrukcją przetwarzania i jej danymi. Jeśli na przykład cała instrukcja przetwarzania w dokumencie ma postać <?xml-stylesheet type="text/css" href="style.css"?>, sama instrukcja to xml-stylesheet, a jej dane to type="text/css" href="style.css".

Poniżej pokazano kod metody processingInstruction, która obsługuje te metody; zwróć uwagę na to, że przed dodaniem danych instrukcji sprawdzamy, czy instrukcja w ogóle jakieś dane ma --> :[Author:T]

public void processingInstruction(String target, String data)

{

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<?";

displayStrings[numberDisplayLines] += target;

if(data != null && data.length() > 0) {

displayStrings[numberDisplayLines] += data;

}

displayStrings[numberDisplayLines] += "?>";

numberDisplayLines++;

}

Obsługa początku elementu

Początek elementu można obsłużyć metodą startElement. Skoro mamy do czynienia z nowym elementem, dodajemy do wcięcia cztery spacje (będą używane przy przetwarzaniu potomków bieżącego elementu), następnie wyświetlamy nazwę używając parametru rawName:

public void startElement(String uri, String localName,

String rawName, Attributes attributes)

{

displayStrings[numberDisplayLines] = indent;

indent += " ";

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += rawName;

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

}

Powyższy kod wystarczy do wyświetlenia znacznika początkowego elementu, ale co z elementami mającymi atrybuty?

Obsługa atrybutów

Jednym z argumentów przekazywanych metodzie startElement jest obiekt odpowiadający interfejsowi Attributes:

public void startElement(String uri, String localName,

String rawName, Attributes attributes)

{

.

.

.

}

Obiekt ten udostępnia atrybuty elementu, jego metody zestawiono w tabeli 9.3. Do atrybutów opisanych w tym interfejsie można dostać się korzystając ze wskaźnika, nazwy lub nazwy kwalifikowanej przestrzenią nazw.

Tabela 9.3.
Metody interfejsu Attributes

Metoda

Opis

int getIndex(java.lang.String rawName)

Pobiera wskaźnik atrybutu wskazanego nazwą bez przestrzeni nazw.

int getIndex(java.lang.String uri, java.lang.String localPart

Pobiera wskaźnik atrybutu na podstawie przestrzeni nazw i nazwy lokalnej.

int getLength()

Pobiera liczbę atrybutów z listy.

java.lang.String getLocalName(int index)

Pobiera nazwę lokalną atrybutu o podanym indeksie.

java.lang.String getRawName(int index)

Pobiera nazwę atrybutu o podanym indeksie.

java.lang.String getType(int index)

Pobiera typ atrybutu o podanym indeksie.

java.lang.String getType(java.lang.String rawName)

Pobiera typ atrybutu według samej nazwy.

java.lang.String getType(java.lang.String uri, java.lang.String localName)

Pobiera typ atrybutu według przestrzeni nazw i nazwy lokalnej.

java.lang.String getURI(int index)

Pobiera adres URI przestrzeni nazw atrybutu według indeksu tego atrybutu.

java.lang.String getValue(int index)

Pobiera wartość atrybutu o podanym indeksie.

java.lang.String getValue(java.lang.String rawName)

Pobiera wartość atrybutu według jego nazwy.

java.lang.String getValue(java.lang.String uri, java.lang.String localName)

Pobiera wartość atrybutu według przestrzeni nazw i nazwy lokalnej.

Liczbę atrybutów określamy metodą getLength obiektu Attributes, następnie nazwy i wartości kolejnych atrybutów pobieramy za pomocą metod getRawName i getValue, do poszczególnych atrybutów odwołujemy się za pomocą indeksów. Zwróć uwagę, że zaczynamy od sprawdzenia, czy element ma w ogóle jakieś atrybuty - sprawdzamy, czy wartość parametru attributes jest różna od null:

public void startElement(String uri, String localName,

String rawName, Attributes attributes)

{

displayStrings[numberDisplayLines] = indent;

indent += " ";

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += rawName;

if (attributes != null) {

int numberAttributes = attributes.getLength();

for (int loopIndex = 0; loopIndex < numberAttributes; loopIndex++) {

displayStrings[numberDisplayLines] += ' ';

displayStrings[numberDisplayLines] +=

attributes.getRawName(loopIndex);

displayStrings[numberDisplayLines] += "=\"";

displayStrings[numberDisplayLines] +=

attributes.getValue(loopIndex);

displayStrings[numberDisplayLines] += '"';

}

}

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

}

I to tyle, jeśli chodzi o atrybuty.

Obsługa tekstu

Wiele elementów dokumentu zamówienia.xml zawiera tekst: IMIĘ, NAZWISKO i inne. Do wyświetlania tekstu używamy metody zwrotnej characters.

Metoda ta jest wywoływana z trzema argumentami: tablicą elementów typu char zawierającą tekst, wskaźnikiem początkowym tablicy oraz długością tekstu. Jeśli element zawiera tylko węzeł tekstowy, wskaźnik początkowy jest zawsze równy 0.

Żeby obsłużyć tekst znajdujący się wewnątrz elementów, zaimplementujemy metodę characters konwertującą tablicę znakową na obiekt String o nazwie characterData - zwróć uwagę na metodę trim klasy String używaną do usunięcia spacji wiodących i końcowych:

public void characters(char characters[], int start, int length)

{

String characterData = (new String(characters, start, length)).trim();

.

.

.

}

Aby usunąć tekst odpowiadający wcięciom, czyli spacjom początkowym występującym w dokumencie zamówienia.xml, dodamy instrukcję if i dopiero potem będziemy dodawać sam tekst do wyniku:

public void characters(char characters[], int start, int length)

{

String characterData = (new String(characters, start, length)).trim();

if(characterData.indexOf("\n") < 0 && characterData.length() > 0) {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += characterData;;

numberDisplayLines++;

}

}

Zatem z tekstem już sobie poradziliśmy. Domyślnie parser SAX przetwarza także białe znaki z dokumentu użyte do wizualizacji wcięć.

Obsługa pomijalnych białych znaków

Jak zatem można nakazać pominięcie zbędnych białych znaków? W przypadku parsera SAX jest to prostsze niż w parserze DOM. Parser SAX musi wiedzieć, jaki tekst ma być pomijany, zatem trzeba podać mu gramatykę dokumentu zamówienia.xml - na przykład w postaci DTD:

<?xml version="1.0" encoding="iso-8859-2" standalone="yes"?>

<!DOCTYPE DOKUMENT [

<!ELEMENT DOKUMENT (KLIENT)*>

<!ELEMENT KLIENT (IMIĘNAZWISKO,DATA,ZAMÓWIENIA)>

<!ELEMENT IMIĘNAZWISKO (NAZWISKO,IMIĘ)>

<!ELEMENT NAZWISKO (#PCDATA)>

<!ELEMENT IMIĘ (#PCDATA)>

<!ELEMENT DATA (#PCDATA)>

<!ELEMENT ZAMÓWIENIA (POZYCJA)*>

<!ELEMENT POZYCJA (PRODUKT,ILOŚĆ,CENA)>

<!ELEMENT PRODUKT (#PCDATA)>

<!ELEMENT ILOŚĆ (#PCDATA)>

<!ELEMENT CENA (#PCDATA)>

]>

<DOKUMENT>

<KLIENT STATUS="Rzetelny kredytobiorca">

<IMIĘNAZWISKO>

<NAZWISKO>Smith</NAZWISKO>

<IMIĘ>Sam</IMIĘ>

</IMIĘNAZWISKO>

<DATA>15 października 2001</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>Pomidory</PRODUKT>

<ILOŚĆ>8</ILOŚĆ>

<CENA>5zł</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>Pomarańcze</PRODUKT>

<ILOŚĆ>24</ILOŚĆ>

<CENA>9.98zł</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

<KLIENT STATUS="Kredytobiorca niesolidny">

<IMIĘNAZWISKO>

<NAZWISKO>Jones</NAZWISKO>

<IMIĘ>Polly</IMIĘ>

</IMIĘNAZWISKO>

<DATA>20 października 2001</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>Chleb</PRODUKT>

<ILOŚĆ>12</ILOŚĆ>

<CENA>28.80zł</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>Jabłka</PRODUKT>

<ILOŚĆ>6</ILOŚĆ>

<CENA>6.00zł</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

<KLIENT STATUS="Rzetelny kredytobiorca">

<IMIĘNAZWISKO>

<NAZWISKO>Weber</NAZWISKO>

<IMIĘ>Bill</IMIĘ>

</IMIĘNAZWISKO>

<DATA>25 października 2001</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>Asparagus</PRODUKT>

<ILOŚĆ>12</ILOŚĆ>

<CENA>11.90zł</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>Sałata</PRODUKT>

<ILOŚĆ>6</ILOŚĆ>

<CENA>31.50zł</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

</DOKUMENT>

Teraz przy napotkaniu pomijalnych znaków białych (takich jak spacje tworzące wcięcia elementów) nie będzie wywoływał metody zwrotnej characters, natomiast wywoła w takiej sytuacji metodę ignorableWhitespace. Oznacza to, że można wykomentować instrukcję if użytą uprzednio do odrzucania pomijalnych białych znaków:

public void characters(char characters[], int start, int length)

{

String characterData = (new String(characters, start, length)).trim();

//if(characterData.indexOf("\n") < 0 && characterData.length() > 0) {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += characterData;;

numberDisplayLines++;

//}

}

Jeśli zatem chcesz, aby zbędne białe znaki były pomijane, wystarczy poinformować parser SAX, które z nich są faktycznie zbędne; zrobić to można za pomocą DTD.

Zwróć uwagę na to, że możesz zapisać tak metodę ignorableWhitespace, aby traktowała pomijane białe znaki tak, jak jest to akurat potrzebne. Można ich wcale nie pomijać, jeśli się je przekaże metodzie zwrotnej characters:

public void ignorableWhitespace(char characters[], int start, int length)

{

characters(characters, start, length);

}

Domykanie znaczników

Jak dotąd obsłużyliśmy już początki wszystkich elementów za każdym razem zwiększając wcięcie, aby prawidłowo wyświetlone zostały elementy potomne. Musimy jeszcze wyświetlić znaczniki końcowe tym razem zmniejszając wielkość wcięcia. Robimy to stosując metodę zwrotną endElement, która jest wywoływana za każdym razem, kiedy parser SAX znajduje koniec elementu:

public void endElement(String uri, String localName, String rawName)

{

indent = indent.substring(0, indent.length() - 4);

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "</";

displayStrings[numberDisplayLines] += rawName;

displayStrings[numberDisplayLines] += '>';

numberDisplayLines++;

}

Na koniec zostało nam już tylko jedno: obsługa błędów i ostrzeżeń.

Obsługa błędów i ostrzeżeń

Interfejs DefaultHandler definiuje kilka metod zwrotnych pozwalających obsłużyć ostrzeżenia i błędy generowane przez parser. Metody te to warning do obsługi ostrzeżeń, error do obsługi błędów parsera oraz fatalError do obsługi błędów krytycznych - tak poważnych, że parser nie może kontynuować swojego działania.

Każda z tych metod otrzymuje jako parametr obiekt klasy SAXParseException zawierający metodę getMessage zwracającą komunikat ostrzeżenia lub błędu. Komunikaty te będziemy wyświetlać przy pomocy metody System.err.println, która wypisuje dane do strumienia błędów Javy, który domyślnie odpowiada konsoli:

public void warning(SAXParseException exception)

{

System.err.println("OSTRZEŻENIE! " +

exception.getMessage());

}

public void error(SAXParseException exception)

{

System.err.println("BŁĄD! " +

exception.getMessage());

}

public void fatalError(SAXParseException exception)

{

System.err.println("BŁĄD KRYTYCZNY! " +

exception.getMessage());

}

I to już wszystko, czego potrzebujemy. Wyniki parsowania dokumentu zamówienia.xml pokazano na rysunku 9.1. Dodatkowo użyto filtru more, aby zatrzymać wyświetlanie po każdym ekranie. Program działa, a cały jego kod znajdziesz na wydruku 9.1.

Rysunek 9.1.

Parsowanie dokumentu XML parserem SAX.

0x01 graphic

Wydruk 9.1.
IndentingParserSAX.java

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class IndentingParserSAX extends DefaultHandler

{

static String displayStrings[] = new String[1000];

static int numberDisplayLines = 0;

static String indent = "";

public static void displayDocument(String uri)

{

try {

IndentingParserSAX SAXHandler = new IndentingParserSAX();

SAXParser parser = new SAXParser();

parser.setContentHandler((ContentHandler) SAXHandler);

parser.setErrorHandler((ErrorHandler) SAXHandler);

parser.parse(uri);

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

public void startDocument()

{

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] +=

"<?xml version=\"1.0\" encoding=\"iso-8859-2\"?>";

numberDisplayLines++;

}

public void processingInstruction(String target, String data)

{

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<?";

displayStrings[numberDisplayLines] += target;

if(data != null && data.length() > 0) {

displayStrings[numberDisplayLines] += data;

}

displayStrings[numberDisplayLines] += "?>";

numberDisplayLines++;

}

public void startElement(String uri, String localName,

String rawName, Attributes attributes)

{

displayStrings[numberDisplayLines] = indent;

indent += " ";

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += rawName;

if (attributes != null) {

int numberAttributes = attributes.getLength();

for (int loopIndex = 0; loopIndex < numberAttributes; loopIndex++) {

displayStrings[numberDisplayLines] += ' ';

displayStrings[numberDisplayLines] +=

attributes.getRawName(loopIndex);

displayStrings[numberDisplayLines] += "=\"";

displayStrings[numberDisplayLines] +=

attributes.getValue(loopIndex);

displayStrings[numberDisplayLines] += '"';

}

}

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

}

public void characters(char characters[], int start, int length)

{

String characterData = (new String(characters, start, length)).trim();

if(characterData.indexOf("\n") < 0 && characterData.length() > 0) {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += characterData;;

numberDisplayLines++;

}

}

public void ignorableWhitespace(char characters[], int start, int length)

{

//characters(characters, start, length);

}

public void endElement(String uri, String localName, String rawName)

{

indent = indent.substring(0, indent.length() - 4);

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "</";

displayStrings[numberDisplayLines] += rawName;

displayStrings[numberDisplayLines] += '>';

numberDisplayLines++;

}

public void warning(SAXParseException exception)

{

System.err.println("OSTRZEŻENIE! " +

exception.getMessage());

}

public void error(SAXParseException exception)

{

System.err.println("BŁĄD! " +

exception.getMessage());

}

public void fatalError(SAXParseException exception)

{

System.err.println("BŁĄD KRYTYCZNY! " +

exception.getMessage());

}

public static void main(String[] args)

{

displayDocument(args[0]);

for(int index = 0; index < numberDisplayLines; index++){

System.out.println(displayStrings[index]);

}

}

}

Filtrowanie dokumentów XML

W poprzednim przykładzie wyświetlaliśmy cały dokument, jednak w procesie filtrowania można uzyskać tylko wybraną część dokumentu. Filtrowanie pozwala wybrać z dokumentu tylko te elementy, które są w danej chwili istotne.

Oto przykład nazwany searcherSAX.java. Umożliwiamy użytkownikowi wskazać przeszukiwany dokument i wskazać elementy, które mają być wybrane - oto jak wybrać elementy POZYCJA z dokumentu zamówienia.xml:

%java searcherSAX zamówienia.xml POZYCJA

Program taki nie będzie skomplikowany, szczególnie kiedy już mamy parser wzbogacający dokument o stosowne wcięcia. Pamiętaj jednak, że tym razem musimy wybrać nie tylko elementy wskazane przez użytkownika, ale także ich elementy potomne. Nowy program, searcherSAX.java, utworzymy na podstawie IndentingParserSAX.java. Wystarczy jedynie dodać kontrolę, kiedy element ma być pokazywany, a kiedy nie. Jeśli bieżący element jest elementem wybranym przez użytkownika, zmiennej logicznej printFlag przypiszemy wartość true:

public void startElement(String uri, String localName,

String rawName, Attributes attributes)

{

if (rawName.equals(searchFor)) {

printFlag = true;

}

.

.

.

}

Teraz można już sprawdzać, czy zmienna printFlag ma wartość true; jeśli tak, bieżący element wraz z atrybutami ma być wyświetlony:

public void startElement(String uri, String localName,

String rawName, Attributes attributes)

{

if (rawName.equals(searchFor)) {

printFlag = true;

}

if (printFlag) {

displayStrings[numberDisplayLines] = indent;

indent += " ";

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += rawName;

if (attributes != null) {

int numberAttributes = attributes.getLength();

for (int loopIndex = 0; loopIndex < numberAttributes; loopIndex++) {

displayStrings[numberDisplayLines] += ' ';

displayStrings[numberDisplayLines] +=

attributes.getRawName(loopIndex);

displayStrings[numberDisplayLines] += "=\"";

displayStrings[numberDisplayLines] +=

attributes.getValue(loopIndex);

displayStrings[numberDisplayLines] += '"';

}

}

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

}

}

To samo możemy zrobić z metodą zwrotną dodającą tekst, character:

public void characters(char characters[], int start, int length)

{

if (printFlag) {

String characterData = (new String(characters, start, length)).trim();

if(characterData.indexOf("\n") < 0 && characterData.length() > 0) {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += characterData;;

numberDisplayLines++;

}

}

}

Zwróć uwagę, że wartości zmiennej printFlag nie zmieniamy na false póki nie skończy się poszukiwany element, kiedy wyświetlony jest już on i jego elementy potomne. Dopiero po zakończeniu takiego elementu zerujemy zmienną printFlag:

public void endElement(String uri, String localName, String rawName)

{

if (printFlag) {

indent = indent.substring(0, indent.length() - 4);

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "</";

displayStrings[numberDisplayLines] += rawName;

displayStrings[numberDisplayLines] += '>';

numberDisplayLines++;

}

if (rawName.equals(searchFor)) {

printFlag = false;

}

}

Na tym nasza praca się kończy. Poniższe wywołanie wyszukuje elementy POZYCJA w dokumencie zamówienia.xml:

%java searcherSAX zamówienia.xml POZYCJA | more

Wyniki pokazano na rysunku 9.2., zaś cały program na wydruku 9.2.

Rysunek 9.2.

Filtrowanie dokumentu XML przy użyciu parsera SAX

0x01 graphic

Wydruk 9.2.
searcherSAX.java

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class searcherSAX extends DefaultHandler

{

static String displayStrings[] = new String[1000];

static int numberDisplayLines = 0;

static String indent = "";

static boolean printFlag;

static String searchFor;

public static void displayDocument(String uri)

{

try {

IndentingParserSAX SAXHandler = new IndentingParserSAX();

SAXParser parser = new SAXParser();

parser.setContentHandler((ContentHandler) SAXHandler);

parser.setErrorHandler((ErrorHandler) SAXHandler);

parser.parse(uri);

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

public void startDocument()

{

if(printFlag){

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] +=

"<?xml version=\"1.0\" encoding=\"iso-8859-2\"?>";

numberDisplayLines++;

}

}

public void processingInstruction(String target, String data)

{

if(printFlag){

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<?";

displayStrings[numberDisplayLines] += target;

if(data != null && data.length() > 0) {

displayStrings[numberDisplayLines] += data;

}

displayStrings[numberDisplayLines] += "?>";

numberDisplayLines++;

}

}

public void startElement(String uri, String localName,

String rawName, Attributes attributes)

{

if (rawName.equals(searchFor)) {

printFlag = true;

}

if (printFlag) {

displayStrings[numberDisplayLines] = indent;

indent += " ";

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += rawName;

if (attributes != null) {

int numberAttributes = attributes.getLength();

for (int loopIndex = 0; loopIndex < numberAttributes; loopIndex++) {

displayStrings[numberDisplayLines] += ' ';

displayStrings[numberDisplayLines] +=

attributes.getRawName(loopIndex);

displayStrings[numberDisplayLines] += "=\"";

displayStrings[numberDisplayLines] +=

attributes.getValue(loopIndex);

displayStrings[numberDisplayLines] += '"';

}

}

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

}

}

public void characters(char characters[], int start, int length)

{

if (printFlag) {

String characterData = (new String(characters, start, length)).trim();

if(characterData.indexOf("\n") < 0 && characterData.length() > 0) {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += characterData;;

numberDisplayLines++;

}

}

}

public void ignorableWhitespace(char characters[], int start, int length)

{

if(printFlag){

//characters(characters, start, length);

}

}

public void endElement(String uri, String localName, String rawName)

{

if (printFlag) {

indent = indent.substring(0, indent.length() - 4);

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "</";

displayStrings[numberDisplayLines] += rawName;

displayStrings[numberDisplayLines] += '>';

numberDisplayLines++;

}

if (rawName.equals(searchFor)) {

printFlag = false;

}

}

public void warning(SAXParseException exception)

{

System.err.println("OSTRZEŻENIE! " +

exception.getMessage());

}

public void error(SAXParseException exception)

{

System.err.println("BŁĄD! " +

exception.getMessage());

}

public void fatalError(SAXParseException exception)

{

System.err.println("BŁĄD KRYTYCZNY! " +

exception.getMessage());

}

public static void main(String[] args)

{

String arg = args[0];

searchFor = args[1];

displayDocument(args[0]);

for(int index = 0; index < numberDisplayLines; index++){

System.out.println(displayStrings[index]);

}

}

}

Tworzone dotąd przykłady dawały wyniki w postaci tekstu wyprowadzanego metodą System.out.println. Jednak jak już wspomniano w poprzednim rozdziale, dzisiaj niewiele przeglądarek działa w ten sposób - teraz zajmiemy się tworzeniem przeglądarek posiadających własne okienka.

Tworzenie przeglądarki okienkowej

Napisaliśmy już parser realizujący wcięcia, który wyświetlany tekst zapisywał w tablicy displayStrings, zatem łatwo będzie teraz wyświetlić ten tekst w oknie Javy, tak samo, jak to robiliśmy w poprzednim rozdziale. Utworzymy nowy przykład o nazwie browserSAX.java. Utworzymy tam obiekt nowej klasy AppFrame, jego konstruktorowi przekażemy displayStrings oraz liczbę wierszy do wyświetlenia, następnie wywołamy jego metodę show pokazującą nowe okno:

import java.awt.*;

import java.awt.event.*;

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class browserSAX extends DefaultHandler

{

.

.

.

public static void main(String[] args)

{

displayDocument(args[0]);

AppFrame f = new AppFrame(displayStrings, numberDisplayLines);

f.setSize(300, 500);

f.addWindowListener(new WindowAdapter() { public void

windowClosing(WindowEvent e) {System.exit(0);}});

f.show();

}

}

Klasa AppFrame pochodzi od klasy Frame Javy, wyświetla przekazany jej tekst:

class AppFrame extends Frame

{

String displayStrings[];

int numberDisplayLines;

public AppFrame(String[] d, int n)

{

displayStrings = d;

numberDisplayLines = n;

}

public void paint(Graphics g)

{

Font font;

font = new Font("Courier", Font.PLAIN, 12);

g.setFont(font);

FontMetrics fontmetrics = g.getFontMetrics(getFont());

int y = fontmetrics.getHeight();

for(int index = 0; index < numberDisplayLines; index++) {

y += fontmetrics.getHeight();

g.drawString(displayStrings[index], 5, y);

}

}

}

Działanie programu browserSAX pokazano na rysunku 9.3, gdzie w okienku Javy wyświetlony został plik zamówienia.xml. Całość kodu pokazano na wydruku 9.3.

Rysunek 8.3.

Przeglądarka graficzna stworzona przy użyciu parsera SAX

0x01 graphic

Wydruk 9.3.
browserSAX.java

import java.awt.*;

import java.awt.event.*;

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class browserSAX extends DefaultHandler

{

static String displayStrings[] = new String[1000];

static int numberDisplayLines = 0;

static String indent = "";

public static void displayDocument(String uri)

{

try {

IndentingParserSAX SAXHandler = new IndentingParserSAX();

SAXParser parser = new SAXParser();

parser.setContentHandler((ContentHandler) SAXHandler);

parser.setErrorHandler((ErrorHandler) SAXHandler);

parser.parse(uri);

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

public void startDocument()

{

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] +=

"<?xml version=\"1.0\" encoding=\"iso-8859-2\"?>";

numberDisplayLines++;

}

public void processingInstruction(String target, String data)

{

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<?";

displayStrings[numberDisplayLines] += target;

if(data != null && data.length() > 0) {

displayStrings[numberDisplayLines] += data;

}

displayStrings[numberDisplayLines] += "?>";

numberDisplayLines++;

}

public void startElement(String uri, String localName,

String rawName, Attributes attributes)

{

displayStrings[numberDisplayLines] = indent;

indent += " ";

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += rawName;

if (attributes != null) {

int numberAttributes = attributes.getLength();

for (int loopIndex = 0; loopIndex < numberAttributes; loopIndex++) {

displayStrings[numberDisplayLines] += ' ';

displayStrings[numberDisplayLines] +=

attributes.getRawName(loopIndex);

displayStrings[numberDisplayLines] += "=\"";

displayStrings[numberDisplayLines] +=

attributes.getValue(loopIndex);

displayStrings[numberDisplayLines] += '"';

}

}

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

}

public void characters(char characters[], int start, int length)

{

String characterData = (new String(characters, start, length)).trim();

if(characterData.indexOf("\n") < 0 && characterData.length() > 0) {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += characterData;;

numberDisplayLines++;

}

}

public void ignorableWhitespace(char characters[], int start, int length)

{

//characters(characters, start, length);

}

public void endElement(String uri, String localName, String rawName)

{

indent = indent.substring(0, indent.length() - 4);

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "</";

displayStrings[numberDisplayLines] += rawName;

displayStrings[numberDisplayLines] += '>';

numberDisplayLines++;

}

public void warning(SAXParseException exception)

{

System.err.println("OSTRZEŻENIE! " +

exception.getMessage());

}

public void error(SAXParseException exception)

{

System.err.println("BŁĄD! " +

exception.getMessage());

}

public void fatalError(SAXParseException exception)

{

System.err.println("BŁĄD KRYTYCZNY! " +

exception.getMessage());

}

public static void main(String[] args)

{

displayDocument(args[0]);

AppFrame f = new AppFrame(displayStrings, numberDisplayLines);

f.setSize(300, 500);

f.addWindowListener(new WindowAdapter() { public void

windowClosing(WindowEvent e) {System.exit(0);}});

f.show();

}

}

class AppFrame extends Frame

{

String displayStrings[];

int numberDisplayLines;

public AppFrame(String[] d, int n)

{

displayStrings = d;

numberDisplayLines = n;

}

public void paint(Graphics g)

{

Font font;

font = new Font("Courier", Font.PLAIN, 12);

g.setFont(font);

FontMetrics fontmetrics = g.getFontMetrics(getFont());

int y = fontmetrics.getHeight();

for(int index = 0; index < numberDisplayLines; index++) {

y += fontmetrics.getHeight();

g.drawString(displayStrings[index], 5, y);

}

}

}

Teraz, kiedy potrafimy już dokumenty XML parsować i wyświetlać w odrębnym oknie, równie dobrze możemy zacząć używać zwykłej grafiki. W następnym przykładzie utworzymy przeglądarkę graficzną, która nie będzie w ogóle pokazywała danych tekstowych, ale na podstawie dokumentu XML utworzy rysunek.

Tworzenie przeglądarki graficznej

W poprzednim rozdziale stosując parser DOM utworzyliśmy przeglądarkę wyświetlającą kółka. Pouczające będzie zrobienie tego samego przy wykorzystaniu parsera SAX, gdyż w ten sposób zobaczysz, jak pobrać tylko wartości potrzebnych atrybutów. Tym razem utworzymy przeglądarkę circlesSAX.java, która będzie potrafiła odczytywać dokumenty XML takie, jak pokazany poniżej kółka.xml, zawierające opis środków i promieni okręgów w atrybutach elementów OKRĄG:

<?xml version = "1.0" encoding="iso-8859-2"?>

<!DOCTYPE DOKUMENT [

<!ELEMENT DOKUMENT (OKRĄG|ELIPSA)*>

<!ELEMENT OKRĄG EMPTY>

<!ELEMENT ELIPSA EMPTY>

<!ATTLIST OKRĄG

X CDATA #IMPLIED

Y CDATA #IMPLIED

PROMIEŃ CDATA #IMPLIED>

<!ATTLIST ELIPSA

X CDATA #IMPLIED

Y CDATA #IMPLIED

SZEROKOŚĆ CDATA #IMPLIED

WYSOKOŚĆ CDATA #IMPLIED>

]>

<DOKUMENT>

<OKRĄG X='200' Y='160' PROMIEŃ='50' />

<OKRĄG X='170' Y='100' PROMIEŃ='15' />

<OKRĄG X='80' Y='200' PROMIEŃ='45' />

<OKRĄG X='200' Y='140' PROMIEŃ='35' />

<OKRĄG X='130' Y='240' PROMIEŃ='25' />

<OKRĄG X='270' Y='300' PROMIEŃ='45' />

<OKRĄG X='210' Y='240' PROMIEŃ='25' />

<OKRĄG X='60' Y='160' PROMIEŃ='35' />

<OKRĄG X='160' Y='260' PROMIEŃ='55' />

</DOKUMENT>

Tym razem cała sztuczka będzie polegała na pobraniu wartości atrybutów X, Y oraz PROMIEŃ. Wartości te zapisywać będziemy w tablicach odpowiednio x, y i radius. Pobieranie wartości atrybutów elementu w parserze SAX jest prostsze niż w parserze DOM. Metoda startElement otrzymuje jako parametr obiekt Attributes, wystarczy tylko użyć jego metody getValue podając nazwę potrzebnego parametru:

import java.awt.*;

import java.awt.event.*;

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class circlesSAX extends DefaultHandler

{

static int numberFigures = 0;

static int x[] = new int[100];

static int y[] = new int[100];

static int radius[] = new int[100];

.

.

.

public void startElement(String uri, String localName,

String rawName, Attributes attrs)

{

if (rawName.equals("OKRĄG")) {

x[numberFigures] = Integer.parseInt(attrs.getValue("X"));

y[numberFigures] = Integer.parseInt(attrs.getValue("Y"));

radius[numberFigures] =

Integer.parseInt(attrs.getValue("PROMIEŃ"));

numberFigures++;

}

}

.

.

.

public static void main(String[] args)

{

displayDocument(args[0]);

AppFrame f = new AppFrame(numberFigures, x, y, radius);

f.setSize(400, 400);

f.addWindowListener(new WindowAdapter() {public void

windowClosing(WindowEvent e) {System.exit(0);}});

f.show();

}

}

Mamy zatem już dane o okręgach, teraz należy wyświetlić w klasie AppFrame tak samo, jak robiliśmy to w poprzednim rozdziale:

class AppFrame extends Frame

{

int numberFigures;

int[] xValues;

int[] yValues;

int[] radiusValues;

public AppFrame(int number, int[] x, int[] y, int[] radius)

{

numberFigures = number;

xValues = x;

yValues = y;

radiusValues = radius;

}

public void paint(Graphics g)

{

for(int loopIndex = 0; loopIndex < numberFigures; loopIndex++){

g.drawOval(xValues[loopIndex], yValues[loopIndex],

radiusValues[loopIndex], radiusValues[loopIndex]);

}

}

}

To już wszystko; wyniki pokazano na rysunku 9.4 - wyświetlony został plik kółka.xml. Cały kod programu pokazano na wydruku 9.4.

Rysunek 8.4.

Graficzna przeglądarka XML zrealizowana przy użyciu parsera SAX

0x01 graphic

Wydruk 9.4.
circlesSAX.java

import java.awt.*;

import java.awt.event.*;

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class circlesSAX extends DefaultHandler

{

static int numberFigures = 0;

static int x[] = new int[100];

static int y[] = new int[100];

static int radius[] = new int[100];

public static void displayDocument(String uri)

{

try {

IndentingParserSAX SAXHandler = new IndentingParserSAX();

SAXParser parser = new SAXParser();

parser.setContentHandler((ContentHandler) SAXHandler);

parser.setErrorHandler((ErrorHandler) SAXHandler);

parser.parse(uri);

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

public void startElement(String uri, String localName,

String rawName, Attributes attrs)

{

if (rawName.equals("OKRĄG")) {

x[numberFigures] = Integer.parseInt(attrs.getValue("X"));

y[numberFigures] = Integer.parseInt(attrs.getValue("Y"));

radius[numberFigures] =

Integer.parseInt(attrs.getValue("PROMIEŃ"));

numberFigures++;

}

}

public void warning(SAXParseException exception)

{

System.err.println("OSTRZEŻENIE! " +

exception.getMessage());

}

public void error(SAXParseException exception)

{

System.err.println("BŁĄD! " +

exception.getMessage());

}

public void fatalError(SAXParseException exception)

{

System.err.println("BŁĄD KRYTYCZNY! " +

exception.getMessage());

}

public static void main(String[] args)

{

displayDocument(args[0]);

AppFrame f = new AppFrame(numberFigures, x, y, radius);

f.setSize(400, 400);

f.addWindowListener(new WindowAdapter() {public void

windowClosing(WindowEvent e) {System.exit(0);}});

f.show();

}

}

class AppFrame extends Frame

{

int numberFigures;

int[] xValues;

int[] yValues;

int[] radiusValues;

public AppFrame(int number, int[] x, int[] y, int[] radius)

{

numberFigures = number;

xValues = x;

yValues = y;

radiusValues = radius;

}

public void paint(Graphics g)

{

for(int loopIndex = 0; loopIndex < numberFigures; loopIndex++){

g.drawOval(xValues[loopIndex], yValues[loopIndex],

radiusValues[loopIndex], radiusValues[loopIndex]);

}

}

}

Nawigacja po dokumentach XML

Interfejs Node dostępny w parserze DOM zawiera wszystkie standardowe metody DOM W3C pozwalające nawigować po dokumencie: getPreviousSibling, getFirstChild, getLastChild i getParent. W przypadku parsera SAX rzecz wygląda inaczej, gdyż parser nie tworzy drzewa węzłów, zatem metody wyliczone powyżej nie mają zastosowania.

Jeśli chcesz znaleźć konkretny element, musisz zrobić to sam. W poprzednim rozdziale znajdowaliśmy nazwisko trzeciej osoby z pliku spotkania.xml:

<?xml version="1.0" encoding="iso-8859-2"?>

<SPOTKANIA>

<SPOTKANIE TYP="nieformalne">

<PRZEWODNICZĄCY>Ted Bond</PRZEWODNICZĄCY>

<TYTUŁ>XML w praktycznych zastosowaniach</TYTUŁ>

<NUMER>2079</NUMER>

<TEMAT>XML</TEMAT>

<DATA>6/1/2002</DATA>

<OSOBY>

<OSOBA STATUS="obecny">

<IMIĘ>Edward</IMIĘ>

<NAZWISKO>Samson</NAZWISKO>

</OSOBA>

<OSOBA STATUS="nieobecny">

<IMIĘ>Ernestyna</IMIĘ>

<NAZWISKO>Johnson</NAZWISKO>

</OSOBA>

<OSOBA STATUS="obecny">

<IMIĘ>Betty</IMIĘ>

<NAZWISKO>Richardson</NAZWISKO>

</OSOBA>

</OSOBY>

</SPOTKANIE>

</SPOTKANIA>

Nietrudno jest zrobić to samo stosując parser SAX, wymaga jednak to nieco więcej kodowania. Zaczniemy od odnalezienia trzeciego elementu OSOBA i ustawienia zmiennej thirdPersonFlag na true, kiedy taki element zostanie znaleziony:

public void startElement(String uri, String localName,

String rawName, Attributes attributes)

{

if(rawName.equals("OSOBA")) {

personCount++;

}

if(personCount == 3) {

thirdPersonFlag = true;

}

.

.

.

}

Kiedy parser SAX parsuje element IMIĘ trzeciej osoby, ustawiamy na true zmienną firstNameFlag; podczas parsowania elementu NAZWISKO trzeciej osoby ustawiamy na true zmienną lastNameFlag:

public void startElement(String uri, String localName,

String rawName, Attributes attributes)

{

if(rawName.equals("OSOBA")) {

personCount++;

}

if(personCount == 3) {

thirdPersonFlag = true;

}

if(rawName.equals("IMIĘ") && thirdPersonFlag) {

firstNameFlag = true;

}

if(rawName.equals("NAZWISKO") && thirdPersonFlag) {

firstNameFlag = false;

lastNameFlag = true;

}

}

Sprawdzając wartości zmiennych firstNameFlag i lastNameFlag możemy zapisać imię i nazwisko trzeciej osoby w metodzie zwrotnej character:

public void characters(char characters[], int start, int length)

{

String characterData = (new String(characters, start, length)).trim();

if(characterData.indexOf("\n") < 0 && characterData.length() > 0) {

if(firstNameFlag) {

firstName = characterData;

}

if(lastNameFlag) {

lastName = characterData;

}

}

}

Kiedy parser SAX zakończy pracę z trzecim elementem OSOBA, wyświetlimy nazwisko trzeciej osoby:

public void endElement(String uri, String localName, String rawName)

{

if(thirdPersonFlag && lastNameFlag){

System.out.println("Trzecia osoba: " + firstName + " " + lastName);

thirdPersonFlag = false;

firstNameFlag = false;

lastNameFlag = false;

}

}

W ten właśnie sposób wybiera się w parserze SAX konkretne elementy z dokumentu - trzeba po prostu poczekać, aż parser do odpowiedniego elementu dotrze. Oto wyniki działania naszego programu:

%java navSAX spotkania.xml

Trzecia osoba: Betty Richardson

Cały kod programu navSAX.java pokazano na wydruku 9.5.

import org.xml.sax.*;

import org.xml.sax.helpers.DefaultHandler;

import org.apache.xerces.parsers.SAXParser;

public class navSAX extends DefaultHandler

{

int personCount;

boolean thirdPersonFlag = false;

boolean firstNameFlag = false, lastNameFlag = false;

String firstName, lastName;

public static void displayDocument(String uri)

{

try {

IndentingParserSAX SAXHandler = new IndentingParserSAX();

SAXParser parser = new SAXParser();

parser.setContentHandler((ContentHandler) SAXHandler);

parser.setErrorHandler((ErrorHandler) SAXHandler);

parser.parse(uri);

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

public void startElement(String uri, String localName,

String rawName, Attributes attributes)

{

if(rawName.equals("OSOBA")) {

personCount++;

}

if(personCount == 3) {

thirdPersonFlag = true;

}

if(rawName.equals("IMIĘ") && thirdPersonFlag) {

firstNameFlag = true;

}

if(rawName.equals("NAZWISKO") && thirdPersonFlag) {

firstNameFlag = false;

lastNameFlag = true;

}

}

public void characters(char characters[], int start, int length)

{

String characterData = (new String(characters, start, length)).trim();

if(characterData.indexOf("\n") < 0 && characterData.length() > 0) {

if(firstNameFlag) {

firstName = characterData;

}

if(lastNameFlag) {

lastName = characterData;

}

}

}

public void endElement(String uri, String localName, String rawName)

{

if(thirdPersonFlag && lastNameFlag){

System.out.println("Trzecia osoba: " + firstName + " " + lastName);

thirdPersonFlag = false;

firstNameFlag = false;

lastNameFlag = false;

}

}

public void warning(SAXParseException exception)

{

System.err.println("OSTRZEŻENIE! " +

exception.getMessage());

}

public void error(SAXParseException exception)

{

System.err.println("BŁĄD! " +

exception.getMessage());

}

public void fatalError(SAXParseException exception)

{

System.err.println("BŁĄD KRYTYCZNY! " +

exception.getMessage());

}

public static void main(String[] args)

{

displayDocument(args[0]);

}

}

Modyfikowanie dokumentów XML

W poprzednim rozdziale pokazano, że parser DOM pakietu XML for Java zawiera metody umożliwiające modyfikowanie dokumentu rezydującego w pamięci, takie jak insertBefore czy addChild. Parser SAX nie udostępnia od razu całego drzewa dokumentu, wobec czego nie ma w nim podobnych metod.

Postać dokumentu XML można jednak modyfikować także w parserze SAX - należy w tym celu samemu wywoływać różne metody zwrotne. Poprzednio zmodyfikowaliśmy dokument zamówienia.xml tworząc zamówienia2.xml do każdego elementu OSOBA dodając element DRUGIE_IMIĘ o treści XML. Bez problemu możemy zrobić to samo stosując metody parsera SAX. Wystarczy poczekać na element IMIĘ i wtedy utworzyć nowy element wywołując metody zwrotne startElement, characters i endElement:

public void endElement(String uri, String localName, String rawName)

{

indent = indent.substring(0, indent.length() - 4);

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "</";

displayStrings[numberDisplayLines] += rawName;

displayStrings[numberDisplayLines] += '>';

numberDisplayLines++;

if (rawName.equals("IMIĘ")) {

startElement("", "DRUGIE_IMIĘ", "DRUGIE_IMIĘ", null);

characters("XML".toCharArray(), 0, "XML".length());

endElement("", "DRUGIE_IMIĘ", "DRUGIE_IMIĘ");

}

}

W metodzie main zapiszemy zmodyfikowany dokument pod nową nazwą:

public static void main(String args[])

{

displayDocument(args[0]);

try {

FileWriter filewriter = new FileWriter("zamówienia2.xml");

for(int loopIndex = 0; loopIndex < numberDisplayLines; loopIndex++){

filewriter.write(displayStrings[loopIndex].toCharArray());

filewriter.write('\n');

}

filewriter.close();

}

catch (Exception e) {

e.printStackTrace(System.err);

}

}

W wyniku otrzymamy nowy plik zamówienia2.xml z dodanymi elementami DRUGIE_IMIĘ:

<?xml version="1.0" encoding="iso-8859-2"?>

<DOKUMENT>

<KLIENT STATUS="Rzetelny kredytobiorca">

<IMIĘNAZWISKO>

<NAZWISKO>

Smith

</NAZWISKO>

<IMIĘ>

Sam

</IMIĘ>

<DRUGIE_IMIĘ>

XML

</DRUGIE_IMIĘ>

</IMIĘNAZWISKO>

<DATA>

15 października 2001

</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>

Pomidory

</PRODUKT>

<ILOŚĆ>

8

</ILOŚĆ>

<CENA>

5zł

</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>

Pomarańcze

</PRODUKT>

<ILOŚĆ>

24

</ILOŚĆ>

<CENA>

9.98zł

</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

<KLIENT STATUS="Kredytobiorca niesolidny">

<IMIĘNAZWISKO>

<NAZWISKO>

Jones

</NAZWISKO>

<IMIĘ>

Polly

</IMIĘ>

<DRUGIE_IMIĘ>

XML

</DRUGIE_IMIĘ>

</IMIĘNAZWISKO>

<DATA>

20 października 2001

</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>

Chleb

</PRODUKT>

<ILOŚĆ>

12

</ILOŚĆ>

<CENA>

28.80zł

</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>

Jabłka

</PRODUKT>

<ILOŚĆ>

6

</ILOŚĆ>

<CENA>

6.00zł

</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

<KLIENT STATUS="Rzetelny kredytobiorca">

<IMIĘNAZWISKO>

<NAZWISKO>

Weber

</NAZWISKO>

<IMIĘ>

Bill

</IMIĘ>

<DRUGIE_IMIĘ>

XML

</DRUGIE_IMIĘ>

</IMIĘNAZWISKO>

<DATA>

25 października 2001

</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>

Asparagus

</PRODUKT>

<ILOŚĆ>

12

</ILOŚĆ>

<CENA>

11.90zł

</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>

Sałata

</PRODUKT>

<ILOŚĆ>

6

</ILOŚĆ>

<CENA>

31.50zł

</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

</DOKUMENT>

W ten sposób skończyliśmy pracę z pakietem XML for Java i w ogóle chwilowo skończyliśmy pracę z Javą. Teraz zajmiemy się łączami i wskaźnikami XML: XLinks i XPointers.

-->

32 Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

Rozdział 1 Pierwsze kroki (Nagłówek strony)

32 C:\Moje dokumenty\Wojtek Romowicz\Książki\XML Vademecum profesjonalisty\r09-01.doc

C:\Moje dokumenty\Wojtek Romowicz\Książki\XML Vademecum profesjonalisty\r09-01.doc 1

[Author:T]

Wyszukiwarka

Podobne podstrony:
r03-01, ## Documents ##, XML Vademecum profesjonalisty
r05-01, ## Documents ##, XML Vademecum profesjonalisty
r04-01, ## Documents ##, XML Vademecum profesjonalisty
r07-01, ## Documents ##, XML Vademecum profesjonalisty
r06-01, ## Documents ##, XML Vademecum profesjonalisty
r12-01, ## Documents ##, XML Vademecum profesjonalisty
r08-01, ## Documents ##, XML Vademecum profesjonalisty
r11-01, ## Documents ##, XML Vademecum profesjonalisty
r10-01, ## Documents ##, XML Vademecum profesjonalisty
Projektowanie Baz Danych Xml Vademecum Profesjonalisty [XML]
Projektowanie baz danych XML Vademecum profesjonalisty pxmlvp 2
Projektowanie baz danych XML Vademecum profesjonalisty pxmlvp
Projektowanie baz danych XML Vademecum profesjonalisty 2
Projektowanie Baz Danych Xml Vademecum Profesjonalisty [XML]
Projektowanie baz danych XML Vademecum profesjonalisty pxmlvp
Projektowanie Baz Danych Xml Vademecum Profesjonalisty R 5 Architektura Systemu Baz Danych
Projektowanie baz danych XML Vademecum profesjonalisty
Asembler dla procesorow Intel Vademecum profesjonalisty asinvp
CorelDRAW 11 Vademecum profesjonalisty Tom 2

więcej podobnych podstron