ruby - rails encoding utf 8
ruby 1.9, force_encoding, pero compruebe (9)
(actualización: ver https://github.com/jrochkind/scrub_rb )
Así que codifiqué una solución para lo que necesitaba aquí: https://github.com/jrochkind/ensure_valid_encoding/blob/master/lib/ensure_valid_encoding.rb
Pero solo mucho más recientemente me di cuenta de que esto realmente está incorporado en el stdlib, solo necesita, de manera contraintuitiva, pasar ''binario'' como la "codificación de origen":
a = "bad: /xc3/x28 okay".force_encoding("utf-8")
a.encode("utf-8", "binary", :undef => :replace)
=> "bad: �( okay"
Sí, eso es exactamente lo que quería. Así que resulta que esto está incorporado en 1.9 stdlib, simplemente no está documentado y pocas personas lo saben (¿o quizás pocas personas que hablan inglés lo saben?). Aunque vi estos argumentos utilizados de esta manera en un blog en algún lugar, ¡así que alguien más lo sabía!
Tengo una cadena que he leído de algún tipo de entrada.
Que yo sepa, es UTF8. Bueno:
string.force_encoding("utf8")
Pero si esta cadena tiene bytes que no son de hecho UTF8 legal, quiero saber ahora y actuar.
Por lo general, ¿force_encoding ("utf8") aumentará si encuentra dichos bytes? Creo que no lo hará.
Si estuviera haciendo un #encode , podría elegir entre las opciones útiles con qué hacer con los caracteres que no son válidos en la codificación de origen (o codificación de destino).
Pero no estoy haciendo un #encode, estoy haciendo un #force_encoding. No tiene tales opciones.
¿Tendría sentido
string.force_encoding("utf8").encode("utf8")
para obtener una excepción de inmediato? Normalmente, la codificación de utf8 a utf8 no tiene ningún sentido. ¿Pero tal vez esta es la manera de hacer que se aumente de inmediato si hay bytes no válidos? ¿O usa la opción :replace
, etc. para hacer algo diferente con bytes no válidos?
Pero no, parece que tampoco puede hacer que funcione.
¿Nadie sabe?
1.9.3-p0 :032 > a = "bad: /xc3/x28 okay".force_encoding("utf-8")
=> "bad: /xC3( okay"
1.9.3-p0 :033 > a.valid_encoding?
=> false
Está bien, pero ¿cómo encuentro y elimino esos bytes malos? Curiosamente, esto NO plantea:
1.9.3-p0 :035 > a.encode("utf-8")
=> "bad: /xC3( okay"
Si estuviera convirtiendo a una codificación diferente, lo haría!
1.9.3-p0 :039 > a.encode("ISO-8859-1")
Encoding::InvalidByteSequenceError: "/xC3" followed by "(" on UTF-8
O si se lo dijera, lo reemplazaría con un "?" =>
1.9.3-p0 :040 > a.encode("ISO-8859-1", :invalid => :replace)
=> "bad: ?( okay"
Así que Ruby tiene la inteligencia de saber cuáles son los bytes defectuosos en utf-8, y de reemplazar em con otra cosa, cuando se convierte a una codificación diferente. Pero no quiero convertir a una codificación diferente, quiero mantenerme utf8, pero es posible que desee aumentar si hay un byte no válido allí, o que quiera reemplazar los bytes no válidos con caracteres de reemplazo.
¿No hay alguna manera de conseguir que Ruby haga esto?
actualización Creo que finalmente se ha agregado a ruby en 2.1, con String # scrub presente en la versión preliminar 2.1 para hacer esto. Así que busca eso!
Aquí hay 2 situaciones comunes y cómo tratarlas en Ruby 2.1+ . Lo sé, la pregunta se refiere a Ruby v1.9, pero quizás esto sea útil para que otros encuentren esta pregunta a través de Google.
Situación 1
Tienes una cadena UTF-8 con posiblemente algunos bytes no válidos
Eliminar los bytes inválidos:
str = "Partly valid/xE4 UTF-8 encoding: äöüß"
str.scrub('''')
# => "Partly valid UTF-8 encoding: äöüß"
Situación 2
Tiene una cadena que podría estar en codificación UTF-8 o ISO-8859-1
Verifique qué codificación es y conviértala a UTF-8 (si es necesario):
str = "String in ISO-8859-1 encoding: /xE4/xF6/xFC/xDF"
unless str.valid_encoding?
str.encode!( ''UTF-8'', ''ISO-8859-1'', invalid: :replace, undef: :replace, replace: ''?'' )
end #unless
# => "String in ISO-8859-1 encoding: äöüß"
Notas
Los fragmentos de código anteriores suponen que Ruby codifica todas sus cadenas en
UTF-8
de forma predeterminada. A pesar de que este es casi siempre el caso, puede asegurarse de esto iniciando sus scripts con# encoding: UTF-8
.Si es inválido, es posible detectar la mayoría de las codificaciones de múltiples bytes como
UTF-8
(en Ruby, ver:#valid_encoding?
). Sin embargo, NO es (fácilmente) posible detectar la invalidez de codificaciones de un solo byte comoISO-8859-1
. Por lo tanto, el fragmento de código anterior no funciona al revés, es decir, detectar si una cadena es una codificaciónISO-8859-1
válida.A pesar de que
UTF-8
ha vuelto cada vez más popular como la codificación predeterminada en la web,ISO-8859-1
y otros saboresLatin1
siguen siendo muy populares en los países occidentales, especialmente en América del Norte. Tenga en cuenta que existen varias codificaciones de un solo byte que son muy similares, pero varían ligeramente de ISO-8859-1. Ejemplos:CP1252
(también conocido comoWindows-1252
),ISO-8859-15
De acuerdo, aquí hay una forma realmente aburrida de rubí puro para hacerlo, yo mismo lo descubrí. Probablemente se realiza para la mierda. ¿Qué diablos, rubí? No selecciono mi propia respuesta por ahora, con la esperanza de que alguien más aparezca y nos dé algo mejor.
# Pass in a string, will raise an Encoding::InvalidByteSequenceError
# if it contains an invalid byte for it''s encoding; otherwise
# returns an equivalent string.
#
# OR, like String#encode, pass in option `:invalid => :replace`
# to replace invalid bytes with a replacement string in the
# returned string. Pass in the
# char you''d like with option `:replace`, or will, like String#encode
# use the unicode replacement char if it thinks it''s a unicode encoding,
# else ascii ''?''.
#
# in any case, method will raise, or return a new string
# that is #valid_encoding?
def validate_encoding(str, options = {})
str.chars.collect do |c|
if c.valid_encoding?
c
else
unless options[:invalid] == :replace
# it ought to be filled out with all the metadata
# this exception usually has, but what a pain!
raise Encoding::InvalidByteSequenceError.new
else
options[:replace] || (
# surely there''s a better way to tell if
# an encoding is a ''Unicode encoding form''
# than this? What''s wrong with you ruby 1.9?
str.encoding.name.start_with?(''UTF'') ?
"/uFFFD" :
"?" )
end
end
end.join
end
Más ranting en http://bibwild.wordpress.com/2012/04/17/checkingfixing-bad-bytes-in-ruby-1-9-char-encoding/
En ruby 2.1, el stdlib finalmente soporta esto con scrub
.
Lo único en lo que puedo pensar es en transcodificar algo y regresar que no dañará la cuerda en el viaje de ida y vuelta:
string.force_encoding("UTF-8").encode("UTF-32LE").encode("UTF-8")
Aunque parece un desperdicio.
Para comprobar que una cadena no tiene secuencias no válidas, intente convertirla a la codificación binaria :
# Returns true if the string has only valid sequences
def valid_encoding?(string)
string.encode(''binary'', :undef => :replace)
true
rescue Encoding::InvalidByteSequenceError => e
false
end
p valid_encoding?("/xc0".force_encoding(''iso-8859-1'')) # true
p valid_encoding?("/u1111") # true
p valid_encoding?("/xc0".force_encoding(''utf-8'')) # false
Este código reemplaza los caracteres no definidos, porque no nos importa si hay secuencias válidas que no se pueden representar en binario. Solo nos importa si hay secuencias inválidas.
Una ligera modificación de este código devuelve el error real, que contiene información valiosa sobre la codificación incorrecta:
# Returns the encoding error, or nil if there isn''t one.
def encoding_error(string)
string.encode(''binary'', :undef => :replace)
nil
rescue Encoding::InvalidByteSequenceError => e
e.to_s
end
# Returns truthy if the string has only valid sequences
def valid_encoding?(string)
!encoding_error(string)
end
puts encoding_error("/xc0".force_encoding(''iso-8859-1'')) # nil
puts encoding_error("/u1111") # nil
puts encoding_error("/xc0".force_encoding(''utf-8'')) # "/xC0" on UTF-8
Si está haciendo esto para un caso de uso "de la vida real", por ejemplo, para analizar diferentes cadenas ingresadas por los usuarios, y no solo para poder "decodificar" un archivo totalmente aleatorio que podría estar compuesto de tantas codificaciones. como desee, entonces supongo que al menos podría suponer que todos los caracteres de cada cadena tienen la misma codificación.
Entonces, en este caso, ¿qué pensarías de esto?
strings = [ "UTF-8 string with some utf8 chars /xC3/xB2 /xC3/x93",
"ISO-8859-1 string with some iso-8859-1 chars /xE0 /xE8", "..." ]
strings.each { |s|
s.force_encoding "utf-8"
if s.valid_encoding?
next
else
while s.valid_encoding? == false
s.force_encoding "ISO-8859-1"
s.force_encoding "..."
end
s.encode!("utf-8")
end
}
No soy un "pro" Ruby de ninguna manera, así que perdona si mi solución es incorrecta o incluso un poco ingenua ...
Solo trato de devolver lo que puedo, y esto es a lo que he llegado, mientras estaba (todavía estoy) trabajando en este pequeño analizador de cadenas codificadas arbitrariamente, lo que estoy haciendo para un estudio de proyecto.
Mientras publico esto, debo admitir que ni siquiera lo he probado por completo ... yo ... solo obtuve un par de resultados "positivos", pero me sentí tan entusiasmado de haber encontrado lo que estaba luchando por encontrar ( y durante todo el tiempo que pasé leyendo sobre esto en SO ..) sentí la necesidad de compartirlo lo más rápido posible, con la esperanza de que pudiera ayudar a ahorrar algo de tiempo a cualquiera que haya estado buscando esto durante tanto tiempo como yo. He estado ... .. si funciona como se esperaba :)
Una forma simple de provocar una excepción parece ser:
untrusted_string.match /./
asegúrese de que su archivo de script esté guardado como UTF8 y pruebe lo siguiente
# encoding: UTF-8
p [a = "bad: /xc3/x28 okay", a.valid_encoding?]
p [a.force_encoding("utf-8"), a.valid_encoding?]
p [a.encode!("ISO-8859-1", :invalid => :replace), a.valid_encoding?]
Esto le da a mi sistema Windows7 lo siguiente
["bad: /xC3( okay", false]
["bad: /xC3( okay", false]
["bad: ?( okay", true]
Por lo tanto, se reemplaza tu mala reputación, puedes hacerlo de inmediato de la siguiente manera
a = "bad: /xc3/x28 okay".encode!("ISO-8859-1", :invalid => :replace)
=> "bad: ?( okay"
EDITAR: aquí una solución que funciona en cualquier codificación arbitraria, la primera codifica solo los caracteres incorrectos, la segunda simplemente se reemplaza por una?
def validate_encoding(str)
str.chars.collect do |c|
(c.valid_encoding?) ? c:c.encode!(Encoding.locale_charmap, :invalid => :replace)
end.join
end
def validate_encoding2(str)
str.chars.collect do |c|
(c.valid_encoding?) ? c:''?''
end.join
end
a = "bad: /xc3/x28 okay"
puts validate_encoding(a) #=>bad: ?( okay
puts validate_encoding(a).valid_encoding? #=>true
puts validate_encoding2(a) #=>bad: ?( okay
puts validate_encoding2(a).valid_encoding? #=>true