Vita da tecnici

Entries categorized as ‘java encoding’

Java encoding addedum no. 5: il peccato capitale String.getBytes()

Gennaio 31, 2008 · 1 Commento

Si conclude con questo articolo la panoramica sull’encoding in Java e sulle ragioni delle eccezioni che spesso si incontrano nel fare il parsing dei file xml.

Questo ultimo articolo è dedicato alla fonte più ricorrente di tali errori. la chiamata del metodo getBytes della classe String senza parametri (parametri nella getBytes?? eh sì, esite anche la getBytes che accetta un argomento). L’esperienza mi insegna che dove c’è una getBytes() e del parsing xml prima o poi compaiono errodi di encoding. Perchè?

Perchè effettua l’encoding usando il charset della piattaforma su cui gira java e quindi varia da sistema operativo a sistema operativo. Chi riceve la sequenza di caratteri tradotta in byte non sa che encoding è stato usato, per cui si può solo indovinare. Se proprio serve serializzare dei caratteri in uno stream di byte la prima cosa da fare è concordare l’encoding in modo che chi riceve e chi trasmette possa scambiarsi i dati. Consiglio di utilizzare UTF-8 che consente di inviare caratteri di vario tipo (anche quelli non europei). Neanche a farlo apposta in questi giorni mi sono scontrato con del mio codice (sigh) che per fare il parsing di un file xml richiede come parametro un InputStream; peccato che la funzione chiamante passasse una stringa. Per passare da una stringa ad un inputstream si fa getBytes e poi si passa il risultato ad un ByteArrayInputStream. E zac!! ci si espone ad errori di encoding perchè i parser leggono da InputStream l’encoding da utilizzare che non c’entra nulla con quello dello stream che è ottenuto da una getBytes. La soluzione è di aggiungere un metodo che accetta come parametro un reader e usare un StringReader.

Categorie: Pensieri sparsi · java encoding · xml caratteri accentati · xml encoding

Java encoding addedum no. 4 xml all’interno dell’xml

Gennaio 22, 2008 · Nessun Commento

Questo è il penultimo dei post che riguarda l’xml encoding e tratta un argomento che si può presentare quando si ha a che fare con delle applicazioni che si basano su scambio di messaggi xml. Capita a volte che le applicazioni message oriented abbiano una struttura a due livelli:

  1. il primo livello di xml trasmette i campi generali (es il mittente, il nome del servizio, il contenuto informativo)
  2. il secondo livello di xml trasmette il contenuto vero proprio ed è rappresentato da un campo del primo livello

Esempio

<message>

<sender>Giovanni</sender>

<service>invio_fattura</service>

<content><![CDATA[

<?xml version="1.0" encoding="ISO-8859-1"?>
<fattura> 	<numero>2</numero>  	<data>20080131</data> ... ...  </fattura>

]]></content>

</message>

Tale strutturazione si usa quando si vuole creare uno scheletro di comunicazione da usare per diversi servizi che vengono chiesti a dei fornitori in tempi diversi. Gli sviluppi successivi possono riutilizzare “l’infrastruttura” di comunicazione e concentrarsi solo sul contenuto senza dover configurare endpoint diversi e “menate varie”.

Una soluzione di questo tipo pone un problema di encoding particolare; infatti per evitare il famoso Invalid byte 2 of 3-byte UTF-8 sequenceed annessi e connessi è necessario capire cosa succede se il messaggio viene trasportato via rete tramite protocollo http.

Il contenuto del tag content è a tutti gli effetti una stringa che, nel caso di cdata non viene toccata (si può inserire il contenuto facendo l’escaping dei caratteri, ma il messaggio diventa molto meno leggibile ad occhio nudo). La domanda da porsi è: se all’interno di CDATA ci sono dei caratteri accentati con che encoding vengono trasmessi?

La risposta è: a priori non è dato saperlo. Eh? Perchè?

