如何在Ruby中替换带重音符号的拉丁字符?


73

我有一个ActiveRecord模型,Foo其中有一个name字段。我希望用户能够按名称搜索,但我希望搜索忽略大小写和任何重音符号。因此,我还要存储一个canonical_name要搜索的字段:

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

我需要填写“此处的内容”以替换带重音符号的字符。有没有比这更好的了

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

而且,就此而言,由于我没有使用Ruby 1.9,所以无法将这些Unicode文字放入我的代码中。实际的正则表达式看起来丑陋得多。


2
即使在1.8中,也可以使用“ ruby​​ -Ku”
Keltia

这个问题早已解决,下面有很多不错的评论。现在重新阅读它,我想澄清一件事:这个想法是创建一个文本版本,该文本仅可以使用ASCII字符进行搜索,而不是实际上强制数据。请注意,有两个数据库属性:namecanonical_name。我主张浪费实际数据,而只是创建一种没有变音符号的搜索方式,所有语言的用户通常都不会使用变音符号。
James A. Rosen

1
实际上,每个答案都是错误的答案。您需要使用Unicode归类算法,且比较强度仅设置为1级。其他一切都搞砸了。
tchrist 2012年

8
@tchrist,所以您出现在讨论中说“那些家伙错了”,但除了提供最合理的答案外,没有提供其他任何内容吗?o_O请回答真实的问题,以便我对您的令人讨厌表示怀疑。
jcollum 2012年

@tchrist“错误”可能取决于个人要求。的确,做错了事情可能会再次困扰那些不知道后果的人(因此,如果他们不了解,就不会增加他们会增加的要求)。但是直到有人告诉他们后果,他们才会听从这个建议。
开尔文

Answers:


62

Rails已经有一个用于规范化的内置函数,您只需要使用它来规范化您的字符串以形成KD,然后删除其他字符(即重音符号),如下所示:

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

1
形式KD的+1,它也将连字符“ ffi”转换为“ ffi”。
克里斯蒂安-恢复莫妮卡C

4
我试图在Rails应用程序之外的另一个脚本中使用它。我认为这会是activesupport,但要求它后,我仍然获得NoMethodErrornormalize。你知道我需要什么吗?
Helder S Ribeiro

4
它处于activesupport中,但您必须这样做:ActiveSupport :: Multibyte :: Chars.new(“àáâãäå”)。mb_chars.normalize(:kd).gsub(/ [^ \ x00- \ x7F] / n,'')。downcase.to_s
存在,

7
这很棒,但我必须mb_chars像基督徒一样。foo.mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,'').to_s.split
Sam Soffes 09年

50
至少在Rails3中,String#parameterize起作用了……所以“öüâ” .parameterize ==“ oua”
foz

96

ActiveSupport::Inflector.transliterate (需要Rails 2.2.1+和Ruby 1.9或1.8.7)

例:

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


