处理包含多种字符编码的字符串

9 投票
5 回答
3737 浏览
提问于 2025-04-11 09:27

我不太确定该怎么问这个问题,而且我也没有找到答案,所以希望有人能帮我。

我正在写一个Python应用程序,它可以连接到一个远程主机,并接收字节数据。我使用Python内置的struct模块来解包这些数据。我的问题出在字符串上,因为它们包含多种字符编码。这里有一个这样的字符串示例:

"^L这是一个例子 ^G字符串,包含多种 ^J字符编码"

不同编码的开始和结束是用特殊的转义字符来标记的:

  • ^L - 拉丁语1
  • ^E - 中欧
  • ^T - 土耳其语
  • ^B - 波罗的海
  • ^J - 日语
  • ^C - 西里尔字母
  • ^G - 希腊语

等等……我需要一种方法把这种字符串转换成Unicode,但我真的不知道该怎么做。我读过关于Python的codecs和字符串的encode/decode,但还是没搞明白。我还要提到的是,我无法控制主机输出字符串的方式。

希望有人能帮我入门。

5 个回答

3

我会写一个编解码器,它会逐步扫描字符串,并在接收到字节时进行解码。简单来说,你需要把字符串分成一块一块的,每一块都有一致的编码,然后解码这些块,并把它们加到后面的字符串上。

7

这里有一个相对简单的例子,教你怎么做...

# -*- 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)
4

这个字符串没有现成的解码功能,因为它其实是一个自定义的编码方式。你只需要根据那些控制字符把字符串拆分开,然后再进行解码。

这里有一个(速度很慢的)示例函数,它可以处理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"是表示“换行”的控制字符,所以你的输入数据会有一些有趣的限制。)

撰写回答