PDA

View Full Version : [Java] Ordinamento di un file XML tramite Jdom


odiojava.net
24-09-2008, 17:49
Salve!
Stò avendo problemi nel fare l'ordinamento di un file xml usando le librerie jdom.
Questo è il codice che ho scritto:

int selezione = 0;
String locazione = casellasfoglia.getText();
impegno ciccio = new impegno (locazione,selezione);
int alto = ciccio.getnumele(locazione);
Element agenda = new Element("agenda");
Element appuntamento = new Element("appuntamento");
Element gdescrizione = new Element ("descrizione");
Element nome = new Element("nome");
Element cognome = new Element("cognome");
Element gdata = new Element("data");
Element gora = new Element ("ora");
Element gpriorita = new Element ("priorita");
for (int i=0;i<ciccio.getnumele(locazione);i++){
ciccio = new impegno (locazione,i);
gdescrizione = new Element ("descrizione"); gdescrizione.setText(ciccio.getdescrizione());
nome = new Element("nome"); nome.setText(ciccio.getnome());
cognome = new Element("cognome"); cognome.setText(ciccio.getcognome());
gdata = new Element("data"); gdata.setText(ciccio.getdatastring());
gora = new Element ("ora"); gora.setText(ciccio.getora());
gpriorita = new Element ("priorita"); gpriorita.setText(ciccio.getpriorita());
appuntamento = new Element("appuntamento");
appuntamento.addContent(gdescrizione);
appuntamento.addContent(nome);
appuntamento.addContent(cognome);
appuntamento.addContent(gdata);
appuntamento.addContent(gora);
appuntamento.addContent(gpriorita);
agenda.addContent(appuntamento);
}
Element agenda2 = agenda;
List backup = agenda.getChildren("appuntamento");
List backup2 = agenda2.getChildren("appuntamento");
Iterator pointer = backup.iterator();
Iterator pointer2 = backup2.iterator();
String descrizionetmp = new String();
String nometmp = new String();
String cognometmp = new String();
String datatmp = new String();
String oratmp = new String();
String prioritatmp = new String();
int giorno1 = 0;
int mese1 = 0;
int anno1 = 0;
GregorianCalendar data1 = new GregorianCalendar();
int giorno2 = 0;
int mese2 = 0;
int anno2 = 0;
GregorianCalendar data2 = new GregorianCalendar();

