-1

我必须将大量 EBCDIC 500 编码文件的大文件(最大 2GB)转换为 Latin-1。由于我只能找到 EBCDIC 到 ASCII 转换器(dd,重新编码)并且文件包含一些额外的专有字符代码,我想我会编写自己的转换器。

我有字符映射,所以我对技术方面很感兴趣。

到目前为止,这是我的方法:

# char mapping lookup table
EBCDIC_TO_LATIN1 = {
  0xC1:'41', # A
  0xC2:'42', # B
  # and so on...
}

BUFFER_SIZE = 1024 * 64
ebd_file = file(sys.argv[1], 'rb')
latin1_file = file(sys.argv[2], 'wb')

  buffer = ebd_file.read(BUFFER_SIZE)
  while buffer:
    latin1_file.write(ebd2latin1(buffer))
    buffer = ebd_file.read(BUFFER_SIZE)

ebd_file.close()
latin1_file.close()

这是进行转换的函数:

def ebd2latin1(ebcdic):

   result = []
   for ch in ebcdic:
     result.append(EBCDIC_TO_LATIN1[ord(ch)])

   return ''.join(result).decode('hex')

问题是从工程的角度来看,这是否是一种明智的方法。它有一些严重的设计问题吗?缓冲区大小是否正常?等等...

至于有些人不相信的“专有字符”:每个文件都包含一年价值的 SGML 格式的专利文件。专利局一直在使用 EBCDIC,直到 2005 年改用 Unicode。所以每个文件中有数千个文档。它们由一些不属于任何 IBM 规范的十六进制值分隔。它们是由专利局添加的。此外,在每个文件的开头都有几位 ASCII 数字告诉您文件的长度。我真的不需要这些信息,但如果我想处理文件,那么我必须处理它们。

还:

