Шаг 24 - Класс DBFRecord

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

/////////////////////////////////////////
////
///			DBFRecord Class
//
class DBFRecord {
	DBFRecordType *Type;
	char *Content;
	int Length;
	DBFRecordTypeField *Temp;
public:
	DBFRecord(DBFRecordType *type_);
	~DBFRecord();
	void SetField(char *name_,void *var);
	void GetField(char *name_,void *var);
	void Write(FILE *f_out);
};

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

Размер одного поля записи не может превышать 256 байт, поэтому будет лучше если мы определим глобальный буфер такого размера для работы:

#define MAX_DBF_FIELD 257

char dbfbuffer[MAX_DBF_FIELD];
char dbfpattern[10];

Сразу объявил еще одну переменную dbfpattern, она понадобится в некоторых процедурах.

Давайте разберемся с конструктором класса. Понятное дело, что для выделения памяти требуется знать структуру записи и ее размер. Эту структуру мы сообщаем в переменной type_. Это значит, что сам класс записи не будет содержать в себе структуру и будет ее брать извне, поэтому перед созданием записи нам придется создавать ее структуру.

DBFRecord::DBFRecord(DBFRecordType *type_){
	Content=(char *)malloc(Length=type_->Length());
	if (Content!=NULL){
		memset(Content,' ',Length);
	};
	Temp=NULL;
	Type=type_;
};

Обратите внимание, что пустая запись DBF файла состоит из пробелов, т.е. попросто говоря все данные хранятся в текстовом формате.

А теперь наистандартнейший деструктор:

DBFRecord::~DBFRecord(){
	if (Content!=NULL) free(Content);
};

Теперь нам остались самые интересные процедуры. Естественно это процедуры записи и считывания данных из полей записи. Первым делом запись :-)

void DBFRecord::SetField(char *name_,void *var){
	long l=0;

	if (Temp!=NULL){
		if (strcmp(Temp->Name,name_)==0) goto cont;
	};
	Temp=Type->FindField(name_);
	if (Temp==NULL) return;
cont:
	switch (Temp->Type) {
		case 'F': case 'N':{
			sprintf(dbfpattern,"%%%d.%df",Temp->TotalLen,Temp->DecimalLen);
			sprintf(dbfbuffer,dbfpattern,*(float *)var);
			memmove(Content+Temp->Position-1,
				dbfbuffer+(strlen(dbfbuffer)-Temp->TotalLen),Temp->TotalLen);
			break;
		};//case 'F'
		case 'C':{
			l=strlen((char*)var);
			memset(Content+Temp->Position-1,' ',Temp->TotalLen);
			if (Temp->TotalLen>l)
				memmove(Content+Temp->Position-1+Temp->TotalLen-l,var,l);
			else
				memmove(Content+Temp->Position-1,
				(char*)var+l-Temp->TotalLen,Temp->TotalLen);
			break;
		};//case 'C'
		case 'L':{
			if (*(char *)var==0)
				*(Content+Temp->Position-1)='F';
			else
				*(Content+Temp->Position-1)='T';
			break;
		};//case 'L'
	};//switch
};

Запись у нас осуществляется в поле с названием name_. Данные не имеют определенного типа, поэтому это указатель типа void. Процедура находит в списке типов полей Type поле с именем name_ и определяет его тип по содержимому переменной DBFRecordTypeField->Type. Далее уже записываем данные по соответствующему формату. Эта процедура пока не реализует возможность записи полей типа memo и date. Данные поля типа date хранятся в американском формате MM/DD/YY и думаю вам придется самим написать соответствующий кусок кода реализующий работу с датой в вашей конктерной операционной системе.

Теперь процедура считывания работающая по такому же принципу.

void DBFRecord::GetField(char *name_,void *var){
	long i,l;
	char *t=NULL;
	if (Temp!=NULL){
		if (strcmp(Temp->Name,name_)==0) goto cont;
	};
	Temp=Type->FindField(name_);
	if (Temp==NULL) return;
cont:
	switch (Temp->Type) {
		case 'F': case 'N':{
			memmove(dbfbuffer,Content+Temp->Position-1,Temp->TotalLen);
			dbfbuffer[Temp->TotalLen]=0;
			*(float *)var=atof(dbfbuffer);
			break;
		};//case 'F'
		case 'C':{
			t=Content+Temp->Position-1;
			l=0;
			while ((*t==' ')&&(l<Temp->TotalLen)){t++; l++;}
			i=0;
			while (l<Temp->TotalLen){
				*((char *)var+i)=*t;
				t++;l++;i++;
			};
			*((char *)var+i+1)=0;
			break;
		};//case 'C'
		case 'L':{
			if (*(Content+Temp->Position-1)=='F')
				*(char *)var=0;
			else
				*(char *)var=1;
			break;
		};//case 'L'
	};//switch
};

Тут слова можно сказать те же самые, но стоит отметить то, что при возвращении текстовой строки не производится контроль за какими-либо переполнениями, поэтому прежде чем получатьб строковый параметр выделите достаточное количество памяти, а проще всего пользоваться созданным нами ранее глобальным буфером dbfbuffer. Считав в него строку Вы далее можете ее копировать в другие переменные.

Осталась функция вывода записи. Она выводит запись целиком в файл f_out. Потом вероятно мы с ее помощью будем записывать данные в DBF файл. Процедуру считывания записи из файла придумаем позже, когда она понадобится.

void DBFRecord::Write(FILE *f_out){
	if (Content!=NULL)
		for (long i=0;i<Length;i++)
			fprintf(f_out,"%c",*(Content+i));
};

Дальше уже будем думать над классом обеспечивающим работу с самими файлами DBF. Если обнаружите какие-либо глюки в классах, то пишите и будем исправлять.


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