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 BaumManipulationsmö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 DOM- 1 + PI: editor: normal 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 public public public static static static static final final final final short short short short ELEMENT_NODE = 1; ATTRIBUTE_NODE = 2; TEXT_NODE = 3; 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 public public public public public public public public public Node boolean NodeList Node Node Node Node Document boolean NamedNodeMap getParentNode(); hasChildNodes(); getChildNodes(); getFirstChild(); getLastChild(); getPreviousSibling(); getNextSibling(); getOwnerDocument(); hasAttributes(); 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(); // eliminiert leere Text-Knoten und verschmelzt // 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 import import import import javax.xml.parsers.DocumentBuilder; javax.xml.parsers.DocumentBuilderFactory; org.w3c.dom.Document; org.xml.sax.SAXException; 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 import import import import javax.xml.transform.Transformer; javax.xml.transform.TransformerFactory; javax.xml.transform.dom.DOMSource; javax.xml.transform.stream.StreamResult; 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 [email protected]) • 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) SAX-1 void skippedEntity(String name) void startPrefixMapping(String prefix, String uri) void endPrefixMapping(String prefix) 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 String String getLocalName(int index) getQName(int index) 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 import import import javax.xml.parsers.SAXParserFactory; javax.xml.parsers.SAXParser; org.xml.sax.SAXException; org.xml.sax.SAXParseException; • Factory-Instanzierung: SAXParserFactory factory = SAXParserFactory.newInstance(); SAX-2 • Parser-Instanzierung und Parsen: DefaultHandler handler = new MyDefaultHandler(); try{ SAXParser saxParser = factory.newSAXParser(); saxParser.parse(new File(argv[0]), handler); } catch (Throwable t)… 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/ Performance-Vergleich Ergebnis der Tests: 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!