XML/Parsing di file XML

< XML
Indice del libro

Obiettivi di apprendimento

  • Comprendere il concetto di parsing dei file XML
  • Utilizzare API diverse per l'elaborazione di file XML
  • Essere consapevoli delle differenze tra i diversi approcci per il parsing dei file XML
  • Decidere quando utilizzare una particolare tecnica

Nei capitoli precedenti ci è stato insegnato come creare file XML. Ciò ha comportato lo sviluppo di documenti XML, fogli di stile e schemi, e la loro validazione. In questo capitolo, ci concentreremo su diversi approcci per il parsing di file XML e quando utilizzarli.

Ma prima, è tempo di rinfrescare ciò che abbiamo imparato sul parsing.

Il processo di analisi dei file XML

modifica

Uno degli obiettivi del formato XML era quello di migliorare i formati di dati grezzi come il puro testo, includendo descrizioni dettagliate del significato del contenuto. Ora, per poter leggere i file XML utilizziamo un parser che espone il contenuto del documento attraverso una API (Application Programming Interface). In altre parole, un'applicazione client accede al contenuto del documento XML attraverso un'interfaccia, invece di dover interpretare il codice XML da sola!

Semplice parsing di un testo

modifica

Un modo per estrarre i dati da un documento XML è la semplice analisi del testo, sfogliando tutti i caratteri del documento e controllando il modello desiderato:

<house>
<value><int>150,000</int></value>
</house>

Diciamo che siamo interessati al valore della casa. Usando il parsing del testo, analizziamo il file per la sequenza di caratteri <value><int> e lo chiamiamo pattern di partenza. Poi, scansioneremo ulteriormente il documento alla ricerca del pattern finale (cioè </int></value>). Infine, dichiariamo che la stringa di testo tra questi due pattern è il valore del tag <house>.

Perché non funziona così

modifica

Ovviamente, questo approccio non è adatto per estrarre informazioni da documenti XML complessi e di grandi dimensioni, poiché dovremmo sapere esattamente come si presenta il file e dove si trovano le informazioni necessarie. Da un punto di vista più generale, la struttura e la semantica di un file XML è determinata dalla composizione del documento, dai suoi tag e attributi - quindi, abbiamo bisogno di un strumento in grado di riconoscere e comprendere questa struttura e di evidenziare eventuali errori in essa. Inoltre, deve fornire il contenuto del documento attraverso un'interfaccia, in modo che altre applicazioni possano accedervi senza difficoltà. Questo strumento è noto come parser XML.

Cosa fa un parser

modifica

Quasi tutti i programmi che hanno bisogno di elaborare documenti XML utilizzano un parser XML per estrarre le informazioni memorizzate nel documento XML, al fine di evitare le difficoltà che si verificano durante la lettura e l'interpretazione dei dati XML grezzi. Il parser di solito è una libreria di classi (ad esempio un insieme di file di classe Java) che legge un dato documento e controlla se è ben formato secondo le specifiche del W3C. Quindi, qualsiasi software client può utilizzare i metodi dell'interfaccia fornita dall'API del parser per accedere alle informazioni che il parser ha recuperato dal file XML.

Nel complesso, Il parser evita all'utente di avere a che fare con i complessi dettagli di XML, come l'assemblaggio di informazioni distribuite su diversi file XML, il controllo dei vincoli di buona formazione e così via.

Parsing: un esempio

modifica

Per illustrare più chiaramente cosa significhi analizzare un file XML, è stato creato il seguente esempio che contiene informazioni su alcune città. Tiene anche traccia di chi è in vacanza e mostra il processo di parsing con i metodi di analisi attualmente più comuni.

Esempio: cities.xml

modifica
<?xml version="1.0" encoding="UTF-8" ?>
<cities>
<city vacation="Sam">
<cityName>Atlanta</cityName>
<cityCountry>USA</cityCountry> 
</city>
<city vacation="David">
<cityName>Sydney</cityName>
<cityCountry>Australia</cityCountry> 
</city>
<city vacation="Pune">
<cityName>Atene</cityName>
<cityCountry>Grecia</cityCountry> 
</city>
</cities>

