Pyparsing - 如何从混合jascii/ascii文本文件中解析jascii文本?

4 投票
3 回答
768 浏览
提问于 2025-04-16 16:59

我有一个文本文件,里面混合了jascii/shift-jis和ascii文本。我正在使用pyparsing,但无法将这些字符串分割成小块。

下面是一个示例代码:

from pyparsing import *

subrange = r"[\0x%x40-\0x%x7e\0x%x80-\0x%xFC]"
shiftJisChars = u''.join(srange(subrange % (i,i,i,i)) for i in range(0x81,0x9f+1) + range(0xe0,0xfc+1))
jasciistring = Word(shiftJisChars)

jasciistring.parseString(open('shiftjis.txt').read())

我得到的结果是:

Traceback (most recent call last):
  File "test.py", line 7, in 
    jasciistring.parseString(open('shiftjis.txt').read())
  File "C:\python\lib\site-packages\pyparsing.py", line 1100, in parseString
    raise exc pyparsing.ParseException

这是文本文件的内容:

"‚s‚ˆ‚‰‚“@‚‰‚“@‚@‚“‚ˆ‚‰‚†‚”[‚Š‚‰‚“@‚“‚”‚’‚‰‚Ž‚‡B"

(没有引号)

3 个回答

0

我首先注意到的一点是,你没有把文件以二进制格式打开。我建议你使用类似 open('shiftjis.txt', 'rb') 的代码。因为你知道这个文件里有一些字符超出了普通ASCII范围,所以通常最好把文件以二进制格式打开,然后再把内容解码成Unicode。也许下面这样的代码可以工作(假设'shift-jis'是正确的编码名称):

text = open('shiftjis.txt', 'rb').read().decode('shift-jis')
jasciistring.parseString(text)

如果 parseString() 期待的是一个 str 对象(而不是 unicode 对象),那么你可以把最后一行改成用UTF-8编码 text

jasciistring.parseString(text.encode('utf-8'))

我唯一的其他建议是确认 jasciistring 是否包含正确的语法;因为你是通过十六进制范围构建它的,我认为你需要先把它当作二进制的 str 来处理,然后再解码成 unicode 对象。

0

你看到的“文本文件内容”是乱码(因为用错了编码方式来解码文件,所以显示成了乱七八糟的东西)。我猜测用了错误的编码,重新编码了文本,然后用ShiftJIS解码,结果得到了:

# coding: utf8
import codecs
s = u'‚s‚ˆ‚‰‚“@‚‰‚“@‚@‚“‚ˆ‚‰‚†‚”[‚Š‚‰‚“@‚“‚”‚’‚‰‚Ž‚‡B'
s = s.encode('cp1252').decode('shift-jis','replace')
print s

输出结果

This@is@�shift[jis@stringB

所以默认的美国Windows编码并不完全正确 :^)

你很可能只需要用shift_jis编码来读取原始文件:

import codecs
f = codecs.open('shiftjis.txt','rb','shift_jis')
data = f.read()
f.close

data将会是一个包含解码后字符的Unicode字符串。

1

当你遇到非ASCII字符或字节的问题时,直接把它们打印到控制台,然后复制粘贴到你的提问中并没有太大帮助。你看到的内容往往和实际的内容不一样。你应该使用内置的 repr() 函数(在Python 3.x中是 ascii())来尽可能清晰地展示你的数据。

这样做:

python -c "print repr(open('shiftjis.txt', 'rb').read())"

然后把结果复制粘贴到你的问题编辑中。

在等待解决方案的同时,反向分析你的数据:Windows的代码页可能是个好线索,cp1252 是最常见的。正如 @Mark Tolonen 所示,cp1252 几乎匹配,但有一个错误。进一步调查显示,其他的 cp125x 编码会产生2、3或5个错误。根据我所知,只有 cp125x 编码会把看起来像逗号的东西(实际上是 U+201A 单低9引号)映射到 shift-jis 的起始字节 \x82。我得出的结论是,问题出在 cp1252,错误是由于传输过程中损坏造成的。

另一种可能性是,原始编码不是 shift-jis,而是它的超集,微软的 cp932,这是在日本Windows上使用的。不过,问题序列 '\x82@'cp932 中也是无效的。无论如何,如果你想处理的文件来自日本Windows机器,使用 cp932 会比 shift-jis 更好。

从你的问题和代码中并不明显你想做什么,也不清楚为什么要用字节范围而不是直接把数据解码成Unicode。我不使用 pyparsing,但看起来你提供的子范围可能是格式错误的。

