2009-06-11 17 views
14

Estoy tratando de encontrar una manera "adecuada" de ordenar cadenas UTF-8 en Ruby on Rails.Ordenando cadenas UTF-8 en RoR

En mi aplicación, tengo un cuadro de selección que se rellena con los países. Como mi aplicación está traducida, cada configuración regional existente tiene un archivo countries.yml que relaciona la identificación de un país con el nombre localizado de ese país. No puedo ordenar las cadenas manualmente en el archivo yml porque necesito que el ID sea coherente en todas las configuraciones regionales.

Lo que he hecho es crear un método ascii_name que utiliza la gema unidecode para convertir los caracteres acentuados y no latinos a su equivalente ASCII (por ejemplo, "Afeganistão" se convertiría en "Afeganistao"), y luego ordene que:

require 'unidecode' 

class Country 
    def ascii_name 
    Unidecoder.decode(name).gsub("[?]", "").gsub(/`/, "'").strip 
    end 
end 

Country.all.sort_by(:&ascii_name) 

sin embargo, existen problemas obvios con esto:

  • Se puede no propiamente locales especie no latinos, ya que puede no ser un personaje latino análoga directa.
  • No hace ninguna distinción entre una carta y todas las formas acentuadas de esa carta (así, por ejemplo, A y A se convierten en intercambiables)

¿Alguien sabe de una mejor manera que podía ordenar mis cuerdas?

Respuesta

8

http://github.com/grosser/sort_alphabetical

Esta joya debe ayudar. Agrega los métodos sort_alphabetical y sort_alphabetical_by a Enumberable.

+0

¡Gracias, ese era exactamente el tipo de complemento que estaba buscando! –

+1

Este complemento se basa en la descomposición de NFD http://en.wikipedia.org/wiki/Unicode_equivalence#Normal_forms y falla en algunos casos. No todos los caracteres diacríticos pueden descomponerse de esta manera (por ejemplo, la letra polaca Ł no puede). – skalee

+1

@skalee ¿Tiene alguna sugerencia acerca de cómo clasificar correctamente las cadenas de texto utf-8 con caracteres polacos? – mdrozdziel

1

Hay un par de formas de hacerlo. Es posible que desee convertir las cadenas UTF a cadenas hexadecimales y luego ordenarlos:

s.split(//).collect { |x| x.unpack('U').to_s }.join 

o puede usar la biblioteca iconv. Leer sobre ella y utilizarla en su caso (de DZone):

#add this to environment.rb 
#call to_iso on any UTF8 string to get a ISO string back 
#example : "Cédez le passage aux français".to_iso 

class String 
    require 'iconv' #this line is not needed in rails ! 
    def to_iso 
    Iconv.conv('ISO-8859-1', 'utf-8', self) 
    end 
end 
+1

Hm, la ordenación por el valor hexadecimal parece poner mis cadenas en orden alfabético, pero realmente no entiendo cómo funciona, ¿puede explicar eso? Además, sigue ordenando Á antes de A, lo cual me parece retrógrado. –

+4

También tenga cuidado: la clasificación Unicode depende de la configuración regional. Los diferentes países tienen un orden diferente en su diccionario. –

+0

Bueno, la conversión a hexadecimal le da un orden que se entiende mejor por las funciones de ordenación. Experimentaría un poco, usando valores hexadecimales formateados con 2 o 3 lugares decimales. o incluso use valores decimales para cada personaje. No soy un gran usuario de UTF, pero de los comentarios de Rutger parece que lo que estás tratando de hacer no tiene una respuesta exacta. –

-2

Ha intentado acceder al método mb_chars para cada una de sus cuerdas país? mb_chars es un proxy que agrega ActiveSupport que define versiones seguras Unicode de todos los métodos String. Si el comparador es consciente de Unicode, la clasificación debería funcionar correctamente.

+1

El problema con el uso de mb_chars es lo mismo que ordenar straight; porque en el juego de caracteres A-Z viene antes de Ä, los caracteres acentuados no se ordenarán en la ubicación correcta. –

0

Lo que estás tratando de hacer es una proposición muy desordenada. No hay forma de hacer una transliteración transparente en todos los caracteres Unicode porque el significado de los dígrafos cambia de configuración regional a configuración regional, y las cadenas pueden crecer ENORMES (si digamos que reemplazas 10 símbolos chinos con sus equivalentes fonéticos). No vayas allí.

¿Por qué quieres nombres transcritos en primer lugar? Para las URL? Los navegadores manejan decentemente las URL Unicode, por lo que está inventando un gran problema de la nada. Si necesita ID, preprocese sus listas para incluir una ID numérica estable por país y úsela como identificador. O guarde el nombre inglés del país como identitificador (puede descargar listas de países con ISO de configuración regional de forma gratuita).

Si realmente desea una buena transliteración para Unicode (y esto no es lo que desea en este caso) vea las bibliotecas IBM ICU, hay una gema inactiva para ellas.

+0

Pregunta claramente pregunta sobre la clasificación de cadenas localizadas. No se trata de una transliteración. –

1

La única solución de trabajo que encontré hasta ahora (al menos para Ruby 1.8 porque Ruby 1.9 debería manejar mejor Unicode) es Unicode by Yoshida Masato. Puede encontrar el método Unicode.strcmp allí.

EDIT: Disculpa, esta solución también utiliza la descomposición de NFD con todas sus limitaciones.

4

La única solución que he encontrado hasta ahora es utilizar ActiveSupport::Inflector.transliterate(string) para reemplazar los caracteres Unicode con los ASCII y ordenar:

Country.all.sort_by do |country| 
    ActiveSupport::Inflector.transliterate country.name 
end 

Ahora el único problema es que esto iguala "a" con "a" (DIN 5007-1) y termino con "Ägypten" antes de "Albanien" mientras que yo esperaría que sea al revés. Afortunadamente, la transliteración es configurable sobre cómo reemplazar caracteres.

Ver documentación: http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-transliterate

+0

¿Está buscando simplemente un método para transliterar cadenas o un método para ordenar cadenas de acuerdo con una intercalación de localización? – toro2k

+0

Las últimas cadenas de clasificación por una intercalación de localización. – Kostas

+0

Incluso con la intercalación adecuada (supongo 'de_DE.UFT8') es normal que * Ägypten * aparezca antes * Albanien *. – toro2k

10

Rubí peforms comparaciones de cadenas en base a valores de bytes de caracteres:

%w[à a e].sort 
# => ["a", "e", "à"] 

para cotejar adecuadamente cuerdas según la configuración regional, el ffi-icu joya podría ser utilizado:

require "ffi-icu" 

ICU::Collation.collate("it_IT", %w[à a e]) 
# => ["a", "à", "e"] 

ICU::Collation.collate("de", %w[a s x ß]) 
# => ["a", "s", "ß", "x"] 

Como alternativa:

collator = ICU::Collation::Collator.new("it_IT") 
%w[à a e].sort { |a, b| collator.compare(a, b) } 
# => %w[a à e] 

Actualización Para probar cómo deben cotejarse las cadenas de acuerdo con las reglas de configuración regional, el proyecto de la ICU proporciona this nice tool.

+0

Lo único que no me gusta de "ffi-icu" es que depende de "libicu". Pero supongo que esto es bastante omnipresente en los sistemas UNIX, ¿verdad? – Kostas

+0

Por lo general, no está instalado de forma predeterminada, pero está disponible en casi cualquier sistema. – toro2k