Является ли это разумным подходом для конвертера 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()