Просмотр DBF файлов онлайн на ExtJS. Часть 1. Формат DBF

В данной серии статей опишу создание 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.

One Reply to “Просмотр DBF файлов онлайн на ExtJS. Часть 1. Формат DBF”

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *