Ruby 超级不敏感的正则表达式来匹配带有重音符号和其他变音符号的学校名称
Ruby super-insensitive Regex to match school names with accents and other diacritics
这个问题已经在其他编程语言中被问到,但是你如何在 Ruby 上执行不区分重音的正则表达式?
我现在的代码是这样的
scope :by_registered_name, ->(regex){
where(:name => /#{Regexp.escape(regex)}/i)
}
我想也许我可以用点替换非字母数字+空白字符,并删除 escape
,但是没有更好的方法吗?恐怕我这样做会抓到奇怪的东西...
我现在的目标是法语,但如果我也可以针对其他语言修复它,那就太棒了。
我正在使用 Ruby 2.3,如果有帮助的话。
我发现我的要求其实有点强,我还需要捕捉破折号之类的东西。我基本上是导入一个学校数据库(URL here,标签是<nom>
),我希望人们能够通过输入学校名称找到他们的学校。搜索查询和搜索请求都可能包含重音符号,我认为最简单的方法是使 "both" 不敏感。
- "Télécom" 应该匹配 "Telecom"
- "établissement" 应匹配 "etablissement"
- "Institut supérieur national de l'artisanat - Chambre de métiers et de l'Artisanat en Moselle" 应匹配“artisanat chambre de métiers
- "Ecole hôtelière d'Avignon (CCI du Vaucluse)" 应与Ecole hoteliere d'avignon匹配"(括号可以跳过)
- "Ecole française d'hôtesses" 应该匹配 "ecole francaise d'hot"
我在那个数据库中发现了一些疯狂的东西,我认为我会考虑清理这个输入
- "Académie internationale de management - Hotel & Tourism Management Academy"应该匹配"Hotel Tourism"(注意XML中的&其实是写成
&
)
MongoDB 的解决方案似乎是使用 text
index, which is diacritic insensitive. French is supported。
自从我上次使用 MongoDB 以来已经有很长一段时间了,但是如果您使用的是 Mongoid,我认为您会在模型中创建一个 text
索引,如下所示:
index(name: "text")
...然后这样搜索:
scope :by_registered_name, ->(str) {
where(:$text => { :$search => str })
}
有关详细信息,请参阅 $text
query operator 的文档。
原(错误)答案
As it turns out I was thinking about the question backwards, and wrote this answer initially. I'm preserving it since it might still come in handy. If you were using a database that didn't offer this kind of functionality (like, it seems, MongoDB does), a possible workaround would be to use the following technique to store a sanitized name along with the original name in the database, and then likewise sanitize queries.
由于您使用的是 Rails,因此您可以使用方便的 ActiveSupport::Inflector.transliterate
:
regex = /aäoöuü/
transliterated = ActiveSupport::Inflector.transliterate(regex.source, '\?')
# => "aaoouu"
new_regex = Regexp.new(transliterated)
# => /aaoouu/
或者简单地说:
Regexp.new(ActiveSupport::Inflector.transliterate(regex.source, '\?'))
您会注意到我提供了 '\?'
作为第二个参数,这是将替换任何无效 UTF-8 字符的替换字符串。这是因为默认的替换字符串是 "?"
,如您所知,它在正则表达式中具有特殊含义。
还要注意 ActiveSupport::Inflector.transliterate
比类似的 I18n.transliterate
做的多一点。这是它的来源:
def transliterate(string, replacement = "?")
I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize(
ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
:replacement => replacement)
end
最内层的方法调用 ActiveSupport::Multibyte::Unicode.tidy_bytes
清除所有无效的 UTF-8 字符。
更重要的是,ActiveSupport::Multibyte::Unicode.normalize
"normalizes" the characters. For example, ê
looks like one character but it's actually two: LATIN SMALL LETTER E and COMBINING CIRCUMFLEX ACCENT. Calling I18n.transliterate("ê")
would yield e?
, which probably isn't what you want, so normalize
is called to turn ê
into ê
, which is just one character: LATIN SMALL LETTER E WITH CIRCUMFLEX. Calling I18n.transliterate
on ê
(the former) would yield e?
, which probably isn't what you want, so that normalize
step before transliterate
is important. (If you're interested in how that works, read about Unicode equivalence and normalization。)
这个问题已经在其他编程语言中被问到,但是你如何在 Ruby 上执行不区分重音的正则表达式?
我现在的代码是这样的
scope :by_registered_name, ->(regex){
where(:name => /#{Regexp.escape(regex)}/i)
}
我想也许我可以用点替换非字母数字+空白字符,并删除 escape
,但是没有更好的方法吗?恐怕我这样做会抓到奇怪的东西...
我现在的目标是法语,但如果我也可以针对其他语言修复它,那就太棒了。
我正在使用 Ruby 2.3,如果有帮助的话。
我发现我的要求其实有点强,我还需要捕捉破折号之类的东西。我基本上是导入一个学校数据库(URL here,标签是<nom>
),我希望人们能够通过输入学校名称找到他们的学校。搜索查询和搜索请求都可能包含重音符号,我认为最简单的方法是使 "both" 不敏感。
- "Télécom" 应该匹配 "Telecom"
- "établissement" 应匹配 "etablissement"
- "Institut supérieur national de l'artisanat - Chambre de métiers et de l'Artisanat en Moselle" 应匹配“artisanat chambre de métiers
- "Ecole hôtelière d'Avignon (CCI du Vaucluse)" 应与Ecole hoteliere d'avignon匹配"(括号可以跳过)
- "Ecole française d'hôtesses" 应该匹配 "ecole francaise d'hot"
我在那个数据库中发现了一些疯狂的东西,我认为我会考虑清理这个输入
- "Académie internationale de management - Hotel & Tourism Management Academy"应该匹配"Hotel Tourism"(注意XML中的&其实是写成
&
)
MongoDB 的解决方案似乎是使用 text
index, which is diacritic insensitive. French is supported。
自从我上次使用 MongoDB 以来已经有很长一段时间了,但是如果您使用的是 Mongoid,我认为您会在模型中创建一个 text
索引,如下所示:
index(name: "text")
...然后这样搜索:
scope :by_registered_name, ->(str) {
where(:$text => { :$search => str })
}
有关详细信息,请参阅 $text
query operator 的文档。
原(错误)答案
As it turns out I was thinking about the question backwards, and wrote this answer initially. I'm preserving it since it might still come in handy. If you were using a database that didn't offer this kind of functionality (like, it seems, MongoDB does), a possible workaround would be to use the following technique to store a sanitized name along with the original name in the database, and then likewise sanitize queries.
由于您使用的是 Rails,因此您可以使用方便的 ActiveSupport::Inflector.transliterate
:
regex = /aäoöuü/
transliterated = ActiveSupport::Inflector.transliterate(regex.source, '\?')
# => "aaoouu"
new_regex = Regexp.new(transliterated)
# => /aaoouu/
或者简单地说:
Regexp.new(ActiveSupport::Inflector.transliterate(regex.source, '\?'))
您会注意到我提供了 '\?'
作为第二个参数,这是将替换任何无效 UTF-8 字符的替换字符串。这是因为默认的替换字符串是 "?"
,如您所知,它在正则表达式中具有特殊含义。
还要注意 ActiveSupport::Inflector.transliterate
比类似的 I18n.transliterate
做的多一点。这是它的来源:
def transliterate(string, replacement = "?")
I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize(
ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
:replacement => replacement)
end
最内层的方法调用 ActiveSupport::Multibyte::Unicode.tidy_bytes
清除所有无效的 UTF-8 字符。
更重要的是,ActiveSupport::Multibyte::Unicode.normalize
"normalizes" the characters. For example, ê
looks like one character but it's actually two: LATIN SMALL LETTER E and COMBINING CIRCUMFLEX ACCENT. Calling I18n.transliterate("ê")
would yield e?
, which probably isn't what you want, so normalize
is called to turn ê
into ê
, which is just one character: LATIN SMALL LETTER E WITH CIRCUMFLEX. Calling I18n.transliterate
on ê
(the former) would yield e?
, which probably isn't what you want, so that normalize
step before transliterate
is important. (If you're interested in how that works, read about Unicode equivalence and normalization。)