Come sbarazzarsi dei caratteri non-ascii in ruby

Ho un CGI Ruby (non i binari) che raccoglie foto e didascalie da un modulo web. I miei utenti sono molto interessati all’utilizzo di virgolette e legature intelligenti, stanno incollando da altre fonti. La mia app web non si comporta bene con questi caratteri non ASCII, c’è una routine di manipolazione delle stringhe Ruby rapida che può eliminare i caratteri non ASCII?

Usa codifica String #

Il modo ufficiale per convertire tra codifiche di stringhe a partire da Ruby 1.9 consiste nell’usare la codifica String # .

Per rimuovere semplicemente caratteri non ASCII, puoi fare ciò:

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" 

Si noti che i primi 5 caratteri nel risultato sono “abce1” – il “á” è stato scartato, uno “ë” è stato scartato, ma un altro “ë” sembra essere stato convertito in “e”.

La ragione di ciò è che a volte ci sono molti modi per esprimere lo stesso carattere scritto in Unicode. “Á” è un singolo punto di codice Unicode. Anche il primo “ë” è. Quando Ruby li vede durante questa conversione, li elimina.

Ma il secondo “ë” sono due codepoint: una semplice “e”, proprio come si troverà in una stringa ASCII, seguita da una “combinazione di segni diacritici” ( questo ), che significa “metti una dieresi sul personaggio precedente”. “. Nella stringa Unicode, questi sono interpretati come un singolo “grapheme”, o carattere visibile. Durante la conversione, Ruby mantiene la semplice “e” ASCII e scarta il segno di combinazione.

Se decidi di fornire alcuni valori di sostituzione specifici, puoi eseguire questa operazione:

 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" 

Aggiornare

Alcuni hanno segnalato problemi con l’opzione :universal_newline . L’ho visto a intermittenza, ma non sono stato in grado di rintracciare la causa.

Quando succede, vedo Encoding::ConverterNotFoundError: code converter not found (universal_newline) . Tuttavia, dopo alcuni aggiornamenti RVM, ho appena eseguito lo script sopra nelle seguenti versioni di Ruby senza problemi:

  • ruby-1.9.2-P290
  • ruby-1.9.3-p125
  • ruby-1.9.3-P194
  • ruby-1.9.3-P362
  • ruby-2.0.0-Preview2
  • ruby-head (as of 12-31-2012)

Detto questo, non sembra essere una funzionalità deprecata o un bug in Ruby. Se qualcuno conosce la causa, si prega di commentare.

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

Ecco il mio suggerimento usando Iconv.

 class String def remove_non_ascii require 'iconv' Iconv.conv('ASCII//IGNORE', 'UTF8', self) end end 

Con un po ‘di aiuto da @masakielastic ho risolto questo problema per i miei scopi personali usando il metodo #chars.

Il trucco è quello di abbattere ogni personaggio nel suo blocco separato in modo che il ruby possa fallire .

Ruby ha bisogno di fallire quando si confronta con codice binario, ecc. Se non permetti a Ruby di andare avanti e fallire è una strada difficile quando si tratta di questa roba. Quindi uso il metodo char # String per suddividere la stringa data in una matrice di caratteri. Poi passo a quel codice in un metodo di disinfezione che consente al codice di avere “microfailures” (il mio conio) all’interno della stringa.

Quindi, data una stringa “sporca”, diciamo che hai usato File#read su un’immagine. (il mio caso)

 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 
 class String def strip_control_characters self.chars.reject { |char| char.ascii_only? and (char.ord < 32 or char.ord == 127) }.join end end 

Quick GS ha rivelato questa discussione che suggerisce il seguente metodo:

 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 

No, non vi è una mancanza di rimozione di tutti i caratteri oltre a quelli di base (che è raccomandato sopra). La migliore soluzione sarebbe gestire correttamente questi nomi (dato che la maggior parte dei filesystem oggi non ha problemi con i nomi Unicode). Se i tuoi utenti incollano legature, sicuramente lo faranno anche loro. Se il filesystem è un tuo problema, estrai il file e imposta il nome del file su alcuni md5 (questo ti permette anche di condividere facilmente i caricamenti in bucket che scansionano molto velocemente poiché non hanno mai troppe voci).