Interazione tra Java ed XML con JDOM
In questo articolo viene analizzato con diversi esempi di codice come utilizzare JDOM per far parlare le nostre applicazioni Java in XML
JDOM è una libreria java open source per la creazione e manipolazione di file xml molto potente e semplice da usare. Per scaricare la libreria, la documentazione ufficiale e qualche esempio potete visitare il sito ufficiale di JDOM, http://www.jdom.org.
Per questo articolo ho utilizzato JDOM 1.0, scaricabile all’indirizzo http://www.jdom.org/dist/binary/jdom-1.0.zip. Per compilare avrete bisogno solo del file jdom.jar (presente nella cartella build, dentro l’archivio da scaricare). Sempre nell’archivio linkato qui sopra trovate la documentazione javadoc ed i sorgenti (essendo JDOM open source, come già detto).
Per gli esempi che ho preparato per questo articolo mi appoggerò su un file xml con questa struttura:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<archivio_cd> <album id = "x"> <titolo>titolo album</titolo> <gruppo>nome gruppo</gruppo> <genere>genere</genere> <anno_di_uscita>anno</anno_di_uscita> <tracce numero_tracce = "n"> <traccia numero_traccia = "1">titolo traccia 1</traccia> <traccia numero_traccia = "2">titolo traccia 2</traccia> ... ... <traccia numero_traccia = "n">titolo traccia n</traccia> </tracce> </album> ... ... </archivio_cd> |
N.B.: per utilizzare le classi di JDOM, dovrete importare il package org.jdom (e sotto-package, quando occorre), assicurandovi che il file jdom.jar sia presente nel vostro classpath!
Compilare ed eseguire programmi con JDOM
Dalla shell (o dall’emulatore del dos) scrivete:
1 2 |
javac -classpath (path della libreria); File.java java -cp (path della libreria); File |
ad esempio:
1 2 |
javac -classpath ./jdom.jar; File.java java -cp ./jdom.jar; File |
dove il . (punto) indica la directory corrente.
Nel caso java o javac (od entrambi) siano comandi non validi, assicuratevi di aver installato correttamente l’sdk (se non è presente nel vostro sistema, scaricatelo da http://www.java.sun.com) ed eventualmente modificate le variabili d’ambiente giuste ($PATH in linux, qualcosa di simile – presumo – per windows).
N.B. 1: sia in java che in javac dovrebbe essere utilizzabile il comando -cp per indicare il classpath. Nella mia versione dell’sdk (1.4.2 per linux) -cp va bene solo per java… Controllate il comando corretto lanciando java e javac senza argomenti.
N.B. 2: in ambiente linux, anzichè usare ; (punto e virgola) per separare l’opzione del classpath dal resto degli argomenti bisognera’ usare : (due punti). Su linux infatti il punto e virgola è usato per dare due o piu’ comandi con una stessa istruzione.
Creare e gestire gli elementi: aggiungere testo, attributi e figli
Prima di scrivere dati nel file, dovrete prepare gli elementi. Vediamo come fare. Un qualsiasi elemento xml (come ‘archivio_cd’, ‘album’, ‘tracce’, ecc.) e’ rappresentato in JDOM da un oggetto della classe org.jdom.Element. Per esempio, ecco l’elemento ‘album’:
1 |
Element elementoAlbum = new Element("album"); |
Per aggiungere attributi all’elemento, dobbiamo chiamare su quell’istanza il metodo setAttribute nel seguente modo:
1 |
elementoAlbum.setAttribute("id", "0"); |
dove il primo parametro è il nome dell’attributo, mentre il secondo è il valore dello stesso. Entrambi devono essere stringhe. Non c’è limite al numero di attributi inseribili in un Element, basta che abbiano nomi diversi.
Attenzione! Durante la compilazione non vengono eseguiti controlli sulla presenza di doppioni tra gli attributi, attenzione quindi! Per leggere il valore di un attributo invece, si utilizza il metodo getAttributeValue:
1 |
String idAlbum = elementoAlbum.getAttributeValue("id"); |
L’argomento di getAttributeValue ovviamente è il nome dell’attributo di cui vogliamo leggere il valore. Nel caso l’attributo non esistesse, verrebbe restituito null. Il valore dell’attributo è restituito come stringa. Se abbiamo bisogno di quel valore come tipo primitivo dobbiamo usare le classi wrapper di java.lang:
1 2 3 4 5 6 7 8 9 |
try { int valoreInt = Integer.parseInt(stringa); double valoreDouble = Double.parseDouble(stringa); ... ... } catch (NumberFormatException ex) { System.err.println(stringa + " non rappresenta un numero valido!"); } |
Per aggiungere del testo ad un elemento (come per esempio, aggiungere all’elemento ‘titolo’ il titolo dell’album) si utilizza il metodo setText. La stringa passata andrà a comporre il testo dell’elemento.
1 2 3 4 5 |
Element elementoTitolo = new Element("titolo"); elementoTitolo.setText("Powerslave"); Element elementoGruppo = new Element("gruppo"); elementoGruppo.setText("Iron Maiden"); |
Come per gli attributi, si può ottere il testo di un elemento:
1 2 |
String testo1 = elemento.getText(); String testo2 = elemento.getTextTrim(); |
getText restituisce il testo di un elemento cosi’ come viene letto, mentre getTextTrim restituisce il testo di un elemento dopo che sono stati tolti gli (eventuali) spazi iniziali e finali (ed è la stessa cosa di getText().trim()). Se andate di fretta, potete ottenere il testo di un elemento direttamente dall’elemento genitore:
1 2 |
String testo1 = elementoAlbum.getChildText("gruppo"); String testo2 = elementoAlbum.getChildTextTrim("gruppo"); |
Questo codice restituisce il testo dell’elemento figlio di ‘elementoAlbum’ che si chiama ‘gruppo’. La seconda versione toglie gli spazi iniziali e finali eventualmente presenti. Se vogliamo aggiungere ad un elemento dei sotto elementi, dobbiamo chiamare su quell’elemento il metodo addContent:
1 2 3 4 5 6 7 8 9 |
elementoAlbum.addContent(elementoTitolo); elementoAlbum.addContent(elementoGruppo); for (int i = 0; i < vettoreElementiTracce.length; i++) { elementoTracce.addContent(vettoreElementiTracce[i]); } elementoAlbum.addContent(elementoTracce); |
Come aggiungete elementi, li potete anche togliere. Basta chiamare il metodo removeContent sull’elemento a cui dobbiamo togliere un sotto elemento, passando come argomento l’Element da rimuovere.
1 |
elementoRadice.removeContent(elementoAlbum); |
Dato poi un oggetto Element, potrete sempre ottenere sia l’elemento genitore che gli eventuali elementi figli. Ovviamente la radice ha l’elemento genitore null. Per ottenere l’elemento genitore basta chiamare il metodo getParentElement (ereditato da org.jdom.Content) sull’elemento che ci interessa:
1 |
Element elementoGenitore = elemento.getParentElement(); |
Per ottenere gli elementi figli invece, si chiama il metodo getChildren, anch’esso ereditato da Content:
1 |
List elementiFigli = elemento.getChildren(); |
In questo modo ‘elementiFigli’ conterrà l’elenco di tutti i figli (di primo livello) dell’oggetto chiamante. Se si desidera filtrare il risultato, per ottenere solo un certo tipo di elementi figli, dobbiamo passare a getChildren una stringa con il nome degli elementi che si desidera ottenere:
1 |
List elementiFigli = elemento.getChildren("traccia"); |
Dopo l’esecuzione di questo codice, ‘elementiFigli’ conterrà tutti i figli di ‘elemento’ che si chiamano ‘traccia’. I figli che non soddisfano il “filtro” verranno ignorati.
N.B.: il valore restituito da getChildren non sarà mai null! Infatti, nel caso l’elemento non abbia figli (o ne ha ma non corrispondono al filtro) viene restituita una lista vuota.
Da un elemento si può poi ottenere un unico elemento figlio:
1 |
Element elementoFiglio = elemento.getChild("titolo"); |
In questo semplice esempio, getChild restituisce il primo elemento figlio di ‘elemento’ che si chiama ‘titolo’. Questa possibilità è utile quando siamo sicuri che un certo elemento ha un unico figlio con quel particolare nome.
Creare il documento
Dopo aver caricato l’albero xml con degli elementi, bisogna creare un oggetto org.jdom.Document che vada a contenere il tutto, a partire da un nodo detto radice (il primo elemento che appare all’inizio di un file xml). Si può agire in due modi:
- si passa l’elemento radice al costruttore di Document;
- si crea un Document usando il costruttore senza parametri e si imposta successivamente la radice;
1 2 3 4 5 6 |
Document documento = new Document(elementoArchivioCd); // oppure Document documento = new Document(); documento.setRootElement(elementoArchivioCd); |
Scrivere su file
Dopo aver preparato l’oggetto Document, siamo pronti per scriverlo su un file. Per fare ciò abbiamo bisogno di un oggetto della classe org.jdom.output.XMLOutputter e di un OutputStream:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
XMLOutputter xmlOutputter = new XMLOutputter(); xmlOutputter.setFormat(Format.getPrettyFormat()); try { FileOutputStream fileOutputStream = new FileOutputStream(new File("archivio_cd.xml")); xmlOutputter.output(documento, fileOutputStream); } catch (FileNotFoundException ex) { System.err.println(ex); } catch (IOException ex) { System.err.println(ex); } |
Il metodo setFormat serve ad indicare all’oggetto XMLOutputter in che modo verrà formattato il file che si sta creando. Di default tutto il contenuto sarà scritto senza indentazione su un unica riga (e sarà abbastanza illeggibile). Format.getPrettyFormat farà in modo che si vada a capo dopo ogni elemento e che gli elementi figli vengano indentati correttamente (volendo si può modificare a piacere il valore di indentazione).
N.B. 1: il metodo output di XMLOutputter accetta un qualsiasi java.io.OutputStream e java.io.Writer
N.B. 2: in alcune versioni di JDOM è possibile, se il file su cui scrivere non esiste, che questo non venga creato! Nel caso questo succedesse provvedete voi a crearlo con il metodo createNewFile di un oggetto java.io.File.
N.B. 3: ogni volta che l’albero xml viene modificato e volete modificare anche il contenuto del file, l’unica soluzione è la riscrittura completa di tutto l’albero: JDOM non permette di modificare il file solo dove è necessario, ma lo riscrive completamente. Perciò, in caso stiate manipolando grandi quantità di dati, evitate di far riscrivere il tutto ogni dieci secondi perchè ci perdete un sacco di tempo… 😉
Leggere da file
Per leggere il contenuto di un file xml dobbiamo creare un istanza della classe org.input.SAXBuilder. Se non si verificheranno errori otterremo il contenuto del file all’interno di un oggetto Document.
1 2 3 4 5 6 7 8 9 10 11 12 |
Document documento = null; SAXBuilder saxBuilder = new SAXBuilder(); try { documento = saxBuilder.build(new File("archivio_cd.xml")); } catch (JDOMException ex) { System.err.println(ex); } catch (IOException ex) { System.err.println(ex); } |
L’unica cosa da fare sarà istanziare un SAXBuilder e chiamare il metodo build passando come parametro il File che rappresenta il file xml che vogliamo leggere. In caso di errori durante la lettura verrà sollevata una IOException, mentre se l’errore è a livello dell’xml verrà lanciata una JDOMException (per esempio, se il contenuto non è well-formed).
Dopo aver ottenuto il Document, possiamo ottenere l’elemento radice attraverso il metodo getRootElement:
1 |
Element elementoRadice = documento.getRootElement(); |
In questo modo possiamo ottenere facilmente ogni altro nodo, attraverso i metodi visti in precedenza.
Esplorare la struttura del file xml caricato
Abbiamo visto che quando si usa getChildren su un elemento per ottenerne gli elementi figli, otteniamo questi (se presenti) all’interno di una lista. Come in qualsiasi lista degna di tale nome, non possiamo accedere direttamente all’elemento che ci interessa, ma dobbiamo scorrere gli elementi uno ad uno.
Java ci viene in aiuto fornendoci degli oggetti particolari, gli iteratori, che si occupano di scorrere per noi una qualsiasi Collection (come la List, ad esempio, ma non solo!). Usare un iteratore è molto semplice, come mostra questo esempio:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import java.util.Iterator; import java.util.List; ... ... List lista = elemento.getChildren(); // ottengo un iteratore alla lista // chiamando il metodo iterator() della Collection Iterator iteratore = lista.iterator(); // si cicla finchè hasNext() restituisce true, // cioè se ci sono ancora elementi da scorrere while (iteratore.hasNext()) { // ottengo l'elemento corrente // chiamando next() sull'iteratore Element elementoCorrente = (Element)iteratore.next(); ... ... } |
Se per esempio volessimo visualizzare sulla console il contenuto del nostro archivio cd, possiamo creare un metodo simile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
void visualizza(Element radice) { List elencoAlbum = radice.getChildren("album"); Iterator iteratoreAlbum = elencoAlbum.iterator(); while (iteratoreAlbum.hasNext()) { Element album = (Element)iteratoreAlbum.next(); System.out.println("ID album: " + album.getAttributevalue("id")); System.out.println("titolo..: " + album.getChildTextTrim("titolo")); System.out.println("gruppo..: " + album.getChildTextTrim("gruppo")); System.out.println("genere..: " + album.getChildTextTrim("genere")); System.out.println("anno....: " + album.getChildTextTrim("anno")); System.out.println("\ntracce presenti: " + album.getChild("tracce").getAttributeValue("numero_tracce")); List elencoTracce = album.getChild("tracce").getChildren("traccia"); Iterator iteratoreTracce = elencoTracce.iterator(); while (iteratoreTracce.hasNext()) { Element traccia = (Element)iteratoreTracce.next(); System.out.println( traccia.getAttributeValue("numero_traccia") + ") " + traccia.getTextTrim()); } } } |
Ecco altre semplici metodi, a titolo d’esempio:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
Document preparaDocumento() { // creo l'elemento radice Element radice = new Element("archivio_cd"); // creo i sotto elementi Element album = new Element("album"); album1.setAttribute("id", "0"); Element titolo = new Element("titolo"); titolo.setText("Images and Words"); Element gruppo = new Element("gruppo"); gruppo.setText("Dream Theater"); Element genere = new Element("genere"); genere.setText("progressive metal"); Element anno = new Element("anno_di_uscita"); anno.setText("1992"); Element tracce = new Element("tracce"); tracce.setAttribute("numero_tracce", "8"); // vettore con i sotto elementi di 'tracce' Element[] vettoreTracce = new Element[8]; for (int i = 0; i < tracce.length; i++) { vettoreTracce[i] = new Element("traccia"); vettoreTracce[i].setAttribute("numero_traccia", "" + (i + 1)); } vettoreTracce[0].setText("Pull me under"); vettoreTracce[1].setText("Another day"); vettoreTracce[2].setText("Take the time"); vettoreTracce[3].setText("Surrounded"); vettoreTracce[4].setText("Metropolis part I: the miracle and the sleeper"); vettoreTracce[5].setText("Under a glass moon"); vettoreTracce[6].setText("Wait for sleep"); vettoreTracce[7].setText("Learning to live"); // aggiungo le tracce all'elemento 'tracce' for (int i = 0; i < tracce.length; i++) { tracce.addContent(vettoreTracce[i]); } // aggiungo i vari sotto elementi all'elemento 'album' album.addContent(titolo); album.addContent(gruppo); album.addContent(genere); album.addContent(anno); album.addContent(tracce); // aggiungo 'album' alla radice radice.addContent(album); // restituisco un istanza del documento return new Document(radice); } void scrivi(Document documento) { // oggetto che si occupa di scrivere il documento nel file XMLOutputter xmlOutputter = new XMLOutputter(); // imposto il formato del file xml prodottp xmlOutputter.setFormat(Format.getPrettyFormat()); try { // stream per la scrittura FileOutputStream fileOutputStream = new FileOutputStream(new File("archivio_cd.xml")); xmlOutputter.output(documento, fileOutputStream); } catch (FileNotFoundException ex) { System.err.println(ex); } catch (IOException ex) { System.err.println(ex); } } Document leggi() { Document documento = null; // oggetto che mi legge dal file indicato // e che mi ricostruisce la struttura xml SAXBuilder saxBuilder = new SAXBuilder(); try { // ottengo il contenuto del file documento = saxBuilder.build(new File("archivio_cd.xml")); } catch (JDOMException ex) { System.err.println(ex); return null; } catch (IOException ex) { System.err.println(ex); return null; } return documento; } |
Lavorare con più alberi XML
Può capitare di dover lavorare con più alberi xml all’interno dello stesso programma e di dover far interagire gli elementi dei vari alberi, ad esempio facendo degli scambi. La cosa di per sè non e’ complicata ma dobbiamo prestare attenzione ad una cosa: in JDOM ogni elemento può avere al massimo un unico genitore. Per questo, il seguente codice genera un errore a runtime:
1 2 3 4 |
// elemento1 e' un figlio di radice1 // elemento2 e' figlio di radice2 elemento1.addContent(elemento2); // errore!!! |
Sto cercando di aggiungere ‘elemento2’ ad ‘elemento1’, ma ‘elemento2’ ha già un genitore!
Il modo più semplice per risolvere il problema consiste nell’usare il metodo detach: questo metodo letteralmente “stacca” l’elemento che lo chiama dal proprio elemento genitore, permettendoci di spostarlo da altre parti senza problemi.
1 2 |
elemento2.detach(); elemento1.addContent(elemento2); |
Conclusioni
Spero che questo semplice tutorial vi sia stato utile 🙂
Tuttavia, se volete approfondire la vostra conoscenza su JDOM non posso non consigliarvi di dare un occhiata alla documentazione ufficiale. E’ stata creata con javadoc ed è veramente chiara e completa, con molti esempi ed utili consigli (tutto in inglese purtroppo).
Troverete un sacco di classi e metodi che per brevità non ho illustrato in questo articolo ma che possono essere molto interessanti. Ad esempio, c’è la possibilità di creare DTD, namespace e gestire diverse funzionalità avanzate che probabilmente nemmeno conoscevate 🙂
In ogni caso, per qualunque chiarimento non esitate a scrivermi!