Ruby:按字节长度限制UTF-8字符串

Kel*_*vin 11 ruby string byte utf-8 rabbitmq

这个RabbitMQ页面说明:

队列名称最多可包含255个字节的UTF-8字符.

在ruby(1.9.3)中,如何通过字节计数截断UTF-8字符串而不会破坏字符中间?结果字符串应该是符合字节限制的最长有效UTF-8字符串.

jog*_*aco 13

对于Rails> = 3.0,您有ActiveSupport :: Multibyte :: Chars限制方法.

来自API文档:

- (Object) limit(limit) 
Run Code Online (Sandbox Code Playgroud)

将字符串的字节大小限制为字节数而不会破坏字符.当字符串的存储因某种原因受限时可用.

例:

'?????'.mb_chars.limit(7).to_s # => "??"
Run Code Online (Sandbox Code Playgroud)


Fre*_*ung 10

bytesize 将以字节为单位给出字符串的长度(只要字符串的编码设置正确),像slice这样的操作不会破坏字符串.

一个简单的过程就是遍历字符串

s.each_char.each_with_object('') do|char, result| 
  if result.bytesize + char.bytesize > 255
    break result
  else
    result << char
  end
end
Run Code Online (Sandbox Code Playgroud)

如果你很狡猾,你可以直接复制前63个字符,因为任何unicode字符在utf-8中最多只有4个字节.

请注意,这仍然不完美.例如,假设你的字符串的最后4个字节是字符'e'并且结合了尖锐的重音.切片最后2个字节会产生一个仍然是utf8的字符串,但就用户看到的内容而言,会将输出从"é"更改为"e",这可能会改变文本的含义.当你只是命名RabbitMQ队列时,这可能不是什么大不了的事,但在其他情况下可能很重要.例如,在法语中,简报标题为"Unpoliciertué"的意思是"一名警察被杀"而"Un policier tue"的意思是"警察杀人".

  • +1仅为警察示例:).Google翻译确认了这一点.发音听起来不同. (4认同)

Kel*_*vin 5

我想我找到了一些有用的东西。

def limit_bytesize(str, size)
  str.encoding.name == 'UTF-8' or raise ArgumentError, "str must have UTF-8 encoding"

  # Change to canonical unicode form (compose any decomposed characters).
  # Works only if you're using active_support
  str = str.mb_chars.compose.to_s if str.respond_to?(:mb_chars)

  # Start with a string of the correct byte size, but
  # with a possibly incomplete char at the end.
  new_str = str.byteslice(0, size)

  # We need to force_encoding from utf-8 to utf-8 so ruby will re-validate
  # (idea from halfelf).
  until new_str[-1].force_encoding('utf-8').valid_encoding?
    # remove the invalid char
    new_str = new_str.slice(0..-2)
  end
  new_str
end
Run Code Online (Sandbox Code Playgroud)

用法:

>> limit_bytesize("abc\u2014d", 4)
=> "abc"
>> limit_bytesize("abc\u2014d", 5)
=> "abc"
>> limit_bytesize("abc\u2014d", 6)
=> "abc—"
>> limit_bytesize("abc\u2014d", 7)
=> "abc—d"
Run Code Online (Sandbox Code Playgroud)

更新...

没有 active_support 的分解行为:

>> limit_bytesize("abc\u0065\u0301d", 4)
=> "abce"
>> limit_bytesize("abc\u0065\u0301d", 5)
=> "abce"
>> limit_bytesize("abc\u0065\u0301d", 6)
=> "abcé"
>> limit_bytesize("abc\u0065\u0301d", 7)
=> "abcéd"
Run Code Online (Sandbox Code Playgroud)

使用 active_support 分解行为:

>> limit_bytesize("abc\u0065\u0301d", 4)
=> "abc"
>> limit_bytesize("abc\u0065\u0301d", 5)
=> "abcé"
>> limit_bytesize("abc\u0065\u0301d", 6)
=> "abcéd"
Run Code Online (Sandbox Code Playgroud)


aki*_*kim 5

Rails 6 将提供一个String#truncate_bytes,其行为类似于truncate,但采用字节计数而不是字符计数。当然,它返回一个有效的字符串(它不会在多字节字符的中间盲目剪切)。

\n\n

摘自文档:

\n\n
>> "".size\n=> 20\n>> "".bytesize\n=> 80\n>> "".truncate_bytes(20)\n=> "\xe2\x80\xa6"\n
Run Code Online (Sandbox Code Playgroud)\n