JOOX è una libreria che permette di creare, leggere e manipolare documenti xml (può essere considerata un wrapper del package org.w3c.dom
) utilizzando delle fluent api, concatenando ad esempio una sequenza di operazioni in cascata.
Il funzionamento di JOOX è incentrato su poche classi di fondamentale importanza:
- JOOX: costituisce l'entry point per l'accesso a tutte le altre funzionalità fornite dalla libreria.
- Match: rappresenta una collezione di elementi DOM corrispondendi ad un particolare filtro di selezione (Filter).
- Filter e FastFilter: rappresentano i filtri che vengono applicati ai Match per selezionare un sottoinsieme degli elementi del Match in base ai criteri di filtraggio definiti.
- Each: è la classe mediante la quale è possibile definire delle funzioni di callback che vengono invocare per tutti gli elementi all'interno di un Match.
Accesso JQuery-like
Dal momento che JOOX trae ispirazione da JQuery può essere utile definire, per chi ha avuto già a che fare con la famosa libreria javascript, delle modalità di accesso simili.
In particolare a partire da Java 5 è possibile utilizzare il costrutto “import static
” per importare una o più classi e procedere quindi all'invocazione dei metodi statici senza fare riferimento al nome della classe statica che li contiene.
import static org.joox.JOOX.*;
Parsing di un documento xml
Per effettuare il parsing di un documento xml si usa il metodo $
della classe static JOOX.
Come detto è possibile accedere a tale metodo facendo riferimento direttamente alla classe JOOX (quindi JOOX.$(...)
) oppure una volta effettuato l'import static della classe senza fare riferimento a questa (quindi direttamente con $(...)
).
Quindi supponendo di voler effettuare il parsing di un documento xml memorizzato nel file documento.xml utilizzeremo la seguente istruzione:
Match m = $(new File("c:/documento.xml"));
Come vediamo il risultato di questa istruzione produce un'istanza della classe Match
che wrappa una lista ordinata di elementi DOM senza duplicati fornendo operazioni utili effettuabili su tutti gli elementi in essa contenuti.
Per gli esempi successivi utilizzeremo il seguente documento xml:
<documento>
<libri>
<libro id="1">
<nome>Io uccido</nome>
<autori>
<autore>Giorgio Faletti</autore>
</autori>
</libro>
<libro id="2">
<nome>I promessi sposi</nome>
<autori>
<autore>Alessandro Manzoni</autore>
</autori>
</libro>
<libro id="3">
<nome>Flatlandia</nome>
<autori>
<autore>Edwin Abbott</autore>
</autori>
</libro>
</libri>
<films>
<film id="1">
<nome>La vita è bella</nome>
<attori>
<attore>Roberto Benigni</attore>
<attore>Nicoletta Braschi</attore>
</attori>
</film>
</films>
</documento>
Funzioni di base (prima parte)
Alcune delle principali funzioni di base che è possibile applicare sulle istanze della classe Match
(e quindi su tutti gli elementi DOM in esse contenuti) sono:
isEmpty()
: restituisce true se l'istanza Match non contiene elementi DOMisNotEmpty()
: restituisce true se l'istanza Match non contiene elementi DOMsize()
: restituisce il numero di elementi DOM presenti all'interno del Matchtag()
: estrae il nome del tag da ogni elemento DOM presente all'interno del Matchcontent()
: restituisce il contenuto di ogni elemento DOM presente all'interno del Match
Utilizzo delle classi Filter e FastFilter
A partire da una classe Match
è possibile selezionare un subset degli elementi DOM in essa contenuta (ottenendo una nuova istanza della classe Match) attraverso l'utilizzo delle funzioni filter e find:
filter
: agisce direttamente sugli elementi DOM presenti all'interno della classe Matchfind
: agisce sui figli di ogni elemento DOM presente all'interno della classe Match
Entrambi questi metodi utilizzano come argomenti un Filter
(o FastFilter
) rappresentante le condizioni di selezione degli elementi del Match.
Alcuni dei filtri più comuni e semplici da utilizzare sono:
tag
estrae tutti gli elementi corrispondenti ad un dato tag
m.find(tag("libro"));
leaf
estrae tutti gli elementi che sono nodi-foglia
m.find(leaf());
ids
estrae tutti gli elementi che hanno id fra quelli passati come argomenti
m.find(ids(1,3));
odd
estrae gli elementi di posto dispari
m.find(tag("libro")).find(odd());
even
estrae gli elementi di posto pari
m.find(tag("libro")).find(odd());
E' possibile effettuare una composizione logica di due o più filtri mediante and, or, not come negli esempi seguenti:
m.find(or(tag("libro"), tag("film")));
m.find(and(tag("libro"), ids("1")));
m.find(and(tag("libro"), not(ids("1"))));
Funzioni di base (seconda parte)
Navigazione attraverso gli elementi DOM
Ovviamente attraverso JOOX è possibile navigare direttamente attraverso gli elementi del documento xml.
child(n)
recupera il figlio n-esimo di tutti gli elementi del Match
Match secondo_libro = m.find(tag("libri")).child(1);
next()
recupera l'elemento immediatamente successivo a ciascuno degli elementi del Match
Match terzo_libro = secondo_libro.next();
prev()
recupera l'elemento immediatamente precedente a ciascuno degli elementi del Match
Match primo_libro = secondo_libro.prev();
sibling()
recupera tutti gli elementi adiacenti a ciascuno degli elementi del Match
Match adiacenti = primo_libro.siblings();
parent()
recupera l'elemento padre di ciascuno degli elementi del Match
Match padre = primo_libro.parent();
parents()
recupera tutti gli antenati di ciascun elemento del Match
Match antenati = adiacenti.parents();
Modifica del documento xml
E' possibile infine modificare la struttura di un documento xml attraverso apposite funzioni:
prepend(...)
inserisce in testa al contenuto di ogni elemento DOM del Match
il codice xml passato come argomento
primo_libro.prepend("primo");
append(...)
inserisce in coda al contenuto di ogni elemento DOM del Match
il codice xml passato come argomento
primo_libro.append("ultimo");
append(...)
inserisce in coda al contenuto di ogni elemento DOM del Match
il codice xml passato come argomento
primo_libro.append("ultimo");
content(...)
costituisce il contenuto di ogni elemento DOM del Match
con il codice passato come argomento
primo_libro.content("contenuto");
attr(attributo, valore)
modifica il contenuto dell'attributo di ogni elemento DOM del Match
con quello passato come secondo argomento
secondo_libro.attr("attributo", "valore");
Eseguire una funzione per ogni elemento DOM di un Match
Per eseguire una funzione personalizzata per ogni elemento DOM all'interno dell'istanza di un Match è possibile utilizzare la funzione each(...)
passando come argomento l'istanza di una classe che implementa l'interfaccia Each
.
Tale interfaccia è sostanzialmente costituita da un metodo con la seguente firma:
public void each(Context context)
che viene invocato per ogni elemento del Match
dove Context
è rappresentativo dell'elemento DOM corrente e costituisce il punto di accesso per recuperare le informazioni su di esso.
La seguente implementazione ad esempio stampa il contenuto testuale di tutti gli elementi del Match in grassetto:
public class BoldEach implements Each {
@Override
public void each(Context context) {
System.out.println(""+context.element().getTextContent()+"");
}
}
E viene utilizzata nel seguente modo:
istanza_match.each(new BoldEach());
In questo modo è particolarmente semplice l'implementazione di funzioni di parsing avanzate, concettualmente simili a dei callback: per esempi più sofisticati (utilizzo di xslt, associazione con ORM, etc) rimandiamo ad articoli successivi.