Analizziamo il “viaggio” del messaggio; se si usa un web service client (tipo quelli generati da AXIS) abbiamo a disposizione un metodo di un oggetto che, presi i parametri di input, crea un xml e tramite un http client, lo invia “come stringa” (è una semplificazione, ma serve per non perdersi nei dettagli) all’http server. l’http client specifica un encoding nell’header http; il server legge l’encoding e ricostruisce una stringa che rappresenta il messaggio xml. A questo punto sul server il web service può essere implementato tramite un metodo che accetta gli stessi parametri del client (stile AXIS) o viene fornito un albero JDOM, DOM4J o XOM da cui prelevare i dati (stile Spring Web Services). In entrambi i casi il contenuto del campo content è accedibile come stringa. Stringa = sequenza di caratteri per cui non esiste problema di encoding, la conversione byte-caratteri è stata presa in carico dall’http server o dalla libreria che implementa i meccanismi di web service.

Che problemi ci sono allora?

Che il contenuto di content deve essere dato in pasto alla nostra libreria XML preferita che crea un albero o una serie di eventi; ma nell’header del messaggio contenuto in content è specificato un encoding che non ha più alcun legame con l’encoding reale. Tale informazione sull’encoding va ignorata.

Come si fa?

La soluzione l’ho citata nel post precedente: si usa un reader al posto di un input stream.

Ricetta facile e veloce: se dobbiamo dare in pasto ad una libreria xml del contenuto che proviene da una stringa utilizziamo un reader e non un inputstream

Categorie: change encoding · java encoding · xml caratteri accentati · xml encoding

Java encoding addedum no. 3: InputStream vs Reader

Dicembre 4, 2007 · Nessun Commento

In questo articolo porrò le fondamenta di quello che sarà l’argomento di un altro post, l’xml all’interno dell’xml.

Capire la differenza fra Reader ed InputStream è fondamentale se si vuole riuscire ad evitare errori di vario tipo legati all’encoding.

Quel e’ la differenza fra InputStream e Reader?

Il primo ragiona in byte, il secondo in caratteri che tradotto significa: il primo non fa il decoding il secondo sì. (per OutputStream e Writer si applica il concetto complementare di encoding)

La differenza è “tutta” qui. Vediamo cosa significa: se in un programma che tratta XML (dove quindi l’encoding/decoding è richiesto) passo un InputStream cosa fa la libreria(DOM4J per esempio)? Legge la prima riga, il prologo con <?xml cerca l’encoding e costruisce un Reader che trasforma i byte in caratteri secondo le regole del charset. Se invece alla libreria passo un reader, essa comincia  caricare i caratteri senza controllare l’encoding del prologo.

Quindi in un caso il prologo viene letto (e quindi viene fatta una operazione di decoding) nell’altro viene ignorato (il decoding lo ha già fatto il reader).

Sapere che esistono queste due possibilità è molto utile infatti ci sono casi dove il prologo e la relativa informazione sull’encoding deve essere letta (per esempio quando si legge da file system), mentre ce ne sono altri in cui deve essere ignorata (per esempio quando l’xml viene passato come stringa).

Nel prossimo post affronterò l’ulteriore complicazione dell’xml dentro xml, caso che può essere abbastanza frequente nel caso di webservice che si scambiano nessaggi.

Categorie: change encoding · java encoding · xml caratteri accentati · xml encoding

Java XML encoding addendum n.2: i caratteri accentati

Novembre 14, 2007 · 2 Commenti

Dalle statitiche del blog vedo che uno dei termini più richiesti è xml caratteri accentati. Questo posto vuole provare a dare una soluzione di come si trattano i caratteri accentati.

Supponiamo di avere una tag il cui contenuto è:

àèìòù€

e di voler creare un file xml che contenga tali caratteri.

L’esempio con DOM4J è il seguente :

import java.io.FileOutputStream;

import org.dom4j.Document;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;

public class TestXMLEncodingAccento {

public static void main(String[] args) throws Exception {
Document document=DocumentFactory.getInstance().createDocument();
Element e=document.addElement(”CaratteriAccentati”);
e.setText(”àèìòù€”);
OutputFormat outformat = OutputFormat.createPrettyPrint();
outformat.setEncoding(”ISO-8859-1″);
//outformat.setEncoding(”UTF-8″);
//outformat.setEncoding(”ISO-8859-15″);
FileOutputStream fos=new FileOutputStream(”c:/temp/testaccenti.xml”);
XMLWriter writer = new XMLWriter(fos, outformat);
writer.write(document);
writer.flush();
fos.close();
}

}