下面是一个使用正则表达式对输入进行分词的例子。注意,pyparsing的语法稍有不同(使用 \0xff 而不是Python的 `\xff`)。

代码:

import re, unicodedata

input_bytes = '\x82s\x82\x88\x82\x89\x82\x93@\x82\x89\x82\x93@\x82@\x82\x93\x82\x88\x82\x89\x82\x86\x82\x94[\x82\x8a\x82\x89\x82\x93@\x82\x93\x82\x94\x82\x92\x82\x89\x82\x8e\x82\x87B'

p_ascii = r'[\x00-\x7f]'
p_hw_katakana = r'[\xa1-\xdf]' # half-width Katakana
p_jis208 = r'[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc]'
p_bad = r'.' # anything else

kinds = ['jis208', 'ascii', 'hwk', 'bad']

re_matcher = re.compile("(" + ")|(".join([p_jis208, p_ascii, p_hw_katakana, p_bad]) + ")")

for mobj in re_matcher.finditer(input_bytes):
    s = mobj.group()
    us = s.decode('shift-jis', 'replace')
    print ("%-6s %-9s %-10r U+%04X %s"
        % (kinds[mobj.lastindex - 1], mobj.span(), s, ord(us), unicodedata.name(us, '<no name>'))
        )

输出:

jis208 (0, 2)    '\x82s'    U+FF34 FULLWIDTH LATIN CAPITAL LETTER T
jis208 (2, 4)    '\x82\x88' U+FF48 FULLWIDTH LATIN SMALL LETTER H
jis208 (4, 6)    '\x82\x89' U+FF49 FULLWIDTH LATIN SMALL LETTER I
jis208 (6, 8)    '\x82\x93' U+FF53 FULLWIDTH LATIN SMALL LETTER S
ascii  (8, 9)    '@'        U+0040 COMMERCIAL AT
jis208 (9, 11)   '\x82\x89' U+FF49 FULLWIDTH LATIN SMALL LETTER I
jis208 (11, 13)  '\x82\x93' U+FF53 FULLWIDTH LATIN SMALL LETTER S
ascii  (13, 14)  '@'        U+0040 COMMERCIAL AT
jis208 (14, 16)  '\x82@'    U+FFFD REPLACEMENT CHARACTER
jis208 (16, 18)  '\x82\x93' U+FF53 FULLWIDTH LATIN SMALL LETTER S
jis208 (18, 20)  '\x82\x88' U+FF48 FULLWIDTH LATIN SMALL LETTER H
jis208 (20, 22)  '\x82\x89' U+FF49 FULLWIDTH LATIN SMALL LETTER I
jis208 (22, 24)  '\x82\x86' U+FF46 FULLWIDTH LATIN SMALL LETTER F
jis208 (24, 26)  '\x82\x94' U+FF54 FULLWIDTH LATIN SMALL LETTER T
ascii  (26, 27)  '['        U+005B LEFT SQUARE BRACKET
jis208 (27, 29)  '\x82\x8a' U+FF4A FULLWIDTH LATIN SMALL LETTER J
jis208 (29, 31)  '\x82\x89' U+FF49 FULLWIDTH LATIN SMALL LETTER I
jis208 (31, 33)  '\x82\x93' U+FF53 FULLWIDTH LATIN SMALL LETTER S
ascii  (33, 34)  '@'        U+0040 COMMERCIAL AT
jis208 (34, 36)  '\x82\x93' U+FF53 FULLWIDTH LATIN SMALL LETTER S
jis208 (36, 38)  '\x82\x94' U+FF54 FULLWIDTH LATIN SMALL LETTER T
jis208 (38, 40)  '\x82\x92' U+FF52 FULLWIDTH LATIN SMALL LETTER R
jis208 (40, 42)  '\x82\x89' U+FF49 FULLWIDTH LATIN SMALL LETTER I
jis208 (42, 44)  '\x82\x8e' U+FF4E FULLWIDTH LATIN SMALL LETTER N
jis208 (44, 46)  '\x82\x87' U+FF47 FULLWIDTH LATIN SMALL LETTER G
ascii  (46, 47)  'B'        U+0042 LATIN CAPITAL LETTER B

注意1:你不需要循环连接 O(N**2) 的字符范围。

如果“jascii”只是指“全角拉丁字母(大写|小写)[A-Z]”,那么 (a) 你的范围太大了 (b) 你可以很容易地使用UNICODE字符范围而不是字节范围来做到这一点(当然,前提是先解码你的数据)。

撰写回答