2011-12-19 13 views
8

Ho appena aggiornato da Ruby 1.9.2 a Ruby 1.9.3p0 (revisione 2011-10-30 33570). L'applicazione My Rails utilizza postgresql come database back-end. Le impostazioni internazionali del sistema sono UTF8, così come la codifica del database. Anche la codifica predefinita dell'applicazione rails è UTF8. Ho utenti cinesi che inseriscono caratteri cinesi e caratteri inglesi. Le stringhe sono memorizzate come stringhe codificate UTF8. VersioneRails: problemi di codifica con gli hash serializzati nonostante UTF8

Rails: 3.0.9

Dal momento che l'aggiornamento alcune delle stringhe cinesi esistenti nel database non sono più visualizzate correttamente. Questo non ha effetto su tutte le stringhe, ma solo su quelle che fanno parte di un hash serializzato. Tutte le altre stringhe memorizzate come semplici stringhe sembrano ancora corrette.


Esempio:

Questo è un hash serializzata che viene memorizzato come una stringa UTF8 nel database:

broken = "--- !map:ActiveSupport::HashWithIndifferentAccess \ncheckbox: \"1\"\nchoice: \"Round Paper Clips \\xEF\\xBC\\x88\\xE5\\x9B\\x9E\\xE5\\xBD\\xA2\\xE9\\x92\\x88\\xEF\\xBC\\x89\\r\\n\"\ninfo: \"10\\xE7\\x9B\\x92\"\n" 

Al fine di convertire questa stringa per un hash rubino, ho deserializzare con YAML.load:

broken_hash = YAML.load(broken) 

Questo restituisce un hash con contenuti incomprensibili:

{"checkbox"=>"1", "choice"=>"Round Paper Clips ï¼\u0088å\u009B\u009Eå½¢é\u0092\u0088ï¼\u0089\r\n", "info"=>"10ç\u009B\u0092"} 

si suppone La roba incomprensibile per essere UTF8-encoded cinese. broken_hash['info'].encoding mi dice che Ruby pensa che questo sia #<Encoding:UTF-8>. Non sono d'accordo.

È interessante notare che tutte le altre stringhe che non sono state serializzate prima sembrano comunque soddisfacenti. Nello stesso record un campo diverso contiene caratteri cinesi che sembrano giusti --- nella console di rails, nella console psql e nel browser. Ogni stringa --- non importa se hash serializzato o stringa semplice --- salvati nel database poiché anche l'aggiornamento sembra soddisfacente.


ho cercato di convertire il testo confuso da un possibile codifica sbagliata (come GB2312 o ANSI) a UTF-8, nonostante l'affermazione di ruby ​​che questa era già UTF-8 e, naturalmente, ho fallito. Questo è il codice che ho usato:

require 'iconv' 
Iconv.conv('UTF-8', 'GB2312', broken_hash['info']) 

Questo fallisce perché rubino non sa cosa fare con le sequenze illegali nella stringa.

In realtà voglio solo eseguire uno script per correggere tutte le stringhe hash seriamente cancellate, presumibilmente interrotte, e utilizzarle. C'è un modo per convertire queste stringhe spezzate in qualcosa che assomiglia di nuovo al cinese?


