Усечение юникода, чтобы он соответствовал максимальному размеру при кодировании для переноса
Учитывая строку Unicode и эти требования:
- Строка кодируется в некоторый формат последовательности байтов (например, UTF-8 или JSON unicode escape)
- Закодированная строка имеет максимальную длину
Например, для службы push push требуется JSON-кодирование с максимальным общим размером пакета 256 байтов.
Каков наилучший способ обрезания строки, чтобы она перекодировала в действительный Unicode и что она отображается правильно?
(Человеческое понимание языка не является обязательным; усеченная версия может выглядеть странно, например, для сиротского символа объединения или тайской гласной, до тех пор, пока программное обеспечение не падает при обработке данных.)
См. также:
Ответы
Ответ 1
def unicode_truncate(s, length, encoding='utf-8'):
encoded = s.encode(encoding)[:length]
return encoded.decode(encoding, 'ignore')
Вот пример строки unicode, где каждый символ представлен 2 байтами в UTF-8:
>>> unicode_truncate(u'абвгд', 5)
u'\u0430\u0431'
Ответ 2
Один из свойств UTF-8 состоит в том, что его легко пересинхронизировать, то есть легко найти границы символов Юникода в закодированном потоке. Все, что вам нужно сделать, это вырезать закодированную строку с максимальной длиной, затем пройти назад от конца, удалив все байты, которые являются 127, - это часть или начало многобайтового символа.
Как написано сейчас, это слишком просто - стирает до последнего ASCII char, возможно, всю строку. Нам нужно проверить, нет ли урезанного двухбайтного (начало с 110yyyxx
) трехбайтного (1110yyyy
) или четырехбайтного (11110zzz
)
Реализация Python 2.6 в ясном коде. Оптимизация не должна быть проблемой - независимо
длины, мы проверяем только последние 1-4 байта.
# coding: UTF-8
def decodeok(bytestr):
try:
bytestr.decode("UTF-8")
except UnicodeDecodeError:
return False
return True
def is_first_byte(byte):
"""return if the UTF-8 @byte is the first byte of an encoded character"""
o = ord(byte)
return ((0b10111111 & o) != o)
def truncate_utf8(bytestr, maxlen):
u"""
>>> us = u"ウィキペディアにようこそ"
>>> s = us.encode("UTF-8")
>>> trunc20 = truncate_utf8(s, 20)
>>> print trunc20.decode("UTF-8")
ウィキペディ
>>> len(trunc20)
18
>>> trunc21 = truncate_utf8(s, 21)
>>> print trunc21.decode("UTF-8")
ウィキペディア
>>> len(trunc21)
21
"""
L = maxlen
for x in xrange(1, 5):
if is_first_byte(bytestr[L-x]) and not decodeok(bytestr[L-x:L]):
return bytestr[:L-x]
return bytestr[:L]
if __name__ == '__main__':
# unicode doctest hack
import sys
reload(sys)
sys.setdefaultencoding("UTF-8")
import doctest
doctest.testmod()
Ответ 3
Это будет сделано для UTF8, если вам нравится делать это в регулярном выражении.
import re
partial="\xc2\x80\xc2\x80\xc2"
re.sub("([\xf6-\xf7][\x80-\xbf]{0,2}|[\xe0-\xef][\x80-\xbf]{0,1}|[\xc0-\xdf])$","",partial)
"\xc2\x80\xc2\x80"
Его обложка от U + 0080 (2 байта) до строк U + 10FFFF (4 байта) utf8
На самом деле он прямолинейный, как алгоритм UTF8
От U + 0080 до U + 07FF Для этого потребуется 2 байта 110yyyxx 10xxxxxx
Его среднее значение, если вы видите только один байт в конце, как 110yyyxx (0b11000000 до 0b11011111)
Это [\xc0-\xdf]
, оно будет частичным.
От U + 0800 до U + FFFF требуется 3 байта 1110yyyy 10yyyyxx 10xxxxxx
Если в конце вы видите только 1 или 2 байта, он будет частичным.
Он будет соответствовать этому шаблону [\xe0-\xef][\x80-\xbf]{0,1}
От U + 10000-U + 10FFFF требуется 4 байта 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx
Если в конце вы увидите только 1 - 3 байта, это будет частичным
Он будет соответствовать этому шаблону [\xf6-\xf7][\x80-\xbf]{0,2}
Обновление:
Если вам нужен только базовый многоязычный самолет, вы можете удалить последний шаблон. Это будет сделано.
re.sub("([\xe0-\xef][\x80-\xbf]{0,1}|[\xc0-\xdf])$","",partial)
Сообщите мне, есть ли какие-либо проблемы с этим регулярным выражением.
Ответ 4
Для форматирования JSON (unicode escape, например \uabcd
) для этого я использую следующий алгоритм:
- Кодировать строку Unicode в формат обратного слэша-escape, который в конечном итоге будет в версии JSON
- Усечь на 3 байта больше, чем мой конечный предел
- Используйте регулярное выражение для обнаружения и отрубания частичного кодирования значения Unicode
Итак (в Python 2.5), с some_string
и требованием разрезать примерно до 100 байт:
# Given some_string is a long string with arbitrary Unicode data.
encoded_string = some_string.encode('unicode_escape')
partial_string = re.sub(r'([^\\])\\(u|$)[0-9a-f]{0,3}$', r'\1', encoded_string[:103])
final_string = partial_string.decode('unicode_escape')
Теперь final_string
возвращается в Юникод, но гарантированно будет помещаться в пакет JSON позже. Я усекался до 103, потому что чисто-Unicode-сообщение было бы закодировано в 102 байта.
Отказ от ответственности: проверяется только на базовом многоязычном языке. Да, я знаю.
Ответ 5
Проверьте последний символ строки. Если высокий бит установлен, тогда
это не последний байт в символе UTF-8, поэтому выполните резервное копирование и повторите попытку
пока не найдете тот, который есть.
mxlen=255
while( toolong.encode("utf8")[mxlen-1] & 0xc0 == 0xc0 ):
mxlen -= 1
truncated_string = toolong.encode("utf8")[0:mxlen].decode("utf8")