Работа со строкой, содержащей несколько кодировок символов

Я не совсем уверен, как задать этот вопрос на самом деле, и я не близок к тому, чтобы найти ответ, поэтому я надеюсь, что кто-то может мне помочь.

Я пишу приложение Python, которое подключается к удаленному хосту и получает обратно байтовые данные, которые я распаковываю с помощью встроенного модуля структуры Python. Моя проблема связана со строками, поскольку они включают несколько кодировок символов. Вот пример такой строки:

"^LЭто пример строки ^G с несколькими кодировками символов ^J"

Где начинается и заканчивается различное кодирование, помечается специальными escape-символами:

  • ^L - латиница1
  • ^ E - Центральная Европа
  • ^T - турецкий
  • ^ B - Балтика
  • ^ J - японский
  • ^C - кириллица
  • ^G - греческий

И так далее... Мне нужен способ преобразовать такую ​​строку в Unicode, но я действительно не знаю, как это сделать. Я читал о кодеках Python и string.encode/decode, но на самом деле я не мудрее. Я также должен упомянуть, что у меня нет контроля над тем, как строки выводятся хостом.

Я надеюсь, что кто-то может помочь мне с тем, как начать работу над этим.


person Alex McBride    schedule 13.10.2008    source источник
comment
Ваш синтаксический анализатор выдает какую-либо ошибку или у вас осталась допустимая строка Python, но с непригодными для использования кодировками? Если да, то все можно исправить. Приведите пример строки, пожалуйста.   -  person DzinX    schedule 13.10.2008
comment
Я имею в виду другой пример, отличный от приведенного выше, поскольку в приведенном выше примере есть только символы ASCII.   -  person DzinX    schedule 13.10.2008


Ответы (5)


Встроенных функций для декодирования такой строки нет, так как это действительно собственный кодек. Вам просто нужно разделить строку на эти управляющие символы и декодировать ее соответствующим образом.

Вот (очень медленный) пример такой функции, которая обрабатывает latin1 и shift-JIS:

latin1 = "latin-1"
japanese = "Shift-JIS"

control_l = "\x0c"
control_j = "\n"

encodingMap = {
    control_l: latin1,
    control_j: japanese}

def funkyDecode(s, initialCodec=latin1):
    output = u""
    accum = ""
    currentCodec = initialCodec
    for ch in s:
        if ch in encodingMap:
            output += accum.decode(currentCodec)
            currentCodec = encodingMap[ch]
            accum = ""
        else:
            accum += ch
    output += accum.decode(currentCodec)
    return output

Более быстрая версия может использовать str.split или регулярные выражения.

(Кроме того, как вы можете видеть в этом примере, «^J» является управляющим символом для «новой строки», поэтому ваши входные данные будут иметь некоторые интересные ограничения.)

person Glyph    schedule 13.10.2008

Вот относительно простой пример того, как это сделать...

# -*- coding: utf-8 -*-
import re

# Test Data
ENCODING_RAW_DATA = (
    ('latin_1',    'L', u'Hello'),        # Latin 1
    ('iso8859_2',  'E', u'dobrý večer'),  # Central Europe
    ('iso8859_9',  'T', u'İyi akşamlar'), # Turkish
    ('iso8859_13', 'B', u'Į sveikatą!'),  # Baltic
    ('shift_jis',  'J', u'今日は'),        # Japanese
    ('iso8859_5',  'C', u'Здравствуйте'), # Cyrillic
    ('iso8859_7',  'G', u'Γειά σου'),   # Greek
)

CODE_TO_ENCODING = dict([(chr(ord(code)-64), encoding) for encoding, code, text in ENCODING_RAW_DATA])
EXPECTED_RESULT = u''.join([line[2] for line in ENCODING_RAW_DATA])
ENCODED_DATA = ''.join([chr(ord(code)-64) + text.encode(encoding) for encoding, code, text in ENCODING_RAW_DATA])

FIND_RE = re.compile('[\x00-\x1A][^\x00-\x1A]*')

def decode_single(bytes):
    return bytes[1:].decode(CODE_TO_ENCODING[bytes[0]])

result = u''.join([decode_single(bytes) for bytes in FIND_RE.findall(ENCODED_DATA)])

assert result==EXPECTED_RESULT, u"Expected %s, but got %s" % (EXPECTED_RESULT, result)
person zellyn    schedule 13.10.2008

Я бы написал кодек, который постепенно сканировал строку и декодировал байты по мере их поступления. По сути, вам нужно будет разделить строки на куски с последовательной кодировкой, декодировать их и добавить к строкам, которые следуют за ними.

person Aaron Maenpaa    schedule 13.10.2008

Вам определенно нужно сначала разбить строку на подстроки с разными кодировками и декодировать каждую отдельно. Ради интереса обязательная "однострочная" версия:

import re

encs = {
    'L': 'latin1',
    'G': 'iso8859-7',
    ...
}

decoded = ''.join(substr[2:].decode(encs[substr[1]])
             for substr in re.findall('\^[%s][^^]*' % ''.join(encs.keys()), st))

(без проверки ошибок, а также вам нужно решить, как обрабатывать символы '^' в подстроках)

person dF.    schedule 13.10.2008
comment
Ты сделал точно такую ​​же ошибку, как и я! - person ; 13.10.2008

Я не думаю, что у вас есть какой-либо способ убедить человека, у которого есть другая машина, переключиться на юникод?

В конце концов, это одна из причин, по которой был изобретен Unicode.

person Powerlord    schedule 13.10.2008
comment
Как я уже сказал, у меня нет контроля над самим хостом. Хост на самом деле является компьютерной игрой, к которой подключается мое приложение, и я полагаю, что именно так оно обрабатывает рендеринг текста внутри себя. - person Alex McBride; 13.10.2008