Inhalt Aufgaben eines XML-Prozessors Parser-Modelle

Werbung
Inhalt
• Einführung
181.139 VU Semistrukturierte Daten 2
XML-APIs (Teil 1)
25.4.2006
• APIs für XML-Prozessoren (XML-Parser)
–
–
–
–
DOM
SAX
JDOM
StAX
• API für XSLT-Prozessoren:
Reinhard Pichler
– 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)
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.
• 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
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
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):
Serialisierer:
– XML-Dokument wird einmal durchlaufen => sequentieller Zugriff
• Generiert aus der internen Datenstruktur ein XML-Dokument
– Applikation fordert Analyse der nächsten syntaktischen Einheit an
• Kümmert sich dabei um Wohlgeformtheit des XML-Dokuments
– Beispiel: StAX
1
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.
Beispiel für einen DOM-Baum
<sentence>
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
Wichtige Eigenschaften der
Node Types
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
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
2
Node Interface
• Node Types:
public
public
public
public
• Node Properties:
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
• Methoden zur Navigation im Baum:
public
public
public
public
public
public
public
public
public
public
Node Interface
Node
boolean
NodeList
Node
Node
Node
Node
Document
boolean
NamedNodeMap
getParentNode();
hasChildNodes();
getChildNodes();
getFirstChild();
getLastChild();
getPreviousSibling();
getNextSibling();
getOwnerDocument();
hasAttributes();
getAttributes();
Node Interface
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 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;
Einige Subinterfaces von „Node“
• Einige zusätzliche Methoden von „Document“:
• 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);
– 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.
3
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
Einige weitere Anweisungen
• Einstellungen des Parsers (z.B. validieren: ja/nein)
• Neuen Knoten erzeugen und einfügen:
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
factory.setValidating(true);
factory.setNamespaceAware(true);
try {
DocumentBuilder builder = …
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
• Neuen DOM-Baum erzeugen:
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.newDocument();
} catch (ParserConfigurationException pce)
DOM-Ausgabe mittels “transformer”
• Importe:
import
import
import
import
import
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 */
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);
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- 3
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
}
4
SAX-Parser
Funktionsweise von SAX
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
• 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)
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)
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 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.
5
Attributes Interface
•
Benutzung von SAX in JAXP
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.
SAXParser-Interface
DefaultHandler
• Das SAXParser-Interface in JAXP ist einfach ein Wrapper um den
XMLReader.
• Deklaration des DefaultHandlers (in org.xml.sax.helpers)
• 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.
Mindest-Code eines SAX-Parsers
javax.xml.parsers.SAXParserFactory;
javax.xml.parsers.SAXParser;
org.xml.sax.SAXException;
org.xml.sax.SAXParseException;
– von DefaultHandler erben
– die tatsächlich benötigten callback Funktionen überschreiben
Einige weitere Anweisungen
SAXParserFactory factory =
SAXParserFactory.newInstance();
factory.setValidating(true);
factory.setNamespaceAware(true);
• Factory-Instanzierung:
SAXParserFactory factory =
SAXParserFactory.newInstance();
• Enthält default-Deklarationen für alle callback
Funktionen dieser 4 Event Handler
• Default-Verhalten: do nothing
• Bequeme Definition eines eigenen SAX-Parsers:
• Einstellungen des Parsers (z.B. validieren: ja/nein)
• Importe: DOMBuilder/Factory, DOM:
import
import
import
import
public class DefaultHandler extends Object
implements EntityResolver, DTDHandler,
ContentHandler, ErrorHandler
• Output des XMLReaders als XML-Dokument:
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)…
mittels „transformer“, analog zur DOM-Ausgabe, d.h.:
– Erzeuge mittels XMLReader eine SAXSource
– Aufruf des transformers mit dieser SAXSource als Input
SAX-3
6
SAX-Ausgabe mittels “transformer”
SAX-Filter
• 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);
Funktionsweise eines SAX-Filters
• Vorbereitung:
Verwendung eines SAX-Filters
• XMLFilter Interface: erweitert XMLReader um 2 Methoden:
– Applikation teilt dem Filter mit, auf welchen Reader er horchen muss
– void setParent(XMLReader parent), XMLReader getParent()
– Applikation registriert ihre Event Handler beim Filter
– „parent“ = Reader, auf den der Filter horchen muss
• Start des Parse-Vorgangs:
– Applikation ruft parse()-Methode des Filters auf
– Filter ruft parse()-Methode des Readers auf
– 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)
• Parse-Vorgang:
– Reader erzeugt Events => ruft callback Funktionen des Filters auf
– Filter ruft innerhalb seiner callback Funktionen die callback Funktionen der
Applikation auf.
– 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.
• Filter ist also gleichzeitig Event Handler und Reader
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/
7
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
Ü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.
Erlaubte Annahme: XML-Dokument enhält keine Namespaces
Ü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!
8
Herunterladen