{ { If this code is used commercially, please send a few bucks to } { Bill Himmelstoss, PO BOX 23246, Jacksonville, FL 32241-3246, } { Otherwise, it's freely distributable. } unit DBF; interface uses Objects, OString; type TYMDDate = record Year, Month, Day: Byte; end; PDatabase = ^TDatabase; TDatabase = object(TObject) DatabaseType: Byte; LastUpdate: TYMDDate; NumRecords: Longint; FirstRecordPos: Word; RecordLength: Word; S: TDosStream; Pathname: TOString; Modified: Boolean; Fields: TCollection; constructor Init(APathname: TOString); constructor InitCreate(APathname: TOString; AFields: PCollection); destructor Done; virtual; procedure RefreshHeader; procedure UpdateHeader; function GetRecord(RecordNum: Longint): Pointer; procedure PutRecord(RecordNum: Longint; Rec: Pointer); procedure Append(Rec: Pointer); procedure Zap; procedure RefreshFields; end; PFieldDef = ^TFieldDef; TFieldDef = object(TObject) Name: TOString; DataType: Char; Displacement: Longint; Length: Byte; Decimal: Byte; constructor Init( AName: String; ADataType: Char; ALength, ADecimal: Byte); destructor Done; virtual; constructor Load(var S: TStream); procedure Store(var S: TStream); end; implementation uses WinDos; constructor TDatabase.Init(APathname: TOString); begin inherited Init; Pathname.InitText(APathname); S.Init(Pathname.CString, stOpen); if S.Status <> stOk then Fail; Fields.Init(5, 5); RefreshHeader; end; constructor TDatabase.InitCreate(APathname: TOString; AFields: PCollection); const Terminator: Byte = $0D; var Year, Month, Day, Dummy: Word; procedure CopyField(Item: PFieldDef); far; begin Fields.Insert(Item); end; procedure WriteFieldSubrecord(Item: PFieldDef); far; begin Item^.Store(S); Inc(RecordLength, Item^.Length); end; begin inherited Init; DatabaseType := $03; GetDate(Year, Month, Day, Dummy); LastUpdate.Year := Year - 1900; LastUpdate.Month := Month; LastUpdate.Day := Day; NumRecords := 0; RecordLength := 0; Pathname.InitText(APathname); S.Init(Pathname.CString, stCreate); if S.Status <> stOk then Fail; UpdateHeader; S.Seek(32); { beginning of field subrecords } Fields.Init(AFields^.Count, 5); AFields^.ForEach(@CopyField); Fields.ForEach(@WriteFieldSubrecord); S.Write(Terminator, SizeOf(Terminator)); Modified := true; FirstRecordPos := S.GetPos; UpdateHeader; end; destructor TDatabase.Done; begin if Modified then UpdateHeader; Pathname.Done; S.Done; Fields.Done; inherited Done; end; procedure TDatabase.RefreshHeader; var OldPos: Longint; begin OldPos := S.GetPos; S.Seek(0); S.Read(DatabaseType, SizeOf(DatabaseType)); S.Read(LastUpdate, SizeOf(LastUpdate)); S.Read(NumRecords, SizeOf(NumRecords)); S.Read(FirstRecordPos, SizeOf(FirstRecordPos)); S.Read(RecordLength, SizeOf(RecordLength)); S.Seek(OldPos); RefreshFields; end; procedure TDatabase.UpdateHeader; var OldPos: Longint; Reserved: array[12..31] of Char; begin OldPos := S.GetPos; S.Seek(0); S.Write(DatabaseType, SizeOf(DatabaseType)); S.Write(LastUpdate, SizeOf(LastUpdate)); S.Write(NumRecords, SizeOf(NumRecords)); S.Write(FirstRecordPos, SizeOf(FirstRecordPos)); S.Write(RecordLength, SizeOf(RecordLength)); FillChar(Reserved, SizeOf(Reserved), #0); S.Write(Reserved, SizeOf(Reserved)); S.Seek(OldPos); end; function TDatabase.GetRecord(RecordNum: Longint): Pointer; var Temp: Pointer; Pos: Longint; begin Temp := NIL; GetMem(Temp, RecordLength); if Temp <> NIL then begin Pos := FirstRecordPos + ((RecordNum - 1) * RecordLength); if S.GetPos <> Pos then S.Seek(Pos); S.Read(Temp^, RecordLength); end; GetRecord := Temp; end; procedure TDatabase.Append(Rec: Pointer); begin if Assigned(Rec) then begin Modified := true; Inc(NumRecords); PutRecord(NumRecords, Rec); end; end; procedure TDatabase.PutRecord(RecordNum: Longint; Rec: Pointer); var Pos: Longint; begin if Assigned(Rec) and (RecordNum <= NumRecords) then begin Pos := FirstRecordPos + ((RecordNum - 1) * RecordLength); if S.GetPos <> Pos then S.Seek(Pos); S.Write(Rec^, RecordLength); end; end; procedure TDatabase.Zap; var T: TDosStream; Temp, D, N, E: TOString; F: File; begin D.Init(fsDirectory); N.Init(fsFilename); E.Init(fsExtension); FileSplit(Pathname.CString, D.CString, N.CString, E.CString); D.RecalcLength; N.RecalcLength; E.RecalcLength; Temp.InitText(D); Temp.Append(N); Temp.AppendP('.TMP'); D.Done; N.Done; E.Done; T.Init(Temp.CString, stCreate); S.Seek(0); T.CopyFrom(S, FirstRecordPos - 1); T.Done; S.Done; Assign(F, Pathname.CString); Erase(F); Assign(F, Temp.CString); Rename(F, Pathname.CString); S.Init(Pathname.CString, stOpen); NumRecords := 0; Modified := false; UpdateHeader; end; procedure TDatabase.RefreshFields; var Terminator: Byte; HoldPos: Longint; FieldDef: PFieldDef; begin S.Seek(32); { beginning of Field subrecords } repeat HoldPos := S.GetPos; S.Read(Terminator, SizeOf(Terminator)); if Terminator <> $0D then begin S.Seek(HoldPos); FieldDef := New(PFieldDef, Load(S)); Fields.Insert(FieldDef); end; until Terminator = $0D; end; constructor TFieldDef.Init( AName: String; ADataType: Char; ALength, ADecimal: Byte); begin inherited Init; Name.InitTextP(AName); DataType := ADataType; Length := ALength; Decimal := ADecimal; Displacement := 0; end; destructor TFieldDef.Done; begin Name.Done; inherited Done; end; constructor TFieldDef.Load(var S: TStream); var AName: array[1..11] of Char; Reserved: array[18..31] of Char; begin S.Read(AName, SizeOf(AName)); Name.Init(SizeOf(AName)); Name.SetText_(@AName[1], 11); S.Read(DataType, SizeOf(DataType)); S.Read(Displacement, Sizeof(Displacement)); S.Read(Length, SizeOf(Length)); S.Read(Decimal, SizeOf(Decimal)); S.Read(Reserved, SizeOf(Reserved)); end; procedure TFieldDef.Store(var S: TStream); var Reserved: array[18..31] of Char; begin S.Write(Name.CString^, 11); S.Write(DataType, SizeOf(DataType)); S.Write(Displacement, Sizeof(Displacement)); S.Write(Length, SizeOf(Length)); S.Write(Decimal, SizeOf(Decimal)); FillChar(Reserved, SizeOf(Reserved), #0); S.Write(Reserved, SizeOf(Reserved)); end; end. program DbfTest; uses dbf, wincrt, ostring, objects, strings; type PDbfTest = ^TDbfTest; TDbfTest = record Deleted: Char; { ' '=no, '*'=yes } AcctNo: array[1..16] of Char; Chunk: array[1..8] of Char; Baskard: array[1..5] of Char; Extra: array[1..8] of Char; Sandwich: array[1..25] of Char; end; var rec: PDbfTest; database: tdatabase; pathname: tostring; temp: string; fields: tcollection; procedure DoShow; procedure show(item: pfielddef); far; begin writeln( item^.name.cstring:15, ' ', item^.datatype, ' ', item^.length:10, ' ', item^.decimal:10, ' '); end; begin database.fields.foreach(@show); end; begin InitWinCrt; fields.init(5, 0); fields.insert(new(pfielddef, init('ACCTNO', 'C', 16, 0))); fields.insert(new(pfielddef, init('CHUNK', 'N', 8, 2))); fields.insert(new(pfielddef, init('BASKARD', 'C', 5, 0))); fields.insert(new(pfielddef, init('EXTRA', 'D', 8, 0))); fields.insert(new(pfielddef, init('SANDWICH', 'C', 25, 0))); pathname.inittextp('c:\dbftest.dbf'); database.initcreate(pathname, @fields); pathname.done; DoShow; New(Rec); with Rec^ do begin Acctno := '1313558000001005'; { <-will self-check, but not valid } Chunk := ' 10.00'; Baskard := 'ABCDE'; Extra := '19931125'; Sandwich := 'Turkey Leftovers '; end; database.append(rec); dispose(rec); rec := database.getrecord(1); writeln(rec^.acctno, ' ', rec^.Sandwich); dispose(rec); database.done; end.