while (alto > 0)
{
pointer = backup.iterator();
pointer2 = backup2.iterator();
agenda2 = (Element) pointer2.next();
for (int c=0;c<alto-1;c++)
{
agenda = (Element) pointer.next();
agenda2 = (Element) pointer2.next();
giorno1 = Integer.parseInt(agenda.getChild("data").getTextTrim().substring(0,2));
mese1 = Integer.parseInt(agenda.getChild("data").getTextTrim().substring(3,5));
anno1 = Integer.parseInt(agenda.getChild("data").getTextTrim().substring(6,10));
data1 = new GregorianCalendar(anno1,(mese1-1),giorno1);
giorno2 = Integer.parseInt(agenda2.getChild("data").getTextTrim().substring(0,2));
mese2 = Integer.parseInt(agenda2.getChild("data").getTextTrim().substring(3,5));
anno2 = Integer.parseInt(agenda2.getChild("data").getTextTrim().substring(6,10));
data2 = new GregorianCalendar(anno2,(mese2-1),giorno2);
System.out.println("C: " + c + ", nome agenda1 :" + agenda.getChild("nome").getTextTrim()+ ", nome agenda2 :" + agenda2.getChild("nome").getTextTrim() + ", data1 :" +
data1.get(GregorianCalendar.DAY_OF_MONTH) + "/" +
(data1.get(GregorianCalendar.MONTH)+1) + "/" +
data1.get(GregorianCalendar.YEAR) + ", data2 :" +
data2.get(GregorianCalendar.DAY_OF_MONTH) + "/" +
(data2.get(GregorianCalendar.MONTH)+1) + "/" +
data2.get(GregorianCalendar.YEAR));
if (data1.after(data2)==true)
{
// //temp <- agenda
System.out.println("Scambio effettuato tra " + agenda.getChild("nome").getTextTrim() + " e " + agenda2.getChild("nome").getTextTrim());
descrizionetmp = agenda.getChild("descrizione").getTextTrim();
nometmp = agenda.getChild("nome").getTextTrim();
cognometmp = agenda.getChild("cognome").getTextTrim();
datatmp = agenda.getChild("data").getTextTrim();
oratmp = agenda.getChild("ora").getTextTrim();
prioritatmp = agenda.getChild("priorita").getTextTrim();
//agenda <-- agenda2
agenda.getChild("descrizione").setText(agenda2.getChild("descrizione").getTextTrim());
agenda.getChild("nome").setText(agenda2.getChild("nome").getTextTrim());
agenda.getChild("cognome").setText(agenda2.getChild("cognome").getTextTrim());
agenda.getChild("data").setText(agenda2.getChild("data").getTextTrim());
agenda.getChild("ora").setText(agenda2.getChild("ora").getTextTrim());
agenda.getChild("priorita").setText(agenda2.getChild("priorita").getTextTrim());
//agenda2 <-- tmp
agenda2.getChild("descrizione").setText(descrizionetmp);
agenda2.getChild("nome").setText(nometmp);
agenda2.getChild("cognome").setText(cognometmp);
agenda2.getChild("data").setText(datatmp);
agenda2.getChild("ora").setText(oratmp);
agenda2.getChild("priorita").setText(prioritatmp);
}
}
alto--;
}
pointer = backup.iterator();
pointer2 = backup2.iterator();
XMLOutputter xmlOut = new XMLOutputter();
xmlOut.setFormat(Format.getPrettyFormat());
FileOutputStream output;
try {
output = new FileOutputStream(new File(locazione));
xmlOut.output(new Document(agenda),output);
} catch (IOException erIO) {
System.out.println("IO Exception");
}

Ok magari a primo impatto sembra molto incasinato come codice.. ed in effetti lo è :P
Bisogna focalizzarsi principalmente sulla parte dell'ordinamento (fatto tramite Bubble sort), quando inizia il ciclo While per intenderci.
Il codice viene compilato (stranamente) senza problemi ma quando vado in esecuzione ho quest'errore:
Exception in thread "AWT-EventQueue-0" org.jdom.IllegalAddException: The Content already has an existing parent "agenda"
Quell'errore non mi è nuovo, l'ho già visto quando usavo il "agenda.addcontent" a sproposito.. ma stavolta pare sia diverso.
Il problema infatti avviene al momento della scrittura del file, dopo aver ordinato il tutto.
Commentando il codice di scrittura infatti non ci sono problemi di sorta.
Ho provato anche a commentare la fase dello "scambio", ma il problema resta sempre lo stesso.
Se commento tutto l'ordinamento non ci sono problemi.
Insomma.. in teoria il problema è in quello che viene eseguito dentro il for ma non dentro l'if (c'è un solo for quindi non è difficile capire a quel mi riferisco :P ) ma non riesco proprio a capire cosa diavolo può essere.

ps: stò scrivendo il tutto con netbeans e dopo aver fatto una GUI, quindi c'è un riferimento ad una casella, per l'appunto "casellasfoglia"

Grazie, mi ci stò ammattendo :P

andbin
24-09-2008, 20:02
Una delle caratteristiche interessanti di JDOM è che una lista di nodi non è rappresentata da classi specifiche e poco maneggevoli come NodeList per il DOM ma è gestita come una collezione java.util.List. E quindi è molto più pratica, intuitiva e maneggevole.

Con questo voglio dire che se devi solo scambiare 2 nodi nella lista, basta semplicemente scambiare i due oggetti nella lista, non scambiare tutti i nodi di testo contenuti nei nodi figli!!!!

E non ti conviene nemmeno usare gli iteratori ... basterebbe sfruttare size() ed agire a livello di indici.

E visto che la data è in un formato ben preciso e la usi diverse volte, ti conviene pure fare un metodo che converte la stringa in un Calendar o Date.

