środa, 4 kwietnia 2007

Przetwarzanie XML w językach Java i PHP (cz.1)


Realizując praktyki z bazą IBM DB2 9 (w szczególności jej hybrydowym silnikiem) napotkałem na brak informacji na temat przetwarzania danych XML dostępnych w postaci strumieni.

Podczas lektury książki J2EE. Vademecum Programisty doszedłem nawet do błędnego wniosku jakoby przed wersją 1.6 Javy nie była taka obsługa zaimplementowana w standardzie. Wszędzie były tylko informacje jak parsować pliki XML.

Po przeszukaniu internetu natrafiłem na STreaming Api for XML (StAX) i jego dokumentacje w postaci JSR-173. Okazało się, że Java SE 1.6 zawiera nawet interfejs do niego, jednak brak jest implementacji. Na stronie http://stax.codehaus.org/ szybko tą implementację znalazłem, a później natrafiłem jeszcze na implementację Sun'a (pod adresem https://sjsxp.dev.java.net/) oraz Bea Weblogic (pod adresem http://dev2dev.bea.com/xml/stax.html). Ponieważ pierwsza z tych trzech stanowi implementację referencyjną będzie użyta w niniejszym tutorialu.



Niezależnie od wszystkiego postanowiłem jeszcze raz sprawdzić możliwości analizy strumieni tekstowych z XMLem dla standardów Simple Api for XML (SAX) i Document Object Model (DOM). Okazało się, że tkwiłem w błędnym przekonaniu.



SAX



SAX jest uznawany za najszybszy i najmniej pamięciożerny parser XML. Opiera się o reprezentację zdarzeniową, w której otwarcie i zamknięcie każdego dokumentu, znacznika czy występowanie tekstu jest określane jako zdarzenie.

Niewątpliwą wadą SAXa jest brak wiedzy o położeniu w pliku XML (stanu). Jest to również dość ciężki do implementacji we własnych projektach interfejs.

Pracę z SAXem rozpoczynamy od stworzenia klasy reagującej na zdarzenia (u mnie MyHandler). Poniższy przykład opiera się na kodzie zawartym w książce J2EE. Vademecum Programisty dostępnej w sprzedaży wydawnictwa Helion:

package org.f2k.test;


import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class MyHandler extends DefaultHandler {
StringBuffer textBuffer;
static private Writer out;

public MyHandler() throws UnsupportedEncodingException{
out = new OutputStreamWriter(System.out, "UTF8");
}

public void startDocument() throws SAXException{
nl();
nl();
emit("START OF DOCUMENT");
nl();
emit("");
}

public void endDocument() throws SAXException{
nl();
emit("END OF DOCUMENT");
try {
nl();
out.flush();
} catch (IOException e) {
throw new SAXException("IO Exception", e);
}
}

public void startElement(String namespaceURI,
String sName, // nazwa skrócona
String qName, // nazwa pełna
Attributes attrs) throws SAXException{

echoText();
nl();
emit("ELEMENT: ");
String eName = sName; // nazwa elementu
// bez uwzględnienia nazwy przestrzeni nazw
if ("".equals(eName))
eName = qName;
emit("<"+eName);
if (attrs != null) {
for (int i = 0; i < attrs.getLength(); i++) {
String aName = attrs.getLocalName(i); // nazwa atrybutu
if ("".equals(aName)) aName = attrs.getQName(i);
nl();
emit(" ATR: ");
emit(aName);
emit("\t\"");
emit(attrs.getValue(i));
emit("\"");
}
}
if (attrs.getLength() > 0) nl();
emit(">");
}

public void endElement(String namespaceURI,
String sName, // nazwa skrócona
String qName // nazwa pełna
)throws SAXException {
echoText();
nl();
emit("END_OF_ELEM: ");
String eName = sName; // nazwa elementu
if ("".equals(eName))
eName = qName;
emit("");
}

public void characters(char buf[], int offset, int len)
throws SAXException{
String s = new String(buf, offset, len);
if (textBuffer == null) {
textBuffer = new StringBuffer(s);
} else {
textBuffer.append(s);
}
}


// Metody pomocnicze
private void echoText() throws SAXException{
if (textBuffer == null) return;
nl();
emit("CHARS: |");
String s = ""+textBuffer;
emit(s);
emit("|");
textBuffer = null;
}

private void emit(String s) throws SAXException{
try {
out.write(s);
out.flush();
} catch (IOException e) {
throw new SAXException("IO Exception", e);
}
}

private void nl() throws SAXException{
String lineEnd = System.getProperty("line.separator");
try {
out.write(lineEnd);
} catch (IOException e) {
throw new SAXException("IO Exception", e);
}
}
}

Skoro mamy już klasę obsługującą zdarzenia przygotujmy klasę testową, dzięki której będziemy mogli podać String zawierający XML:

package org.f2k.test;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.helpers.DefaultHandler;

public class SAXTest {
SAXTest(String xml){
this(new ByteArrayInputStream(xml.getBytes()));
}

SAXTest(InputStream stream){
try {
DefaultHandler handler = new MyHandler();
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(stream, handler);
} catch (Exception e) {
e.printStackTrace();
}
}
}

Mamy właściwie wszystko co potrzeba dla SAX. Możemy jeszcze dla obiektu factory ustawić opcję sprawdzania dokumentu XML według DTD, itp. Ale to pozostawiam już programistom:)



