【问题标题】:Truncating unicode so it fits a maximum size when encoded for wire transfer截断 unicode,以便在为电汇编码时适合最大大小
【发布时间】:2010-12-21 00:56:56
【问题描述】:

给定一个 Unicode 字符串和这些要求:

  • 字符串被编码为某种字节序列格式(例如 UTF-8 或 JSON unicode 转义)
  • 编码后的字符串有最大长度

例如,iPhone 推送服务需要 JSON 编码,最大总数据包大小为 256 字节。

截断字符串以使其重新编码为有效的 Unicode 并合理正确显示的最佳方法是什么?

(人类语言理解不是必需的 - 截断的版本可能看起来很奇怪,例如对于孤立的组合字符或泰语元音,只要软件在处理数据时不会崩溃。)

另见:

【问题讨论】:

    标签: python json unicode truncate


    【解决方案1】:
    def unicode_truncate(s, length, encoding='utf-8'):
    encoded = s.encode(encoding)[:length]
    return encoded.decode(encoding, 'ignore')
    

    这是一个 unicode 字符串示例,其中每个字符在 UTF-8 中用 2 个字节表示:

    >>> unicode_truncate(u'абвгд', 5)
    u'\u0430\u0431'
    

    【讨论】:

    • 我真的很喜欢这个建议!代码行数很少,似乎在大多数情况下都可以使用。显然它可能会搞砸组合字符,但我明确表示这在问题中是可以的。
    • 请注意,此答案依赖于'ignore',有时会避免使用UnicodeDecodeError: 'utf-8' codec can't decode bytes in position [x-y]: unexpected end of data'ignore' 的这种使用似乎不是问题,因为它也没有用作 encode() 的 arg。
    【解决方案2】:

    UTF-8 的一个特性是它很容易重新同步,即很容易在编码的字节流中找到 unicode 字符边界。您需要做的就是以最大长度剪切编码字符串,然后从末尾向后移动,删除任何大于 127 的字节——这些是多字节字符的一部分或开头。

    正如现在写的那样,这太简单了——将擦除到最后一个 ASCII 字符,可能是整个字符串。我们需要做的是检查没有截断的二字节(以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()
    

    【讨论】:

    • 谢谢,kaizer.se。我为 JSON 的反斜杠转义格式实现了一个非常相似的算法,但很高兴知道 UTF-8 解决方案!
    • 如果您传递要序列化为 JSON 的字符串,请注意:如果字符串包含某些字符,这些字符将被转义,并且字符串的大小会增长。您不能简单地将原始 UTF-8 截断为 X 字节。 (假设字符串是 r'\\\\\\\\\\\\\\' - X \'s。当序列化为 JSON 时,大小会加倍。
    • @Thanatos:我的理解好像问题中有两种选择:序列化为 UTF-8 字节流或 JSON 对象,而不是其组合。
    • @u0b34a0f6ae 对于苹果推送服务,有效载荷长度限制为 256 字节,要计算的字节为 UTF-8 编码的 JSON。棘手的部分是您需要在 JSON 有效负载中尽可能截断字段值,以保持在 256 字节的限制之下。每个代码点 1 到 4 个字节的 UTF-8 编码选项适用于 Apple 的推送服务,但上次我使用 \uXXXX\uYYYY 代理对方法(JSON 规范的一部分)检查以编码代码点。
    【解决方案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 algorithm 一样,它真的很简单

    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 转义,例如\uabcd),我使用以下算法来实现:

      • 将 Unicode 字符串编码为反斜杠转义格式,最终将采用 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 重新采用 Unicode,但保证以后可以放入 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")
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-08-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-20
          相关资源
          最近更新 更多