СЕРГЕЙ СУПРУНОВ
Реинкарнация данных II: memo-поля
В прошлом номере журнала (см. №4 за 2005 г.) мы рассмотрели несколько способов переноса данных из таблиц формата DBF в PostgreSQL. Однако чтобы не перегружать статью, вопрос работы с полями типа memo, достаточно широко используемыми в FoxPro, был оставлен без внимания. Теперь пришло время восполнить данный пробел.
Формат memo-полей
Если нужно сохранить в одном поле данные большого или неопределенного объема, в FoxPro (как и в некоторых других СУБД) используются поля специального типа – так называемые memo-поля. Данные такого поля физически сохраняются в отдельном файле (в случае с FoxPro он имеет то же имя, что и dbf-файл, но с расширением fpt). Логически мемо-поля связываются с таблицей таким образом, что доступны, как и любое другое поле текущей записи.
Файл fpt для упрощения работы с данными хранит их поблочно. Размер блока по умолчанию в FPD 2.6 составляет 64 байта. Заголовок fpt-файла имеет размер 512 байт, структура его следующая:
Таблица. Структура заголовка fpt-файла
Смещение
|
Длина поля
|
Назначение
|
0
|
4
|
Номер следующего свободного блока
|
4
|
2
|
Не используется
|
6
|
2
|
Размер блока
|
8
|
504
|
Не используется
|
Начиная с 513-го байта от начала файла, располагаются блоки данных. Значение поля может занимать один или несколько блоков. Каждый блок, являющийся первым для записи, содержит 8-байтный заголовок (4 байта сигнатура 0х00000001 и 4 байта – длина поля), после которого следуют собственно данные.
Для обеспечения связи таблицы с данными в fpt-файле в каждой записи dbf-файла memo-полю отводится 10 байт, в которые заносится номер первого блока данных. Причем номер хранится в символьном виде с ведущими пробелами. Например, для указания на блок 8 данное поле будет содержать значение « 8» (в шестнадцатеричном виде «20 20 20 20 20 20 20 20 20 38», где 38 – ASCII-код символа «8»). Нумерация блоков идет от начала файла, то есть включает и заголовок.
Например, при стандартной длине блока 64 байта записи данных будут начинаться с блока 8 (длина заголовка 512, деленная на размер блока 64).
С чем предстоит бороться
Теоретически формат DBF позволяет хранить в поле типа memo до 4 Гб данных. Однако на практике из-за внутренних ограничений FoxPro на длину строки оперировать с полями свыше 65504 символов становится проблематично. На практике длина этого поля редко превышает несколько тысяч символов. Поэтому вполне допустимо полагать, что данные из поля типа memo могут быть размещены в поле типа text таблицы PostgreSQL (максимальное значение поля – 1 Гб). Так что размеры полей проблемой считать не будем.
Сложнее то, что memo-поля могут содержать разнообразные символы, включая символ перевода строки. Поскольку при обработке текстового файла PostgreSQL воспринимает этот символ как разделитель записей, то необходимо позаботиться об его экранировании. Причем в случае формата конца строки в стиле DOS (CR+LF) экранировать нужно как символ перевода строки (0x0D), так и символ возврата каретки (0x0A).
Можно использовать формат CSV для загрузки в PostgreSQL – в этом случае значением поля будут считаться все символы, заключенные в «кавычки». «Кавычками» в данном случае может выступать любой символ, он задается как параметр команды COPY. Теперь внутри поля нужно экранировать «кавычки» (путем удвоения), однако в данном случае больше шансов найти подходящий символ, мало используемый внутри поля данных. К тому же исключаются проблемы с различными стилями перевода строки.
Реализация на FoxPro
FoxPro работает с memo-полями прозрачно, так что никаких усилий со стороны программиста не требуется. Не забудьте поставить символ «» перед символами-разделителями:
Листинг 1. Файл memo2pg.prg (FoxPro)
close databases
* Это – комментарий с начала строки
&& А так выделяются комментарии в произвольном месте строки
use wmem && Открываем таблицу с memo-полями
m.delimiter = chr(9) && символ Tab
m.txf = fcreate('memo2pg.txt')
scan
* Текстовое поле – экранируем разделители и символ «\»
m.descr = strtran(Descr, '\', '\\')
m.descr = strtran(m.descr, m.delimiter, ;
'\' + m.delimiter)
* В memo-поле дополнительно экранируем символы конца строки (ASCII-коды 10 и 13)
m.memfld = strtran(Memfld, '\', '\\')
m.memfld = strtran(m.memfld, m.delimiter, ;
'\' + m.delimiter)
m.memfld = strtran(m.memfld, chr(13), ;
'\' + chr(13))
m.memfld = strtran(m.memfld, chr(10), ;
'\' + chr(10))
* Записываем результат в файл, разделяя поля символом, хранящимся в переменной m.delimiter
=fputs(m.txf, m.descr + ;
m.delimiter + ;
m.memfld)
endscan
=fclose(m.txf)
wait window 'Finished.'
Рисунок. Рабочее окно FoxPro
Если вы решите работать с CSV-форматом, то вместо табуляции в качестве разделителя будет использоваться запятая, а каждое поле данных, независимо от типа, нужно будет окружить символом-«кавычкой», например «”», который используется по умолчанию. Ну и внутри полей все «кавычки» должны быть удвоены, чтобы исключить их специальную интерпретацию.
Реализация на Python
А вот здесь нам придется применить все накопленные выше знания по формату файлов DBF и FPT. Поскольку полностью сценарий разбора DBF-файла приводился в прошлой статье, здесь ограничимся только той частью, которая отвечает за получение данных из мемо-поля. За основу взят сценарий dbf2pg.py, рассмотренный в предыдущей статье (см. листинг 2). В него добавлена обработка полей типа «M» в цикл, обрабатывающий каждую запись (соответствующие строки выделены красным шрифтом):
Листинг 2. Фрагмент сценария dbf2pg.py, добавленные строки
.. .. ..
for i in range(num):
fld = dbf.read(fields[i].len)
# Добавлено: vvvvvvv
if fields[i].type == 'M':
fld = memo2pg(fld)
# ----------^^^^^^^
if fields[i].type == 'D':
fld = fld[:4] + '-' + fld[4:6] + '-' + fld[6:]
.. .. ..
Здесь при обнаружении поля типа memo считанное значение трактуется как содержащее номер первого блока данных, и вызывается функция разбора fpt-файла:
Листинг 3. Фрагмент сценария dbf2pg.py, функция memo2pg
def memo2pg(startblock):
# Номер блока преобразуем в число
startblock = int(startblock)
fpt = open(basetabname + '.fpt', 'rb')
fpt.read(6)
# Считываем размер блока
blocksize = int(ord(fpt.read(1)) * 256 + ord(fpt.read(1)))
# Смещаемся к началу блока данных
fpt.seek(blocksize * startblock)
fpt.read(4)
# Считываем размер поля данных
fieldsize = ord(fpt.read(1)) * 16777216 + ord(fpt.read(1)) * 65536 + ord(fpt.read(1)) * 256 + ord(fpt.read(1))
# Читаем данные
data = fpt.read(fieldsize)
fpt.close()
# Перекодировка, экранирование и проч.
data = unicode(data, 'cp866').encode('koi8-r')
data = data.replace('\\', '\\\\')
data = data.replace('\x0A', '\\' + '\x0A')
data = data.replace('\x0D', '\\' + '\x0D')
data = data.replace(delimiter, '\\' + delimiter)
return data
Наконец, нужно позаботиться, чтобы Python не обрабатывал символы перевода строки самостоятельно, для чего вместо вывода каждой сформированной строки с помощью оператора print мы будем осуществлять запись в файл, открытый в двоичном режиме, и формировать конец строки вручную так, как нам нужно:
Листинг 4. Фрагмент сценария dbf2pg.py, запись строки в файл
.. .. ..
# print line[:-1]
outfile.write(line[:-1] + '\r\n')
.. .. ..
В результате dbf2pg.py теперь может обрабатывать и memo-поля.
Заключение
Язык FoxPro предоставляет развитые средства для работы со своими файлами (выглядело бы странно, если бы это было не так), и осуществить конвертирование файла DBF в другой формат c его помощью – дело нескольких строк кода. Однако не беда, если вы не очень дружны с ним или не можете им воспользоваться по каким-то причинам. Двоичный формат DBF, как вы могли убедиться, достаточно прост и удобен. Так что при необходимости реализовать его обработку имеющимися подручными средствами труда не составит.