it-swarm.dev

Como faço para substituir caracteres latinos acentuados em Ruby?

Eu tenho um modelo ActiveRecord, Foo, que possui um campo name. Eu gostaria que os usuários pudessem pesquisar por nome, mas eu gostaria que a pesquisa ignorasse maiúsculas e minúsculas toques. Assim, também estou armazenando um campo canonical_name com o qual pesquisar:

class Foo
  validates_presence_of :name

  before_validate :set_canonical_name

  private

  def set_canonical_name
    self.canonical_name ||= canonicalize(self.name) if self.name
  end

  def canonicalize(x)
    x.downcase.  # something here
  end
end

Eu preciso preencher o "algo aqui" para substituir os caracteres acentuados. Existe alguma coisa melhor que

x.downcase.gsub(/[àáâãäå]/,'a').gsub(/æ/,'ae').gsub(/ç/, 'c').gsub(/[èéêë]/,'e')....

E, por falar nisso, como não estou no Ruby 1.9, não posso colocar esses literais Unicode no meu código. As expressões regulares reais parecerão muito mais feias.

66
James A. Rosen

O Rails já vem embutido para normalizar, você só tem que usar isto para normalizar sua string para formar KD e então remover os outros chars (isto é, acentuação) assim:

>> "àáâãäå".mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,'').downcase.to_s
=> "aaaaaa"
56
unexist

ActiveSupport::Inflector.transliterate (requer Rails 2.2.1+ e Ruby 1.9 ou 1.8.7)

exemplo: 

>> ActiveSupport::Inflector.transliterate("àáâãäå").to_s => "aaaaaa"

85
Mark Wilden

Melhor ainda é usar o I18n:

1.9.3-p392 :001 > require "i18n"
 => false
1.9.3-p392 :002 > I18n.transliterate("Olá Mundo!")
 => "Ola Mundo!"
38
Diego Moreira

Eu tentei muitas dessas abordagens, mas elas não estavam atingindo um ou vários desses requisitos:

  • Respeite os espaços
  • Respeito 'ñ' personagem
  • Respeito caso (eu sei que não é um requisito para a pergunta original, mas não é difícil mover uma string para lowcase )

Tem sido isso:

# coding: utf-8
string.tr(
  "ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšſŢţŤťŦŧÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųŴŵÝýÿŶŷŸŹźŻżŽž",
  "AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnOOOOOOooooooOoOoOoRrRrRrSsSsSsSssTtTtTtUUUUuuuuUuUuUuUuUuUuWwYyyYyYZzZzZz"
)

- http://blog.slashpoundbang.com/post/12938588984/remove-all-accents-and-diacritics-from-string-in-Ruby

Você tem que modificar um pouco a lista de personagens para respeitar o caractere 'ñ', mas é um trabalho fácil.

19
fguillen

Minha resposta: o parâmetro String # parameterize :

"Le cœur de la crémiére".parameterize
=> "le-coeur-de-la-cremiere"

Para programas não-Rails:

Instale o activeesupport: gem install activesupport então:

require 'active_support/inflector'
"a&]'s--3\014\xC2àáâã3D".parameterize
# => "a-s-3-3d"
10
Dorian

Eu acho que você talvez não saiba realmente o que seguir nesse caminho. Se você está desenvolvendo para um mercado que tem este tipo de letras, seus usuários provavelmente pensarão que você é uma espécie de ...pip. Porque 'å' não é nem perto de 'a' em qualquer significado para um usuário. Tome um caminho diferente e leia sobre a pesquisa de uma maneira não-ascii. Este é apenas um daqueles casos em que alguém inventou unicode e collation .

muito tarde PS :

http://www.w3.org/International/wiki/Case_foldinghttp://www.w3.org/TR/charmod-norm/#sec-WhyNormalization

Além disso, eu não tenho nenhuma maneira como o link para o agrupamento vai para uma página do msdn, mas deixo lá. Deveria ter sido http://www.unicode.org/reports/tr10/

7
Jonke

Decomponha a string e remova marcas de não espaçamento dela.

irb -ractive_support/all
> "àáâãäå".mb_chars.normalize(:kd).gsub(/\p{Mn}/, '')
aaaaaa

Você também pode precisar disso se usado em um arquivo .rb.

# coding: utf-8