Il programma non fa altro che creare una stringa con i caratteri in questione, metterli in un tag e salvare un file con un certo encoding. L’encoding è la chiave di tutto in quanto determina il valore dei byte che rappresentato il carattere che vengono poi salvati su disco. “L’equazione” è carattere->sequenza di byte dipendente dall’encoding->file.

Se proviamo ognuno dei tre tipi di encoding e ogni volta apriamo il file con un editor “evoluto” dovremmo vedere i caratteri accentati sempre visualizzati correttamente. Se proviamo ad utilizzare come output System.out il risultato dipende dall’encoding della console in uso, se coincide vediamo tutto corretto altrimenti compaiono dei caratteri strani. Se compaiono dei caratteri strani (tranne per il simbolo dell’euro che è un discorso a parte) significa che il lettore non sa gestire l’encoding, ma il file è scritto bene.

Un discorso a parte merita il simbolo dell’euro, questo compare solo nel caso di encoding UTF-8 e ISO-8859-15. Perchè? Perchè ISO-8859-1 non ha nella sua mappa di traduzione simbolo->sequenza di byte tale carattere per cui non è possibile salvare il simbolo € correttamente. ISO-8859-15 è stato fatto apposta per supportare il simbolo dell’euro (e visto che la codifica utilizza un solo byte per fare spazio all’euro è stato rimosso un altro simbolo).

Alla prossima

Categorie: Pensieri sparsi · java encoding · xml caratteri accentati · xml encoding

Java encoding addedum no. 1

Ottobre 18, 2007 · Nessun Commento

**If there are some non italian readers that wish to learn more about java encoding please leave a comment in english and I’ll try to post more articles (english readable) on the topic.**

Ho visto che molte delle ricerche che portano al blog hanno le parole chiava java ed encoding. Ho pensato di dedicare qualche post all’argomento.

Il primo e’: Come cambiare encoding ad un file?

Faccio copia ed incolla di un sorgente Java che preso un file con un certo encoding (da specificare esplicitamente)  ne crea una sua versione con un encoding differente.

public class FileReEncoder {

private static final int BUFFER_SIZE=2048;

public static void main(String[] args) throws Exception {
String fileNameIn=”test1″;
String fileNameOut=”test2″;
String encodingIn=”UTF-8″;
String encodingOut=”ISO-8859-1″;
File fileIn=new File(fileNameIn);
if (!fileIn.exists() || !fileIn.canRead()) {
System.err.println(”Il file ” + fileNameIn + ” non esiste o è illeggibile”);
System.exit(-1);
}
FileInputStream fis= new FileInputStream(fileIn);
InputStreamReader reader= new InputStreamReader(fis,Charset.forName(encodingIn));
try {
FileOutputStream fos=new FileOutputStream(fileNameOut);
OutputStreamWriter writer= new OutputStreamWriter(fos, Charset.forName(encodingOut));
try {
char[] bufferChar=new char[BUFFER_SIZE];
int charsRead=0;
while ((charsRead=reader.read(bufferChar))>0) {
writer.write(bufferChar, 0, charsRead);
}
} finally {
writer.close();
}
} finally {
reader.close();
}

}

}

Enjoy!

Se ci sono altre domande o cose che vorreste vedere trattate sull’argomento suggerite pure.

**If there are some non italian readers that wish to learn more about java encoding please leave a comment in english and I’ll try to post more articles (english readable) on the topic.**

Categorie: change encoding · java encoding · xml encoding

L’encoding questo (semi)sconosciuto

Settembre 10, 2007 · 2 Commenti

Una delle cose che viene sempre utilizzata quando si scrive un file, ma di cui si ha poca conoscenza è l’encoding.

Cos’è l’encoding?

E’ il modo in cui un simbolo (una lettera, un numero, una valuta) viene tradotto in bit. Oggi esiste una specifica standard de facto che associa ad un simbolo un codice numerico. Questa specifica si chiama Unicode. Essa non fa altro che definire un valore numerico ad ogni simbolo che può essere scritto (quindi comprende caratteri di tutte le lingue occidentali, simboli cinesi, cherokee, etc), ma non stabilisce come tale valore numerico venga salvato fisicamente. Qui entra in gioco l’encoding che consente alla forma numerica del simbolo di poter essere salvata. Gli encoding più noti “da queste parti” sono ISO-8859-1, ISO-8859-15 (aggiunge il simbolo dell’euro al 8859-1) e UTF-8. Essi differiscono per come i smboli vengono salvati su disco.

