ruby 1.9, encodage de force, mais vérifiez
J'ai une chaîne que j'ai lue à partir d'une sorte d'entrée.
Au meilleur de ma connaissance, C'est UTF8. OK:
string.force_encoding("utf8")
Mais si cette chaîne contient des octets qui ne sont pas en fait UTF8 légal, je veux savoir maintenant et agir.
Ordinairement, force_encoding("utf8") se déclenchera-t-il s'il rencontre de tels octets? Jecrois que ce ne sera pas le cas.
Si je faisais un #encode je pourrais choisir parmi les options pratiques avec quoi faire avec les caractères qui sont non valide dans l'encodage source (ou l'encodage de destination).
Mais je ne fais pas un #encode, je fais un #force_encoding. Il n'a pas de telles options.
Serait-il logique de
string.force_encoding("utf8").encode("utf8")
Pour obtenir une exception tout de suite? Normalement encodage à partir de utf8 à utf8 n'a pas de sens. Mais peut-être que c'est le moyen de le faire lever tout de suite s'il y a des octets invalides? Ou utilisez l'option :replace
etc pour faire quelque chose de différent avec des octets invalides?
Mais non, ne semble pas faire que travailler.
Quelqu'un sait?
1.9.3-p0 :032 > a = "bad: xc3x28 okay".force_encoding("utf-8")
=> "bad: xC3( okay"
1.9.3-p0 :033 > a.valid_encoding?
=> false
Ok, mais comment puis-je trouver et éliminer ces mauvais octets? Curieusement, cela ne soulève pas:
1.9.3-p0 :035 > a.encode("utf-8")
=> "bad: xC3( okay"
Si je convertissais en un encodage différent, ce serait!
1.9.3-p0 :039 > a.encode("ISO-8859-1")
Encoding::InvalidByteSequenceError: "xC3" followed by "(" on UTF-8
Ou si je le disais, il le remplacerait par un"?" =>
1.9.3-p0 :040 > a.encode("ISO-8859-1", :invalid => :replace)
=> "bad: ?( okay"
Ruby a donc l'intelligence de savoir quels sont les mauvais octets en utf-8 , et de les remplacer par autre chose-lors de la conversion en un encodage différent. Mais je ne veux pas convertir en un encodage différent, je veux rester utf8-mais je pourrais vouloir soulever s'il y a un octet invalide là-dedans, ou je pourrais vouloir remplacer les octets invalides par des caractères de remplacement.
N'y a-t-il pas un moyen d'amener ruby à faire ça?
Update je crois que cela a finalement été ajouté à ruby dans 2.1, Avec String#scrub présent dans la version 2.1 preview pour ce faire. Regardez donc pour que!
9 réponses
(mise à jour: voir https://github.com/jrochkind/scrub_rb)
J'ai donc codé une solution à ce dont j'avais besoin ici: https://github.com/jrochkind/ensure_valid_encoding/blob/master/lib/ensure_valid_encoding.rb
Mais ce n'est que beaucoup plus récemment que je me suis rendu compte que cela est réellement intégré dans le stdlib, il vous suffit, un peu contre-intuitivement, de passer 'binary' comme "codage source":
a = "bad: \xc3\x28 okay".force_encoding("utf-8")
a.encode("utf-8", "binary", :undef => :replace)
=> "bad: �( okay"
Oui, c'est exactement ce que je voulais. Il s'avère donc que cela est intégré dans 1.9 stdlib, c'est juste sans papiers et peu de gens le savent (ou peut-être que peu de gens qui parlent anglais le savent?). Bien que j'ai vu ces arguments utilisés de cette façon sur un blog quelque part, donc quelqu'un d'autre le savait!
Assurez-vous que votre scriptfile lui-même est enregistré en UTF8 et essayez ce qui suit
# 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?]
Cela donne sur mon système windows7 ce qui suit
["bad: \xC3( okay", false]
["bad: \xC3( okay", false]
["bad: ?( okay", true]
Donc votre mauvais caractère est remplacé, vous pouvez le faire tout de suite comme suit
a = "bad: \xc3\x28 okay".encode!("ISO-8859-1", :invalid => :replace)
=> "bad: ?( okay"
EDIT: voici une solution qui fonctionne sur tout encodage arbitraire, le premier code uniquement les caractères incorrects, le second remplace simplement par un ?
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
Pour vérifier qu'une chaîne n'a pas de séquences non valides, essayez de la convertir enbinaire encodage:
# 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
CE code remplace les caractères non définis, car nous ne nous soucions pas s'il existe des séquences valides qui ne peuvent pas être représentées en binaire. Nous ne nous soucions que s'il y a des séquences invalides.
Une légère modification de ce code renvoie l'erreur réelle, qui contient des informations précieuses sur le codage incorrect:
# 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
À propos de la seule chose à laquelle je peux penser est de transcoder à quelque chose et de revenir qui n'endommagera pas la chaîne dans l'aller-retour:
string.force_encoding("UTF-8").encode("UTF-32LE").encode("UTF-8")
Semble plutôt inutile, cependant.
Ok, voici une façon vraiment boiteuse de le faire, j'ai compris moi-même. Il fonctionne probablement pour la merde. ce que le diable, ruby? Ne pas sélectionner ma propre réponse pour l'instant, en espérant que quelqu'un d'autre se présentera et nous donnera quelque chose de mieux.
# 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
Plus rodomontades à http://bibwild.wordpress.com/2012/04/17/checkingfixing-bad-bytes-in-ruby-1-9-char-encoding/
Si vous faites cela pour un cas d'utilisation "réel" - par exemple pour analyser différentes chaînes saisies par les utilisateurs, et pas seulement pour pouvoir" décoder " un fichier totalement aléatoire qui pourrait être composé d'autant d'encodages que vous le souhaitez, alors je suppose que vous pourriez au moins supposer que tous les charcteurs pour chaque chaîne ont le même encodage.
Alors, dans ce cas, que penseriez-vous de cela?
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
}
Je ne suis pas un Ruby "pro" en aucune façon, alors pardonnez-moi si ma solution est mal ou même un peu naïf..
J'essaie juste de rendre ce que je peux, et c'est ce à quoi je suis arrivé, alors que je travaillais (je suis toujours) sur ce petit analyseur pour les chaînes arbitrairement codées, ce que je fais pour un projet d'étude.
Pendant que je poste ceci, je dois admettre que je ne l'ai même pas complètement testé.. I.. j'ai juste obtenu quelques résultats "positifs", mais je me sentais tellement excité d'avoir peut-être trouvé ce que j'avais du mal à trouver (et pendant tout le temps que j'ai passé à lire à ce sujet sur SO..) que je ressentais juste le besoin de le partager aussi vite que possible, en espérant que cela pourrait aider à gagner du temps à tous ceux qui ont cherché cela aussi longtemps que je l'ai été... .. si cela fonctionne comme prévu:)
Un moyen simple de provoquer une exception semble être:
Untrusted_string.correspondre /./
Voici 2 situations courantes et comment les traiter dans Ruby 2.1 + . Je sais, la question se réfère à Ruby V1. 9, mais peut-être que cela est utile pour les autres trouvant cette question via Google.
Situation 1
Vous avez une chaîne UTF-8 avec éventuellement quelques octets invalides
Supprimer les octets non valides:
str = "Partly valid\xE4 UTF-8 encoding: äöüß"
str.scrub('')
# => "Partly valid UTF-8 encoding: äöüß"
Situation 2
Vous avez une chaîne qui peut être en UTF-8 ou en ISO-8859-1
Vérifiez quel encodage il est et convertir en UTF-8 (si nécessaire):
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: äöüß"
Notes
Les extraits de code ci-dessus supposent que Ruby code toutes vos chaînes dans
UTF-8
par défaut. Même si c'est presque toujours le cas, vous pouvez vous en assurer en démarrant vos scripts avec# encoding: UTF-8
.S'il est invalide, il est possible par programmation de détecter la plupart des encodages multi-octets comme
UTF-8
(dans Ruby, voir:#valid_encoding?
). Cependant, il N'est pas (facilement) possible de détecter par programme l'invalidité des encodages à un octet commeISO-8859-1
. Ainsi, l'extrait de code ci-dessus ne fonctionne pas dans l'autre sens, c'est à dire détecter si une Chaîne est valideISO-8859-1
codage.Même si
UTF-8
est devenu de plus en plus populaire comme encodage par défaut sur le web,ISO-8859-1
et d'autres saveursLatin1
sont toujours très populaires dans les pays occidentaux, en particulier en Amérique du Nord. Sachez qu'il existe plusieurs encodages à un octet qui sont très similaires, mais légèrement varier de ISO-8859-1. Exemples:CP1252
(alias.Windows-1252
),ISO-8859-15