В данной серии статей опишу создание ExtJS приложения для просмотра DBF файлов. DBF — это формат баз данных используемых в Microsoft FoxPro, некогда популярное средство разработки десктопных приложений.
Работающий пример онлайн редактора DBFможно посмотреть по этой ссылке: http://jobtools.ru/js/dbfshow/Логично предположить, что реализация делится на серверную — это PHP и клиентскую часть — javascript с использованием ExtJS. Начнем с серверной части.
Для начала необходимо разобраться с форматом файлов DBF, что он из себя представляет:
Структура файла dbf
32 | Заголовок файла DBF |
n*m | Дескрипторы полей в количестве n. m=32 |
1 | Терминальный байт CHR(13). |
m*k | Записи с данными (длиной k и количеством m) |
Заголовок файла dbf
0 | 0x00 | 1 | Сигнатура. |
1 | 0x01 | 3 | Дата последней модификации в виде ГГММДД |
4 | 0x04 | 4 | Число записей в базе |
8 | 0x08 | 2 | Полная длина заголовка (с дескрипторами полей) |
10 | 0x0A | 2 | Длина одной записи |
12 | 0x0C | 2 | Зарезервировано (всегда 0) |
14 | 0x0E | 1 | Флаг, указывающий на наличие незавершенной транзакции dBASE IV |
15 | 0x0F | 1 | Флаг шифрования таблицы dBASE IV |
16 | 0x10 | 12 | Зарезервированная область для многопользовательского использования |
28 | 0x1C | 1 | Флаг наличия индексного MDX-файла |
29 | 0x1D | 1 | Идентификатор кодовой страницы файла (dBASE IV, Visual FoxPro, XBase). |
30 | 0x1E | 2 | Зарезервировано (всегда 0) |
32 | 0x20 | 32 | Только в dBASE 7. Идентификатор языкового драйвера. |
64 | 0x40 | 4 | Только в dBASE 7. Зарезервировано |
Описание поля (колонки)
0 | 0x00 | 11 | Имя поля |
11 | 0x0B | 1 | Тип поля |
12 | 0x0C | 4 | Зарезервировано |
16 | 0x10 | 1 | Полная длина поля |
17 | 0x11 | 1 | Число десятичных разрядов; для типа C — второй байт длины поля |
30 | 0x1E | 13 | Зарезервировано (всегда 0) |
31 | 0x1F | 1 | Флаг тэга файла MDX (только в dBASE IV) |
Приступим к созданию класса DBF:
<?php class dbf { static $filename = '075.dbf'; // Имя файла static $file; //дескриптор файла static $header; //заголовок static $countColumns; //Кол-во колонок static $countRows; //Кол-во строк static $columns = array(); //Массив колонок</pre> ?>
Чтение dbf начинаем с чтения его заголовка. Смотрим на его структуру и пишем код чтения первых 32 байтов. Оформим это в отдельную процедуру и назовем её ReadHeaders:
function readHeader() { self::$file = fopen(self::$filename, 'r'); //Открыли файл $tmp = fread(self::$file,12); //Считываем первые 12 байтов - в них содержится всё что нам нужно, остальное просто не учитываем $format = 'C1BYTE1/C1YY/C1MM/C1DD/l1RECORDSCOUNT/s1HEADERSIZE/s1RECORDSIZE'; //Описываем формат этих 12 байт self::$header = unpack($format, $tmp); //Производим распаковку наших 12 байт считанных в $tmp, по формату $format self::$countColumns = (self::$header['HEADERSIZE'] - 33) / 32; //Высчитываем кол-во колонок длина заголовка (вместе с колонками) - размер заголовка/размер описания 1-ой колонки self::$countRows = (self::$header['RECORDSCOUNT']); //кол-во строк }
Чтение данных о колонках оформим процедурой ReadColumns()
function readColumns() { fseek(self::$file, 32); //переместились на 33 байт. С него начинается описание колонок $formatColumn = 'A11NAME/c1TIP/l1RESERVED1/C1SIZEBIN/C1ZPT/s1RESERVED2/C1ID/l1RESERVED3/s1RESERVED4/C1MDX/l1POS'; // описали формат колонок $pos =0; $posColumn = 0; $cnt = 0; for ($i=1;$i<=self::$countColumns;$i++) { $tmp = fread(self::$file,32); //считываем данные о колонке $column = unpack($formatColumn, $tmp); //распаковываем данные о колонке $posColumn = $pos; $pos = $pos + $column['SIZEBIN']; //Прибавляем размер еткущей колонки if ($column['TIP']!=0) // Если тип колонки не <>0 только для предохранения 8) { array_push(self::$columns, array( //Добавляем в массив колонок данные о текущей колнке 'NAME'=>trim($column['NAME']), 'TIP'=>chr($column['TIP']), 'ZPT'=>$column['ZPT'], 'SIZEBIN'=>$column['SIZEBIN'], 'MDX'=>$column['MDX'], 'POS'=>$posColumn )); $cnt++; } } self::$countColumns = $cnt; //Общее кол-во колонок }
Процедура для чтения значения ячейки в строке $row, столбце $column:
function getValue($row, $column) { if ($row>self::$header['RECORDSCOUNT']) return "ERR"; //проверка на существование строки if ($column> self::$countColumns) return "ERR"; //проверка на диапазон кол-ва столбцов fseek(self::$file, self::$header["HEADERSIZE"]+1+$row*self::$header["RECORDSIZE"]+self::$columns[$column]["POS"]); //Считываем бинарные данные указанной ячейки $buf = fread(self::$file,self::$columns[$column]["SIZEBIN"]); return $this->parseValue(self::$columns[$column]['TIP'], $buf); //Вызываем спец процедуру по форматированию считанного значения в зависимости от типа колонки }
function parseValue($tip, $buffer) { if (($tip != 'D')&&($tip != 'T')) //Если тип поля не D- дата и нет T- Datetime return iconv("WINDOWS-1251", "WINDOWS-1251",$buffer); if ($tip == 'T') { return $this->JulianDate($buffer); //return jdtogregorian((float)$buffer); } if ($tip == 'D') { $tmp .= $buffer[6]; $tmp .= $buffer[7]; $tmp .= '.'; $tmp .= $buffer[4]; $tmp .= $buffer[5]; $tmp .= '.'; $tmp .= $buffer[0]; $tmp .= $buffer[1]; $tmp .= $buffer[2]; $tmp .= $buffer[3]; return $tmp; } }
Остальные процедуры являются вспомогательными, такие как: getColumnType, getColumnSize и т.п., основной костяк мы описали. Полный исходный код файла dbf.php можно взять здесь dbf.php.
В следующей статье начнем разрабатывать клиентскую часть на js.
Привет
ссылка не работает
поделись классиком пожалуйста очень нужно
spam7379@mail.ru