DOM



DOM jest parserem, który wykorzystuje się do złożonych modyfikacji XML, czy jego reprezentacji w postaci drzewa obiektu. Jego wadą jest fakt, że jest zasobożerny. Cały dokument jest bowiem przetwarzany w pamięci komputera.

W przypadku DOM możemy od razu napisać naszą klasę testową:

package org.f2k.test;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

public class DOMTest {
public DOMTest(String xml){
this(new ByteArrayInputStream(xml.getBytes()));
}

public DOMTest(InputStream stream){
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(stream);
NodeList names = document.getElementsByTagName("name");
for(int i=0; i < names.getLength(); i++){
System.out.println(names.item(i).getTextContent());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

Nasz test wyszukuje w dokumencie XML węzły o nazwie name i wyświetla ich zawartość.



StAX



StAX jest pareserem opracowanym przez firmę BEA. Choć jego działanie opiera się na zdarzeniowej formie XML, to udostępnia nam wiedzę o położeniu w dokumencie poprzez iterator. Pozwala to na szybszą niż w przypadku DOM deserializację prostych obiektów.


Najprostszym użyciem StAXa jest stworzenie funkcji, która będzie analizować zdarzenia. W poniższym przykładzie funkcja processEvent będzie realizować podobne zadanie do klasy MyHandler z testu SAX:

package org.f2k.test;

import java.io.*;
import javax.xml.stream.*;
import javax.xml.stream.events.*;


public class StAXTest {
StringBuffer textBuffer;
static private Writer out;

StAXTest(String xml){
this(new ByteArrayInputStream(xml.getBytes()));
}

StAXTest(InputStream stream){
try {
out = new OutputStreamWriter(System.out, "UTF8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
/*Implementation of StAX*/
System.setProperty("javax.xml.stream.XMLInputFactory",
"com.bea.xml.stream.MXParserFactory");

try {
XMLInputFactory inputFactory
= XMLInputFactory.newInstance();
XMLEventReader eventReader
= inputFactory.createXMLEventReader(stream);
while (eventReader.hasNext()){
XMLEvent event = (XMLEvent) eventReader.next();
processEvent(event);
}
} catch (Exception e) {
e.printStackTrace();
}
}

public void processEvent(XMLEvent event) throws IOException{
if (event.isStartElement()){
echoText();
nl();
emit("ELEMENT: <");
emit(((StartElement) event).getName().toString());
emit(">");
}
if (event.isCharacters()){
String s = ((Characters) event).getData();
if (textBuffer==null){
textBuffer = new StringBuffer(s);
} else {
textBuffer.append(s);
}
}
if (event.isEndElement()){
echoText();
nl();
emit("END_OF_ELEM: ";
emit("");
}
}

// Metody pomocnicze
private void echoText() throws IOException{
if (textBuffer == null) return;
nl();
emit("CHARS: |");
String s = ""+textBuffer;
emit(s);
emit("|");
textBuffer = null;
}
private void emit(String s) throws IOException{
out.write(s);
out.flush();
}
private void nl() throws IOException{
String lineEnd = System.getProperty("line.separator");
out.write(lineEnd);
}
}

Skoro mamy już wszystkie testy pora sprawdzić działanie każdego z parsera. Do tego celu wykorzystałem tabelę Customer z bazy SAMPLE (w wersji XML) dostępnej z DB2 9. Poniżej kod zestawiający połączenie i pobierający rekord:

package org.f2k.test;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import com.ibm.db2.jcc.DB2Xml;

public class DB2Connector {
String password="***";
String user="db2admin";

String url = "jdbc:db2://localhost:50000/SAMPLE";
public DB2Connector(){
try {
Class.forName("com.ibm.db2.jcc.DB2Driver").newInstance();
Connection conn = DriverManager.getConnection(url,
user, password);
PreparedStatement stmt = conn.prepareStatement(
"SELECT INFO FROM MCUSTOMER WHERE CID = ?");
//Należy wcześniej sprawdzić czy użytkownik o CID 1001 istnieje
stmt.setInt(1, 1001);
ResultSet result = stmt.executeQuery();
while (result.next()){
DB2Xml info = (DB2Xml) result.getObject("INFO");
String xmlText = info.getDB2XmlString();
System.out.print("DB2XmlString:\n"+xmlText);
//Dla kodowania innego niż UTF-8 tylko StAX przechodzi test
InputStream stream = info.getDB2XmlBinaryStream("UTF-8");
System.out.println("\n");
System.out.println("DB2Xml from StAX test:");
new StAXTest(stream);
System.out.println("\n");
System.out.println("DB2Xml from SAX test:");
stream.reset();
new SAXTest(stream);
System.out.println("\n");
System.out.println("DB2Xml from DOM test:");
stream.reset();
new DOMTest(stream);
}
result.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args){
new DB2Connector();
}
}




Wyniki



Parsery StAX i SAX powinny zwrócić w wyniku poszatkowaną strukturę XML z opisami zdarzeń. Natomiast DOM powinien wyświetlić imię i nazwisko klienta, wyciągnięte z XMLa (u mnie Kathy Smith).



W drugiej części opiszę systemy przetwarzania XML z PHP. Również na podstawie danych zaczerpniętych z DB2.

Brak komentarzy: