Шаг 25 - Класс DBFFile

Дождались венца нашего творения :-) Все что мы делали до сих пор было предназначено именно для этого класса.

Но перед тем как мы приступим к его написанию надо разобраться со структурой файла DBF, т.к. до сих пор мы разобрались только со структурой задания полей записи. Вобщем все просто, как и везде :-). Заголовок также как и структура задающая поля занимает 32 байта. Содержит следующие данные:

БайтЧто содержит
00Заголовочный байт. Содержит тип DBF файла:
  • FoxBASE+/dBASE III +, без memo - 0x03
  • FoxBASE+/dBASE III +, с memo - 0x83
  • FoxPro/dBASE IV, без memo - 0x03
  • FoxPro с memo - 0xF5
  • dBASE IV с memo - 0x8B
01-03Дата последнего изменения (ГГММДД)
04-07Число записей в файле (unsigned long )
08-09Полная длина заголовка (положение первой записи)
10-11Длина одной записи с данными (+ признак удаления)
12-27Зарезервировано
28Есть ли составной индексный файл (типа .CDX)
29-31Зарезервированы

Далее сразу за заголовком следуют блоки по 32 байта описывающие типы полей записи. Они нами рассматривались раньше. После всего этого дела следует завершающий заголовок байт, значение которого равно 0x01.

После заголовка идут сами записи. Перед каждой записью резервируется один байт для признака удаления записи. Удаленные записи помечаются символом *(звездочка), а не удаленные пробелом (т.е. "пустым" текстовым значением). В конце файла после всех записей записывается байт окончания файла, значение которого 0x1A.

Таким образом мы можем составить класс DBFFile:

/////////////////////////////////////
////
///         DBFFile Class
//

class DBFFile {
	char Version;//версия DBF файла
	char ModifyDate[3]; //Дата модификации ГГММДД
	unsigned long RecordsCount; //количество записей в файле.
	unsigned int FullHeaderLen;//полная длина заголовка
		//(положение первой записи в файле)
	unsigned int RecordLen;//длина записи + признак удаления(1 байт)

	DBFRecordType *Struct;//структура записи файла

	FILE *DBF;
public:
	DBFFile();
	~DBFFile();
	int Open(char *name_);
	int ReadRecord(unsigned long Num, DBFRecord *Buf);
	int WriteRecord(unsigned long Num, DBFRecord *Buf);
	unsigned long AppendRecord(DBFRecord *Buf);
	int CheckRecord(long Num);
	DBFRecordType *GetStruct();
};

Конструктор и деструктор класса выглядят следующим образом:

DBFFile::DBFFile(){
	RecordsCount=0;
	FullHeaderLen=0;
	RecordLen=0;
	Struct=NULL;
	DBF=NULL;
};

DBFFile::~DBFFile(){
	if (DBF!=NULL) fclose(DBF);
	if (Struct!=NULL) delete Struct;
};

Процедура Open(...) открывает уже существующий файл базы данных, считывает заголовок файла и строит список структур полей записи из класса DBFRecordTypeField:

int DBFFile::Open(char *name_){
	int i;
	char type,len,declen;
	DBF=fopen(name_,"rb+");
	if (DBF==NULL) return -1;
	fread(&Version,1,1,DBF);
	fread(ModifyDate,1,3,DBF);
	fread(&RecordsCount,4,1,DBF);
	fread(&FullHeaderLen,2,1,DBF);
	fread(&RecordLen,2,1,DBF);
	fseek(DBF,32,SEEK_SET);
	i=((FullHeaderLen-1)/32)-1;
	Struct=new DBFRecordType();
	while (i>0){
		fread(dbfbuffer,11,1,DBF);
		dbfbuffer[11]=0;
		fread(&type,1,1,DBF);
		fseek(DBF,4,SEEK_CUR);
		fread(&len,1,1,DBF);
		fread(&declen,1,1,DBF);
		Struct->Add(new DBFRecordTypeField(dbfbuffer,type,len,declen));
		fseek(DBF,14,SEEK_CUR);
		i--;
	};
	return 0;
};

