【问题标题】:Splitting ascii/unicode string拆分 ascii/unicode 字符串
【发布时间】:2013-04-17 17:23:59
【问题描述】:

我正在尝试使用 python 解码 ID3v2(MP3 标头)协议。需要解码的数据格式如下。

s1, s2, ... sn-1 是 unicode (utf-16/utf-8) 字符串,最后一个字符串 'sn' 可以是 unicode 或二进制字符串。

data = s1+delimiters+s2+delimiters+...+sn

其中,utf-16 的分隔符是 '\x00'+'\x00' utf-8 的分隔符是'\x00'

我得到 data 和 unicode 类型。现在我必须从data 中提取所有字符串(s1s2、...sn)。为此,我使用split(),如下所示,

#!/usr/bin/python

def extractStrings(encoding_type, data):
    if(encoding_type == "utf-8"): delimitors = '\x00'
    else: delimitors = '\x00'+'\x00'
    return data.split(delimitors)

def main():        
    # Set-1
    encoding_type = "utf-8"
    delimitors = '\x00'
    s1="Hello".encode(encoding_type)
    s2="world".encode(encoding_type)
    data = s1+delimitors+s2
    print extractStrings(encoding_type, data)

    # Set-2
    encoding_type = "utf-16"
    delimitors = '\x00'+'\x00'
    s1="Hello".encode(encoding_type)
    s2="world".encode(encoding_type)
    data = s1+delimitors+s2
    print extractStrings(encoding_type, data)

if __name__ == "__main__":
    main()

输出:

['Hello', 'world']

['\xff\xfeH\x00e\x00l\x00l\x00o', '\x00\xff\xfew\x00o\x00r\x00l\x00d\x00']

它适用于 set-1 数据,但不适用于 set-2。 因为,set-2 中的“数据”

'\xff\xfeH\x00e\x00l\x00l\x00o\x00\x00\x00\xff\xfew\x00o\x00r\x00l\x00d\x00'
                             ^               ^

在分隔符之前有一个额外的'\x00',由于字母“0”,它无法正常工作。

谁能帮我正确解码这两种情况下的“数据”?

更新:

我会尽量简单地解决这个问题。 s1 = 编码 (utf-8/utf-16) 字符串

s2 = 二进制字符串(非 unicode)

utf-16 的分隔符是'\x00'+'\x00',utf-8 的分隔符是'\x00'

数据 = (s1+分隔符)+s2

谁能帮我从“数据”中提取 s1 和 s2 吗?

Update2:解决方案

以下代码符合我的要求,

def splitNullTerminatedEncStrings(self, data, encoding_type, no_of_splits):
data_dec = data.decode(encoding_type, 'ignore')
chunks = data_dec.split('\x00', no_of_splits) 
enc_str_lst = []
for data_dec_seg in chunks[:-1]: 
    enc_str_lst.append(data_dec_seg.encode(encoding_type)) 
data_dec_chunks = '\x00'.join(chunks[:-1])   
if(data_dec_chunks): data_dec_chunks += '\x00'
data_chunks = data_dec_chunks.encode(encoding_type) 
data_chunks_len = len(data_chunks)
enc_str_lst.append(data[data_chunks_len:]) # last segment
return enc_str_lst

【问题讨论】:

  • 抱歉忘了提,最后一个字符串(sn)可能不是 unicode 字符串。在解码 APIC(专辑艺术)帧时,sn 是二进制(图像)字符串。

标签: python unicode split delimiter


【解决方案1】:

为什么不先解码字符串?

Python 2:

decoded = unicode(data, 'utf-8')
# or
decoded = unicode(data, 'utf-16')

Python 3:

decoded = str(data, 'utf-8')
# or
decoded = str(data, 'utf-16')

然后您直接使用与编码无关的数据,并且分隔符始终为单个空值。

【讨论】:

  • 抱歉忘了提,最后一个字符串(sn)可能不是 unicode 字符串。在解码 APIC(专辑艺术)帧时,sn 是二进制(图像)字符串。
【解决方案2】:

其中,utf-16 的分隔符是 '\x00'+'\x00' 而 utf-8 的分隔符是 '\x00'

不完全是。 UTF-16 的分隔符是 \0\0 仅在代码单元边界处。一个代码单元末尾的\0 和另一个代码单元开头的\0 不构成分隔符。 ID3 标准,谈论字节“同步”意味着事实并非如此,但这是错误的。