Insomma ... con un po' di accortezza il codice verrebbe molto più pulito e almeno la metà in meno di quello che hai scritto.

odiojava.net
24-09-2008, 20:24
Grazie mille per la risposta :D
Ecco, il problema è che non sono proprio un esperto di programmazione (ormai s'era capito) quindi anche volendo fare certe cose non saprei come farle :P
Per invertire i 2 nodi l'idea iniziale era appunto di scambiarli tra di loro.. ma viste le difficoltà tecniche ho dovuto abbandonare e fare quest'altro metodo stupido, ma funzionante.
Per questo vista la tua cortesia mi piacerebbe farti un paio di domande :D
- Come faccio a sostituire solo il nodo?
- Come faccio a "selezionare" l'elemento successivo senza l'uso di un iteratore?

Grazie mille ancora :)

andbin
24-09-2008, 20:44
- Come faccio a sostituire solo il nodo?
- Come faccio a "selezionare" l'elemento successivo senza l'uso di un iteratore?List ha get() e set() a cui si passa l'indice, quindi basterebbe usare questi due metodi.
Nota che si può sfruttare una cosa interessante di set() e cioè che ritorna l'elemento precedente!!! E questo permetterebbe di usare solo 1 get e 2 set e sopratutto senza usare variabili temporanee.

Tecnica che è stata utilizzata da un metodo di java.util.Collections che è:
public static void swap(List list, int i, int j)
(solo da Java 1.4 in poi)

odiojava.net
25-09-2008, 01:15
List ha get() e set() a cui si passa l'indice, quindi basterebbe usare questi due metodi.
Nota che si può sfruttare una cosa interessante di set() e cioè che ritorna l'elemento precedente!!! E questo permetterebbe di usare solo 1 get e 2 set e sopratutto senza usare variabili temporanee.

Tecnica che è stata utilizzata da un metodo di java.util.Collections che è:
public static void swap(List list, int i, int j)
(solo da Java 1.4 in poi)

Ti ringrazio ancora una volta per la risposta :)
Il metodo dei get() e set() è interessante, ma non riesco ad applicarlo al mio caso.
Allora, il file xml che stò costruendo è strutturato così:

<agenda>
<appuntamento>
<descrizione>compleanno</descrizione>
<nome>pino</nome>
<cognome>barba</cognome>
<data>01/12/2008</data>
<ora>00:00</ora>
<priorita>Trascurabile</priorita>
</appuntamento>
</agenda>

Io ho dichiarato come Element agenda, appuntamento e tutto il resto, dopodichè ho caricato dentro appuntamento i 6 campi (descrizione, nome etc) e l'ho fatto finchè non finivo di leggere dal file xml. Ogni volta che finivo a leggere quei 6 campi aggiungevo appuntamento ad agenda.
Infine creo una lista chiamata "backup" nella quale colloco i vari appuntamenti appena caricati nell'agenda tramite la stringa:
List backup = agenda.getChildren("appuntamento");
E fin qui tutto ok.
Ora a me serve "ricacciare" da questo Element chiamato agenda la data del primo elemento, per confrontarla con quella del secondo e nel caso sostituirla.
Se faccio backup.get(1) mi restituisce "[Element: <appuntamento/>]", ovviamente ho lo stesso output anche con backup.get(2).
Andando un po' a logica ho provato a fare backup.get(1).getChildren("data") ma getChildren non compare tra i metodi disponibili (backup alla fine è cosa ben diversa da agenda quindi l'errore è motivato).

Come faccio quindi a ricacciarmi la data?
Una volta sorpassato questo scoglio penso di poter fare il resto :D

Grazie ancora :)

andbin
25-09-2008, 08:25
List appuntamenti = agenda.getChildren("appuntamento");
int size = appuntamenti.size();

