Ho deciso di scrivere questo documento nel momento in cui ho cercato sulla rete documenti in italiano relativi al parsing dell'XML con PHP, senza trovarne molti. Allora ho deciso di dare il mio contributo alle persone interessate all'argomento con la stesura di questo documento. Mi sono impegnato molto per cercare di rendere la lettura comprensibile a tutti, partendo da concetti basilari e teorici, fino ad arrivare ad esempi pratici commentati. Chiedo scusa anticipatamente nel caso che, durante la lettura, troviate tratti dove la spiegazione è poco precisa o confusa, tratti con poca chiarezza o con errori grammaticali. In questo caso, ve ne sarei grato se mi segnalaste gli errori, oppure, in alternativa, potrete pubblicare una vostra versione migliorata di questo documento, a seconda delle condizioni poste nella licenza Creative Commons, sotto la quale il presente viene rilasciato.
Cos'è l'XML
L' Extensible Markup Language (XML) è un linguaggio di marcatura simile all'HTML progettato per l'interscambio di documenti strutturati sul web. Infatti, con gli strumenti e la flessibilità che l'XML ci fornisce, possiamo descrivere e fornire una struttura razionale e trasportabile sotto ogni tipo di piattaforma ai dati all'interno di documenti di varie forme, adattando al meglio i dati stessi, ma soprattutto il linguaggio stesso, al tipo di informazione che desideriamo descrivere. Quì segue un esempio di documento XML:
L'esempio è una chiara dimostrazione di ciò che è stato prima detto: abbiamo strutturato i dati adattandoli nel migliore dei modi al tipo di informazione che volevamo descrivere, in questo caso un elenco di persone contenente nome, cognome e matricola di ogni persona. Abbiamo dato un senso ai vari elementi della pagina, identificando e descrivendo le informazioni presenti. Abbiamo adattato il linguaggio XML stesso al tipo di informazioni descritte: l'XML non fa uso di tags predefiniti: potevamo benissimo dare un nome diverso ai tags e alle loro relative proprietà, oppure potevamo fornire tutt'altra struttura al documento, creando ulteriori tag. XML lascia il compito di formattazione e visualizzazione dei dati agli elementi di stile. Punto importante da chiarire è che l'XML non è affatto da considerarsi un sostituto dei database. XML è uno standard definito dal W3C. Maggiori informazioni sull' XML sono reperibili a http://www.w3.org/XML/. Una traduzione in italiano di un articolo sull'XML edito dal W3C è disponibile a http://it.wikipedia.org/wiki/XML
.
Elaborare l'XML
Un documento XML può essere processato da uno strumento chiamato parser, un elaboratore che interpreta i dati a basso livello e costruisce una rappresentazione di questi. Dalla rappresentazione ricavata possiamo distinguere due tipologie di parser, che seguono standard diversi:
Simple API for XML (SAX), tipologia di parser che segue uno standard basato sugli eventi: il documento XML viene elaborato carattere per carattere dal parser, che genera un determinato evento ogni qualvolta un particolare comando viene trovato. SAX non è uno standard W3C, ma è stato sviluppato da membri della mailing list XML-DEV. Per maggiori informazioni su SAX http://www.saxproject.org/.
Document Object Model (DOM), tipologia di parser che segue uno standard più sofisticato e di conseguenza più dispendioso in termini di risorse di sistema, in quanto l'intero documento XML viene caricato in memoria e viene rappresentato in una struttura ad albero di oggetti-nodo, la quale può essere modificata tramite semplici chiamate API. Questa tipologia di parser segue lo standard W3C. Per maggiori informazioni su DOM http://www.w3.org/DOM/.
Simple API for XML (SAX)
Descrivere a grandi linee, con precisione e semplicemente una determinata tecnologia non è affatto semplice. La tecnologia SAX implementa cinque interfacce di programmazione, o per meglio dire API, di cui quattro, di tipo Handler, si occupano dell'operazione di parsing, e una, di tipo Reader, dell'operazione di lettura del documento XML. Ogni interfaccia gestisce le proprie operazioni o può far in modo che il programmatore le gestisca attraverso i metodi, funzioni che operano su un oggetto specifico dichiarate attraverso l'implementazione dell'interfaccia stessa. XMLReader è l'interfaccia di tipo Reader che si occupa di processare e leggere il documento. Durante la lettura del documento XML, il parser SAX è in grado di rilevare alcuni eventi, che corrispondono alla lettura di un elemento tipo l'apertura e la chiusura di un documento XML o l'apertura e la chiusura di un tag. ContentHandler è l'interfaccia che associa, ad ogni metodo implementato, un evento, lanciato in seguito alla lettura di un elemento. In pratica, i metodi implementati dall'interfaccia ContentHandler, forniscono al parser segnali o notifiche sul contenuto logico del documento, necessari al fine del rilevamento degli eventi.
Attraverso quest'interfaccia, possiamo personalizzare la gestione dei tag durante il processo di parsing, decidendo cosa fare quando il reader incontra i tag del documento XML, associando agli eventi prima citati delle funzioni da noi definite, chiamate funzioni di callback. Per far ciò dobbiamo registrare queste funzioni attraverso altre funzioni di registrazione predefinite: praticamente con questa operazione indichiamo al parser quali sono le funzioni da noi definite e a quali eventi rispettivamente corrispondono. In breve, ogni qualvolta viene generato uno specifico evento e richiamato il metodo associato, viene effettuata una chiamata alla funzione di callback da noi definita e associata all'evento stesso. In questo modo possiamo personalizzare le operazioni di parsing: possiamo decidere quali informazioni immagazzinare, quali ignorare e cosa fare esattamente quando viene incontrato un tag. Riscriviamo l'esempio di documento XML prima visto:
Immaginiamo ora di sottoporlo ad una operazione di parsing seguendo lo standard SAX:
Alla prima riga troviamo la dichiarazione XML, che indica l'inizo del documento XML. Alla lettura della dichiarazione XML, il parser evoca l'evento startDocument. Quest'evento verrà evocato una sola volta e prima di qualunque altro evento.
<elenco>
All'apertura di un tag viene generato l'evento startElement. Se esiste una funzione di callback associata all'evento, essa viene richiamata.
Come sopra, viene evocato l'evento startElement e richiamata la funzione di callback se presente. Gli attributi del tag vengono passati come argomento alla funzione di callback.
descrizione persona..........................
L'evento
characters
viene lanciato ogni qualvolta che il parser riceve una notifica di dati carattere, come il contenuto di un tag. Anche a quest'evento può essere associata una funzione di
callback
che verrà richiamata se presente.
</persona>
Alla chiusura di un tag viene generato l'evento
endElement
. Anche in questo caso se è presente una funzione di
callback
associata all'evento verrà richiamata.
Il discorso è uguale nelle successive righe del codice. Gli eventi principali sono
startElement
, lanciato all'avvio di un tag,
characters
, lanciato alla lettura del contenuto di un tag, ed
endElement
, lanciato alla chiusura di un tag. Per informazioni maggiormente dettagliate è consigliato consultare la documentazione ufficiale della tecnologia SAX a http://www.saxproject.org.
PHP e SAX
Parte teorica
PHP offre il supporto del modulo
expat
, che permette il parsing di documenti XML. Il modulo
expat
permette di creare parser XML, settarne i parametri e definire gestori per i vari eventi. Le funzioni relative al parser XML
expat
sono ampiamente documentate nella documentazione ufficiale di PHP, alla pagina
http://it.php.net/manual/it/ref.xml.php. È doveroso dire che, nel presente tutorial, per la vastità dell'argomento, non verranno trattate tutte le funzioni relative ad expat, per cui lo studio della documentazione ufficiale è caldamente consigliato.
Come esempio dimostrativo verrà esaminato un parser che trasforma il documento XML in una struttura ad albero di oggetti-array. L'approccio object-oriented risulta utile non solo al fine di comprendere al meglio l'uso delle funzioni di
callback
, ma anche di dimostrare come sia possibile, tramite queste, mantenere in memoria l'intera struttura del documento XML.
Parte teorico-pratica
Nell'esempio che segue, i dati del documento XML verranno passati alle funzioni di
callback
e inseriti in una struttura ad albero di oggetti-array, dove ogni tag viene rappresentato da un array contenente quattro indici:
name
, che contiene il nome del tag;
attrs
, che contiene un array con le proprietà del tag, ogni proprietà indicizzata col rispettivo nome e contenente il rispettivo valore;
data
, che contiene i dati di testo del tag, o più semplicemente il valore contenuto tra il tag di apertura e quello di chiusura.
Il quarto indice,
child
, contiene i tags figli: praticamente se all'interno del tag <elenco> è presente il tag <persona>, l'indice child dell'array che rappresenta il tag <elenco> avrà come valore un array che rappresenterà il tag <persona> e che conterrà a sua volta i quattro indici sopra citati con i rispettivi valori. Un esempio della rappresentazione del documento infonderà chiarezza:
Abbiamo qui una rappresentazione del documento di esempio in precedenza utilizzato. Come potete notare, gli array sono indicizzati a partire da 0. Nell'indice 0, in questo caso, è contenuta tutta la struttura del tag <elenco>, che si chiude alla fine del documento. L'indice 0 contiene un array, che a sua volta contiene, come prima detto, il nome,
name
, un array con gli attributi,
attrs
, i dati,
data
,
e gli array figli del tag <elenco>. Gli array figli, contenuti a loro volta nell'array
child
, sono indicizzati a partire da 0. In questo caso, <elenco>, contiene due tags figli, che sono rispettivamente contenuti nell'indice 0 e 1 dell'array child, e sono a loro volta array, e a loro volta contengono gli stessi indici di cui godono tutti gli array che rappresentano i tags del documento XML. In questo caso guardare la rappresentazione vale più di mille spiegazioni.
Parte pratica
Diamo finalmente inizio all'atto pratico.
Creiamo l'array
$struct
che conterrà l'intera struttura del documento XML:
$struct = array();
La funzione
xml_parser_create
inizializza un parser XML e restituisce un handle (riferimento o identificativo univoco) che verrà utilizzato dalle altre funzioni XML come riferimento al parser creato. L' handle, in questo caso, verrà memorizzato nella variabile
$xml_parser
.
$xml_parser = xml_parser_create();
Funzioni di callback
Passiamo ora col definire le funzioni di
callback
.
startElement
La funzione che si applica all'evento
startElement
:
function startElement($parser, $name, $attrs)
{
global $struct;
$tag = array("name"=>$name,"attrs"=>$attrs);
array_push($struct,$tag);
}
La funzione deve obbligatoriamente prevedere che le verranno passati tre argomenti, nell'ordine che qui segue:
L'handle di riferimento al parser;
Il nome del tag che ha fatto generare l'evento;
L'array contenente le proprietà del tag.
Ora, ogni qualvolta che verrà evocato l'evento
startElement
, vengono eseguite le operazioni contenute in questa funzione. In questo caso, ogni qualvolta che verrà evocato l'evento
startElement
, verrà creato un array intermedio,
$tag
, che conterrà gli indici "name" e "attrs", e infine verrà accodato all'array globale
$struct
. Praticamente con questa operazione creaiamo l'array che conterrà la rappresentazione del tag che ha generato l'evento.
characters
La funzione associata all'evento
characters
:
function data($parser, $data)
{
global $struct,$i;
if(trim($data)) {
$struct[count($struct)-1]['data']=$data;
}
}
La funzione deve obbligatoriamente prevedere che le verranno passati due argomenti:
L'handle di riferimento al parser;
I dati di testo.
Ogni qualvolta che verrà evocato l'evento
characters
, i dati di testo verranno inseriti
nell'indice "data" dell'ultimo array presente nella struttura
$struct
:
count($struct)
restituisce il numero totale degli indici che costituiscono l'array
$struct
, e, dato che l'indicizzazione inizia da 0, l'indice dell'ultimo array equivale al numero effettivo degli indici stessi meno uno. Quindi, con questa operazione, inseriamo i dati di testo, contenuti nell'indice "data", nell'array creato precendentemente con la funzione
startElement()
, che è appunto l'ultimo array presente in
$struct
. Una precisazione a questo punto è dovuta: l'evento
characters
viene evocato ogni qualvolta che il parser riceve una notifica di dati carattere. Quindi, dobbiamo prevedere che l'evento
characters
ci passi dati che a noi non interessa memorizzare, che possono provocare errori nella visualizzazione della struttura del documento. Il modo migliore e più elegante per ovviare a questo inconveniente è quello di utilizzare la funzione
trim
, che serve a tutt'altro dello scopo per cui ora la utilizziamo, ma in questo caso si rivela utilissima: se la stringa contiene dati nulli, o solo spazi vuoti, restituisce false, e l'istruzione che memorizza i dati nella struttura, contenuta all'interno del ciclo
if
, non verrà eseguita.
La funzione deve obbligatoriamente prevedere che le verranno passati due argomenti:
L'handle di riferimento al parser;
Il nome del tag che ha fatto generare l'evento.
Ogni qualvolta che verrà generato l'evento
endElement
,
l'ultimo array creato
verrà inserito nell'indice
child
del penultimo array. Questa funzione ha il compito di inserire i tags figli nell'indice
child
del tag '
padre
'. Prendiamo in esame una struttura simile a questa:
Come abbiamo già detto in precedenza, la funzione
startElement()
, ad ogni apertura di un tag, crea un array contenente i vari valori di quest'ultimo e lo accoda all'intera struttura $struct. Quindi, è la funzione
endElement()
, che si preoccupa di controllare se il tag che ha generato l'evento sia "
figlio
" di un altro tag, e di conseguenza inserirlo nell'indice child. Ma, quando la funzione
endElement()
sarà eseguita,
startElement()
avrà già compiuto il suo lavoro e l'array rappresentante il tag sarà già stato accodato nel primo albero di
$struct
. Quindi, bisognerebbe copiarne il valore nell'indice child dell'array del tag "
padre
", ed infine cancellarlo dalla struttura. In pratica, se abbiamo il tag <persona>, figlio del tag <elenco
>, all'apertura del tag <persona> la sua rappresentazione verrà inserita nel primo albero di
$struct
. Alla chiusura del tag </persona>, l'indice dell'array rappresentante il tag <elenco>, che in questo caso rappresenta il tag "
padre
", corrisponderà obbligatoriamente al numero effettivo degli indici presenti nell'array
$struct
meno due, o in altre parole al penultimo array creato. È importante precisare, però, che questa regola generale vale soltanto se i tag figli non abbiano a loro volta dei figli: il discorso in quel caso si complica ulteriormente. Ora non ci resta, dunque, che inserire l'intera struttura dell'ultimo array nell'indice child del penultimo array, ed infine eliminare l'ultimo array con la funzione
array_pop
.
registrazione
Dopo aver definito le funzioni di callback, è necessario registrarle ed associarle ai rispettivi eventi: praticamente con questa operazione indichiamo al parser quali sono le funzioni di
callback
e a quali eventi corrispondono.
La funzione
xml_set_element_handler
ha il compito di registrare le funzioni di
callback
associate agli eventi
startElement
ed
endElement
. La funzione deve accettare tre parametri:
L'handle di riferimento al parser;
I
l nome della funzione di
callback
che abbiamo creato e vogliamo associare all'evento
startElement
;
Il nome della funzione di
callback
che abbiamo creato e vogliamo associare all'evento
endElement
.
La funzione
xml_set_character_data_handler
ha il compito di registrare la funzione di
callback
associata all'evento
characters
. La funzione deve accettare due parametri:
L'handle di riferimento al parser;
Il nome della funzione di
callback
che abbiamo creato e vogliamo associare all'evento
characters
.
Lettura e parsing
Dopo i dovuti preparativi passiamo alla parte vera e propria del parsing. Per comodità, memorizziamo il nome del file di cui desideriamo fare il parsing nella variabile
$file
:
$file = "example.xml";
La funzione
xml_parse
avvia il parsing del documento XML.
Segmento dati da analizzare.
Un documento può essere elaborato a segmenti richiamando
xml_parse
diverse volte con nuovi dati, con il parametro
e_finale
settato a
TRUE
quando si elabora l'ultimo segmento.
La funzione
file_get_contents
legge un file e restituisce il suo contenuto in una stringa. La variabile
$parse
risulta utile al fine di verificare se l'operazione di parsing è andata a buon fine:
if(!$parse) {
die("XML parsing error");
}
Per ottenere informazioni specifiche sull'errore, utilizzare le funzioni
xml_error_string
ed
xml_get_error_code
.
Scarichiamo dalla memoria il parser con la funzione
xml_parser_free
:
xml_parser_free($xml_parser);
Infine stampiamo la struttura creata a schermo:
print("<pre>\n");
print_r($struct);
print("</pre>\n");
Il tag <pre> formatta il testo stampato con l'indentazione.
Il codice completo dell'esempio descritto:
I file XML possono essere organizzati come veri e propri database, dalla quale è possibile leggere e inserire dati. È doveroso precisare che l'XML non è stato creato con lo scopo di sostituire i database, ma con l'assenza del supporto di DBMS (tipo mysql) in molti server che offrono freehosting la soluzione di utilizzare le funzioni relative al parser XML sembra quella migliore, più elegante e più economica. Infatti, potremo servirci dell'esempio prima visto per costruire un News Management System dinamico senza necessitare obbligatoriamente del supporto mysql o di qualunque DBMS
. La stessa tecnica può essere applicata per programmare engine dinamiche per la gestire file e downloads, un sistema di statistiche, o magari un CMS, o qualunque altra cosa.
News Management System con PHP & XML-SAX
Ecco come possono essere usate le funzioni relative al parser XML-SAX per costruire un semplice News Management System che non necessita del supporto di alcun DMBS.
Iniziamo con lo strutturare il documento XML che fungerà da database:
Applichiamo un'utile modifica al parser di esempio prima visto, inserendo in esso la funzione
parse_file
, che accetta come argomento il nome del file di cui vogliamo fare il parsing, e restituisce un array contenente l'intero documento strutturato ad albero di oggetti-array.
Le informazioni di formattazione sono contenute nel file
style.css
:
div.news#title{
font-weight: bold;
}
div.news#body {
text-align: left;
margin-left: 0.2cm;
margin-right: 0.2cm;
}
div.news#lnk{
text-align: right;
}
a.news{
font-size: 11px;
color: black;
text-decoration: none;
font-weight: bold;
margin-right: 7mm;
}
Potrete gestire questo News Management System editando direttamente col vostro editor di testi preferito il file
data.xml
o qualunque altro file da voi impostato che funga da database. In alternativa, potrete programmarvi uno script che funga da pannello di amministrazione, che cancella e inserisce news nel database..
Conclusione
Spero solo che, almeno qualcuno, su questo mondo, tragga qualche conoscenza utile dalla lettura di questo documento.
Informazioni sull'autore
Michele Buonaiuto, classe 1988. Studente liceale, amante della musica, del calcio e dell'informatica applicata a networking e sicurezza, entrambi correlati con la programmazione sia orientata al web che al software ad alto e basso livello. Homepage: http://xmen88.altervista.org