a parte normalize(:kd) aqui divide os diacríticos quando possível (ex: o caractere único "n com tilda" é dividido em um n seguido por um caractere tilativo diacrítico de combinação), e a parte gsub então remove todos os caracteres diacríticos.

5
Cheng

Isso pressupõe que você use o Rails.

"anything".parameterize.underscore.humanize.downcase

Dadas as suas necessidades, isso é provavelmente o que eu faria ... Eu acho que é legal, simples e vai ficar atualizado em futuras versões do Rails e do Ruby.

Atualização: dgilperez apontou que parameterize leva um argumento separador, então "anything".parameterize(" ") (deprecated) ou "anything".parameterize(separator: " ") é mais curto e mais limpo.

4
Sudhir Jonathan

Converta o texto para o formulário de normalização D, remova todos os pontos de código com a marca de espaçamento não uniforme (Mn) da categoria unicode e converta-o de volta para o formulário de normalização C. Isso eliminará todos os diacríticos e reduzirá o problema a uma pesquisa sem distinção entre maiúsculas e minúsculas.

Veja http://www.siao2.com/2005/02/19/376617.aspx e http://www.siao2.com/2007/05/14/2629747.aspx para detalhes .

3
CesarB

A chave é usar duas colunas em seu banco de dados: canonical_text e original_text. Use original_text para exibição e canonical_text para pesquisas. Dessa forma, se um usuário pesquisar por "Visual Cafe", verá o resultado "Visual Café". Se ela really quiser um item diferente chamado "Visual Cafe", ele pode ser salvo separadamente.

Para obter os caracteres canonical_text em um arquivo fonte do Ruby 1.8, faça algo assim:

register_replacement([0x008A].pack('U'), 'S')
3
James A. Rosen

Você provavelmente quer decomposição Unicode ("NFD"). Depois de decompor a corda, basta filtrar qualquer coisa que não esteja em [A-Za-z]. æ irá decompor-se para "ae", ã para "a ~" (aproximadamente - o diacrítico se tornará um caractere separado) para que a filtragem deixe uma aproximação razoável.

2
MSalters

Para qualquer um que esteja lendo este desejo de remover todos os caracteres não-ascii this pode ser útil, usei o primeiro exemplo com sucesso.

1
Kris
1
Gene T

Eu tive problemas para obter a solução foo.mb_chars.normalize (: kd) .gsub (/ [^\x00-\x7F]/n, ''). Downcase.to_s para o trabalho. Eu não estou usando o Rails e houve algum conflito com minhas versões do ActiveSupport/Ruby que eu não consegui entender.

Usando a gem Ruby-unf parece ser um bom substituto:

require 'unf'
foo.to_nfd.gsub(/[^\x00-\x7F]/n,'').downcase

Tanto quanto eu posso dizer isso faz a mesma coisa que .mb_chars.normalize (: kd). Isso está correto? Obrigado!

0
eoghan.ocarragain

Se você está usando o PostgreSQL => 9.4 como seu adaptador de banco de dados, talvez você possa adicionar em uma migração sua extensão "unaccent" que eu acho que faz o que você quer, assim:

def self.up
   enable_extension "unaccent" # No falla si ya existe
end

Para testar, no console:

2.3.1 :045 > ActiveRecord::Base.connection.execute("SELECT unaccent('unaccent', 'àáâãäåÁÄ')").first
 => {"unaccent"=>"aaaaaaAA"}

Observe que há distinção entre maiúsculas e minúsculas até agora.

Então, talvez use em um escopo, como:

scope :with_canonical_name, -> (name) {
   where("unaccent(foos.name) iLIKE unaccent('#{name}')")
}

O operador iLIKE torna o caso de pesquisa insensível. Há outra abordagem, usando o tipo de dados citext . Aqui é uma discussão sobre essas duas abordagens. Observe também que o uso da função lower () do PosgreSQL não é recomendado .

Isso economizará um pouco do espaço do banco de dados, já que você não precisará mais do campo cannonical_name e talvez torne seu modelo mais simples, ao custo de algum processamento extra em cada consulta, dependendo se você estiver usando iLIKE ou citext e seu conjunto de dados.

Se você está usando MySQL talvez você possa usar esta solução simples , mas eu não testei.

0
user2553863