2013-03-23 10 views
11

ho il seguente metodo di scrivere un XMLDOM ad un flusso:transformer.setOutputProperty (OutputKeys.ENCODING, "UTF-8") non funziona

public void writeToOutputStream(Document fDoc, OutputStream out) throws Exception { 
    fDoc.setXmlStandalone(true); 
    DOMSource docSource = new DOMSource(fDoc); 
    Transformer transformer = TransformerFactory.newInstance().newTransformer(); 
    transformer.setOutputProperty(OutputKeys.METHOD, "xml"); 
    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); 
    transformer.setOutputProperty(OutputKeys.INDENT, "no"); 
    transformer.transform(docSource, new StreamResult(out)); 
} 

sto testando alcune altre funzionalità XML, e questo è solo il metodo che uso per scrivere su un file. Il mio programma di test genera 33 casi di test in cui i file sono scritti. 28 di loro hanno la seguente intestazione:

<?xml version="1.0" encoding="UTF-8"?>... 

Ma per qualche ragione, 1 dei casi di test ora producono:

<?xml version="1.0" encoding="ISO-8859-1"?>... 

E altri quattro prodotti:

<?xml version="1.0" encoding="Windows-1252"?>... 

come si può vedere chiaramente, sto impostando la chiave di uscita ENCODING su UTF-8. Questi test utilizzati per lavorare su una versione precedente di Java. Non ho eseguito i test in un istante (più di un anno) ma in esecuzione oggi su "Java (TM) SE Runtime Environment (build 1.6.0_22-b04)" Ho questo comportamento divertente.

Ho verificato che i documenti che causano il problema sono stati letti da file che in origine erano codificati. Sembra che le nuove versioni delle librerie stiano tentando di preservare la codifica del file sorgente che è stato letto. Ma non è quello che voglio ... Voglio davvero che l'output sia in UTF-8.

Qualcuno sa di altri fattori che potrebbero impedire al trasformatore di ignorare le impostazioni di codifica UTF-8? C'è qualcos'altro che deve essere impostato sul documento per dire di dimenticare la codifica del file che è stato letto in origine?

UPDATE:

ho verificato lo stesso progetto su un'altra macchina, costruito e ha eseguito i test lì. Su quella macchina passano tutti i test! Tutti i file hanno "UTF-8" nella loro intestazione. Quella macchina ha "Java (TM) SE Runtime Environment (build 1.6.0_29-b11)" Entrambe le macchine eseguono Windows 7. Sulla nuova macchina che funziona correttamente, jdk1.5.0_11 viene utilizzato per creare la build, ma sul vecchio la macchina jdk1.6.0_26 viene utilizzata per creare la build. Le librerie utilizzate per entrambe le build sono esattamente le stesse. Può essere una incompatibilità JDK 1.6 con 1.5 al momento della compilazione?

UPDATE:

Dopo 4,5 anni, la libreria Java è ancora rotto, ma a causa del suggerimento di Vyrx di seguito, ho finalmente avere una soluzione adeguata!

public void writeToOutputStream(Document fDoc, OutputStream out) throws Exception { 
    fDoc.setXmlStandalone(true); 
    DOMSource docSource = new DOMSource(fDoc); 
    Transformer transformer = TransformerFactory.newInstance().newTransformer(); 
    transformer.setOutputProperty(OutputKeys.METHOD, "xml"); 
    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); 
    transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 
    transformer.setOutputProperty(OutputKeys.INDENT, "no"); 
    out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>".getBytes("UTF-8")); 
    transformer.transform(docSource, new StreamResult(out)); 
} 

La soluzione è quella di disabilitare la scrittura dell'intestazione, e scrivere l'intestazione corretta prima serializzazione XML per il vapore in uscita. Lame, ma produce i risultati corretti. I test interrotti più di 4 anni fa sono di nuovo in esecuzione!

+1

Questo in effetti sembra un po 'di bug o incompatibilità problema. È improbabile che qualcuno possa aiutare senza un banco di prova riproducibile. Puoi fornire [SSCCE] (http://sscce.org/) ed elencare tutte le versioni degli strumenti/librerie? – sleske