Думаю комментарии излишни. Все делается строго в соответствии с форматом :-))

Следующая не менее важная функция считывания записи из файла. Для работы данной процедуры требуется уже созданная динамическая переменная Buf класса DBFRecord. При создании этой переменной потребуется воспользоваться структурой записи, которую создает Open(..) на этапе открытия. Num это номер записи файла, которую требуется считать :-)

int DBFFile::ReadRecord(unsigned long Num, DBFRecord *Buf){
	char Deleted;
	if ((DBF==NULL)||(Num>RecordsCount)) return -1;

	fseek(DBF,FullHeaderLen+(RecordLen)*(Num-1),SEEK_SET);
	fread(&Deleted,1,1,DBF);
	Buf->Read(DBF);
	if (Deleted==' ') return 1; else return 0;
};

Результатом данной функции также является признак удаления этой записи. Если считываемая запись была удалена, то функция возвратит 0, если все нормально, то 1.

int DBFFile::WriteRecord(unsigned long Num, DBFRecord *Buf){
	char Deleted=' ';
	if ((DBF==NULL)||(Num>RecordsCount)) return -1;
	fseek(DBF,FullHeaderLen+(RecordLen)*(Num-1),SEEK_SET);
	fwrite(&Deleted,1,1,DBF);
	Buf->Write(DBF);
	return 0;
};

Все аналогично... При записи данных сбрасывается признак удаления, т.е. заполняется пробелом.

Для добавления в файл новой записи у нас будет служить процедура AppendRecord(...). Она также обновляет (увеличивает) значение количества записей в соответствующем поле заголовка файла.

unsigned long DBFFile::AppendRecord(DBFRecord *Buf){

	if (DBF==NULL) return -1;
	char Deleted=' ';
	RecordsCount++;

	fseek(DBF,4,SEEK_SET);
	fwrite(&RecordsCount,4,1,DBF);

	fseek(DBF,FullHeaderLen+(RecordLen)*(RecordsCount-1),SEEK_SET);
	fwrite(&Deleted,1,1,DBF);
	Buf->Write(DBF);
	Deleted=0x1A;
	fwrite(&Deleted,1,1,DBF);

	fflush(DBF);
	return RecordsCount;
};

Все видимо должно быть ясно...

Можно еще тонну всяких процедур придумать, и они у нас еще далеко не все... Пока из таких "вспомогательных" давайте сделаем функцию проверки удаления записи:

int DBFFile::CheckRecord(long Num){
	char Deleted;
	if ((DBF==NULL)||(Num>RecordsCount)) return -1;
	fseek(DBF,FullHeaderLen+(RecordLen)*(Num-1),SEEK_SET);
	fread(&Deleted,1,1,DBF);
	if (Deleted==' ') return 1; else return 0;
};

Есть последний момент. При создании переменной DBFRecord Вам как-то придется получить содержимое переменной Struct, а она у нас является приватной. Для этого давайте напишем функцию:

DBFRecordType *DBFFile::GetStruct(){
	return Struct;
};

При создании этого класса пришлось чуток изменить класс DBFRecord, а именно добавить или изменить процедуры записи и считывания данных:

void DBFRecord::Write(FILE *f_out){
	if (Content!=NULL){
		fwrite(Content,1,Length,f_out);
		fflush(f_out);
	};
};

void DBFRecord::Read(FILE *f_in){
	if (Content!=NULL)
		fread(Content,1,Length,f_in);
};

Добавьте их в прежний класс.

Пока все. Теперь уже можно работать с базами данных. Осталось лишь немногое, это обеспечение создания нового файла DBF, т.е. создание процедур и функций для построения списка Struct и записи полного заголовка в новый файл. Может сделаем позже, а можно и оставить как "хоум ворк" :-))


Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Кузин Андрей.