/* * SAXdemo.java * Demonstration av grunderna i SAX 2.0 för Java * med extremt detaljerade kommentarer. * Stefan Gustavson (stegu@itn.liu.se) 2003-11-14 * * This source code is in the public domain. */// Import-satserna nedan är med flit specifika - ingen "*" någonstans.// Ett latare men mindre tydligt sätt att ordna samma saker vore:// import java.io.*// import org.xml.sax.*// import org.xml.sax.helpers.*// Nedanstående sätt är faktiskt det som rekommenderas för att inte// Java skall gå vilse bland eventuella klasser med likadana namn.// Dessutom är det i mitt tycke mer pedagogiskt, eftersom endast de// klasser som verkligen används importeras.// Satserna är tillagda vartefter de behövdes under programmeringens// gång. När kompilatorn klagar talar den faktiskt om exakt vilka klasser// som är okända, och dokumentationen från Sun ger sedan snabbt information// om vilka paket dessa klasser ligger i. Använd dokumentationen!import java.io.Writer;import java.io.BufferedWriter;import java.io.OutputStreamWriter;import java.io.IOException;import java.io.UnsupportedEncodingException;import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;import org.xml.sax.SAXException;import org.xml.sax.InputSource;import org.xml.sax.Attributes;import org.xml.sax.helpers.DefaultHandler;import org.xml.sax.SAXParseException;// Vår enda klass löser alla problem på en gång. Detta är inte så snyggt,// men det håller detta exempel i en och samma fil, vilket gör det mer// lättläst, lättdistribuerat och även smidigt att skriva ut.// DefaultHandler är en klass som implementerar två interface:// ContentHandler och ErrorHandler. I stället för att skriva// "implements ContentHandler, ErrorHandler" kan man skriva// "extends DefaultHandler" och få alla metoder färdiga.// Defualtmetoderna är tomma (de gör ingenting), men de *finns*,// vilket gör att man kan kompilera sin klass även utan att// implementera alla callback-metoder, även de man inte behöver.class SAXdemo extends DefaultHandler { // "output" är en buffrad wrapper för en OutputStream som vi använder för // att skriva Unicode-text med automatisk omkodning till en 8-bitars // teckenkodning som t ex den gamla soppan som används i Windows // kommandotolk. Det är pinsamt nog inte ens ANSI-kod, utan den antika // DOS-kodningen från 80-talet som används av "Kommandotolken"! // Kodtabellen för denna kodning heter Codepage 850, eller "cp850". private BufferedWriter output = null; String tabbar=""; /////////HÄR DEKLARERAR JAG EN GLOBAL STRING SOM JAG ANVÄNDER I MINA TABMETODER // En explicit konstruktor, för att göra initialiseringar och annat public SAXdemo() { // Glöm inte bort att anropa superklassens konstruktor! // Det är konstruktorn för DefaultHandler som skapar alla bekväma metoder // åt oss. Nu behöver vi faktiskt inte dem längre, eftersom denna klass i // sin färdiga form implementerar alla metoder i interfacet, men under // utvecklingen av en callback-hanterare är det praktiskt att ha de tomma // defaultmetoderna att luta sig på. super(); // Skapa en OutputStreamWriter som hanterar teckenkodningen // i Windows kommandotolk. (Inget av det här är absolut // nödvändigt för funktionen, men praktiskt för att hantera // olika teckenkodningar.) try { output = new BufferedWriter(new OutputStreamWriter(System.out,"cp850")); } // Om "cp850" inte är känd på denna plattform, använd defaultkodningen catch (UnsupportedEncodingException e) { output = new BufferedWriter(new OutputStreamWriter(System.out)); }; } //TVÅ METODER-LÄGG TILL TAB, TA BORT TAB private void addTab(){ tabbar=tabbar + "\t"; } private void removeTab(){ tabbar = tabbar.replaceFirst("\t", ""); } // Vi definierar några privata metoder för att bekvämt kunna skriva ut // text till vår automatöversättande BufferedWriter. // Skriv ut en sträng och mata fram till ny rad private void printout(String s) { try { output.write(tabbar); output.write(s); output.newLine(); } // En eventuell IOException från write() måste hanteras eller skickas // vidare. Vi väljer att hantera felet, men utan att egentligen åtgärda // det. Vi bara berättar att felet uppstod och hoppas på förståelse. catch (IOException e) { System.err.println("I/O Exception: "+ e.getMessage()); } } // Skriv ut en character array, t ex den som kommer från callback-metoden // characters(), och mata fram till ny rad private void printout(char ch[], int start, int length) { try { output.write(tabbar); output.write(ch, start, length); output.newLine(); } catch (IOException e) { System.err.println("I/O Exception: "+ e.getMessage()); } } // Skriv ut en enstaka character utan att mata fram till ny rad. // Vissa specialtecken skrivs ut på speciellt sätt för tydlighets // skull. private void printout(char ch) { try { switch(ch) { // Newline skrivs ut som "\n" case '\n': output.write("\\n"); break; // Tabtecken skrivs ut som "\t" case '\t': output.write("\\t"); break; // CR skrivs ut som "\r" case '\r': output.write("\\r"); break; default: output.write(ch); } } catch (IOException e) { System.err.println("I/O Exception: "+ e.getMessage()); } } // Gör flush() och close() på BufferedWriter-objektet, så att inte text // som vi skrivit ut under exekveringen går förlorad. // (Det räcker egentligen med close(), som gör en implicit flush().) private void endOutput() { try { output.flush(); output.close(); } catch (IOException e) { System.err.println("I/O Exception: "+ e.getMessage()); } } // public static f*cking void main(), Java-applikationens "entry point" public static void main(String args[]) { // Skapa en XMLReader för att kunna parsa en XML-fil // Notera att objektet i sig måste deklareras utanför try{}-blocket, // annars kommer det inte att existera i resten av programmet! // Själva initialiseringen sker dock som sig bör i ett try{}-block. XMLReader xmlreader = null; try { xmlreader = XMLReaderFactory.createXMLReader( "org.apache.xerces.parsers.SAXParser"); } // Det som kan hända här är att den angivna SAX-parsern inte hittas. catch (SAXException e) { System.err.println("Unable to create XMLReader: " + e.getMessage()); } // Skapa och koppla en "event handler" till XMLReadern. // Vår event handler är samma klass som denna klass (vår enda), // vilket kanske är osnyggt för mer realistiska tillämpningar. // Överväg åtminstone att skapa event handlers som egna klasser. // Eftersom man inte kan ha en statisk klass som event handler så // behöver vi instansiera den här klassen. Då körs konstruktorn också, // vilket vi behöver för att output-objektet skall initialiseras. SAXdemo handler = new SAXdemo(); // Vi implementerar både ContentHandler och EventHandler i samma klass. // Detta kanske inte heller är det mest lämpliga sättet alla gånger. xmlreader.setContentHandler(handler); xmlreader.setErrorHandler(handler); // Be om validering, ange en XML-fil och parsa den try { // Be om validering. Vi har en DTD och vill använda den fullt ut! // (Här kan det uppstå en SAXNotRecognizedException eller en // SAXNotSupportedException!) xmlreader.setFeature("http://xml.org/sax/features/validation",true); // Ange vilken fil vi vill parsa. // (Här kan det faktiskt inte uppstå en IOException! // Filen öppnas inte förrän man verkligen anropar parse().) // Filnamnet borde nog egentligen anges som args[1] i stället // för att kompileras in som en konstant sträng. InputSource source = new InputSource("Jspex.xml"); // Parsa filen - det är nu som alla callbacks kommer att anropas! // (Här kan det uppstå en SAXException eller en IOException!) xmlreader.parse(source); } // Om något gick snett, försök inte hantera det, men tala om det catch (Exception e) { System.err.println("Can't open or parse file: "+ e.getMessage()); } // Klart! Här slutar main(). } // Konkreta, icke-tomma implementationer av de flesta callbacks // som krävs av en ContentHandler. De som inte implementeras är: // public void processingInstruction(String target, String data) // public void skippedEntity(String name) // public void endPrefixMapping(String prefix) // public void setDocumentLocator(Locator locator) // Dessa fyra använder i stället defaultmetoderna i "DefaultHandler". public void startDocument() throws SAXException { printout("startDocument"); addTab(); } public void endDocument() throws SAXException { printout("endDocument"); removeTab(); endOutput(); } public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { addTab(); // Beroende på om vi har ett XML namespace aktiverat eller inte så // kommer elementnamnet antingen i "uri" och "name" (med namespace) // eller bara i "qName" (qualified name). Om vi har ett namespace // kommer "uri" att innehålla något, annars är det en tom sträng. // Man bör aldrig förutsätta att det ena eller det andra gäller, // utan alltid bereda sig på båda alternativen! if(uri.equals("")) printout(qName );//"startElement: " + else printout("{"+uri+"}"+localName);//startElement: // Skriv ut eventuella attribut som skickades i argumentet "atts". // Notera att antalet attribut kan vara noll, ett eller flera! // Namnen på attributen hittas genom att loopa över ett index och // plocka dem ett och ett i den ordning de stod i filen, men // attributvärdena hittas på det sätt man normalt använder, nämligen // på sina respektive namn ("attName"). for (int i = 0; i < atts.getLength(); i++) { String attName = atts.getQName(i); printout(" " + attName + ": " + atts.getValue(attName)); } } public void endElement(String uri, String localName, String qName) { removeTab(); if(uri.equals("")) printout(qName);//"endElement: " + else printout("{"+uri+"}"+localName);//endElement: } /* public void endElement(String uri, String localName, String qName){ removeTab(); if(uri.equals("")) printout(tabbar + "</" + qName + ">"); else printout("endElement: {"+uri+"}"+localName); } */ public void characters(char ch[], int start, int length) { printout("Characters:"); printout(ch, start, length); } /* public void ignorableWhitespace(char[] ch, int start, int length) { printout("Ignorable whitespace:"); for(int i=start; i<start+length; i++) { printout(ch[i]); } printout("hej"); // (Bara för att få en radmatning efteråt) } */ // Konkreta, icke-tomma implementationer av alla ErrorHandler-callbacks. // Notera att dessa anropas på fler villkor om man bett om validering. // Exakt vilket fel som signaleras anges som vanligt i Exception-objektet. // Även om man bett om validering stoppas inte parsningen annat än vid ett // "fatalError", så error() bör kanske egentligen avbryta programmet. // För exemplets skull stoppar vi här inte för något, om vi inte måste. public void warning(SAXParseException e) { System.err.println("*** Warning! " + e.getMessage()); } public void error(SAXParseException e) { System.err.println("*** Error! " + e.getMessage()); } public void fatalError(SAXParseException e) { System.err.println("*** Fatal error!" + e.getMessage()); }}