Sulla base delle informazioni memorizzate in questo documento XML, possiamo facilmente controllare chi è in vacanza e dove. Il parser leggerà il file utilizzando una delle varie tecniche presentate più avanti in questo capitolo.

Questo processo è molto complicato e soggetto a errori di ogni tipo. Fortunatamente, non dovremo mai scrivere codice per esso, perché ci sono molti parser gratuiti e perfettamente funzionanti sul web. Tutto quello che facciamo è scaricare una libreria di classe parser e accedere al documento XML attraverso l'interfaccia fornita dal software di parser. Con le build più recenti di Java, la maggior parte dei parser non devono nemmeno essere scaricati. In altre parole, utilizziamo le funzioni o i metodi inclusi nella libreria delle classi per estrarre le informazioni.

Fondamentalmente, un parser legge il documento XML e cerca di riconoscere la struttura del file stesso mentre controlla gli errori. Controlla semplicemente la presenza di tag di inizio/fine, attributi, namespace, prefissi, e così via. Quindi, il software client può accedere alle informazioni derivate da questa struttura utilizzando i metodi forniti dal software di analisi (cioè l'interfaccia).

Il modo migliore per conoscere le funzionalità di un analizzatore è quello di utilizzarli effettivamente; pertanto, la sezione successiva mostra i diversi metodi di analisi.

Parser API (Application Programming Interface)

modifica

Premessa

modifica

Ci sono due approcci "tradizionali" che dominano il mercato in questo momento: un modello push basato su eventi rappresentato da SAX (Simple API for XML) e un modello ad albero che utilizza l'approccio DOM (Document Object Model).

Tuttavia, ci si sta muovendo verso nuovi approcci e tecniche che cercano di superare i difetti inerenti a questi modelli tradizionali - un modello pull basato sugli eventi e un "modello del cursore", come VTD-XML, che ci permette di sfogliare il documento XML proprio come nell'approccio ad albero, ma in modo più semplice e più facile da usare.

SAX (API semplice per XML)

modifica

Descrizione

modifica

Il modello push, tipicamente esemplificato da SAX[1] è il "gold standard" dell'analisi XML, poiché a oggi è probabilmente il metodo più completo e preciso. Le classi SAX forniscono un'interfaccia tra i flussi di input, dai quali vengono letti i documenti XML, e il software client che riceve i dati messi a disposizione dal parser. Il parser sfoglia l'intero documento e attiva gli eventi ogni volta che riconosce un costrutto XML (ad esempio, riconosce un tag di avvio e attiva un evento - il software client viene notificato e può utilizzare oppure no queste informazioni).

Valutazione

modifica

Il vantaggio di un modello di questo tipo è che non abbiamo bisogno di memorizzare l'intero documento XML in memoria, dato che stiamo leggendo solo un'informazione alla volta. Se si ricorda che la struttura XML è un insieme di nodi di vario tipo, analizzare il documento con un parser SAX significa passare attraverso un nodo alla volta. In questo modo è possibile leggere anche documenti XML molto grandi in modo efficiente per la memorizzazione. Tuttavia, il fatto che il parser fornisca solo informazioni sul nodo attualmente letto implica anche che il programmatore del software client ha il compito di salvare alcune informazioni in una struttura di dati separata (ad esempio i genitori o i figli del nodo attualmente elaborato). Inoltre, l'approccio SAX è praticamente in sola lettura, poiché è difficile modificare la struttura XML quando non abbiamo una visione globale.

Infatti, il parser ha il controllo di ciò che viene letto e quando questo accade. L'utente può solo attendere il verificarsi di un determinato evento e quindi utilizzare le informazioni memorizzate nel nodo attualmente elaborato.

Esempio: TGSAXParser.java

modifica

Come accennato in precedenza, il modo migliore per comprendere appieno il concetto del processo di analisi è quello di utilizzarlo nella pratica. Nel seguente esempio di codice verranno visualizzate le informazioni sul nome e sul paese delle città in cui si trovano le persone in vacanza. Per l'implementazione è stata utilizzata l'API SAX che fa parte del pacchetto Xerces parser:[2]


// importa le classi API SAX di base delle API SAX
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;

public class TGSAXParser extends DefaultHandler
{
    public boolean onVacation = false;

    // cosa fare quando un evento di un elemento di partenza è stato attivato
    public void startElement(String uri, String name, String qName, Attributes atts)
    {
        // memorizza la stringa nel file XML          
        String vacationer = atts.getValue("vacation");
        String cityName = atts.getValue("cityName");
        String cityCountry = atts.getValue("cityCountry");

        // se il tag iniziale è "città", impostare vacanziere a true
        if (qName.equals("city") && (vacationer != null))
        {
            onVacation = true;
            System.out.print("\n" + vacationer + " is on vacation in ");
        }
        if (qName.equals("cityName") && onVacation)
            {                       
            }
        if (qName.equals("cityCountry") && onVacation)
        {                       
        }
    }

    /**Questo metodo viene utilizzato per interrompere la stampa 
	/delle informazioni una volta che l'elemento è stato modificato.
    */
    public void endElement(String uri, String name, String qName)
    {
        //reset flag
        if (qName.equals("city"))
        {
            onVacation = false;
        }
    }

    /**Questo metodo viene attivato per memorizzare e stampare i valori tra
    *i tag XML. Stamperà quei valori solo se onVacation == true..
    */
    public void characters(char[] ch, int start, int length)
    {
        if (onVacation)
        {
            for (int i = start; i < start + length; i++)
            System.out.print(ch[i]);
        }
    }

    public static void main(String[] args)
    {
        System.out.println("People on vacation in the following cities:");

        try
        {
            // crea un parser SAX dal pacchetto Xerces
            XMLReader xml = XMLReaderFactory.createXMLReader();
            TGSAXParser handler = new TGSAXParser();
            xml.setContentHandler(handler);
            xml.setErrorHandler(handler);
            FileReader r = new FileReader("cities.xml");
            xml.parse(new InputSource(r));
        }
        catch (SAXException se)
        {
            System.out.println("XML Parsing Error: " + se);
        } 
        catch (IOException io) 
        {
            System.out.println("File I/O Error: " + io);
        }
    }
}

DefaultHandler: come accennato in precedenza, il SAX è completamente guidato da un evento. Pertanto, abbiamo bisogno di un gestore che "ascolta" il flusso di input proveniente dal file di input (cities.xml in questo caso).

L'API SAX fornisce classi di interfaccia, che dobbiamo estendere con il nostro codice per leggere il nostro specifico documento XML. Per includere il nostro codice nelle API SAX, dobbiamo solo estendere l'interfaccia DefaultHandler con la nostra classe e impostare il gestore di contenuti alla nostra classe personalizzata (che consiste di tre metodi: startElement, endElement e caratteri).

I metodi startElement() e endElement(): questi metodi sono invocati ogni volta che il paser SAX trova rispettivamente un tag di inizio o di fine. L'API SAX fornisce gli spezzoni vuoti per entrambi i metodi e noi dobbiamo riempirli con il nostro codice.

In questo caso, vogliamo che il nostro programma faccia qualcosa ogni volta che l'attributo vacation è impostato, così impostiamo una variabile booleana come true ogni volta che troviamo un tale elemento ed elaboriamo il nodo stampando la sequenza di caratteri tra il tag iniziale e quello finale. Il metodo character viene chiamato automaticamente ogni volta che un evento startElement e endElement è stato attivato, ma stampa la stringa di caratteri solo se l'attributo onVacation è impostato.

DOM (Document Object Model)

modifica

Descrizione

modifica

L'altro approccio popolare è il modello ad albero rappresentato dal DOM (Document Object Model).[3] Questo metodo funziona in modo simile a un parser SAX, poiché legge il documento XML da un flusso di input sfogliando il file e riconoscendo le strutture XML.

Questa volta, invece di restituire il contenuto del documento in una serie di piccoli frammenti, il metodo DOM mappa la gerarchia XML in un oggetto ad albero DOM che contiene tutto ciò che proviene dal documento XML originale. Tutto ciò che proviene da elementi, commenti, informazioni testuali o istruzioni di elaborazione è memorizzato nell'oggetto albero come nodi, a partire dal documento stesso come nodo principale.

Ora che tutte le informazioni di cui abbiamo bisogno sono conservate in memoria, accediamo ai dati utilizzando i metodi forniti dal software di analisi per leggere o modificare gli oggetti all'interno dell'albero. Questo facilita l'accesso casuale al contenuto del documento XML e offre la possibilità di modificare i dati in esso contenuti o addirittura di creare nuovi file XML trasformando un DOM in un documento XML.

Valutazione

modifica

Tuttavia, il principale aspetto negativo di questo approccio è che richiede molta più memoria e quindi non è adatto a situazioni in cui si utilizzano file XML di grandi dimensioni. Ma soprattutto, è un po' più complesso del semplice metodo SAX anche per piccoli e semplici problemi.

Esempio: MyDOMParser.java

modifica

Nel seguente esempio di codice, viene nuovamente creata una lista di città con persone in vacanza, ma questa volta con l'approccio ad alberi:

// Importa tutte le classi API DOM necessarie
import org.apache.xerces.parsers.*;
import org.apache.xerces.dom.*;
import org.w3c.dom.*;
public class MyDOMParser{
public static void main(String[] args) {
System.out.println("People on vacation in the following cities:");  
try {
// crea un oggetto parser DOM
DOMParser parser = new DOMParser();
parser.parse("cities.xml"); 

// memorizza l'oggetto albero in una variabile
         org.w3c.dom.Document doc  = parser.getDocument();

// restituisce una lista di tutti gli elementi della città nella mia lista delle città
	 NodeList list = doc.getElementsByTagName("city");

// Ora, per ogni elemento della lista delle città, controlla se
// l'attributo "vacanza" è impostato e se sì, stampa 
// l'informazione sul vacanziere.
for(int i = 0, length = list.getLength(); i < length; i++){
Element city  = (Element)list.item(i);
Attr vacationer = city.getAttributeNode("vacation");
if(vacationer!= null){
String v = vacationer.getValue();
System.out.print(v + " is vacationing in ");

// Informazioni sul nome della città e del paese
// direttamente dall'oggetto albero DOM
ParentNode cityname = (ParentNode)
doc.getElementsByTagName("cityName").item(i);
ParentNode country = (ParentNode)
doc.getElementsByTagName("cityCountry").item(i);
System.out.println(cityname.getTextContent() + ", " + country.getTextContent());
}
}
} catch (Exception e) {         
System.out.println(e.getMessage());
}     
}
}

parser.getDocument(): una volta analizzato il documento XML, l'oggetto albero viene temporaneamente memorizzato nella variabile parser. Per lavorare con l'oggetto DOM, dobbiamo creare una variabile che lo contiene (di tipo org.w3c.dom.Document).

Poi, creiamo una lista di nodi che contengono tutti gli elementi con il nome del tag city. Il parser trova questi nodi sfogliando l'albero DOM. Quindi, ci limitiamo a passare attraverso ognuno degli elementi della città, a controllare se l'attributo vacation è impostato e, in caso affermativo, a visualizzare tutte le informazioni sul vacanziere.

Xerces fornisce un utile metodo chiamato getTextContent() che ci permette di accedere direttamente al nodo di testo di un nodo elemento, evitando tutte le difficoltà che emergono da spazi bianchi non necessari e simili.

  1. www.saxproject.org
  2. Xerces 2 Homepage
  3. W3C Recommendation