2011-12-19 14 views
8

Acabo de actualizar de ruby ​​1.9.2 a ruby ​​1.9.3p0 (revisión 2011-10-30 33570). La aplicación My rails usa postgresql como base de datos. La configuración regional del sistema es UTF8, al igual que la codificación de la base de datos. La codificación predeterminada de la aplicación de rieles también es UTF8. Tengo usuarios chinos que ingresan caracteres chinos, así como caracteres en inglés. Las cadenas se almacenan como cadenas codificadas en UTF8. VersiónRieles: problemas de codificación con valores hash serializados a pesar de UTF8

Rieles: 3.0.9

Desde la actualización de algunas de las cadenas chinas existentes en la base de datos ya no se muestran correctamente. Esto no afecta a todas las cadenas, sino solo a aquellas que forman parte de un hash serializado. Todas las otras cadenas que se almacenan como cadenas simples todavía parecen ser correctas.


Ejemplo:

Este es un hash serializado que se almacena como una cadena UTF8 en la base de datos:

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" 

el fin de convertir esta cadena para un hash rubí, I deserializarlo con YAML.load:

broken_hash = YAML.load(broken) 

Esto devuelve un hash con contenidos confusos:

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

El material ilegible se supone que es UTF-8 codificados chino. broken_hash['info'].encoding me dice que ruby ​​piensa que esto es #<Encoding:UTF-8>. Estoy en desacuerdo.

Curiosamente, todas las demás cadenas que no se serializaron antes se ven bien, sin embargo. En el mismo registro, un campo diferente contiene caracteres chinos que se ven exactamente en la consola de rieles, la consola psql y el navegador. Cada cadena --- no importa si es un hash serializado o cadena simple --- guardada en la base de datos ya que la actualización también se ve bien.


Me trataron de convertir el texto ilegible de una posible codificación equivocada (como GB2312 o ANSI) a UTF-8 a pesar de la afirmación de rubí que esto ya era UTF-8 y por supuesto que no. Este es el código que utilicé:

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

Esta falla porque rubí no sabe qué hacer con secuencias de ilegales en la cadena.

Realmente solo quiero ejecutar un script para arreglar todas las antiguas cadenas de hash serializadas presumiblemente rotas y terminarlo. ¿Hay alguna manera de convertir estas cuerdas rotas a algo parecido al chino otra vez?


Soy el mejor jugador con el codificada UTF-8 cadena en la cadena de texto (llamados "roto" en el ejemplo anterior). Esta es la cadena china que está codificado en la cadena serializada:

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

me di cuenta de que es fácil de convertir esto en una verdadera cadena codificada UTF-8 por la representación no literal que (la eliminación de las barras invertidas de escape).

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

Esto devuelve una adecuada cadena china de codificación UTF-8: "(回形针)\r\n"

se desmorona cuando utilizo YAML.load(...) para convertir la cadena en un hash rubí. Tal vez debería procesar la cadena sin procesar antes de alimentarlo al YAML.load. Simplemente me pregunto por qué esto es tan ...


¡Interesante! Esto probablemente se deba al motor "psych" de YAML que se usa por defecto ahora en 1.9.3. Cambié al motor "syck" con YAML::ENGINE.yamler = 'syck' y las cadenas rotas se analizaron correctamente.

+0

¿Cuál es el tipo de columna para los hash serializados? –

+0

@muistooshort: el tipo de columna es 'text'. – rekado

+0

¿Qué sucede si cambia la columna a 'binario'? Eso debería sacar la cadena como "8 bits ASCII" (es decir, bytes sin procesar) y tal vez eso pondrá 'YAML.load' en forma. Como prueba rápida, puede 'broken.force_encoding ('binary')' before 'YAML.load (broken)'. –

Respuesta

12

Esto parece haber sido causado por una diferencia en el comportamiento de los dos motores YAML disponibles "syck" y "psych". Para configurar el motor YAML a Syck:

YAML::ENGINE.yamler = 'syck'

Para configurar el motor YAML de nuevo a psych:

YAML::ENGINE.yamler = 'psych'

El motor "Syck" procesa las cuerdas como se esperaba y los convierte en hashes con cadenas chinas apropiadas. Cuando se usa el motor "psych" (predeterminado en ruby ​​1.9.3), la conversión da como resultado cadenas distorsionadas.

Agregar la línea anterior (la primera de las dos) a config/application.rb soluciona este problema. El motor "syck" ya no se mantiene, por lo que probablemente debería utilizar esta solución para comprar algo de tiempo para que las cadenas aceptables para "psych".

+0

Parece que estábamos viendo las mismas cosas al mismo tiempo. Volvería a codificar todo en el formato Psych o abandonaría YAML completamente y serializaría manualmente usando JSON o algún otro formato estable/portátil. –

+0

Por cierto, puede aceptar su propia respuesta y creo que tiene sentido hacerlo en este caso. –

9

Desde el 1.9.3 NEWS file:

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

Al parecer, los motores Syck y Psych YAML tratar las series no ASCII en diferentes e incompatibles maneras.

Dado un hash como que tiene:

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

Usando el viejo motor 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" 

obtenemos el formato de doble barra invertida fea la que tiene actualmente en su base de datos. Cambiando a Psych:

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

Las cadenas permanecen en formato UTF-8 normal. Si metemos la pata manualmente la codificación de ser latino-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" 

A continuación, obtener el tipo de tonterías que se está viendo.

La documentación de YAML es bastante delgada, así que no sé si puede obligar a Psych a comprender el antiguo formato de Syck. Yo creo que hay tres opciones:

  1. utilizar el viejo motor Syck sin apoyo y en desuso, que había necesidad de YAML::ENGINE.yamler = 'syck' antes de YAML nada.
  2. Cargue y decodifique todo su YAML usando Syck y luego vuelva a codificar y guárdelo usando Psych.
  3. Deje de usar serialize para serializar/deserializar manualmente usando JSON (o algún otro formato de texto estable, predecible y portátil) o use una tabla de asociación para que no esté almacenando datos serializados.
+0

Ja, eso es genial: has enviado tu respuesta un minuto después de que me di cuenta. Ahora he arreglado temporalmente las aplicaciones forzando el uso de "syck". Eventualmente, tendré que hacerlo de la manera difícil y volver a codificar todo con "psych". Realmente no me gustan los cambios incompatibles. – rekado

+2

@rekado: Me alejaría completamente de YAML, creo que es un formato horrible para la serialización de datos y los tipos de Rails fueron tontos al usarlo para 'serializar'. Pero también soy un hereje natural :) –

Cuestiones relacionadas