web-dev-qa-db-fra.com

Comment se débarrasser des caractères non ascii dans ruby

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?

60
Andre Garzia

Utiliser l'encodage String #

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"

Mise à jour

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:

  • Rubis-1.9.2-p290
  • Rubis-1.9.3-p125
  • Rubis-1.9.3-p194
  • Rubis-1.9.3-p362
  • Ruby-2.0.0-preview2
  • Tête de rubis (au 31/12/2012)

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.

136
Nathan Long

class String
 def remove_non_ascii(replacement="") 
   self.gsub(/[\u0080-\u00ff]/, replacement)
 end
end
39
klochner

Voici ma suggestion en utilisant Iconv.

class String
  def remove_non_ascii
    require 'iconv'
    Iconv.conv('ASCII//IGNORE', 'UTF8', self)
  end
end
20
Scott
class String
  def strip_control_characters
    self.chars.reject { |char| char.ascii_only? and (char.ord < 32 or char.ord == 127) }.join
  end
end
2
Diego Carrion

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
2
boulder_ruby

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).

0
Julik

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
0
Joseph Weissman