J'ai un Ruby CGI (pas Rails) qui sélectionne les photos et les légendes d'un formulaire Web. Mes utilisateurs sont très désireux d'utiliser des citations et des ligatures intelligentes, ils collent à partir d'autres sources. Mon application Web ne gère pas bien ces caractères non ASCII, existe-t-il une rapide Ruby routine de manipulation de chaînes qui peut se débarrasser des caractères non ASCII?
La manière officielle de convertir entre les encodages de chaînes à partir de Ruby 1.9 est d'utiliser String # encode .
Pour supprimer simplement les caractères non ASCII, vous pouvez procéder comme suit:
some_ascii = "abc"
some_unicode = "áëëçüñżλφθΩ????????"
more_ascii = "123ABC"
invalid_byte = "\255"
non_ascii_string = [some_ascii, some_unicode, more_ascii, invalid_byte].join
# See String#encode documentation
encoding_options = {
:invalid => :replace, # Replace invalid byte sequences
:undef => :replace, # Replace anything not defined in ASCII
:replace => '', # Use a blank for those replacements
:universal_newline => true # Always break lines with \n
}
ascii = non_ascii_string.encode(Encoding.find('ASCII'), encoding_options)
puts ascii.inspect
# => "abce123ABC"
Notez que les 5 premiers caractères du résultat sont "abce1" - le "á" a été supprimé, un "ë" a été supprimé, mais un autre "ë" semble avoir été converti en "e".
La raison en est qu'il existe parfois plusieurs façons d'exprimer le même caractère écrit en Unicode. Le "á" est un point de code Unicode unique. Le premier "ë" l'est aussi. Lorsque Ruby les voit lors de cette conversion, il les rejette.
Mais le deuxième "ë" est deux points de code: un simple "e", comme vous le trouverez dans une chaîne ASCII, suivi d'une "combinaison de signes diacritiques" ( ce un ), ce qui signifie "mettre un tréma sur le caractère précédent". Dans la chaîne Unicode, ceux-ci sont interprétés comme un seul "graphème", ou caractère visible. Lors de la conversion, Ruby = conserve le caractère clair ASCII "e" et supprime la marque de combinaison.
Si vous décidez de fournir des valeurs de remplacement spécifiques, vous pouvez le faire:
REPLACEMENTS = {
'á' => "a",
'ë' => 'e',
}
encoding_options = {
:invalid => :replace, # Replace invalid byte sequences
:replace => "", # Use a blank for those replacements
:universal_newline => true, # Always break lines with \n
# For any character that isn't defined in ASCII, run this
# code to find out how to replace it
:fallback => lambda { |char|
# If no replacement is specified, use an empty string
REPLACEMENTS.fetch(char, "")
},
}
ascii = non_ascii_string.encode(Encoding.find('ASCII'), encoding_options)
puts ascii.inspect
#=> "abcaee123ABC"
Certains ont signalé des problèmes avec l'option :universal_newline
. J'ai vu cela par intermittence, mais je n'ai pas été en mesure de retrouver la cause.
Lorsque cela se produit, je vois Encoding::ConverterNotFoundError: code converter not found (universal_newline)
. Cependant, après quelques mises à jour RVM, je viens d'exécuter le script ci-dessus sous les versions Ruby sans problème:
Compte tenu de cela, il ne semble pas être une fonctionnalité obsolète ou même un bogue dans Ruby. Si quelqu'un connaît la cause, veuillez commenter.
class String
def remove_non_ascii(replacement="")
self.gsub(/[\u0080-\u00ff]/, replacement)
end
end
Voici ma suggestion en utilisant Iconv.
class String
def remove_non_ascii
require 'iconv'
Iconv.conv('ASCII//IGNORE', 'UTF8', self)
end
end
class String
def strip_control_characters
self.chars.reject { |char| char.ascii_only? and (char.ord < 32 or char.ord == 127) }.join
end
end
Avec un peu d'aide de @masakielastic, j'ai résolu ce problème à mes fins personnelles en utilisant la méthode #chars.
L'astuce consiste à décomposer chaque caractère en son propre bloc séparé afin que = Ruby peut échouer .
Ruby a besoin de pour échouer lorsqu'il est confronté au code binaire, etc. Si vous ne permettez pas à Ruby d'aller de l'avant et d'échouer) c'est une route difficile quand il s'agit de ce genre de choses. J'utilise donc la méthode String # chars pour diviser la chaîne donnée en un tableau de caractères. Ensuite, je passe ce code à une méthode de nettoyage qui permet au code d'avoir des "microfailures" (mon monnaie) dans la chaîne.
Donc, étant donné une chaîne "sale", disons que vous avez utilisé File#read
sur une photo. (mon cas)
dirty = File.open(filepath).read
clean_chars = dirty.chars.select do |c|
begin
num_or_letter?(c)
rescue ArgumentError
next
end
end
clean = clean_chars.join("")
def num_or_letter?(char)
if char =~ /[a-zA-Z0-9]/
true
elsif char =~ Regexp.union(" ", ".", "?", "-", "+", "/", ",", "(", ")")
true
end
end
Non, il ne manque pas de supprimer tous les caractères à côté des caractères de base (ce qui est recommandé ci-dessus). La meilleure solution serait de gérer ces noms correctement (puisque la plupart des systèmes de fichiers d'aujourd'hui n'ont aucun problème avec les noms Unicode). Si vos utilisateurs collent des ligatures, ils voudront certainement les récupérer également. Si le système de fichiers est votre problème, résumez-le et définissez le nom de fichier sur un md5 (cela vous permet également de partager facilement les téléchargements dans des compartiments qui analysent très rapidement car ils n'ont jamais trop d'entrées).
Quick GS a révélé cette discussion qui suggère la méthode suivante:
class String
def remove_nonascii(replacement)
n=self.split("")
self.slice!(0..self.size)
n.each { |b|
if b[0].to_i< 33 || b[0].to_i>127 then
self.concat(replacement)
else
self.concat(b)
end
}
self.to_s
end
end