如何在Ruby中检测字符串中的特定Unicode字符?

18

在Ruby 1.8.7中(没有支持\p{}的Unicode属性的强大Oniguruma正则表达式引擎),如果给定一个字符串,我想要确定该字符串是否包含一个或多个中文、日文或韩文字符;即

class String
  def contains_cjk?
    ...
  end
end

>> '日本語'.contains_cjk?
=> true
>> '광고 프로그램'.contains_cjk?
=> true
>> '艾弗森将退出篮坛'.contains_cjk?
=> true
>> 'Watashi ha bakana gaijin desu.'.contains_cjk?
=> false

我猜测这将归结为查看字符串中的任何字符是否在Unihan CJKV Unicode块中,但我觉得值得问一下是否有现成的Ruby解决方案。

你是否正在使用 Ruby 的 1.9 版本或者是一个不支持良好的 Unicode 正则表达式的旧版本?如果你正在使用 1.9 版本,那么你应该可以使用(某些)Unicode 属性,例如 \p{InCJKUnifiedIdeographs} 或者甚至是 \p{Han} - tchrist
1.8.7版本不包含Oniguruma;问题已更新。 - Josh Glover
4个回答

49

(Ruby 1.9.2)

#encoding: UTF-8
class String
  def contains_cjk?
    !!(self =~ /\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}/)
  end
end

strings= ['日本', '광고 프로그램', '艾弗森将退出篮坛', 'Watashi ha bakana gaijin desu.']
strings.each{|s| puts s.contains_cjk?}

#true
#true
#true
#false

\p{}匹配一个Unicode字符脚本。
支持以下脚本:阿拉伯文、亚美尼亚文、巴厘文、孟加拉文、注音符号、盲文、布吉文、布希德文、加拿大土著文、卡里亚文、占语文、切罗基文、通用文、科普特文、楔形文字、塞浦路斯文、西里尔文、德瑟利特文、天城体梵文、格鲁吉亚文、格拉哥里文、哥特文、希腊文、古吉拉特文、果鲁穆奇文、汉字、朝鲜文、哈努诺文、希伯来文、平假名、继承的、卡纳达文、片假名、克耶文、可罗克斯特文、高棉文、老挝文、拉丁文、莲花Schrift、林布文、线性B、吕底亚文、吕底亚文、马拉雅拉姆文、蒙古文、缅甸文、新傣仂文、N'Ko、欧甘文、奥尔切克文、古意大利文、古波斯文、奥里亚文、奥斯曼文、八思巴文、腓尼基文、热朗文、古代北欧文字、萨拉斯瓦提文、信书文字、锡尔赫特字母、占位符字母、叙利亚文、塔加洛字母、塔格班瓦文、泰乐文、泰米尔文、泰卢固文、塔纳文、泰文、藏文、提非纳文、乌加里特文、瓦伊文和彝文。

哇,Ruby Regexp source


2
我不得不在文件顶部添加“#encoding:UTF-8”才能使其正常工作。否则,我会收到无效字符属性名称错误。 - Morrowless
1
更有意义的做法是使用 [p{Han}\p{Katakana}\p{Hiragana}\p{Hangul}] - tchrist
@tchrist 的结果是 false, true, false, true - steenslag
1
@steenslag,如果/a|b|c|d/无法获得与[abcd]相同的答案,则存在错误。当a、b、c、d中的每个字符都是单个字符或匹配单个字符的内容(如\w\p{Han})时,它们应该能够获得相同的答案。最好非常仔细地检查一下。 - tchrist
新的源链接(Ruby 2.0):https://github.com/ruby/ruby/blob/trunk/doc/regexp.rdoc - Juanito Fatas
显示剩余2条评论

9

考虑到我使用的是Ruby 1.8.7版本,以下是我能够做到的最佳方案:

class String
  CJKV_RANGES = [
      (0xe2ba80..0xe2bbbf),
      (0xe2bfb0..0xe2bfbf),
      (0xe38080..0xe380bf),
      (0xe38180..0xe383bf),
      (0xe38480..0xe386bf),
      (0xe38780..0xe387bf),
      (0xe38880..0xe38bbf),
      (0xe38c80..0xe38fbf),
      (0xe39080..0xe4b6bf),
      (0xe4b780..0xe4b7bf),
      (0xe4b880..0xe9bfbf),
      (0xea8080..0xea98bf),
      (0xeaa080..0xeaaebf),
      (0xeaaf80..0xefbfbf),
  ]

  def contains_cjkv?
    each_char do |ch|
      return true if CJKV_RANGES.any? {|range| range.member? ch.unpack('H*').first.hex }
    end
    false
  end
end


strings = ['日本', '광고 프로그램', '艾弗森将退出篮坛', 'Watashi ha bakana gaijin desu.']
strings.each {|s| puts s.contains_cjkv? }

#true
#true
#true
#false

虽然有些hack,但它确实有效。它实际上可以检测多种印度语脚本,因此可能真的应该叫做contains_asian?

也许我应该为其他被困在Ruby 1.8中的可怜I18N(国际化)黑客制作一个gem。


我认为其他人可能会发现它有帮助。 - Geo
我也有一个被困在1.8上的项目。这个解决方案对我不起作用,但我从另一个 Stack Overflow 线程中改编了一个解决方案 - 请看我的回答。 - Henrik N

1
我写了一个小宝石,将steenslag上面的方法打包起来:

https://github.com/jpatokal/script_detector

它还可以尝试区分日语、韩语、简体中文和繁体中文,但由于汉字统一的复杂性,它只能在大块文本中可靠地工作。


我想知道这是否是最新的图书馆。 - Nakilon

0
基于此代码的Ruby 1.8解决方案,并使用Josh Glover在此线程中提供的API。
class String
  CJKV_RANGES = [
    (0x4E00..0x9FFF),
    (0x3400..0x4DBF),
    (0x20000..0x2A6DF),
    (0x2A700..0x2B73F),
  ]

  def contains_cjkv?
    unpack("U*").any? { |char|
      CJKV_RANGES.any? { |range| range.member?(char) }
    }
  end
end

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