[顺便说一句:不幸的是,许多标签读取工具确实是这样理解的,结果是任何带有双零字节的序列(例如 UTF-16BE 中的 U+0100,U+0061 Āa,或者,正如您所发现的,UTF-16LE 中字符串末尾的任何 ASCII)都会破坏帧。因此,UTF-16 文本格式(UTF-16+BOM 0x01 和 UTF-16BE 0x02)完全不可靠,所有标签编写者都应避免使用。文本格式 0x00 对于纯 ASCII 以外的任何内容都不可靠。 UTF-8 是赢家!]

如果您有一个编码终止字符串列表结构,如为T 帧指定的结构(TXXX 除外),那么简单的方法是在 U+0000 上拆分之前对其进行解码终结者:

def extractStrings(encoding_type, data):
    chars = data.decode(encoding_type)
    # chars is now a Unicode string, delimiter is always character U+0000
    return chars.split(u'\0')

如果data 是一个完整的ID3 框架,恐怕你不能用一个split() 处理它。 T 系列以外的帧包含编码终止字符串、仅 ASCII 终止字符串、二进制对象(没有终止)和整数字节/字值的混合。 APIC 就是这样一种情况,但对于一般情况,您必须提前知道要解析的每一帧的结构,并逐个使用每个字段,并在进行时手动查找每个终止符。

要在 UTF-16 编码的数据中找到与代码单元对齐的终止符而不误解 Āa 等,您可以使用正则表达式,例如:

ix= re.match('((?!\0\0)..)*', data, re.DOTALL).end()
s, remainder= data[:ix], data[ix+2:]

这真的不是很有趣 - ID3v2 不是一个非常干净的格式。在我的脑海中并且未经测试,这种事情是我可能的处理方式:

p= FrameParser(data)
if frametype=='APIC':
    encoding= p.encoding()
    mimetype= p.string()
    pictype= p.number(1)
    desc= p.encodedstring()
    img= p.binary()

class FrameParser(object):
    def __init__(self, data):
        self._data= data
        self._ix= 0
        self._encoding= 0

    def encoding(self): # encoding byte - remember for later call to unicode()
        self._encoding= self.number(1)
        if not 0<=self._encoding<4:
            raise ValueError('Unknown ID3 text encoding %r' % self._encoding)
        return self._encoding

    def number(self, nbytes= 1):
        n= 0
        for i in nbytes:
            n*= 256
            n+= ord(self._data[self._ix])
            self._ix+= 1
        return n

    def binary(self): # the whole of the rest of the data, uninterpreted
        s= self._data[self._ix:]
        self._ix= len(self._data)
        return s

    def string(self): # non-encoded, maybe-terminated string
        return self._string(0)

    def encodedstring(self): # encoded, maybe-terminated string
        return self._string(self._encoding)

    def _string(self, encoding):
        if encoding in (1, 2): # UTF-16 - look for double zero byte on code unit boundary
            ix= re.match('((?!\0\0)..)*', self._data[self._ix:], re.DOTALL).end()
            s= self._data[self._ix:self._ix+ix]
            self._ix+= ix+2
        else: # single-byte encoding - look for first zero byte
            ix= self._data.find('\0', self._ix)
            s= self._data[self._ix:self._ix+ix] if ix!=-1 else self._data[self._ix:]
            self._ix= ix if ix!=-1 else len(self._data)
        return s.decode(['windows-1252', 'utf-16', 'utf-16be', 'utf-8][encoding])

【讨论】:

  • 抱歉忘了提,最后一个字符串(sn)可能不是 unicode 字符串。在解码 APIC(专辑艺术)帧时,sn 是二进制(图像)字符串。
  • APIC“图片数据”之类的二进制字符串不应通过任何文本处理代码发送 - 它们既不是潜在的 Unicode 也不是零终止的。
  • 我只想将“数据”提取(拆分)成段 s1、s2 .. sn。那我就只处理文本,分别处理二进制数据。
  • 哦...如果您在谈论完全解析 ID3 帧,恐怕它比这复杂得多。更新
【解决方案3】:

以下代码符合我的要求,

def splitNullTerminatedEncStrings(self, data, encoding_type, no_of_splits):
data_dec = data.decode(encoding_type, 'ignore')
chunks = data_dec.split('\x00', no_of_splits) 
enc_str_lst = []
for data_dec_seg in chunks[:-1]: 
    enc_str_lst.append(data_dec_seg.encode(encoding_type)) 
data_dec_chunks = '\x00'.join(chunks[:-1])   
if(data_dec_chunks): data_dec_chunks += '\x00'
data_chunks = data_dec_chunks.encode(encoding_type) 
data_chunks_len = len(data_chunks)
enc_str_lst.append(data[data_chunks_len:]) # last segment
return enc_str_lst

【讨论】:

    猜你喜欢
    • 2013-07-19
    • 2017-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-22
    相关资源
    最近更新 更多