Подтвердить что ты не робот

Усечение юникода, чтобы он соответствовал максимальному размеру при кодировании для переноса

Учитывая строку Unicode и эти требования:

  • Строка кодируется в некоторый формат последовательности байтов (например, UTF-8 или JSON unicode escape)
  • Закодированная строка имеет максимальную длину

Например, для службы push push требуется JSON-кодирование с максимальным общим размером пакета 256 байтов.

Каков наилучший способ обрезания строки, чтобы она перекодировала в действительный Unicode и что она отображается правильно?

(Человеческое понимание языка не является обязательным; усеченная версия может выглядеть странно, например, для сиротского символа объединения или тайской гласной, до тех пор, пока программное обеспечение не падает при обработке данных.)

См. также:

4b9b3361

Ответ 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")