+0

Ci sono molti posti dove controllare la tua Locale. Il tuo computer locale ha una locale, il tuo IDE potrebbe avere un Locale e il tuo processo JVM ha un Locale. Ho visto problemi come questo quando i miei Local stavano cambiando. Come stai facendo i test? java.exe, maven, IDE? –

+0

Come ho specificato direttamente UTF-8, le impostazioni internazionali non dovrebbero avere importanza, ma per rispondere direttamente alla tua domanda, il codice di prova viene richiamato come una chiamata da riga di comando a Java.exe, su un sistema Windows, situato sulla costa del Pacifico degli Stati Uniti. e configurato per il fuso orario USA inglese e Pacifico. – AgilePro

risposta

1

Ho avuto lo stesso problema su Android durante la serializzazione di caratteri emoji. Quando si utilizza la codifica UTF-8 nel trasformatore, l'output era costituito da entità carattere HTML (coppie di surrogati UTF-16), che successivamente avrebbero interrotto altri parser che leggevano i dati.

Questo è come ho finito per risolverlo:

StringWriter sw = new StringWriter(); 
sw.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"); 
Transformer t = TransformerFactory.newInstance().newTransformer(); 

// this will work because we are creating a Java string, not writing to an output 
t.setOutputProperty(OutputKeys.ENCODING, "UTF-16"); 
t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 
t.transform(new DOMSource(elementNode), new StreamResult(sw)); 

return IOUtils.toInputStream(sw.toString(), Charset.forName("UTF-8")); 
+0

Sì, sembra che funzioni. NON sono un fan della conversione del mio intero albero XML in una stringa in memoria (in particolare dato che StringWriter non è efficiente). Insisto davvero sullo streaming direttamente sull'output. Una possibile soluzione è invece di aggiungere l'intestazione dopo la serializzazione, per scrivere l'intestazione sul flusso di output PRIMA di serializzare l'XML senza un'intestazione allo stesso flusso di output. Vedrò se funziona. – AgilePro

+1

Ho riscritto questa idea per utilizzare correttamente i flussi e dandoti il ​​merito della risposta. (grazie!) Come hai scritto, avresti tre copie del documento in memoria allo stesso tempo. Per piccoli XML non è un problema, ma in generale avere tre copie di un file di dati importanti in memoria non è efficiente. Un approccio migliore è semplicemente scrivere l'intestazione prima di serializzare l'XML allo scrittore. Ho riscritto la tua risposta per renderlo solo 2 copie dell'XML in memoria. – AgilePro

-1

Qui sto prendendo una foto selvaggia, ma lei dice che stai leggendo i file per i dati dei test. Puoi assicurarti di leggere i file usando la codifica corretta, quindi quando scrivi in ​​OutputStream hai già i dati nella codifica corretta?

Quindi avere qualcosa come nuovo InputStreamReader (nuovo FileInputStream (fileDir), "UTF8").

Non dimenticate che i costruttori singolo argomento FileReader usano sempre la codifica piattaforma predefinita: The constructors of this class assume that the default character encoding and the default byte-buffer size are appropriate.

+0

Non uso mai FileReader. --- Il "Documento" DOM ​​utilizza valori di stringa di caratteri, il che significa che sono già stati convertiti dalla loro forma originale. Sto usando le utilità Java DOM per leggere il file direttamente dal flusso di byte. Il flusso dovrebbe essere interpretato in base all'intestazione XML che specifica la codifica. Ecco come funziona XML. --- Il file sembra essere stato letto correttamente, ed è scritto nella codifica specificata - non solo la codifica che ho richiesto che scrivesse. – AgilePro

2

