Является ли это разумным подходом для конвертера EBCDIC (CP500) в Latin-1?

Мне нужно преобразовать несколько больших файлов (до 2 ГБ) файлов в кодировке EBCDIC 500 в 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. Патентное ведомство использовало EBCDIC до тех пор, пока не перешло на Unicode в 2005 году. Таким образом, в каждом файле тысячи документов. Они разделены некоторыми шестнадцатеричными значениями, которые не являются частью какой-либо спецификации IBM. Они были добавлены патентным ведомством. Кроме того, в начале каждого файла в ASCII есть несколько цифр, указывающих на длину файла. Мне действительно не нужна эта информация, но если я хочу обработать файл, я должен разобраться с ними.

Также:

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

Спасибо за помощь.

6 ответов

EBCDIC 500, он же кодовая страница 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'))

Таким образом, вам не нужно следить за отображениями самостоятельно.

Хотя это может больше не помочь оригинальному постеру, некоторое время назад я выпустил пакет для Python 2.6+ и 3.2+, который добавляет большинство западных 8-битных кодеков мэйнфреймов, включая CP1047 (французский) и CP1141 (немецкий): https://pypi.python.org/pypi/ebcdic. Просто import ebcdic добавить кодеки, а затем использовать open(..., encoding='cp1047') читать или писать файлы.

Если вы правильно настроили таблицу, то вам просто нужно сделать:

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'

используя эту ссылку.

Никакого хрустального шара, никакой информации от OP, поэтому на сайте EPO был небольшой рывок. Найдены свободно загружаемые еженедельные файлы патентной информации, все еще доступные в cp500/SGML, даже несмотря на то, что на веб-сайте говорится, что это будет заменено на utf8/XML в 2006 году:-). Получил файл за неделю 2009 года. Является ли zip-файл, содержащий 2 файла s350927[ab].bin. "bin" означает "не XML". Получил спецификацию! Похоже, что "проприетарные коды" на самом деле являются двоичными полями. Каждая запись имеет фиксированный 252-байтовый заголовок. Первые 5 байтов - это длина записи в EBCDIC, например, шестнадцатеричный F0F2F2F0F8 -> 2208 байтов. Последние 2 байта фиксированного заголовка являются БИНАРНОЙ длиной (избыточной) следующей переменной части. В середине находятся несколько текстовых полей, два 2-байтовых двоичных поля и одно 4-байтовое двоичное поле. Бинарные поля - это серийные номера внутри групп, но я видел только 1. Переменная часть - SGML.

Пример (последняя запись из 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>

Там нет заголовка или трейлера, только этот формат записи.

Итак: если годовые документы ОП похожи на это, мы могли бы помочь ему.

Обновление: выше была версия "2 часа ночи в моем часовом поясе". Вот немного больше информации:

ОП сказал: "в начале каждого файла есть несколько цифр в 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

Ответ 1:

Еще один глупый вопрос: что создало у вас впечатление, что перекодирование приводит к выводу только ASCII? AFAICT он перекодирует ЛЮБОЙ из своего репертуара кодировок в ЛЮБОЙ из своего репертуара, И его репертуар включает в себя IBM cp500 и cp1047 и OF COURSE latin1. Читая комментарии, вы заметите, что мы с Леннертом обнаружили, что в этих двух наборах символов IBM нет "проприетарных" кодов. Таким образом, вы, в конце концов, сможете использовать перекодировку, когда будете уверены, что кодировка у вас есть.

Ответ 2:

Если вам действительно нужно / вы хотите перекодировать IBM cp1047 через Python, вы можете сначала получить отображение из авторитетного источника, обработав его с помощью сценария с некоторыми проверками:

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), с размером буфера, возможно, немного большим, чем 16 КБ, и, конечно, немного меньшим, чем 2 ГБ:-)

Предполагая, что cp500 содержит все ваши "дополнительные символы пропетии", более краткую версию, основанную на ответе Леннарта с использованием 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()
Другие вопросы по тегам