2009-11-27 4 views
23

Dada una cadena Unicode y estos requisitos:Unicode Truncar lo que se ajusta un tamaño máximo cuando codifica para la transferencia de alambre

  • La cadena de ser codificado en algún formato de byte de secuencia (por ejemplo, UTF-8 o JSON de escape Unicode)
  • la cadena codificada tiene una longitud máxima

Por ejemplo, el servicio de empuje iPhone requiere codificación JSON con un tamaño de paquete máximo total de 256 bytes.

¿Cuál es la mejor manera de truncar la cadena para que sea válida a Unicode-re codifica y que muestra razonablemente correctamente?

(comprensión del lenguaje humano no es necesario — la versión truncada puede parecer extraño, por ejemplo para un carácter que combina huérfanos o una vocal tailandés, con tal de que el software no se cuelga al manipular los datos.)

véase también:

Respuesta

22
def unicode_truncate(s, length, encoding='utf-8'): 
    encoded = s.encode(encoding)[:length] 
    return encoded.decode(encoding, 'ignore') 

Aquí se muestra un ejemplo de cadena Unicode, donde cada personaje está representado con 2 bytes en UTF-8:

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

¡Me gusta mucho esta sugerencia! Muy pocas líneas de código y parece que funcionaría en la mayoría de los casos. Obviamente, podría arruinar la combinación de personajes, pero explícitamente dije que está bien en la pregunta. – JasonSmith

+0

Denis, me gustaría aceptar esta respuesta. Acabo de probar con la pseudo-codificación 'unicode_escape' y funciona perfectamente allí. ¿Podría editar el código y parametrizar el códec, por lo que funcionará con cualquier codificación? ¡Gracias! – JasonSmith

+0

¡muy agradable! No me arrepiento de escribir mi respuesta más larga, ya que el conocimiento específico de UTF-8 es interesante. – u0b34a0f6ae

1

Para el formato JSON (escape unicode, p. \uabcd), estoy usando el siguiente algoritmo para lograr esto:

  • codificar la cadena Unicode en el formato de barra invertida de escape de la que sería eventual en la versión JSON
  • Truncar 3 bytes más que mi último límite
  • utilizar una expresión regular para detectar y cortar una codificación parcial de un valor Unicode

So (en Python 2.5), con some_string y un requisito para cortar a alrededor de 100 bytes:

# 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') 

Ahora final_string está de vuelta en Unicode, pero garantiza para caber dentro del paquete JSON más tarde. Trunqué a 103 porque un mensaje puramente Unicode tendría 102 bytes codificados.

Descargo de responsabilidad: Solo probado en el plano multilingüe básico. Sí, sí, lo sé.

8

Una de las propiedades de UTF-8 es que es fácil de sincronizar, es encontrar los límites de los caracteres Unicode fácilmente en la corriente de bytes codificados. Todo lo que necesita hacer es cortar la cadena codificada a la máxima longitud, luego caminar hacia atrás desde el extremo eliminando los bytes que son> 127 - esos son parte de, o el comienzo de un carácter multibyte.

Como está escrito ahora, esto es demasiado simple: se borrará hasta el último carácter ASCII, posiblemente toda la cadena. Lo que tenemos que hacer es comprobar que no hay dos bytes truncada (comenzar con 110yyyxx) de tres bytes (1110yyyy) o de cuatro bytes (11110zzz)

Python 2.6 implementación en código claro. La optimización no debería ser un problema, independientemente de de longitud, solo revisamos los últimos 1-4 bytes.

# 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() 
+0

Gracias, kaizer.se. Implementé un algoritmo muy similar para el formato de escape de barra invertida de JSON, pero es genial conocer la solución UTF-8. – JasonSmith

+0

Tenga cuidado aquí si pasa la cadena que se serializará como JSON: si la cadena contiene ciertos caracteres, se escaparán y el tamaño de la cadena aumentará. No se puede simplemente truncar el UTF-8 original a X bytes. (Di la cadena fue r '\\\\\\\\\\\\\' - X \ 's Esto, cuando se serializa en JSON, se duplicaría en tamaño – Thanatos

+0

@Thanatos:.. Yo lo entendía como si no había dos alternativas en la cuestión:.! ya sea serializar como una corriente de bytes UTF-8 o como un objeto JSON, no una composición del mismo – u0b34a0f6ae

3

Esto le servirá para UTF8, si le gusta hacerlo en expresiones regulares.

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" 

su cubierta de U + 0080 (2 bytes) a U + 10FFFF (4 bytes) cadenas UTF8

Su realmente sencillo al igual que UTF8 algorithm

De U + 0080 a U + 07FF Necesitará 2 bytes 110yyyxx 10xxxxxx Es decir, si solo ve un byte al final como 110yyyxx (0b11000000 a 0b11011111) es [\xc0-\xdf], será parcial.

De U + 0800 a U + FFFF es de 3 bytes necesarios 1110yyyy 10yyyyxx 10xxxxxx Si ve sólo 1 o 2 bytes en el extremo, será parcial. a las coincidencias con este patrón [\xe0-\xef][\x80-\xbf]{0,1}

De U + 10000-U + 10FFFF es de 4 bytes necesarios 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx Si sólo 1 a 3 bytes en el extremo, que será parcial uno Se coincidirá con este patrón [\xf6-\xf7][\x80-\xbf]{0,2}

actualización:

Si sólo necesita Plano multilingüe básico, se puede eliminar la última patrón. Esto lo hara.

re.sub("([\xe0-\xef][\x80-\xbf]{0,1}|[\xc0-\xdf])$","",partial) 

Deseo saber si hay algún problema con esa expresión regular.

+0

Bastante bien.No lo he probado, pero la descripción es bastante útil. – JasonSmith

0

Verificar el último carácter de la cadena. Si está establecido el bit más alto, entonces no es el último byte de un carácter UTF-8, por lo que una copia de seguridad y vuelve a intentarlo hasta que encuentre uno que sea.

mxlen=255   
while(toolong.encode("utf8")[mxlen-1] & 0xc0 == 0xc0): 
    mxlen -= 1 

truncated_string = toolong.encode("utf8")[0:mxlen].decode("utf8") 
Cuestiones relacionadas