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