Ruby方法以删除UTF-8国际字符中的重音为例

86

我正在尝试创建一个“归一化”的字符串副本,以帮助减少数据库中的重复名称。这些名称包含许多国际字符(例如有重音的字母),我想创建一个没有重音的副本。

我找到了下面的方法,但无法使其正常工作。我似乎找不到Unicode Hacks插件。

  # Utility method that retursn an ASCIIfied, downcased, and sanitized string.
  # It relies on the Unicode Hacks plugin by means of String#chars. We assume
  # $KCODE is 'u' in environment.rb. By now we support a wide range of latin
  # accented letters, based on the Unicode Character Palette bundled inMacs.
  def self.normalize(str)
     n = str.chars.downcase.strip.to_s
     n.gsub!(/[à áâãäåÄÄ?]/u,    'a')
     n.gsub!(/æ/u,                  'ae')
     n.gsub!(/[ÄÄ?]/u,                'd')
     n.gsub!(/[çÄ?ÄÄ?Ä?]/u,          'c')
     n.gsub!(/[èéêëÄ?Ä?Ä?Ä?Ä?]/u, 'e')
     n.gsub!(/Æ?/u,                   'f')
     n.gsub!(/[ÄÄ?Ä¡Ä£]/u,            'g')
     n.gsub!(/[ĥħ]/,                'h')
     n.gsub!(/[ììíîïīĩĭ]/u,     'i')
     n.gsub!(/[įıijĵ]/u,           'j')
     n.gsub!(/[ķĸ]/u,               'k')
     n.gsub!(/[Å?ľĺļÅ?]/u,         'l')
     n.gsub!(/[ñÅ?Å?Å?Å?Å?]/u,       'n')
     n.gsub!(/[òóôõöøÅÅ?ÅÅ]/u,  'o')
     n.gsub!(/Å?/u,                  'oe')
     n.gsub!(/Ä?/u,                   'q')
     n.gsub!(/[Å?Å?Å?]/u,             'r')
     n.gsub!(/[Å?Å¡Å?ÅÈ?]/u,          's')
     n.gsub!(/[ťţŧÈ?]/u,           't')
     n.gsub!(/[ùúûüūůűŭũų]/u,'u')
     n.gsub!(/ŵ/u,                   'w')
     n.gsub!(/[ýÿŷ]/u,             'y')
     n.gsub!(/[žżź]/u,             'z')
     n.gsub!(/\s+/,                   ' ')
     n.gsub!(/[^\sa-z0-9_-]/,          '')
     n
  end

我需要“require”一个特定的库/宝石吗?或者也许有人可以推荐另一种方法来解决这个问题。

我不使用Rails,也没有计划这样做。