me a con la stringa codificata UTF-8 nella stringa grezzo (chiamati "spezzato" nell'esempio precedente). Questa è la stringa cinese che è codificato nella stringa serializzata:

chinese = "\\xEF\\xBC\\x88\\xE5\\x9B\\x9E\\xE5\\xBD\\xA2\\xE9\\x92\\x88\\xEF\\xBC\\x89\\r\\n\"

ho notato che è facile da convertire ad un vero e proprio UTF-8 stringa codificata da unescaping esso (rimuovere le barre inverse di fuga).

chinese_ok = "\xEF\xBC\x88\xE5\x9B\x9E\xE5\xBD\xA2\xE9\x92\x88\xEF\xBC\x89\r\n"

Questo restituisce una corretta UTF-8-encoded stringa cinese: "(回形针)\r\n"

La cosa cade a pezzi solo quando uso YAML.load(...) per convertire la stringa in un hash rubino. Forse dovrei elaborare la stringa non elaborata prima che venga inviata a YAML.load. Mi fa solo chiedere perché è così ...


Interessante! Questo è probabilmente dovuto al motore "psicologico" YAML che è usato di default ora nella 1.9.3. Sono passato al motore "syck" con e le stringhe rotte sono state analizzate correttamente.

+0

Qual è il tipo di colonna per gli hash serializzati? –

+0

@muistooshort: il tipo di colonna è 'testo'. – rekado

+0

Cosa succede se cambi la colonna in 'binary'? Dovrebbe ottenere la stringa come "8bit ASCII" (cioè i byte grezzi) e forse questo darà un calcio a "YAML.load" in forma. Come test rapido puoi 'broken.force_encoding ('binary')' prima 'YAML.load (broken)'. –

risposta

12

Questo sembra essere stato causato da una differenza nel comportamento dei due motori YAML disponibili "syck" e "psych". Per impostare il motore YAML per syck:

YAML::ENGINE.yamler = 'syck'

Per impostare il motore YAML torna a psych:

YAML::ENGINE.yamler = 'psych'

Il motore "syck" elabora le stringhe come previsto e li converte in hash con corde cinesi appropriate. Quando viene utilizzato il motore "psicologico" (predefinito in ruby ​​1.9.3), la conversione risulta in stringhe incomplete.

L'aggiunta della riga sopra (la prima delle due) a config/application.rb risolve questo problema. Il motore "syck" non è più mantenuto, quindi dovrei probabilmente usare questa soluzione solo per comprarmi un po 'di tempo per rendere le stringhe accettabili per "psych".

+0

Sembra che stessimo guardando le stesse cose allo stesso tempo. Ricodificare tutto al formato Psych o abbandonare completamente YAML e serializzare manualmente usando JSON o qualche altro formato stabile/portatile. –

+0

BTW, puoi accettare la tua risposta e penso che abbia senso farlo in questo caso. –

9

Dal 1.9.3 NEWS file:

* yaml 
    * The default YAML engine is now Psych. You may downgrade to syck by setting 
    YAML::ENGINE.yamler = 'syck'. 

A quanto pare i motori syck e Psych YAML trattare stringhe non-ASCII in modi diversi e incompatibili.

Dato un hash come si deve:

h = { 
    "checkbox" => "1", 
    "choice" => "Round Paper Clips (回形针)\r\n", 
    "info"  => "10盒" 
} 

Utilizzando il vecchio motore syck:

>> YAML::ENGINE.yamler = 'syck' 
>> h.to_yaml 
=> "--- \ncheckbox: "1"\nchoice: "Round Paper Clips \\xEF\\xBC\\x88\\xE5\\x9B\\x9E\\xE5\\xBD\\xA2\\xE9\\x92\\x88\\xEF\\xBC\\x89\\r\\n"\ninfo: "10\\xE7\\x9B\\x92"\n" 

otteniamo il formato doppio backslash brutto l'hai attualmente nel database. Passaggio a Psych:

>> YAML::ENGINE.yamler = 'psych' 
=> "psych" 
>> h.to_yaml 
=> "---\ncheckbox: '1'\nchoice: ! "Round Paper Clips (回形针)\\r\\n"\ninfo: 10盒\n" 

Le stringhe rimangono nel normale formato UTF-8. Se sbagliamo manualmente la codifica di essere Latin-1:

>> Iconv.conv('UTF-8', 'ISO-8859-1', "\xEF\xBC\x88\xE5\x9B\x9E\xE5\xBD\xA2\xE9\x92\x88\xEF\xBC\x89") 
=> "ï¼\u0088å\u009B\u009Eå½¢é\u0092\u0088ï¼\u0089" 

allora otteniamo il genere di sciocchezze che si sta vedendo.

La documentazione di YAML è piuttosto sottile, quindi non so se è possibile forzare Psych a comprendere il vecchio formato Syck. Penso che tu abbia tre opzioni:

  1. utilizzare il vecchio motore syck supportato e deprecato, avresti bisogno di YAML::ENGINE.yamler = 'syck' prima di YAML nulla.
  2. Carica e decodifica tutto il tuo YAML usando Syck e quindi ricodificalo e salvalo usando Psych.
  3. Interrompere l'utilizzo di serialize in favore della serializzazione/deserializzazione manuale utilizzando JSON (o qualche altro formato di testo stabile, prevedibile e portatile) o utilizzare una tabella di associazione in modo da non memorizzare affatto i dati serializzati.
+0

Ha, è bello: hai inviato la tua risposta un minuto dopo averlo capito. Ora ho risolto temporaneamente le applicazioni forzando l'utilizzo di "syck". Alla fine, dovrò farlo nel modo più duro e ricodificare tutto con "psych". Davvero non mi piacciono i cambiamenti incompatibili. – rekado

+2

@rekado: Mi sposterei completamente da YAML, penso che sia un formato orribile per la serializzazione dei dati e che i ragazzi di Rails fossero stupidi a usarlo per 'serialize'. Ma sono anche un eretico nato naturale :) –