不使用组合标记计算Unicode字符串长度

4
考虑以下Ruby代码,分析一个由三个字节组成的UTF-8字符串:
#encoding: utf-8
s = "\x65\xCC\x81"
p [s.bytesize, s.length, s, s.encoding.name]
#=> [3, 2, "é", "UTF-8"]

如我这个页面所述,上述内容实际上是由两个字符组成:拉丁小写字母e后面跟着组合重音符号。然而,它们看起来像一个字符,当显示固定宽度的文本时,这很重要。

例如,在这个目录列表中查看“moire.svg”的两个条目,并注意其中一个已经破坏了列对齐。

我该如何计算Ruby字符串的“等宽可视长度”,而不包括任何零宽组合字符呢?(一种有效的技术可能是将Unicode字符串转换为其规范表示形式,将上述内容转换为"\xC3\xA9",它也看起来像é,但长度为1。)


你使用的是哪个版本的Ruby?我尝试了你的示例,得到了 [3, 3, "é"] - Ilia Frenkel
@IliaFrenkel 上面所提到的是使用UTF-8进行字符串编码的Ruby 1.9版本。我已经编辑了代码,显示出在任何非默认情况下使用UTF-8的系统上运行独立脚本所需的魔法注释。 - Phrogz
3个回答

5
“unicode_utils”宝石可能会有所帮助。
当前链接:https://github.com/lang/unicode_utils 旧链接:http://unicode-utils.rubyforge.org/UnicodeUtils.html 其中有一个“char_display_width”方法:char_display_width
require "unicode_utils/char_display_width"
UnicodeUtils.char_display_width("別")  # => 2
UnicodeUtils.char_display_width(0x308) # => 0
UnicodeUtils.char_display_width("a")   # => 1

有一个字符串display_width方法:

require "unicode_utils/display_width"
UnicodeUtils.display_width("別れ") => 4
UnicodeUtils.display_width("12") => 2
UnicodeUtils.display_width("a\u{308}") => 1

同时还要看一下each_grapheme。(感谢 Michael Anderson 指出的其他方法。)


刚刚发现这个.. 但我认为使用 each_grapheme 方法进行计数可能更合适。http://unicode-utils.rubyforge.org/UnicodeUtils.html#method-c-each_grapheme - Michael Anderson
1
或者更好的是,有一个display_width可以接受字符串而不是字符。 - Michael Anderson

1
你可以使用正则表达式来获取Unicode属性:
s = "\x65\xCC\x81"
count = s.each_char.inject(0) do |c, char|
  c += 1 unless char=~/\p{Mn}/
  c
end

puts count #=> 1

这种方法在这种情况下是可行的,但在更健壮的解决方案中,您必须确定要排除哪些属性。

@joelparkerhenderson's answer建议使用unicode_utils gem可能是更好的选择,但我还是想完整地包含它。


我喜欢这个答案的简洁性和仅使用核心Ruby。在某些情况下,s.gsub(/\p{Mn}/,'').length不会正常工作吗? - Phrogz
@Phrogz 看起来这个方法可行,而且比我的更简洁。我猜这取决于像 gsub 这样的函数如何与 Unicode 组合标记交互,例如当前的行为是偶然发生的还是故意的,以及未来可能如何改变。我想道德就是确保你有测试。 - matt

-1

我并不是 Ruby 方面的专家,但this给出了以下内容:

def length_utf8
  count = 0
  scan(/./mu) { count += 1 }
  count
end

2
这也会为@Phrogz提供的字符串返回2 - Jordan Running

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