di rispondere alla domanda seguente codice funziona per me. Ciò può richiedere la codifica dell'input e convertire i dati in codifica di output.

 ByteArrayInputStream inStreamXMLElement = new ByteArrayInputStream(strXMLElement.getBytes(input_encoding)); 
     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 
     DocumentBuilder db = dbf.newDocumentBuilder(); 
     Document docRepeat = db.parse(new InputSource(new InputStreamReader(inStreamXMLElement, input_encoding))); 
     Node elementNode = docRepeat.getElementsByTagName(strRepeat).item(0); 

     TransformerFactory tFactory = null; 
     Transformer transformer = null; 
     DOMSource domSourceRepeat = new DOMSource(elementNode); 
     tFactory = TransformerFactory.newInstance(); 
     transformer = tFactory.newTransformer(); 
     transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 
     transformer.setOutputProperty(OutputKeys.ENCODING, output_encoding); 

     ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
     StreamResult sr = new StreamResult(new OutputStreamWriter(bos, output_encoding)); 


     transformer.transform(domSourceRepeat, sr); 
     byte[] outputBytes = bos.toByteArray(); 
     strRepeatString = new String(outputBytes, output_encoding); 
+0

L'errore si verifica solo su alcune versioni di Java.Non ho avuto il tempo di eseguire un'indagine completa su esattamente quale sia l'ambiente che causa il problema, né tantomeno il tempo di postare qui il codice di test, tuttavia è sostanzialmente simile a quello che pubblichi. Quello che stava fallendo erano i test automatizzati che duravano da anni. Il codice che hai incluso sembra un buon esempio di come testare il problema. Non so se sarò in grado di tornare all'ambiente originale che stava fallendo e rieseguire i test lì. Tutto, nella pienezza dei tempi ... – AgilePro

-1

provare a impostare la codifica sul StreamResult particolare:

StreamResult result = new StreamResult(new OutputStreamWriter(out, "UTF-8")); 

In questo modo, dovrebbe essere solo in grado di scrivere in UTF-8.

+2

Il problema è che l'intestazione non è corretta. Se l'intestazione dice che è ISO-8859-1, allora non vorrei che fosse codificato in qualche altro modo. Ho bisogno sia dell'intestazione sia della codifica effettiva del flusso. Ecco perché con queste librerie utilizzo sempre flussi di input/output e non reader/writer ... perché lo standard dice che devi leggere l'intestazione per scoprire che cos'è la codifica. – AgilePro

0

che dire ?:

public static String documentToString(Document doc) throws Exception{ return(documentToString(doc,"UTF-8")); }// 
    public static String documentToString(Document doc, String encoding) throws Exception{ 
    TransformerFactory transformerFactory =TransformerFactory.newInstance(); 
    Transformer transformer = null; 

if ("".equals(validateNullString(encoding))) encoding = "UTF-8"; 
try{ 
    transformer = transformerFactory.newTransformer(); 
    transformer.setOutputProperty(OutputKeys.INDENT, "yes") ; 
    transformer.setOutputProperty(OutputKeys.ENCODING, encoding) ; 
}catch (javax.xml.transform.TransformerConfigurationException error){ 
    return null; 
} 

Source source = new DOMSource(doc);  
StringWriter writer = new StringWriter(); 
Result result = new StreamResult(writer); 

try{ 
    transformer.transform(source,result); 
}catch (javax.xml.transform.TransformerException error){ 
    return null; 
} 
return writer.toString();  
}//documentToString 
1

Ho passato notevole quantità di tempo di debug questo problema perché stava lavorando bene sulla mia macchina (Ubuntu 14 + Java 1.8.0_45), ma non funzionava correttamente in produzione (Alpine Linux + Java 1.7).

Contrariamente alle mie aspettative in seguito alla risposta di cui sopra non ha aiutato.

ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
StreamResult sr = new StreamResult(new OutputStreamWriter(bos, "UTF-8")); 

ma questo ha funzionato come previsto

val out = new StringWriter() 
val result = new StreamResult(out) 
0

potrei aggirare il problema avvolgendo l'oggetto Document passato al costruttore DOMSource. Il metodo getXmlEncoding del mio wrapper restituisce sempre null, tutti gli altri metodi sono delegati all'oggetto Document avvolto.