Ruby: ограничение строки UTF-8 по байтам
На этой странице RabbitMQ указано:
Имена очереди могут содержать до 255 байтов символов UTF-8.
В ruby (1.9.3), как бы я обрезал строку UTF-8 байтом, не разбивая середину символа? Результирующая строка должна быть самой длинной допустимой строкой UTF-8, которая соответствует предельному байту.
Ответы
Ответ 1
Я думаю, что нашел что-то, что работает.
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
Использование:
>> 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"
Обновление...
Развернутое поведение без 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"
Разложимое поведение с active_support:
>> limit_bytesize("abc\u0065\u0301d", 4)
=> "abc"
>> limit_bytesize("abc\u0065\u0301d", 5)
=> "abcé"
>> limit_bytesize("abc\u0065\u0301d", 6)
=> "abcéd"
Ответ 2
Для Rails >= 3.0 у вас есть метод ActiveSupport:: Multibyte:: Chars limit.
Из документов API:
- (Object) limit(limit)
Ограничить размер байта строки количеством байтов без нарушения символов. Используется, когда хранилище для строки ограничено по какой-либо причине.
Пример:
'こんにちは'.mb_chars.limit(7).to_s # => "こん"
Ответ 3
bytesize
даст вам длину строки в байтах while (до тех пор, пока строковая кодировка будет установлена правильно), такие как slice не будут калечить строку.
Простым процессом было бы просто перебрать строку
s.each_char.each_with_object('') do|char, result|
if result.bytesize + char.bytesize > 255
break result
else
result << char
end
end
Если вы были хитрым, вы скопировали первые 63 символа напрямую, так как любой символ юникода не более 4 байтов в utf-8.
Обратите внимание, что это все еще не идеально. Например, представьте, что последние 4 байта вашей строки являются символами "e" и сочетают острый акцент. Нарезание последних 2 байтов создает строку, которая все еще является utf8, но с точки зрения того, что видит пользователь, изменит вывод с 'é' на 'e', что может изменить смысл текста. Это, вероятно, не огромная сделка, когда вы просто называете очереди RabbitMQ, но могут быть важны в других обстоятельствах. Например, на французском языке заголовок информационного бюллетеня "Un policier tué" означает "Полицейский был убит", тогда как "Un policier tue" означает "Убийца полицейских".
Ответ 4
Как насчет этого:
s = "δogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδogδog"
count = 0
while true
more_truncate = "a" + (255-count).to_s
s2 = s.unpack(more_truncate)[0]
s2.force_encoding 'utf-8'
if s2[-1].valid_encoding?
break
else
count += 1
end
end
s2.force_encoding 'utf-8'
puts s2
Ответ 5
Rails 6 предоставит String # truncate_bytes, который ведет себя как truncate
, но принимает количество байтов вместо числа символов. И, конечно, он возвращает допустимую строку (она не вырезает вслепую в середине многобайтового символа).
Взято из документа:
>> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".size
=> 20
>> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".bytesize
=> 80
>> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".truncate_bytes(20)
=> "🔪🔪🔪🔪…"