181.139 VU Semistrukturierte Daten 2 XML-APIs (Teil 1) 25.4.2006 Reinhard Pichler
Inhalt Einführung APIs für XML-Prozessoren (XML-Parser) DOM SAX JDOM StAX API für XSLT-Prozessoren: TrAX API für XQuery-Prozessor: Saxon 8
XML-APIs und Java Einführung Die wichtigsten XML-APIs (DOM und SAX) sind eigentlich programmiersprachen-unabhängig. In der SSD2-Vorlesung werden aber nur die Java-Realisierungen behandelt. JAXP (Java API for XML-Processing): Teil von JDK ab Version 1.4 Enthält Java-Realisierung von DOM und SAX sowie ein XSLT-API (TrAX: Transformation API for XML) JAXP-Packages: javax.xml.parsers (gemeinsames Interface für SAX und DOM Parser von unterschiedlichen Herstellern). org.w3c.dom org.xml.sax javax.xml.transform
Idee eines XML-Prozessors (Parsers) Stellt der Applikation eine einfache Schnittstelle zum Zugriff auf ein XML-Dokument (plus eventuell zum Manipulieren und wieder Abspeichern des XML-Dokuments) zur Verfügung. Ermöglicht die Konzentration auf die logische Struktur bzw. den Inhalt des XML-Dokuments (und nicht auf die exakte Syntax) => wesentlich flexibler/robuster als Text-Parsen.
Aufgaben eines XML-Prozessors Parser: Überprüfung der Wohlgeformtheit und (optional) der Gültigkeit Unterstützung unterschiedlicher Encodierungen Ergänzung von default/fixed-werten Auflösen von internen/externen Entities und Character References Normalisierung von whitespace Generiert aus dem XML-Dokument eine interne Datenstruktur Serialisierer: Generiert aus der internen Datenstruktur ein XML-Dokument Kümmert sich dabei um Wohlgeformtheit des XML-Dokuments
Parser-Modelle Objektmodell Parser: Baut gesamten XML-Baum im Speicher auf => wahlfreier Zugriff Beispiele: DOM, JDOM Push Parser (ereignisorientiert, Kontrolle liegt beim Parser): XML-Dokument wird einmal durchlaufen => sequentieller Zugriff Applikation kann für die möglichen Events (d.h. syntaktische Einheiten) callback Funktionen bereitstellen Beispiel: SAX Pull Parser (ereignisorientiert, Kontrolle liegt bei der Applikation): XML-Dokument wird einmal durchlaufen => sequentieller Zugriff Applikation fordert Analyse der nächsten syntaktischen Einheit an Beispiel: StAX
DOM-Parser DOM DOM: Document Object Model W3C-Recommendation: http://www.w3.org/dom/ DOM-Versionen in Form von Levels : Level 0 (keine Recommendation): nur HTML, für JavaScript in Browsern Level 1: Unterstützung von XML 1.0 und HTML 4.0 Level 2: Namespaces im Element/Attribute Interface, erweiterte Baum- Manipulationsmöglichkeiten, etc. Level 3 (in Entwicklung) : Laden/Speichern, XPath, etc. Plattform- und programmiersprachen-unabhängig; Implementierung muss nicht einmal in OO-Sprache erfolgen. DOM legt logische Struktur eines XML-Dokuments (als Baum) fest und definiert Methoden zum Navigieren im Baum und zum Manipulieren des Baumes.
DOM-Baumstruktur XML Dokument wird als Baumstruktur dargestellt Dieser Baum ist im allgemeinen detaillierter als der Baum laut XPath/XSLT Datenmodell (z.b.: eigene Knoten für Entity, CDATA-Section, etc.) Knoten des Baums sind vom Typ Node Die verschiedenen Knoten-Arten erben von Node : Document : hier ist der ganze DOM-Baum aufgehängt Element, Attr, Text, ProcessingInstruction, Comment DocumentFragment, DocumentType, EntityReference, CDATASection, EntityReference, Notation Wichtige Knoten-Eigenschaften: nodename, nodevalue, nodetype, Attributes
Beispiel für einen DOM-Baum <sentence> The &projectname; <![CDATA[<i>project</i>]]> is <?editor: red?><bold>important</bold><?editor: normal?>. </sentence> Dazugehöriger DOM-Baum: + ELEMENT: sentence + TEXT: The + ENTITY REF: projectname + COMMENT: latest name + TEXT: Eagle + CDATA: <i>project</i> + TEXT: is + PI: editor: red + ELEMENT: bold + TEXT: important + PI: editor: normal DOM- 1
Wichtige Eigenschaften der Node Types
DOM-Interfaces Zentrales Interface: Node Gemeinsame Methoden der Knoten des DOM-Baums z.b.: Navigieren im Baum, Manipulieren des Baums (Knoten löschen/einfügen/ändern), Get/Set Eigenschaften, etc. Subinterfaces von Node für jeden node type Stellen spezifische weitere Methoden zur Verfügung Weitere Interfaces: NamedNodeMap: Sammlung von Knoten, auf die mittels Name oder Index zugegriffen werden kann. NodeList: Sammlung von Knoten, auf die mittels Index zugegriffen werden kann DocumentImplementation
DOM Java-Interface Hierarchie interface org.w3c.dom.node * interface org.w3c.dom.attr * interface org.w3c.dom.characterdata o interface org.w3c.dom.comment o interface org.w3c.dom.text + interface org.w3c.dom.cdatasection * interface org.w3c.dom.document * interface org.w3c.dom.element... * interface org.w3c.dom.processinginstruction interface org.w3c.dom.namednodemap interface org.w3c.dom.nodelist interface org.w3c.dom.domimplementation
Node Interface Node Types: public static final short ELEMENT_NODE = 1; public static final short ATTRIBUTE_NODE = 2; public static final short TEXT_NODE = 3; public static final short CDATA_SECTION_NODE = 4; ENTITY_REFERENCE_NODE = 5; ENTITY_NODE = 6; PROCESSING_INSTRUCTION_NODE = 7; COMMENT_NODE =8; DOCUMENT_NODE = 9; DOCUMENT_TYPE_NODE = 10; DOCUMENT_FRAGMENT_NODE = 11; NOTATION_NODE = 12;
Node Interface Node Properties: public String getnodename(); public String getnodevalue() throws DOMException; public void setnodevalue(string nodevalue) throws DOMException; public short getnodetype(); public String getnamespaceuri(); public String getprefix(); public void setprefix(string prefix) throws DOMException; public String getlocalname();
Node Interface Methoden zur Navigation im Baum: public Node getparentnode(); public boolean haschildnodes(); public NodeList getchildnodes(); public Node getfirstchild(); public Node getlastchild(); public Node getprevioussibling(); public Node getnextsibling(); public Document getownerdocument(); public boolean hasattributes(); public NamedNodeMap getattributes();
Node Interface Methoden zur Manipulation des Baums: public Node insertbefore(node newchild, Node refchild) throws DOMException; public Node replacechild(node newchild, Node oldchild) throws DOMException; public Node removechild(node oldchild) throws DOMException; public Node appendchild(node newchild) throws DOMException;
Node Interface Utilities: public Node clonenode(boolean deep); public void normalize(); verschmelzt // eliminiert leere Text-Knoten und // benachbarte Text-Knoten im ganzen Subbaum public boolean issupported(string feature, String version);
Einige Subinterfaces von Node Einige zusätzliche Methoden von Document : createattribute(), createcomment(), createelement(), createtextnode(), etc getdoctype(), getdocumentelement() Einige zusätzliche Methoden von element : getattribute(), removeattribute(), setattribute() // Attribut wird mittels Attribut-Namen identifiziert getattributenode(), setattributenode(), removeattributenode() // Attribut wird als Input-Parameter vom Typ Node übergeben hasattribute(), etc.
Benutzung von DOM in JAXP
Mindest-Code Importe: DOMBuilder/Factory, DOM: import javax.xml.parsers.documentbuilder; import javax.xml.parsers.documentbuilderfactory; import org.w3c.dom.document; import org.xml.sax.saxexception; import org.xml.sax.saxparseexception; Factory-Instanzierung: DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); Parser-Instanzierung und Parsen: static Document document; try { DocumentBuilder builder = factory.newdocumentbuilder(); document = builder.parse( new File(filename) ); } catch (SAXParseException spe)
Einige weitere Anweisungen Einstellungen des Parsers (z.b. validieren: ja/nein) DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setvalidating(true); factory.setnamespaceaware(true); try { DocumentBuilder builder = Neuen DOM-Baum erzeugen: DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder builder = factory.newdocumentbuilder(); document = builder.newdocument(); } catch (ParserConfigurationException pce)
Einige weitere Anweisungen Neuen Knoten erzeugen und einfügen: document = builder.newdocument(); Element root = (Element) document.createelement("test"); document.appendchild(root); root.appendchild(document.createtextnode("some Text")); DOM-Baum als XML-Dokument ausgeben: DOM- 2 mittels transformer : Eigentlich für XSLT-Transformationen gedacht Erlaubter Input: StreamSource, DOMSource oder SAXSource Erlaubter Output: StreamSource, DOMSource oder SAXSource Ohne Angabe eines XSLT-Stylesheets wird ein XML-Dokument einfach von einem der Input-Formate in eines der Output-Formate umgewandelt.
DOM-Ausgabe mittels transformer Importe: import javax.xml.transform.transformer; import javax.xml.transform.transformerfactory; import javax.xml.transform.dom.domsource; import javax.xml.transform.stream.streamresult; import java.io.*; Transformer instanzieren: TransformerFactory tfactory = TransformerFactory.newInstance(); Transformer transformer = tfactory.newtransformer(); Ausgabe: DOMSource source = new DOMSource(document); StreamResult result = new StreamResult(new File("output.xml")); transformer.transform(source, result); DOM- 3
Beispiel: Suche nach einem Subelement /*@param name tag name for the (first sub-)element to find @param node element node to start searching from @return the Node found */ public Node findsubnode(string name, Node node) { if (node.getnodetype()!= Node.ELEMENT_NODE) return null; if (! node.haschildnodes()) return null; NodeList list = node.getchildnodes(); for (int i=0; i < list.getlength(); i++) { Node subnode = list.item(i); if (subnode.getnodetype() == Node.ELEMENT_NODE) { if (subnode.getnodename().equals(name)) return subnode; } } return null; } DOM- 4
SAX-Parser SAX SAX: Simple API for XML keine W3C Recommendation, aber ein de-facto Standard: http://www.saxproject.org/ Entwickelt von XML-Entwicklern ( jeder kann mitmachen über die Mailing List xml-dev@w3.org) Plattform- und programmiersprachen-unabhängig (auch wenn SAX ursprünglich für Java entwickelt wurde) SAX Versionen: SAX 1.0: entstanden auf Initiative von Peter Murray-Rust (mit dem Ziel, mehrere ähnliche aber inkompatible Java XML-APIs zusammenzuführen), im Mai 1998 freigegeben. SAX 2.0: erweitert um Namespace-Unterstützung; führte zu inkompatibler Erweiterung von SAX 1.0
Funktionsweise von SAX
Funktionsweise von SAX Applikation startet SAX-Parser (d.h.: XMLReader ) SAX-Parser durchläuft das XML-Dokument einmal sequentiell Parser erkennt Events (syntaktische Einheiten) beim Analysieren des XML-Dokuments Parser ruft für jedes Event die entsprechende Callback Funktion auf, die die Applikation bereitstellt. Diese Callback Funktionen sind auf 4 Event Handler aufgeteilt: ContentHandler, ErrorHandler, DTDHandler, EntityResolver Der Speicherbedarf des SAX-Parsers ist konstant! (d.h.: unabhängig von der Größe des XML-Dokuments)
XMLReader Ist der eigentliche SAX-Parser, d.h.: liest das XML-Dokument und ruft die callback Funktionen auf Erlaubt das Setzen/Auslesen bestimmter Properties/Features: setfeature, setproperty, getfeature, getproperty Benutzer registriert die Event Handler (mit den callback Funktionen): setcontenthandler, setdtdhandler, setentityresolver, seterrorhandler Analog dazu get-methoden für die Event Handler: getcontenthandler, getdtdhandler, etc. Methode zum Anstoßen des Parsers: parse
Methoden des ContentHandler void startdocument(): void enddocument() void startelement(string namespaceuri, String localname, String qname, Attributes atts) void endelement(string namespaceuri, String localname, String qname) void characters(char[] ch, int start, int length) void processinginstruction(string target, String data) void setdocumentlocator(locator locator) void ignorablewhitespace(char[] ch, int start, int length) void skippedentity(string name) void startprefixmapping(string prefix, String uri) void endprefixmapping(string prefix) SAX-1
Methoden der anderen Event Handler ErrorHandler: void fatalerror(saxparseexception exception) // non-recoverable error void error(saxparseexception exception) void warning(saxparseexception exception) DTDHandler: void notationdecl(string name, String publicid, String systemid) void unparsedentitydecl(string name, String publicid, String systemid, String notationname) EntityResolver: InputSource resolveentity(string publicid, String systemid) // allows the application to resolve external entities.
Attributes Interface z.b. die startelement() Funktion liefert die Attribute dieses Elements als Attributes Object zurück. Zugriff auf die Attributes mittels getlength() und Attribut-Index: String getlocalname(int index) String getqname(int index) String gettype(int index) // ohne DTD: CDATA // sonst auch ID, IDREF, IDREFS, NMTOKEN, etc. Weitere Zugriffsmöglichkeit: mittels Namespace-qualified name mittels qualified (prefixed) name.
Benutzung von SAX in JAXP
SAXParser-Interface Das SAXParser-Interface in JAXP ist einfach ein Wrapper um den XMLReader. Mit getxmlreader() kann man diesen XMLReader holen. Methoden zum Abfragen von Parser-Eigenschaften: getproperty(), isnamespaceaware(), isvalidating() SAXParser enthält mehrere Varianten von parse()-methoden, mit denen die Event Handler beim XMLReader registriert werden und die parse()-methode des XMLReaders aufgerufen wird. Bequeme Art, die Event Handler zu registrieren: mittels DefaultHandler, der beim Aufruf von parse() übergeben wird.
DefaultHandler Deklaration des DefaultHandlers (in org.xml.sax.helpers) public class DefaultHandler extends Object implements EntityResolver, DTDHandler, ContentHandler, ErrorHandler Enthält default-deklarationen für alle callback Funktionen dieser 4 Event Handler Default-Verhalten: do nothing Bequeme Definition eines eigenen SAX-Parsers: von DefaultHandler erben die tatsächlich benötigten callback Funktionen überschreiben
Mindest-Code eines SAX-Parsers Importe: DOMBuilder/Factory, DOM: import javax.xml.parsers.saxparserfactory; import javax.xml.parsers.saxparser; import org.xml.sax.saxexception; import org.xml.sax.saxparseexception; Factory-Instanzierung: SAXParserFactory factory = SAXParserFactory.newInstance(); Parser-Instanzierung und Parsen: DefaultHandler handler = new MyDefaultHandler(); try{ SAXParser saxparser = factory.newsaxparser(); saxparser.parse(new File(argv[0]), handler); } catch (Throwable t) SAX-2
Einige weitere Anweisungen Einstellungen des Parsers (z.b. validieren: ja/nein) SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setvalidating(true); factory.setnamespaceaware(true); Output des XMLReaders als XML-Dokument: mittels transformer, analog zur DOM-Ausgabe, d.h.: Erzeuge mittels XMLReader eine SAXSource Aufruf des transformers mit dieser SAXSource als Input SAX-3
SAX-Ausgabe mittels transformer Importe + Transformer-Instanzierung: siehe DOM-Ausgabe Zusätzliche Importe (+ Transformer instanzieren: : import javax.xml.transform.sax.saxsource; import javax.xml.transform.stream.streamresult; Ausgabe (d.h.: erzeuge SAXSource mittels XMLReader) XMLReader reader = saxparser.getxmlreader() SAXSource source = new SAXSource(reader, new File("input.xml")); StreamResult result = new StreamResult(new File("output.xml")); transformer.transform(source, result);
SAX-Filter
Funktionsweise eines SAX-Filters Vorbereitung: Applikation teilt dem Filter mit, auf welchen Reader er horchen muss Applikation registriert ihre Event Handler beim Filter Start des Parse-Vorgangs: Applikation ruft parse()-methode des Filters auf Filter ruft parse()-methode des Readers auf Parse-Vorgang: Reader erzeugt Events => ruft callback Funktionen des Filters auf Filter ruft innerhalb seiner callback Funktionen die callback Funktionen der Applikation auf. Filter ist also gleichzeitig Event Handler und Reader
Verwendung eines SAX-Filters XMLFilter Interface: erweitert XMLReader um 2 Methoden: void setparent(xmlreader parent), XMLReader getparent() parent = Reader, auf den der Filter horchen muss Mittels setcontenthandler, etc. werden die Event Handler der Applikation beim Filter registriert Implementierung des XMLFilter Interface: per Hand : ziemlich aufwändig (XMLReader hat 14 Methoden) Eleganterer Weg: mittels XSLT Stylesheet kann ein XMLFilter automatisch erzeugt werden (nächster Termin) Die Klasse XMLFilterImpl in org.xml.sax.helpers stellt einen Default-Filter bereit, der die Requests in beiden Richtungen transparent durchreicht. SAX-4
DOM vs. SAX DOM: Baut gesamten XML-Baum im Speicher auf => wahlfreier Zugriff Manipulation des Baums möglich Hoher Speicherbedarf, langsamer SAX: XML-Dokument wird einmal durchlaufen => sequentieller Zugriff streaming möglich (d.h.: Bearbeiten und Weiterreichen, bevor das ganze Dokument übertragen ist). Geringerer Speicherbedarf, höhere Geschwindigkeit Falls mehrmaliger Zugriff auf Knoten erforderlich: Applikation ist selbst für das Puffern verantwortlich. Low level (DOM-API benutzt SAX-API)
Performance-Vergleich *) Test Cases: 1. Read and parse a small DTD-enforced XML file (approx. 25 elements) 2. Read and parse a large DTD-enforced XML file (approx. 50,000 elements) 3. Navigate the DOM created by Test #2 from root to all children. 4. Build a large DOM (approximately 60,000 elements) from scratch 5. Build an infinitely large DOM from scratch using createelement(...) and similar function calls until a java.outofmemoryerror is raised. Measure the time it takes to hit the "memory wall" within a default (32MB heap) JRE and count how many elements are created. *) Quelle: XML Parsers: DOM and SAX Put to the Test von Steve Franklin, http://www.devx.com/xml/article/16922/
Ergebnis der Tests: Performance-Vergleich
Literatur Spezifikationen: http://www.w3.org/dom/ http://www.saxproject.org/ http://www.jdom.org/ http://www.jcp.org/aboutjava/communityprocess/first/jsr173/ (Online) Bücher und Artikel: Elliotte Rusty Harold: Processing XML with Java http://www.cafeconleche.org/books/xmljava/ J2EE Tutorial (Kap. 4-7): http://java.sun.com/j2ee/1.4/docs/tutorial/doc/
Übungsbeispiel A Schreiben Sie ein Java-Programm Kleinschreibung-DOM, das folgende Funktionalität bereitstellt: 1. Ein XML-Dokument wird mittels DOM-Parser eingelesen 2. optional wird das Dokument validiert 3. Der DOM-Baum wird im Speicher so umgeformt, dass alle Element- und Attribut-Namen in Kleinbuchstaben umgewandelt werden; ansonsten soll das Dokument völlig unverändert bleiben. 4. Anschließend wird der DOM-Baum in eine XML-Datei ausgegeben 5. Das Programm soll 3 Kommandozeilen-Parameter haben: Name der Input-Datei Name der Output-Datei (Validierung beim Einlesen) Ja/Nein Erlaubte Annahme: XML-Dokument enhält keine Namespaces
Übungsbeispiel B Schreiben Sie ein Java-Programm Kleinschreibung-SAX-DOM, das nach außen dieselbe Funktionalität wie Übungsbeispiel A bereitstellt. Ändern Sie jedoch die Implementierung folgendermaßen: Das Einlesen erfolgt mittels SAX-Parser Bauen Sie einen DOM-Baum mit den kleingeschriebenen Elementund Attribut-Namen im Speicher "per Hand" neu auf (d.h.: der DOM-Baum soll nicht einfach mittels Transformer erzeugt werden!) am Ende dieses Schritts müsste der gleiche DOM-Baum im Speicher stehen wie am Ende von Schritt 3 in Übungsbeispiel A. Punkt 4 + 5 wie in Übungsbeispiel A.
Übungsbeispiel C Schreiben Sie ein Java-Programm Kleinschreibung-SAX-Filter, das nach außen die Funktionalität von Übungsbeispiel A dahin gehend vereinfacht, dass nur die Element-Namen in Kleinbuchstaben umgewandelt werden. Verwenden Sie zur Realisierung einen XML Filter, d.h.: gegenüber dem Übungsbeispiel A sind folgende Änderungen nötig: Das Einlesen erfolgt mittels SAX-Parser. Der Filter führt die Übersetzung der Element-Namen durch Die vom Filter erzeugten Events bilden den Input für einen Transformer, der den umgeformten XML-Stream direkt in eine XML-Datei ausgibt. Tipp: Bei Verwendung von XMLFilterImpl müssen nur die Methoden startelement() und endelement() geändert werden!