-1

EBCDIC 500でエンコードされたファイルの多数の大きなファイル(最大2GB)をLatin-1に変換する必要があります。EBCDICからASCIIへのコンバーター(dd、recode)しか見つからず、ファイルには追加の独自の文字コードがいくつか含まれているため、独自のコンバーターを作成すると思いました。

私はキャラクターマッピングを持っているので、技術的な側面に興味があります。

これがこれまでの私のアプローチです。

# 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形式の1年分の特許文書が含まれています。特許庁は、2005年にUnicodeに切り替えるまで、EBCDICを使用してきました。したがって、各ファイルには何千ものドキュメントが含まれています。これらは、IBM仕様の一部ではないいくつかの16進値で区切られています。それらは特許庁によって追加されました。また、各ファイルの先頭には、ファイルの長さを示す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、別名コード ページ 500 は Python エンコーディングの 1 つですが、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、各EBCDIC文字を同等のLatin-1にマップする256文字の文字列です。の文字はEBCDIC_TO_LATIN1、16進表現ではなく、実際の2進値です。たとえば、コードページ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 に答える
0

答え 1:

さらにばかげた質問: recode が出力として ASCII のみを生成するという印象を与えたのは何ですか? AFAICTは、文字セットのレパートリーのいずれかをそのレパートリーのいずれかにトランスコードし、そのレパートリーにはIBM cp500とcp1047、およびOF COURSE latin1が含まれます。コメントを読むと、Lennaert と私がこれら 2 つの 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 に答える
0

クリスタル ボールも OP からの情報もないため、EPO の Web サイトを少し調べました。自由にダウンロードできる毎週の特許情報ファイルが見つかりました。ウェブサイトでは 2006 年に utf8/XML に置き換えられると書かれていますが、cp500/SGML で引き続き利用できます :-)。2009年第27週のファイルを入手。2 つのファイル s350927[ab].bin を含む zip です。「bin」は「非 XML」を意味します。スペック取れた!「独自コード」は実際にはBINARYフィールドである可能性があります。各レコードには、固定の 252 バイトのヘッダーがあります。最初の 5 バイトは EBCDIC のレコード長です。たとえば、16 進数の F0F2F2F0F8 -> 2208 バイトです。固定ヘッダーの最後の 2 バイトは、次の可変部分の BINARY 長 (冗長) です。中央には、いくつかのテキスト フィールド、2 つの 2 バイト バイナリ フィールド、および 1 つの 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>

ヘッダーまたはトレーラー レコードはなく、この 1 つのレコード形式だけです。

つまり、OP の年次ファイルがこのようなものであれば、彼を助けることができるかもしれません。

更新:上記は「私のタイムゾーンの午前2時」バージョンです。ここにもう少し情報があります:

OP は次のように述べています。「各ファイルの先頭には、ファイルの長さを示す ASCII の数字がいくつかあります。」...それを翻訳すると、「各レコードの先頭には、レコードの長さを正確に示すEBCDICの 5桁の数字があります」と、(非常にあいまいな) 一致があります。

ドキュメンテーション ページの URL は次のとおりです。 http://docs.epoline.org/ebd/info.htm
言及されている最初のファイルは仕様です。

週次データのダウンロードページの URL は次のとおりです: http://ebd2.epoline.org/jsp/ebdst35.jsp

考察:私が見たデータはST.35シリーズです。ダウンロード可能な ST.32 もありますが、これは SGML コンテンツのみを含む並列バージョンのようです ("reduced cp437/850"、1 行に 1 つのタグ)。これは、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 に答える
-2

cp500 にすべての「追加の固有文字」が含まれていると仮定すると、codecsモジュールを使用した Lennart の回答に基づくより簡潔なバージョンです。

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 に答える