您必须require 'active_support/inflector'在非Rails项目中(请参阅下面的答案
Dorian

才是

41

更好的是使用I18n:

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

1
在正常的红宝石(非轨道)中,我得到:LoadError:无法加载此类文件-i18n轨道库?无论如何,rails方法ActiveSupport :: Inflector.transliterate实际上会在幕后调用I18n(在进行了规范化以确保它可以删除所有变音符之后)
rogerdpack

1
对于cannot load such file -- i18n,只是sudo gem install i18n
卡米尔·古德塞内

我有一个错误,:en is not a valid locale (I18n::InvalidLocale)直到添加为止I18n.available_locales = [:en]。这也会用带问号的变音符号替换不是拉丁字母的非ASCII字符,例如ruby -ri18n -ne'I18n.available_locales=[:en];puts I18n.transliterate$_'<<<Дあ☆prints ???
nisetama

17

我已经尝试了很多这种方法,但是它们并没有满足以下一项或多项要求:

  • 尊重空间
  • 尊重“ñ”字符
  • 尊重大小写(我知道这不是原始问题的要求,但是将字符串移动到小写字母并不难)

一直这样:

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

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

您必须对字符列表进行一些修改以尊重“ñ”字符,但这是一项容易的工作。


您能否详细说明必须修改字符列表以尊重字符的含义ñ?在我看来,它已经在列表中并与对齐n
user664833 2013年

为了尊重ñ角色,我的意思是不要将其转变为n角色,而要保留它。
fguillen 2013年

我懂了。您能说出为什么这个角色很特别吗?
user664833 2013年

1
抱歉,但是我仍然不明白为什么您选择ñ在项目符号列表中列出要求。ñ是“拉丁小字母n带波浪线”,它是在扩展ASCII字符集,与许多其他人在你的名单一起-看ascii-code.com -而有一些在你的列表的字符是在扩展的ASCII集中,包括ĄĦ。所以我对你为什么挑剔仍然感到困惑ñ
user664833

1
ActiveSupport::Inflector.transliterate似乎满足您的要求,但“保留ñ”除外,而且这种方式是纯红宝石,很好。不幸的是,使用unicode,您可以做一些奇怪的事情,比如在基本上所有前面的char上加一个变音符号,因此这种方法很难全面满足所有情况:|
rogerdpack'6

12

我的答案:String#parameterize方法:

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

对于非Rails程序:

安装activesupport:gem install activesupport然后:

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

与完全不同。参数化与ActiveSupport :: Inflector.transliterate输入:“🕳不要落入此编程陷阱” .parameterize给出:“不落入该编程陷阱” ActiveSupport :: Inflector.transliterate给出:“?不要陷入这种编程陷阱”,这是一个巨大的差异。
Fuzzygroup '17

@fuzzygroup使用markdown的代码格式(例如`method`)有助于阅读注释的一部分。为了回答您的问题,"Le cœur de la crémiére".parameterizeURL是对ASCII最好的UTF-8 ASCII码,它的接收者很好,也很甜
Dorian

7

我认为您可能并不真正走那条路。如果您正在开发具有此类字母的市场,那么您的用户可能会认为您有点...点子。因为对于用户而言,“å”甚至不接近“ a”。走另一条路,阅读有关以非ascii方式搜索的信息。这只是有人发明unicode和collat​​ion的情况之一

PS很晚了

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

除此之外,我没有办法将排序规则的链接转到msdn页面,但是我将其保留在那里。它应该是http://www.unicode.org/reports/tr10/


我全力以赴进行数据库整理,但是有人可能会在我离开一年后切换数据库。我更希望自己具有防御性,至少要在代码中,甚至在数据库中也要这样做。至于强迫用户键入含义:多少英语用户键入简历?还是“视觉咖啡馆”?
James A. Rosen

带状海报的字母为å和ä。如果您将这些内容删除为单词的含义,则它们是没有意义的。您不能删除这些内容并使用剩下的内容。如果您确实在欧洲市场工作,则最好学会使用某种东西进行搜索,而不是浪费用户数据。
琼克

以斯洛伐克语为例,例如á,ä与a非常接近。所有带重音符号的字符也都指向没有重音的字符。很多人根本就不在IM等使用这些
Vojto

@Vojto:在大多数北欧语言中,带重音符号的字符与未带重音符号的字符相距甚远。实际上,这些是非常不同的声音的符号。例如,德语öl(en.bab.la/dictionary/german-english/oel)。或瑞典语ål(豹)和al(树)。
Jonke

太棒了,我只想指出,这不一定适用于所有欧洲语言。我提到了斯洛伐克语,但我猜捷克语,波兰语和克罗地亚语以及几乎所有斯拉夫语言也是如此。搜索引擎等支持按重音字符搜索非常重要-因为在大多数情况下,人们太懒了而无法键入重音。
Vojto 2010年

7

分解字符串并从中删除非空格标记

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

如果在.rb文件中使用,可能还需要此功能。

# coding: utf-8

normalize(:kd)部分在可能的情况下拆分变音符号(例如:“ n与tilda”单个字符被拆分为n,然后再组合变音符tilda字符),然后该gsub部分将删除所有变音符。


也可以在这里查看unexist的答案,本质上可以做到这一点,但是要使用与1.8.x兼容的正则表达式。
rogerdpack '16

2
这应该更高。其他解决方案会完全剥离其他字符集(例如I18n.transliterate('日本語') #=> "???")和'日本語'.parameterize #=> ""。这个答案最符合我的需求,即能够大致匹配标题/作者上的各种数据集。'日本語 àáâãäå'.unicode_normalize(:nfkd).gsub(/\p{Mn}/, '') #=> "日本語 aaaaaa"
Bo Jeanes

4

假设您使用Rails。

"anything".parameterize.underscore.humanize.downcase

根据您的要求,这可能就是我要做的...我认为它简洁,简单,并且会在Rails和Ruby的未来版本中保持最新。

更新:dgilperez指出该parameterize参数带有分隔符,因此"anything".parameterize(" ")(不建议使用)"anything".parameterize(separator: " ")或更短更整洁。


3
会不会"anything".parameterize(" ")更短?
dgilperez 2013年

哦,谢谢,我不知道参数化需要参数。
Sudhir Jonathan

.parameterize(" ")较短,但是,它覆盖了所有内容。而且我找不到将preserve_case参数附加到表达式的方法。 I18n.transliterate最有效。
Jerome

3

将文本转换为规范化形式D,删除所有具有unicode类别非间隔标记(Mn)的代码点,然后将其转换回规范化形式C。这将消除所有变音符号,并且您的问题将减少为不区分大小写的搜索。

有关详细信息,请参见http://www.siao2.com/2005/02/19/376617.aspxhttp://www.siao2.com/2007/05/14/2629747.aspx



所有这些涉及归一化形式的答案都是错误的。您需要进行UCA 1级比较,可能需要进行语言环境定制。
tchrist 2012年

另请参见Cheng的解答
rogerdpack '16

3

关键是在数据库中使用两列: canonical_textoriginal_text。使用original_text用于显示和canonical_text进行搜索。这样,如果用户搜索“ Visual Cafe”,则她会看到“ VisualCafé”结果。如果她确实想要其他商品“ Visual Cafe”,则可以将其单独保存。

要在Ruby 1.8源文件中获取canonical_text字符,请执行以下操作:

register_replacement([0x008A].pack('U'), 'S')

也许是个尼特,但是名字'canonical_text'会让我有些不高兴,因为我们正在做的是有损的。我希望它的名称更像'compatible_text'或'decomposed_text'(尽管我也可以看到反对这些的相同论点)。也许只是“ search_text”?
克里斯蒂安-恢复莫妮卡C

这里的register_replacement是什么?
rogerdpack '16

2

您可能需要Unicode分解(“ NFD”)。分解字符串后,只需过滤掉[A-Za-z]中没有的任何内容。æ将分解为“ ae”,ã分解为“ a〜”(大约-变音符号将成为一个单独的字符),因此过滤会留下合理的近似值。


1
所有这些涉及归一化形式的答案都是错误的。您需要进行UCA 1级比较,可能需要进行语言环境定制。
tchrist 2012年

1
@tchrist:如果您想提供其他答案,请放心。如果您想指出为什么我的答案不起作用,请使用注释,但至少要指出为什么它不起作用。(提示:阅读问题第一称号; UCA比较并不能代替重音字符)。
MSalters 2012年

另请参见Cheng的示例示例
rogerdpack '16


1

对于阅读此内容并希望去除所有非ASCII字符的任何人,可能很有用,我成功地使用了第一个示例。


1
也可以在此处添加示例:)
rogerdpack '16

0

我在使foo.mb_chars.normalize(:kd).gsub(/ [^ \ x00- \ x7F] / n,'')。downcase.to_s解决方案正常工作时遇到问题。我没有使用Rails,与我的activesupport / ruby​​版本存在一些冲突,无法深入了解。

使用ruby-unf宝石似乎是一个很好的替代方法:

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

据我所知,它与.mb_chars.normalize(:kd)的作用相同。这个对吗?谢谢!


0

如果您使用PostgreSQL => 9.4作为数据库适配器,也许您可​​以在迁移中添加它是“ unaccent”扩展名,我认为它可以满足您的要求,例如:

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

为了测试,在控制台中:

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

请注意,到目前为止,区分大小写。

然后,可以在范围内使用它,例如:

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

iLIKE运算符使搜索大小写不敏感。还有另一种使用citext数据类型的方法。是关于这两种方法的讨论。还要注意,不建议使用PosgreSQL的lower()函数

这将为您节省一些数据库空间,因为您将不再需要cannonical_name字段,并且可能使模型更简单,但需要在每个查询中进行一些额外处理,具体取决于您使用的是iLIKE还是citext,并且您的数据集。

如果您使用的是MySQL,也许可以使用此简单的解决方案,但我尚未对其进行测试。


-3

大声笑..我刚刚尝试了..并且它正在工作..我仍然不太确定为什么..但是当我使用这4行代码时:

  • str = str.gsub(/ [^ a-zA-Z0-9] /,“”)
  • str = str.gsub(/ [] + /,“”)
  • str = str.gsub(/ /,“-”)
  • str = str.downcase

它会自动删除文件名中的任何重音..我正试图删除(从文件名中重命名并重命名它们)希望它有所帮助:)


2
它还会删除所有非字母数字字符。即使对于文件名,这可能也不是正确的行为。
查克(Chuck)

这不是预期的行为。
马里亚诺·卡瓦洛

不幸的是,它删除了所有重音字符,并且没有用它们的非重音字符代替它们:|
rogerdpack
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.