Perchè tutta sta menata?

Perchè sempre più spesso mi capita di dover progettare e gestire prodotti che gestiscono file xml e ogni tanto mi vengono presentati degli errori “misteriosi” che sono dovuti al fatto che chi ha prodotto i file non aveva gestito correttamente l’encoding.

Ecco un semplice caso:

<?xml version=”1.0″ encoding=”UTF-8″?>

<specialchars>àèìòù€</specialchars>

Ho riportato un file xml in cui si dichiara l’encoding UTF-8. Si salva il file e tutto dovrebbe essere a posto. Niente di più sbagliato. Non basta scrivere UTF-8 nel file affinchè questo possa essere salvato in tale formato, è necessario *effettuare* l’encoding nel formato specificato. La dichiarazione nel file XML dovrebbe essere il risultato di una operazione di encoding fatta in maniera cosciente. Altrimenti cosa succede?

Il file viene salvato con un encoding diverso e quando viene dato in mano ad un parser xml tutto fila liscio fina a che nel messaggio non compare qualche carattere strano (tipicamente quelli accentati), a quel punto il programma (Java in questo caso) ce lo segnala:

Exception in thread “main” org.dom4j.DocumentException: Error on line 3 of document file:///c:/temp/test_ISO8859-1.xml : Invalid byte 2 of 3-byte UTF-8 sequence. Nested exception: Invalid byte 2 of 3-byte UTF-8 sequence.
at org.dom4j.io.SAXReader.read(SAXReader.java:482)
at org.dom4j.io.SAXReader.read(SAXReader.java:264)
at test.encoding.LoadAndParseXML.main(LoadAndParseXML.java:41)
Nested exception:
org.xml.sax.SAXParseException: Invalid byte 2 of 3-byte UTF-8 sequence.
at org.apache.xerces.util.ErrorHandlerWrapper.createSAXParseException(Unknown Source)
at org.apache.xerces.util.ErrorHandlerWrapper.fatalError(Unknown Source)
at org.apache.xerces.impl.XMLErrorReporter.reportError(Unknown Source)
at org.apache.xerces.impl.XMLErrorReporter.reportError(Unknown Source)
at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source)
at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source)
at org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source)
at org.dom4j.io.SAXReader.read(SAXReader.java:465)
at org.dom4j.io.SAXReader.read(SAXReader.java:264)
at test.encoding.LoadAndParseXML.main(LoadAndParseXML.java:41)

Il messaggio, una volta capito il problema, è chiaro, abbiamo dichiarato un encoding UTF-8, ma il file aveva un encoding diverso che “si è rivelato” quando è comparso un carattere “strano”. Questa è la cosa che tra più in inganno chi scrive software, infatti il programma va quasi sempre e ogni tanto “si rompe”, come mai?

La ragione sta nel fatto che sia ISO-8859-1(5) che UTF-8 usano la stessa rappresentazione binaria per i caratteri ASCII (è una scelta voluta per ragioni di efficienza e compatibilità) per cui non c’è differenza fra l’encoding in UTF-8 e ISO-8859-1 per i caratteri più comunente utilizzati. La differenza sta nei caratteri accentati ed in quelle di altre lingue (es caratteri cirillici). Oggi tutte le librerie che gestiscono file xml gestiscono anche l’encoding, quindi se capita un errore del genere significa che con molta probabilità chi sintentizza il messaggio xml lo fa tramite concatenazione di stringhe (sigh).

Che differenza c’è fra ISO-8859-15 e UTF-8? il primo è in grado di rappresentare un set decisamente inferiore di caratteri in quanto può utilizzare solo un byte per rappresentare un simbolo, mentre UTF-8 ha una codifica con numero di byte variabile che può arrivare fino a 6. L’eccezione riportata prima era: Invalid byte 2 of 3-byte UTF-8 sequence confermando il fatto che il decoder UTF-8 stava esaminando un carattere rappresentato da 3 byte ed il secondo non era quello che si aspettava.

Buon encoding a tutti

Categorie: java encoding · xml encoding