После того как мы создали класс 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. Если обнаружите какие-либо глюки в классах, то пишите и будем исправлять.