Как создать кодировку GSM-7 в Python?

Набор символов GSM-7 определяется как базовая таблица сопоставления + таблица сопоставления расширенных символов (https://en.wikipedia.org/wiki/GSM_03.38#GSM_7-bit_default_alphabet_and_extension_table_of_3GPP_TS_23.038_.2F_GSM_03.38). Это означает, что u'@' должно быть отображено на b'\x00' (байтовая строка длины 1), а u'[' должно быть отображено на b'\x1b<' или b'\x1b\x3c' (байтовая строка длины 2).

Мне удалось заставить работать часть кодирования, расширив encoding_table, но я не уверен, что делать с decoding_table..?

Вот шаблон кодека для полноты:

import codecs
from encodings import normalize_encoding

class GSM7Codec(codecs.Codec):
    def encode(self, input, errors='strict'):
        return codecs.charmap_encode(input, errors, encoding_table)

    def decode(self, input, errors='strict'):
        return codecs.charmap_decode(input, errors, decoding_table)

class GSM7IncrementalEncoder(codecs.IncrementalEncoder):
    def encode(self, input, final=False):
        return codecs.charmap_encode(input, self.errors, encoding_table)[0]

class GSM7IncrementalDecoder(codecs.IncrementalDecoder):
    def decode(self, input, final=False):
        return codecs.charmap_decode(input, self.errors, decoding_table)[0]

class GSM7StreamWriter(codecs.Codec, codecs.StreamWriter): pass
class GSM7StreamReader(codecs.Codec, codecs.StreamReader): pass

_cache = {}

def search_function(encoding):
    """Register the gsm-7 encoding with Python's codecs API. This involves
       adding a search function that takes in an encoding name, and returns
       a codec for that encoding if it knows one, or None if it doesn't.
    """
    if encoding in _cache:
        return _cache[encoding]
    norm_encoding = normalize_encoding(encoding)
    if norm_encoding in ('gsm_7', 'g7', 'gsm7'):
        cinfo = codecs.CodecInfo(
            name='gsm-7',
            encode=GSM7Codec().encode,
            decode=GSM7Codec().decode,
            incrementalencoder=GSM7IncrementalEncoder,
            incrementaldecoder=GSM7IncrementalDecoder,
            streamreader=GSM7StreamReader,
            streamwriter=GSM7StreamWriter,
        )
        _cache[norm_encoding] = cinfo
        return cinfo
    return None

codecs.register(search_function)

и вот определения таблицы:

decoding_table = (
    u"@£$¥èéùìòÇ\nØø\rÅå" +
    u"Δ_ΦΓΛΩΠΨΣΘΞ\x1bÆæßÉ" +
    u" !\"#¤%&'()*+,-./" +
    u"0123456789:;<=>?" +
    u"¡ABCDEFGHIJKLMNO" +
    u"PQRSTUVWXYZÄÖÑܧ" +
    u"¿abcdefghijklmno" +
    u"pqrstuvwxyzäöñüà"
)

encoding_table = codecs.charmap_build(
    decoding_table + '\0' * (256 - len(decoding_table))
)

# extending the encoding table with extension characters
encoding_table[ord(u'|')] = '\x1b\x40'
encoding_table[ord(u'^')] = '\x1b\x14'
encoding_table[ord(u'€')] = '\x1b\x65'
encoding_table[ord(u'{')] = '\x1b\x28'
encoding_table[ord(u'}')] = '\x1b\x29'
encoding_table[ord(u'[')] = '\x1b\x3C'
encoding_table[ord(u'~')] = '\x1b\x3D'
encoding_table[ord(u']')] = '\x1b\x3E'
encoding_table[ord(u'\\')] = '\x1b\x2F'

Часть кодирования теперь работает, но декодирование не работает:

>>> u'['.encode('g7')
'\x1b<'
>>> _.decode('g7')
u'\x1b<'
>>>

Мне не удалось найти хороший источник документации о написании кодировки.


person thebjorn    schedule 12.06.2016    source источник
comment
Вы не можете использовать таблицу для декодирования здесь. Вам нужно будет создать свою собственную функцию для декодирования здесь (обработка посимвольно для поиска многобайтовых последовательностей).   -  person Martijn Pieters    schedule 12.06.2016
comment
Спасибо @MartijnPieters. Я создал ответ на основе вашего комментария.   -  person thebjorn    schedule 12.06.2016


Ответы (1)


Основываясь на комментарии @Martijn Pieters, я изменил код на:

def decode_gsm7(txt, errors):
    ext_table = {
        '\x40': u'|',
        '\x14': u'^',
        '\x65': u'€',
        '\x28': u'{',
        '\x29': u'}',
        '\x3C': u'[',
        '\x3D': u'~',
        '\x3E': u']',
        '\x2F': u'\\',
    }
    chunks = filter(None, txt.split('\x1b'))  # split on ESC
    res = u''
    for chunk in chunks:
        res += ext_table[chunk[0]]  # first character after ESC
        if len(chunk) > 1:
            # charmap_decode returns a tuple..
            decoded, _ = codecs.charmap_decode(chunk[1:], errors, decoding_table)
            res += decoded
    return res, len(txt)


class GSM7Codec(codecs.Codec):

    def encode(self, txt, errors='strict'):
        return codecs.charmap_encode(txt, errors, encoding_table)

    def decode(self, txt, errors='strict'):
        return decode_gsm7(txt, errors)

вроде работает :-)

person thebjorn    schedule 12.06.2016
comment
В GSM7Codec.encode codecs.charmap_encode вызывается с encoding_table , который никогда не был определен. - person Oluwafemi Sule; 20.10.2018
comment
Виноват! Спасибо за ваш ответ - person Oluwafemi Sule; 20.10.2018