1
你正在使用哪个Ruby版本? - Huluk
请查看https://dev59.com/HHM_5IYBdhLWcg3wslbs。 - MurifoX
3
你也可以查看:https://github.com/norman/unidecoder - amalrik maia
我正在使用Ruby 1.9.3版本,我会仔细研究这两种可能的解决方案。我只需要上述方法替换所列字符的结果,如果这些解决方案能够实现这一点,那就太好了,谢谢 :) - Gus Shortz
我终于找到了一些关于Unicode Hack插件(http://www.railslodge.com/plugins/316-unicode-hacks)的参考资料,该插件提供了我提到的`normalize`方法所需的`chars`方法。但是似乎它已不再受支持。 - Gus Shortz
5个回答

255

我通常使用I18n来处理这个问题:

1.9.3p392 :001 > require "i18n"
 => true
1.9.3p392 :002 > I18n.transliterate("Hé les mecs!")
 => "He les mecs!"

3
这份文档介绍了如何转换字符串中的非 ASCII 字符及其它字符,使得这些字符能被使用不同编码的数据库、操作系统等系统所识别。另外,在每种语言环境下设置转换规则也是一种很有用的功能。 - Paul Fioravanti
13
这个功能可能无法在没有基本拉丁映射的字符上正常工作,比如中文字符。它只会把它们转换为问号。(main)> I18n.transliterate("雙屬性集合之空間分群演算法-應用於地理資料") => "?????????????-???????" - David
20
针对纯Ruby而言,如果出现"I18n::InvalidLocale: :en is not a valid locale"的错误提示,可以在使用"I18n.transliterate"之前加上"I18n.available_locales = [:en]"来解决。 - Alter Lagos
1
注意:这并不适用于所有情况。例如,“Bùi Viện”被翻译为“Bui Vi?n”。 - CHawk
3
对我没用:(main)> I18n.transliterate "ŠKODA" => "ŠKODA" - Michael
显示剩余2条评论

37

parameterize方法可以作为一种简单易用的解决方案来删除特殊字符以便将字符串作为可读的标识符使用。

> "Françoise Isaïe".parameterize
=> "francoise-isaie"

1
不过他们没有使用Rails。 - snowangel
3
parameterize 使用 I18n.transliterate: https://github.com/rails/rails/blob/main/activesupport/lib/active_support/inflector/transliterate.rb - Dorian
谢谢你!太棒了 xD - Wordica
请注意,将句号“。”更改为破折号“-”。 - Julien

20

到目前为止,以下是我能够完成所需的唯一方法:

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

但是使用这种方法感觉很“hackish”,我希望能找到更好的方式。


1
这仅适用于ISO-8859-1。你怎么能确定它适用于UTF-8呢? - pts
4
这个函数适用于UTF-8和ruby 2.2.3,并且完全符合我的需求。不过缺少一些罗马尼亚字符。我已经添加了它们:string.tr( "ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšȘșſŢţŤťŦŧȚțÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųŴŵÝýÿŶŷŸŹźŻżŽž", "AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnOOOOOOooooooOoOoOoRrRrRrSsSsSsSsSssTtTtTtTtUUUUuuuuUuUuUuUuUuUuWwYyyYyYZzZzZz") - Alexander
1
谢谢,它起作用了。缺少一些越南字符。我已经添加了它们: tr("ÀÁÂÃÄÅàáâãäåĀāĂ㥹ạảÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêểệễëĒēĔĕĖėĘęĚěẹĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıịỉĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÒÓÔÕÖØòóôộỗổõöøŌōŎŏŐőọỏơởợỡŔŕŖŗŘřŚśŜŝŞşŠšſŢţŤťŦŧÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųụưủửữựŴŵÝýÿŶŷŸŹźŻżŽžứừửữựữốồộỗổờóợỏỡếềễểệẩẫấầậỳỹýỷỵặẵẳằắ", "AAAAAAaaaaaaAaAaAaaaCcCcCcCcCcDdDdDdEEEEeeeeeeEeEeEeEeEeeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiiiJjKkkLlLlLlLlLlNnNnNnNnnNnOOOOOOoooooooooOoOoOoooooooRrRrRrSsSsSsSssTtTtTtUUUUuuuuUuUuUuUuUuUuuuuuuuWwYyyYyYZzZzZzuuuuuooooooooooeeeeeaaaaayyyyyaaaaa") - duyetpt

8

解决方案:

DIACRITICS = [*0x1DC0..0x1DFF, *0x0300..0x036F, *0xFE20..0xFE2F].pack('U*')

def removeaccents(str)
  str
    .unicode_normalize(:nfd)
    .tr(DIACRITICS, '')
    .unicode_normalize(:nfc)
end

样例 (之前/之后):

ÀÁÂÃÄÅàáâãäåĀāĂ㥹ạảÇçĆćĈĉĊċČčĎďÈÉÊËèéêểệễëĒēĔĕĖėĘęĚěẹĜĝĞğĠġĢģĤĥÌÍÎÏìíîïĨĩĪīĬĭĮįİıịỉĴĵĶķĸĹĺĻļĽľÑñŃńŅņŇňÒÓÔÕÖòóôộỗổõöŌōŎŏŐőọỏơởợỡŔŕŖŗŘřŚśŜŝŞşŠšſŢţŤťÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųụưủửữựŴŵÝýÿŶŷŸŹźŻżŽžứừửựữốồộỗổờóợỏỡếềễểệẩẫấầậỳỹýỷỵặẵẳằắ
AAAAAAaaaaaaAaAaAaaaCcCcCcCcCcDdEEEEeeeeeeeEeEeEeEeEeeGgGgGgGgHhIIIIiiiiIiIiIiIiIıiiJjKkĸLlLlLlNnNnNnNnOOOOOooooooooOoOoOoooooooRrRrRrSsSsSsSsſTtTtUUUUuuuuUuUuUuUuUuUuuuuuuuWwYyyYyYZzZzZzuuuuuooooooooooeeeeeaaaaayyyyyaaaaa

解释:

  • 将单码点字符分解为其构成的码点字符(如果适用)。
  • 删除以下块中发现的变音标记码点 (Unicode 15.0.0 参考):
    • Combining Diacritical Marks Supplement (U+1DC0 → U+1DFF)
    • Combining Diacritical Marks (U+0300 → U+036F)
    • Combining Half Marks (U+FE20 → U+FE2F)
  • 重新组合字符。

注意事项:

  • 虽然这些变音符号主要用于文本,但其中一些也可与符号一起使用。当不应该删除这些符号时,这些符号将被删除。
  • 不常见的代码点(例如下标记)未被删除。尽管它们的名称如此,但它们不被 Unicode 参考视为组合标记,而是格式字符。一个例子是上面的阿拉伯语哈姆扎◌ٔ(U+0654),可能甚至无法在您的浏览器中正确显示。
  • 虽然不是实质性的注意事项,但值得一提的是:由空格或断开空格引导的变音符号也将被删除。它们在某些文本渲染软件中显示为独立字符,因此可能不是所需的。

1
我真的很喜欢这个解决方案。不需要任何宝石或其他东西,只需简单清晰的代码。我希望这能得到更多的投票。在我看来,这应该是被接受的答案。 - luis.madrigal

6

如果你正在使用Rails:

"L'Oréal".parameterize(separator: ' ')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接