$ recode IBM500/CR-LF..Latin1 file.ebc
recode: file.ebc failed: Ambiguous output in step `CR-LF..data'

感谢你目前的帮助。

4

6 回答 6

3

EBCDIC 500,又名 Code Page 500,是 Python 编码之一,尽管您链接到 cp1047,但它没有。你用的是哪一个,真的吗?无论如何,这适用于 cp500 (或您拥有的任何其他编码)。

from __future__ import with_statement
import sys
from contextlib import nested

BUFFER_SIZE = 16384
with nested(open(sys.argv[1], 'rb'), open(sys.argv[2], 'wb')) as (infile, outfile):

    while True:
        buffer = infile.read(BUFFER_SIZE)
        if not buffer:
            break
        outfile.write(buffer.decode('cp500').encode('latin1'))

这样您就不需要自己跟踪映射。

于 2009-07-01T22:54:54.473 回答
1

如果您正确设置了表格,那么您只需要执行以下操作:

translated_chars = ebcdic.translate(EBCDIC_TO_LATIN1)

其中ebcdic包含 EBCDIC 字符,EBCDIC_TO_LATIN1是一个 256 字符的字符串,它将每个 EBCDIC 字符映射到其对应的 Latin-1 字符。中的字符EBCDIC_TO_LATIN1是实际的二进制值,而不是它们的十六进制表示。例如,如果您使用的是代码页 500,则前 16 个字节EBCDIC_TO_LATIN1将是

'\x00\x01\x02\x03\x37\x2D\x2E\x2F\x16\x05\x25\x0B\x0C\x0D\x0E\x0F'

使用这个参考

于 2009-07-01T22:36:43.170 回答
1

虽然这可能不再对原始海报有所帮助,但前段时间我发布了一个 Python 2.6+ 和 3.2+ 包,它添加了大多数西方 8 位大型机编解码器,包括 CP1047(法语)和 CP1141(德语):https://pypi .python.org/pypi/ebcdic。只需import ebcdic添加编解码器,然后用于open(..., encoding='cp1047')读取或写入文件。

于 2016-06-05T23:13:49.243 回答
0

没有水晶球,没有来自 OP 的信息,所以在 EPO 网站上翻了一下。找到可免费下载的每周专利信息文件,尽管网站上说它在 2006 年将被 utf8/XML 取代,但仍然可以在 cp500/SGML 中找到 :-)。得到了 2009 年第 27 周的文件。是一个包含 2 个文件 s350927[ab].bin 的 zip。“bin”的意思是“不是 XML”。有规格!看起来“专有代码”实际上是BINARY字段。每条记录都有一个固定的 252 字节标头。前 5 个字节是 EBCDIC 中的记录长度,例如十六进制 F0F2F2F0F8 -> 2208 字节。固定头的最后 2 个字节是后面可变部分的 BINARY 长度(冗余)。中间是几个文本字段、两个 2 字节二进制字段和一个 4 字节二进制字段。二进制字段是组内的序列号,但我看到的都是 1。

示例(来自 s350927b.bin 的最后一条记录):

Record number: 7266
pprint of header text and binary slices:
['EPB102055619         TXT00000001',
 1,
 '        20090701200927 08013627.8     EP20090528NN    ',
 1,
 1,
 '                                     T *lots of spaces snipped*']
Edited version of the rather long SGML:
<PATDOC FILE="08013627.8" CY=EP DNUM=2055619 KIND=B1 DATE=20090701 STATUS=N>
*snip*
<B541>DE<B542>Windschutzeinheit für ein Motorrad
<B541>EN<B542>Windshield unit for saddle-ride type vehicle
<B541>FR<B542>Unité pare-brise pour motocyclette</B540>
*snip*
</PATDOC>

没有头或尾记录,只有这一种记录格式。

所以:如果 OP 的年度文件是这样的,我们也许可以帮助他。

更新:上面是“我的时区凌晨 2 点”版本。这里有更多信息:

OP 说:“在每个文件的开头都有几位 ASCII 数字告诉你文件的长度。” ...将其翻译为“在每条记录的开头, EBCDIC中有五位数字可以准确地告诉您记录的长度”,我们有一个(非常模糊的)匹配!

这是文档页面的 URL:http
://docs.epoline.org/ebd/info.htm 提到的第一个文件是规范。

这是下载每周数据页面的 URL: http: //ebd2.epoline.org/jsp/ebdst35.jsp

观察:我查看的数据在 ST.35 系列中。还可以下载 ST.32,它似乎是仅包含 SGML 内容的并行版本(在“减少的 cp437/850”中,每行一个标签)。这表明ST.35记录的定长头中的字段可能不是很有趣,因此可以跳过,这将大大简化转码任务。

值得一提的是,这是我的(调查性,午夜后写的)代码:
[更新 2:稍微整理了代码;没有功能变化]

from pprint import pprint as pp
import sys
from struct import unpack

HDRSZ = 252

T = '>s' # text
H = '>H' # binary 2 bytes
I = '>I' # binary 4 bytes
hdr_defn = [
    6, T,
    38, H,
    40, T,
    94, I,
    98, H,
    100, T,
    251, H, # length of following SGML text
    HDRSZ + 1
    ]
# above positions as per spec, reduce to allow for counting from 1
for i in xrange(0, len(hdr_defn), 2):
    hdr_defn[i] -= 1

def records(fname, output_encoding='latin1', debug=False):
    xlator=''.join(chr(i).decode('cp500').encode(output_encoding, 'replace') for i in range(256))
    # print repr(xlator)
    def xlate(ebcdic):
        return ebcdic.translate(xlator)
        # return ebcdic.decode('cp500') # use this if unicode output desired
    f = open(fname, 'rb')
    recnum = -1
    while True:
        # get header
        buff = f.read(HDRSZ)
        if not buff:
            return # EOF
        recnum += 1
        if debug: print "\nrecnum", recnum
        assert len(buff) == HDRSZ
        recsz = int(xlate(buff[:5]))
        if debug: print "recsz", recsz
        # split remainder of header into text and binary pieces
        fields = []
        for i in xrange(0, len(hdr_defn) - 2, 2):
            ty = hdr_defn[i + 1]
            piece = buff[hdr_defn[i]:hdr_defn[i+2]]
            if ty == T:
                fields.append(xlate(piece))
            else:
                fields.append(unpack(ty, piece)[0])
        if debug: pp(fields)
        sgmlsz = fields.pop()
        if debug: print "sgmlsz: %d; expected: %d - %d = %d" % (sgmlsz, recsz, HDRSZ, recsz - HDRSZ)
        assert sgmlsz == recsz - HDRSZ
        # get sgml part
        sgml = f.read(sgmlsz)
        assert len(sgml) == sgmlsz
        sgml = xlate(sgml)
        if debug: print "sgml", sgml
        yield recnum, fields, sgml

if __name__ == "__main__":
    maxrecs = int(sys.argv[1]) # dumping out the last `maxrecs` records in the file
    fname = sys.argv[2]
    keep = [None] * maxrecs
    for recnum, fields, sgml in records(fname):
        # do something useful here
        keep[recnum % maxrecs] = (recnum, fields, sgml)
    keep.sort()
    for k in keep:
        if k:
            recnum, fields, sgml = k
            print
            print recnum
            pp(fields)
            print sgml
于 2009-07-02T16:07:48.183 回答
0

答案1:

还有一个愚蠢的问题:是什么让你觉得 recode 只产生 ASCII 作为输出?AFAICT 它将其任何字符集曲目转码为其任何曲目,其曲目包括 IBM cp500 和 cp1047,当然还有 latin1。阅读评论,您会注意到 Lennaert 和我发现这两个 IBM 字符集中没有任何“专有”代码。因此,一旦您确定您实际拥有什么字符集,您可能就可以使用 recode。

答案 2:

如果您确实需要/想要通过 Python 对 IBM cp1047 进行转码,您可能希望首先从权威来源获取映射,通过脚本处理它并进行一些检查:

URL = "http://source.icu-project.org/repos/icu/data/trunk/charset/data/ucm/glibc-IBM1047-2.1.2.ucm"
"""
Sample lines:
<U0000>  \x00 |0
<U0001>  \x01 |0
<U0002>  \x02 |0
<U0003>  \x03 |0
<U0004>  \x37 |0
<U0005>  \x2D |0
"""
import urllib, re
text = urllib.urlopen(URL).read()
regex = r"<U([0-9a-fA-F]{4,4})>\s+\\x([0-9a-fA-F]{2,2})\s"
results = re.findall(regex, text)
wlist = [None] * 256
for result in results:
    unum, inum = [int(x, 16) for x in result]
    assert wlist[inum] is None
    assert 0 <= unum <= 255
    wlist[inum] = chr(unum)
assert not any(x is None for x in wlist)
print repr(''.join(wlist))

然后小心地将输出复制/粘贴到您的转码脚本中,以便与 Vinay 的 buffer.translate(the_mapping) 想法一起使用,缓冲区大小可能比 16KB 大一点,当然比 2GB 小一点:-)

于 2009-07-02T03:39:20.950 回答
-2

假设 cp500 包含您所有的“其他专有字符”,这是基于 Lennart 使用codecs模块的回答的更简洁的版本:

import sys, codecs
BUFFER_SIZE = 64*1024

ebd_file = codecs.open(sys.argv[1], 'r', 'cp500')
latin1_file = codecs.open(sys.argv[2], 'w', 'latin1')

buffer = ebd_file.read(BUFFER_SIZE)
while buffer:
    latin1_file.write(buffer)
    buffer = ebd_file.read(BUFFER_SIZE)

ebd_file.close()
latin1_file.close()
于 2009-07-02T01:03:26.477 回答