for (int i = 0; i < size-1; i++) {
for (int j = i+1; j < size; j++) {
Element appuntamento1 = (Element) appuntamenti.get(i);
Element appuntamento2 = (Element) appuntamenti.get(j);

Calendar calendar1 = metodoPerParsingData(appuntamento1.getChildTextTrim("data"));
Calendar calendar2 = metodoPerParsingData(appuntamento2.getChildTextTrim("data"));

if (calendar1.after(calendar2)) {
appuntamenti.set(i, appuntamento2);
appuntamenti.set(j, appuntamento1);
}
}
}
Dove metodoPerParsingData è un metodo che dovresti fare (meglio un altro nome ;) ... es. parseData), magari statico, che dato un String crea e ritorna un Calendar (GregorianCalendar) facendo il parsing della stringa (come hai fatto).

Tutto qui .... quante righe sono??? Poche e ... pulite e chiare. Nota lo swap dei due elementi ... basta settare gli Element scambiando gli indici.

P.S. il codice non l'ho provato, ovviamente. Ma in linea di massima dovrebbe essere ok.

banryu79
25-09-2008, 09:14
Prendo spunto dal tuo codice Andbin, per modificarlo di pochissimo in modo da renderlo un'implementazione dell'ordinamento Bubblesort, con l'aggiunta di un flag per interrompere l'algoritmo non appena la lista è ordinata (ovvero quando non ci sono più scambi in un'intera iterazione).


List appuntamenti = agenda.getChildren("appuntamento");
int size = appuntamenti.size();
boolean flag = false;

while (size > 0)
{
if (flag)
{
break;
}

for (int i = 0; i < size-1; i++)
{
flag = true;

Element appuntamento1 = (Element) appuntamenti.get(i);
Element appuntamento2 = (Element) appuntamenti.get(i+1);

Calendar calendar1 = metodoPerParsingData(appuntamento1.getChildTextTrim("data"));
Calendar calendar2 = metodoPerParsingData(appuntamento2.getChildTextTrim("data"));

if (calendar1.after(calendar2))
{
appuntamenti.set(i, appuntamento2);
appuntamenti.set(i+1, appuntamento1);
flag = false;
}
}
size--;
}


Non l'ho provato, spero di non aver commesso errori.

odiojava.net
25-09-2008, 12:25
Purtroppo non funziona, c'è lo stesso errore che avevo io :(

Ecco il codice che ho messo:

List backup = agenda.getChildren("appuntamento");
int giorno1 = 0;
int mese1 = 0;
int anno1 = 0;
GregorianCalendar data1 = new GregorianCalendar();
int giorno2 = 0;
int mese2 = 0;
int anno2 = 0;
GregorianCalendar data2 = new GregorianCalendar();
int size = backup.size();
Element appuntamento1;
Element appuntamento2;
for (int i = 0; i < size-1; i++) {
for (int j = i+1; j < size; j++) {
appuntamento1 = (Element) backup.get(i);
appuntamento2 = (Element) backup.get(j);
giorno1 = Integer.parseInt(appuntamento1.getChildTextTrim("data").substring(0,2));
mese1 = Integer.parseInt(appuntamento1.getChildTextTrim("data").substring(3,5));
anno1 = Integer.parseInt(appuntamento1.getChildTextTrim("data").substring(6,10));
data1 = new GregorianCalendar(anno1,(mese1-1),giorno1);
giorno2 = Integer.parseInt(appuntamento2.getChildTextTrim("data").substring(0,2));
mese2 = Integer.parseInt(appuntamento2.getChildTextTrim("data").substring(3,5));
anno2 = Integer.parseInt(appuntamento2.getChildTextTrim("data").substring(6,10));
data2 = new GregorianCalendar(anno2,(mese2-1),giorno2);
if (data1.after(data2)) {
backup.set(i, appuntamento2);
backup.set(j, appuntamento1);
}
}
}

Al momento della compilazione dice:
Note: C:\Users\<mionome>\Documents\NetBeansProjects\Rubrica\src\calendario.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

Però il programma parte ugualmente.
Quando provo a fare l'ordinamento esce lo stesso errore di prima, ovvero:

Exception in thread "AWT-EventQueue-0" org.jdom.IllegalAddException: The Content already has an existing parent "agenda"

Bloccandosi li.. non arriva neanche alla fase di scrittura dell'output :(

edit: facendo 2 prove ho trovato cosa dà l'errore:
backup.set(i, appuntamento2);
backup.set(j, appuntamento1);
Per trovarlo ho messo vari "System.out" in giro per il codice e si blocca sempre dopo aver eseguito quello prima di queste 2 istruzioni :(

andbin
25-09-2008, 13:54
Al momento della compilazione dice:
Note: C:\Users\<mionome>\Documents\NetBeansProjects\Rubrica\src\calendario.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.Questo perché stai usando almeno un JDK 5 o superiore e stai usando List non parametrizzato (JDOM non usa i "generics").
Il warning si potrebbe sopprimere ma comunque non te ne curare più di tanto (anche perché tra poco vedrai come risolvere senza fare dei set() su List).

Quando provo a fare l'ordinamento esce lo stesso errore di prima, ovvero:

Exception in thread "AWT-EventQueue-0" org.jdom.IllegalAddException: The Content already has an existing parent "agenda"Già .... ma il motivo lo si spiega subito. Premetto che non sono espertissimo della libreria JDOM (conosco giusto le caratteristiche a livello generale ed ho fatto qualche prova in passato).
Quando si setta o si aggiunge un elemento nella List fornita da JDOM, controlla che l'elemento non abbia già un parente. Ho anche provato a fare un detach() sul Element ma sembra non funzionare.

Comunque una soluzione l'ho trovata: scambiare non gli elementi stessi ma il loro intero contenuto. E funziona.

Dato un file XML:
<?xml version="1.0" encoding="ISO-8859-1"?>
<agenda>
<appuntamento><data>01/12/2008</data></appuntamento>
<appuntamento><data>05/10/2007</data></appuntamento>
<appuntamento><data>16/07/2008</data></appuntamento>
<appuntamento><data>20/04/2008</data></appuntamento>
</agenda>

Con questo codice, ordino per data e scrivo su un nuovo file:

import java.io.*;
import java.util.*;
import java.text.*;
import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;
import org.jdom.output.Format;

public class TestAgenda {
public static DateFormat df;

public static void main(String[] args) {
try {
df = new SimpleDateFormat("dd/MM/yyyy");
df.setLenient(false);

SAXBuilder builder = new SAXBuilder();
Document doc = builder.build("agenda.xml");

Element agenda = doc.getRootElement();

List appuntamenti = agenda.getChildren("appuntamento");
int size = appuntamenti.size();

for (int i = 0; i < size-1; i++) {
for (int j = i+1; j < size; j++) {
Element appuntamento1 = (Element) appuntamenti.get(i);
Element appuntamento2 = (Element) appuntamenti.get(j);

Calendar calendar1 = parseDate(appuntamento1.getChildTextTrim("data"));
Calendar calendar2 = parseDate(appuntamento2.getChildTextTrim("data"));

if (calendar1.after(calendar2)) {
List content1 = appuntamento1.removeContent();
List content2 = appuntamento2.removeContent();

appuntamento1.setContent(content2);
appuntamento2.setContent(content1);
}
}
}


XMLOutputter outputter = new XMLOutputter();
FileOutputStream output = new FileOutputStream("agenda_ord.xml");
outputter.output(doc, output);
} catch (Exception e) {
e.printStackTrace();
}
}

public static Calendar parseDate (String s) throws ParseException {
Date d = df.parse(s);
Calendar c = Calendar.getInstance();
c.setTime(d);

return c;
}
}
Probabilmente ci sono soluzioni migliori ma ora non ho tempo/voglia di andare ad indagare sulla libreria JDOM.

odiojava.net
25-09-2008, 20:19
Funziona così bene che stò per piangere dalla commozione :cry:
Grazie mille, finalmente il programma è sulla buona strada per essere completato ^_^
Ah visto che ci stò ne approfitto per chiedere una cosetta veloce :D
Sapresti anche come "bloccare in scrittura" un determinato file tramite codice?
In pratica vorrei che mentre il programma è in funzione non si possa modificare il file xml, per poi risbloccarlo ad esecuzione terminata.

Cmq questa è una cosa superflua, la parte importante è fatta :D
Grazie ancora ^__^