www.pudn.com > oicqspysrc.zip > dbf_c.pas


unit DBF_C; 
(* =========================================================================== 
 * dbf.dcu - tDBF : A custom data set which uses a flat binary 
 *             structured datafile for single client usage only. 
 * 
 * Author:  Horacio Jamilis 
 * Copyright (C) 1998, Terabyte Computacion 
 * 
 * =========================================================================== 
 * v 0.91 
 *   C++ Builder version 
 * - Fixed error on deleting records 
 * - Added filtering capabilities (work wrong when there are no records within 
 *   the filter expresion - Only support expresion with one field like 
 *   "NUMFIELD>10" or "TEXTFIELD<='TEST'" or "DATEFIELD=19980626" 
 *   (in yyyymmdd format)) 
 *   the OnFilterRecord event does not work yet. 
 * Especial thanks to Michael Beauregard (Michael_Beauregard@mck.com). 
 * =========================================================================== 
 *) 
 
interface 
 
uses 
  SysUtils, Classes, Db, DsgnIntf; 
 
type 
  TFilenameProperty = class(TStringProperty) 
  public 
    procedure Edit; override; 
    function GetAttributes: TPropertyAttributes; override; 
  end; 
 
  EDBFError = class (Exception); 
 
  pDateTime = ^TDateTime; 
  pBoolean = ^Boolean; 
  pInteger = ^Integer; 
 
  PRecInfo = ^TRecInfo; 
  TRecInfo = record 
    Bookmark: Longint; 
    BookmarkFlag: TBookmarkFlag; 
  end; 
 
  TdbfHeader = record  { Dbase III + header definition        } 
     VersionNumber    :byte;  { version number (03h or 83h ) } 
     LastUpdateYear   :byte;  { last update YY MM DD         } 
     LastUpdateMonth  :byte; 
     LastUpdateDay    :byte; 
     NumberOfRecords  :longint; { number of record in database } 
     BytesInHeader    :smallint;{ number of bytes in header } 
     BytesInRecords   :smallint;{ number of bytes in records } 
     ReservedInHeader :array[1..20] of char;   { reserved bytes in header } 
  end; 
 
  TdbfField = record 
     FieldName   :array[1..11] of char; { Name of this record             } 
     FieldType   :char;           { type of record - C,N,D,L,etc.         } 
     fld_addr    :longint;        { not used } 
     Width       :byte;           { total field width of this record      } 
     Decimals    :byte;           { number of digits to right of decimal  } 
     MultiUser   :smallint;       { reserved for multi user } 
     WorkAreaID  :byte;           { Work area ID } 
     MUser       :smallint;       { reserved for multi_user } 
     SetFields   :byte;           { SET_FIELDS flag } 
     Reserved    :array[1..4] of byte;      { 8 bytes reserved } 
  end;                           { record starts                         } 
 
Type 
  pRecordHeader = ^tRecordHeader; 
  tRecordHeader = record 
    DeletedFlag : char; 
  end; 
 
type 
  TDBF = class(TDataSet) 
  protected 
    FStream: TStream; // the physical table 
    FTableName: string; // table path and file name 
    fDBFHeader : TdbfHeader; 
    // record data 
    fRecordHeaderSize : Integer;   // The size of the record header 
    FRecordCount,                  // current number of record 
    FRecordSize,                   // the size of the actual data 
    FRecordBufferSize,             // data + housekeeping (TRecInfo) 
    FRecordInfoOffset,             // offset of RecInfo in record buffer 
    FCurrentRecord,                // current record (0 to FRecordCount - 1) 
    BofCrack,                      // before the first record (crack) 
    EofCrack: Integer;             // after the last record (crack) 
    FIsTableOpen: Boolean;         // status 
    FFileWidth,                    // field widths in record 
    FFileDecimals,                 // field decimals in record 
    FFileOffset: TList;            // field offsets in record 
    fReadOnly : Boolean;           // Enhancements 
    fStartData : Integer;          // Position in file where data starts 
    function FFieldType(F : char):TFieldType; 
    function FFieldSize(FType:char;FWidth:integer):integer; 
  protected 
    // TDataSet virtual abstract method 
    function AllocRecordBuffer: PChar; override; 
    procedure FreeRecordBuffer(var Buffer: PChar); override; 
    procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override; 
    function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override; 
    function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override; 
    function GetRecordSize: Word; override; 
    procedure InternalAddRecord(Buffer: Pointer; Append: Boolean); override; 
    procedure InternalClose; override; 
    procedure InternalDelete; override; 
    procedure InternalFirst; override; 
    procedure InternalGotoBookmark(Bookmark: Pointer); override; 
    procedure InternalHandleException; override; 
    procedure InternalInitFieldDefs; override; 
    procedure InternalInitRecord(Buffer: PChar); override; 
    procedure InternalLast; override; 
    procedure InternalOpen; override; 
    procedure InternalPost; override; 
    procedure InternalSetToRecord(Buffer: PChar); override; 
    function IsCursorOpen: Boolean; override; 
    procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override; 
    procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override; 
    procedure SetFieldData(Field: TField; Buffer: Pointer); override; 
    // TDataSet virtual method (optional) 
    function GetRecordCount: Integer; override; 
    procedure SetRecNo(Value: Integer); override; 
    function GetRecNo: Integer; override; 
    Procedure WriteHeader; 
  private 
    Procedure _ReadRecord(Buffer:PChar;IntRecNum:Integer); 
    Procedure _WriteRecord(Buffer:PChar;IntRecNum:Integer); 
    Procedure _AppendRecord(Buffer:PChar); 
    Procedure _SwapRecords(Rec1,REc2:Integer); 
    Function _CompareRecords(SortFields:Array of String;Rec1,Rec2:Integer):Integer; 
    Function _ProcessFilter(Buffer:PChar):boolean; 
  public 
    constructor Create(AOwner:tComponent); override; 
    procedure CreateTable; 
    Procedure PackTable; 
    Procedure SortTable(SortFields : Array of String); 
    Procedure UnsortTable; 
    function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override; 
  published 
    property TableName: string read FTableName write FTableName; 
    property ReadOnly : Boolean read fReadOnly write fReadonly default False; 
    property DBFHeader : tDBFHeader read fDBFHeader; 
    // redeclared data set properties 
    property Active; 
    property Filter; 
    property Filtered; 
    property BeforeOpen; 
    property AfterOpen; 
    property BeforeClose; 
    property AfterClose; 
    property BeforeInsert; 
    property AfterInsert; 
    property BeforeEdit; 
    property AfterEdit; 
    property BeforePost; 
    property AfterPost; 
    property BeforeCancel; 
    property AfterCancel; 
    property BeforeDelete; 
    property AfterDelete; 
    property BeforeScroll; 
    property AfterScroll; 
    property OnCalcFields; 
    property OnDeleteError; 
    property OnEditError; 
    property OnNewRecord; 
    property OnPostError; 
  end; 
 
procedure Register; 
 
implementation 
 
uses 
  TypInfo, Dialogs, Windows, Forms, Controls, IniFiles; 
 
Const 
  dfhVersionNumber = 13; 
 
TYPE 
  PBufArray = ^BufArray; 
  BufArray = Array[0..0] of Char; 
 
// **************************************************************************** 
// Low Level Routines for accessing an internal record 
 
// ____________________________________________________________________________ 
// TDBF._ReadRecord 
Procedure TDBF._ReadRecord(Buffer:PChar;IntRecNum:Integer); 
  {-Read a record based on the internal record number (absolute)} 
BEGIN 
  FStream.Position := FStartData + (FRecordSize * IntRecNum); 
 try 
  FStream.ReadBuffer(Buffer^, FRecordSize); 
 except 
 end; 
END; 
 
// ____________________________________________________________________________ 
// TDBF._WriteRecord 
Procedure TDBF._WriteRecord(Buffer:PChar;IntRecNum:Integer); 
  {-Write a record based on the internal record number (absolute)} 
BEGIN 
  FStream.Position := FStartData + (FRecordSize * IntRecNum); 
  FStream.WriteBuffer (Buffer^, FRecordSize); 
END; 
 
// ____________________________________________________________________________ 
// TDBF._AppendRecord 
Procedure TDBF._AppendRecord(Buffer:PChar); 
BEGIN 
  FStream.Position := FStartData + (FRecordSize * (FRecordCount{+FDeletedCount})); 
  FStream.WriteBuffer (Buffer^, FRecordSize); 
END; 
 
///////////////////////////////////////////////// 
////// Part I: 
////// Initialization, opening, and closing 
///////////////////////////////////////////////// 
 
// ____________________________________________________________________________ 
// TDBF.InternalOpen 
// I: open the table/file 
procedure TDBF.InternalOpen; 
var 
  Field : TField; 
  i,j : integer; 
  d : string; 
begin 
  // check if the file exists 
  if not FileExists (FTableName) then 
    raise eDBFError.Create ('Open: Table file not found'); 
 
  // create a stream for the file 
  if fReadOnly then 
    fStream := tFileStream.Create( fTableName, fmOpenRead + fmShareDenyWrite) 
  else 
    FStream := TFileStream.Create (FTableName, fmOpenReadWrite + fmShareExclusive); 
  fStream.ReadBuffer(fDBFHeader,SizeOf(TDBFHeader)); 
 
  // sets cracks and record position 
  BofCrack := -1; 
  EofCrack := fRecordCount{+fDeletedCount}; 
  FCurrentRecord := BofCrack; 
 
  // set the bookmark size 
  BookmarkSize := sizeOf (Integer); 
 
  if not (assigned(FFileOffset)) then 
    FFileOffset := TList.Create; 
  if not (assigned(FFileWidth)) then 
    FFileWidth := TList.Create; 
  if not (assigned(FFileDecimals)) then 
    FFileDecimals := TList.Create; 
 
  // initialize the field definitions 
  // (another virtual abstract method of TDataSet) 
  InternalInitFieldDefs; 
 
  FRecordInfoOffset := FRecordSize; 
  FRecordBufferSize := FRecordSize + sizeof (TRecInfo); 
 
  // if there are no persistent field objects, 
  // create the fields dynamically 
  if DefaultFields then 
    CreateFields; 
  // connect the TField objects with the actual fields 
  BindFields (True); 
 
  for i := 0 to FieldCount-1 do 
    begin 
      Field := Fields[i]; 
      if (Field.DataType = ftFloat) and (Integer(FFileDecimals[i])>0) then 
        begin 
          d := '0.'; 
          for j := 1 to Integer(FFileDecimals[i]) do 
            d := d + '0'; 
          (Field as TFloatField).DisplayFormat := d; 
        end; 
    end; 
 
  // get the number of records and check size 
  fRecordCount := fDBFHeader.NumberOfRecords; 
 
  // everything OK: table is now open 
  FIsTableOpen := True; 
 
  // ShowMessage ('InternalOpen: RecCount: ' + IntToStr (FRecordCount)); 
end; 
 
// Returns the Type of the field 
function TDBF.FFieldType(F : char):TFieldType; 
begin 
  if F = 'C' then 
    FFieldType := ftString 
  else if (F = 'N') or (F = 'F') then 
    FFieldType := ftFloat 
  else if F = 'L' then 
    FFieldType := ftBoolean 
  else if F = 'D' then 
    FFieldType := ftDate 
//    FFieldType := ftString 
  else 
    FFieldType := ftUnknown; 
end; 
 
function TDBF.FFieldSize(FType:char;FWidth:integer):integer; 
begin 
  if FType = 'C' then 
    FFieldSize := FWidth 
  else if (FType = 'N') or (FType = 'F') then 
    FFieldSize := 0 
  else if FType = 'L' then 
    FFieldSize := 0 
  else if FType = 'D' then 
    FFieldSize := 0 
//    FFieldSize := 8 
  else 
    FFieldSize := 0; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.InternalInitFieldDefs 
// I: define the fields 
procedure TDBF.InternalInitFieldDefs; 
var 
  Il : Integer; 
  TmpFileOffset : Integer; 
  NumberOfFields : integer; 
  Fld : TDBFField; 
  FldName : PChar; 
  NewFieldDef : TFieldDef; 
begin 
  FieldDefs.Clear; 
  FStream.Seek(SizeOf(TDbfHeader),soFromBeginning); 
  NumberOfFields := ((fDbfHeader.BytesInHeader-sizeof(DbfHeader))div 32); 
  if not (assigned(FFileOffset)) then 
    FFileOffset := TList.Create; 
  FFileOffset.Clear; 
  if not (assigned(FFileWidth)) then 
    FFileWidth := TList.Create; 
  FFileWidth.Clear; 
  if not (assigned(FFileDecimals)) then 
    FFileDecimals := TList.Create; 
  FFileDecimals.Clear; 
  TmpFileOffset := 0; 
  if (NumberOfFields>0) then 
    begin 
      for Il:=0 to NumberOfFields-1 do 
        begin 
          FStream.Read(Fld,SizeOf(Fld)); 
          GetMem(FldName,Length(Fld.FieldName)+1); 
          CharToOem(PChar(@Fld.FieldName),FldName); 
          NewFieldDef := TFieldDef.Create(FieldDefs); 
          NewFieldDef.DataType := FFieldType(Fld.FieldType); 
          NewFieldDef.Size := FFieldSize(Fld.FieldType,Fld.Width); 
          NewFieldDef.Name := FldName; 
          NewFieldDef.FieldNo := Il+1; 
          NewFieldDef.Required := FALSE; 
          FreeMem(FldName); 
          FFileOffset.Add(Pointer(TmpFileOffset)); 
          FFileWidth.Add(Pointer(Fld.Width)); 
          FFileDecimals.Add(Pointer(Fld.Decimals)); 
          Inc(tmpFileOffset,Fld.Width); 
        end; 
      fRecordSize := tmpFileOffset+FrecordHeaderSize; 
      FStartData := FStream.Position+1; 
    end; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.InternalClose 
// I: close the table/file 
procedure TDBF.InternalClose; 
begin 
  // if required, save updated header 
  if (fDBFHeader.NumberOfRecords <> fRecordCount) or 
    (fDBFHeader.BytesInRecords = 0) then 
    BEGIN 
      fDBFHeader.BytesInRecords := fRecordSize; 
      fDBFHeader.NumberOfRecords := fRecordCount; 
      WriteHeader; 
    END; 
 
  // disconnet field objects 
  BindFields(False); 
  // destroy field object (if not persistent) 
  if DefaultFields then 
    DestroyFields; 
 
  // free the internal list field offsets 
  if Assigned(FFileOffset) then 
    FFileOffset.Free; 
  FFileOffset := nil; 
  if Assigned(FFileWidth) then 
    FFileWidth.Free; 
  FFileWidth := nil; 
  if Assigned(FFileDecimals) then 
    FFileDecimals.Free; 
  FFileDecimals := nil; 
  FCurrentRecord := -1; 
 
  // close the file 
  FIsTableOpen := False; 
  FStream.Free; 
  FStream := nil; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.IsCursorOpen 
// I: is table open 
function TDBF.IsCursorOpen: Boolean; 
begin 
  Result := FIsTableOpen; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.WriteHeader 
procedure TDBF.WriteHeader; 
begin 
//  Assert(FStream<>nil,'fStream=Nil'); 
  if fStream <> nil then 
    begin 
      FSTream.Seek(0,soFromBeginning); 
      FStream.WriteBuffer(fDBFHeader,SizeOf(TDbfHeader)); 
    end; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.Create 
constructor TDBF.Create(AOwner:tComponent); 
BEGIN 
  inherited create(aOwner); 
  fRecordHeaderSize := SizeOf(tRecordHeader); 
END; 
 
// ____________________________________________________________________________ 
// TDBF.CreateTable 
// I: Create a new table/file 
procedure TDBF.CreateTable; 
var 
  Ix : Integer; 
//  DescribF : TBDescribField; 
  Offs : Integer; 
  Fld : TDbfField; 
  FldName : PChar; 
  i : integer; 
begin 
  CheckInactive; 
  //  InternalInitFieldDefs; 
  // create the new file 
  if FileExists (FTableName) and 
    (MessageDlg ('File ' + FTableName + 
      ' already exists. OK to override?', 
      mtConfirmation, [mbYes, mbNo], 0) = mrNo) then 
    Exit; 
  if FieldDefs.Count = 0 then 
  begin 
    for Ix := 0 to FieldCount - 1 do 
    begin 
      with Fields[Ix] do 
      begin 
        if FieldKind = fkData then 
          FieldDefs.Add(FieldName,DataType,Size,Required); 
      end; 
    end; 
  end; 
  FStream := TFileStream.Create (FTableName, 
    fmCreate or fmShareExclusive); 
  try 
    FillChar(fDBFHeader,SizeOf(TDbfHeader),0); 
    fDBFHeader.BytesInRecords := 0; // filled later 
    fDBFHeader.NumberOfRecords := 0; // empty 
    WriteHeader; 
    Offs:=0; 
    for Ix:=0 to FieldDefs.Count-1 do 
      begin 
        with FieldDefs.Items[Ix] do 
        begin 
          FillChar(Fld,SizeOf(TDbfField),#0); 
          Fld.FieldType := 'C'; 
          Fld.Width := Size; 
          GetMem(FldName,SizeOf(FieldDefs.Items[Ix].Name)); 
          OemToChar(PChar(FieldDefs.Items[Ix].Name),FldName); 
          for i := 1 to Length(FldName) do 
            Fld.FieldName[i] := FldName[i]; 
          Fld.FieldName[Length(FldName)+1] := #0; 
          FreeMem(FldName); 
          Inc(Offs,Fld.Width); 
          FStream.Write(Fld,SizeOf(TDbfField)); 
        end; 
      end; 
    fStartData := FStream.Position; 
    fDBFHeader.BytesInRecords := Offs; 
    FRecordSize := Offs+FRecordHeaderSize; 
    WriteHeader; 
  finally 
    // close the file 
    fStream.Free; 
    fStream := nil; 
  end; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.PackTable 
//Enhancement: Remove all deleted items from the table. 
Procedure TDBF.PackTable; 
var 
  NewStream, OldStream : tStream; 
  PC : PChar; 
  Ix : Integer; 
//  DescribF : TBDescribField; 
  NewDataFileHeader : tDBFHeader; 
  DataBuffer : Pointer; 
  NumberOfFields : integer; 
  Fld : TDBFField; 
BEGIN 
  OldStream := Nil; 
  NewStream := Nil; 
  CheckInactive; 
//  if Active then 
//    raise eBinaryDataSetError.Create ('Dataset must be closed before packing.'); 
  if fTableName = '' then 
    raise EDBFError.Create('Table name not specified.'); 
  if not FileExists (FTableName) then 
    raise EDBFError.Create('Table '+fTableName+' does not exist.'); 
  PC := @fTablename[1]; 
  CopyFile(PChar(PC),PChar(PC+',old'+#0),False); 
  // create the new file 
  if FieldDefs.Count = 0 then 
  begin 
    for Ix := 0 to FieldCount - 1 do 
    begin 
      with Fields[Ix] do 
      begin 
        if FieldKind = fkData then 
          FieldDefs.Add(FieldName,DataType,Size,Required); 
      end; 
    end; 
  end; 
  TRY 
    NewStream := TFileStream.Create (FTableName+',new', 
      fmCreate or fmShareExclusive); 
    OldStream := tFileStream.Create (fTableName+',old', 
      fmOpenRead or fmShareExclusive); 
    OldStream.ReadBuffer(NewDataFileHeader,SizeOf(TDbfHeader)); 
    NewStream.WriteBuffer(NewDataFileHeader,SizeOf(TDbfHeader)); 
    NumberOfFields := ((NewDataFileHeader.BytesInHeader-sizeof(TDbfHeader))div 32); 
    for IX := 0 to NumberOfFields do 
      BEGIN 
        OldStream.Read(Fld,SizeOf(TDbfField)); 
        NewStream.Write(Fld,SizeOf(TDbfField)); 
      END; 
    GetMem(DataBuffer,NewDataFileHeader.BytesInRecords); 
    REPEAT 
      IX := OldStream.Read(DataBuffer^,NewDataFileHeader.BytesInRecords); 
      if (IX = NewDataFileHeader.BytesInRecords) and (pRecordHeader(DataBuffer)^.DeletedFlag <> '*') then 
        NewStream.WRite(DataBuffer^,NewDataFileHeader.BytesInRecords); 
    Until IX <> NewDataFileHeader.BytesInRecords; 
    FreeMem(DataBuffer,NewDataFileHeader.BytesInRecords); 
  finally 
    // close the file 
    NewStream.Free; 
    OldStream.Free; 
  end; 
  CopyFile(PChar(PC+',new'+#0),PChar(PC),False); 
  DeleteFile(Pchar(PC+',new'+#0)); 
  DeleteFile(Pchar(PC+',old'+#0)); 
END; 
 
// ____________________________________________________________________________ 
// TDBF._SwapRecords 
// Enhancement: Quick swap of two records.  Used primarily for sorting. 
Procedure TDBF._SwapRecords(Rec1,REc2:Integer); 
VAR 
  Buffer1, Buffer2 : PChar; 
  Bookmark1, BOokmark2 : TBookmarkFlag; 
BEGIN 
  Rec1 := Rec1 - 1; 
  Rec2 := Rec2 - 1; 
  if Rec1 < 0 then Exit; 
  if Rec2 < 0 then Exit; 
  Buffer1 := AllocRecordBuffer; 
  Buffer2 := AllocRecordBuffer; 
  _ReadRecord(Buffer1,Rec1); 
  _ReadRecord(Buffer2,Rec2); 
  Bookmark1 := GetBookmarkFlag(Buffer1); 
  Bookmark2 := GetBookmarkFlag(Buffer2); 
  SetBookmarkFlag(Buffer1,Bookmark2); 
  SetBookmarkFlag(Buffer2,Bookmark1); 
  _WriteRecord(Buffer1,Rec2); 
  _WriteRecord(Buffer2,Rec1); 
  StrDispose(Buffer1); 
  StrDispose(Buffer2); 
END; 
 
// ____________________________________________________________________________ 
// TDBF._CompareRecords 
// Compare two records.  Returns -1 if REC1 < REC2, 0 if REC1 = REC2, or 
// 1 if REC1 > REC2. 
Function TDBF._CompareRecords(SortFields:Array of String;Rec1,Rec2:Integer):Integer; FAR; 
{-Compare the records Rec1, Rec2 and return -1 if Rec1 < Rec2, 0 if Rec1 = Rec2, 
  1 if Rec1 > Rec2 } 
VAR 
  IX : Integer; 
 
  Function CompareHelper(KeyId:String;Rec1,Rec2:Integer):Integer; 
  VAR 
    SKey1, SKey2 : String; 
    IKey1, IKey2 : Integer; 
    fKey1, fKey2 : Double; 
    dKey1, dKey2 : tDateTime; 
    CompareType : tFieldType; 
    KeyField : tField; 
  BEGIN 
    KeyField := FieldByName(KeyID); 
    CompareType := KeyField.DataType; 
    Case CompareType of 
      ftFloat, 
      ftCurrency, 
      ftBCD : 
        BEGIN 
          _ReadRecord(ActiveBuffer,Rec1-1); 
          fKey1 := KeyField.AsFloat; 
          _ReadRecord(ActiveBuffer,Rec2-1); 
          fKey2 := KeyField.AsFloat; 
          if fKey1 < fKey2 then 
            Result := -1 
          else 
            if fKey1 > fKey2 then 
              Result := 1 
            else 
              Result := 0; 
        END; 
      ftSmallInt, 
      ftInteger, 
      ftWord : 
        BEGIN 
          _ReadRecord(ActiveBuffer,Rec1-1); 
          IKey1 := KeyField.AsInteger; 
          _ReadRecord(ActiveBuffer,Rec2-1); 
          IKey2 := KeyField.AsInteger; 
          if IKey1 < IKey2 then 
            Result := -1 
          else 
            if IKey1 > IKey2 then 
              Result := 1 
            else 
              Result := 0; 
        END; 
      ftDate, 
      ftTime, 
      ftDateTime : 
        BEGIN 
          _ReadRecord(ActiveBuffer,Rec1-1); 
          dKey1 := KeyField.AsDateTime; 
          _ReadRecord(ActiveBuffer,Rec2-1); 
          dKey2 := KeyField.AsDateTime; 
          if dKey1 < dKey2 then 
            Result := -1 
          else 
            if dKey1 > dKey2 then 
              Result := 1 
            else 
              Result := 0; 
        END; 
      else 
        BEGIN 
          _ReadRecord(ActiveBuffer,Rec1-1); 
          SKey1 := KeyField.AsString; 
          _ReadRecord(ActiveBuffer,Rec2-1); 
          SKey2 := KeyField.AsString; 
          if SKey1 < SKey2 then 
            Result := -1 
          else 
            if SKey1 > SKey2 then 
              Result := 1 
            else 
              Result := 0; 
        END; 
    END; 
  END; 
 
BEGIN 
  IX := 0; 
  REPEAT // Loop through all available sortfields until not equal or no more sort fiels. 
    Result := CompareHelper(SortFields[IX],Rec1,Rec2); 
    Inc(IX); 
  UNTIL (Result <> 0) or (IX > High(SortFields)); 
END; 
 
 
// ____________________________________________________________________________ 
// TDBF.SortTable 
// Enhancement: Sort the table by the fields passed. 
Procedure TDBF.SortTable(SortFields : Array of String); 
 
  { This is the main sorting routine. It is passed the number of elements and the 
    two callback routines. The first routine is the function that will perform 
    the comparison between two elements. The second routine is the procedure that 
    will swap two elements if necessary } // Source: UNDU #8 
 
  procedure QSort(uNElem: Integer); 
  { uNElem - number of elements to sort } 
 
    procedure qSortHelp(pivotP: Integer; nElem: word); 
    label 
      TailRecursion, 
      qBreak; 
    var 
      leftP, rightP, pivotEnd, pivotTemp, leftTemp: word; 
      lNum: Integer; 
      retval: integer; 
    begin 
      TailRecursion: 
        if (nElem <= 2) then 
          begin 
            if (nElem = 2) then 
 
              begin 
                rightP := pivotP +1; 
                if (_CompareRecords(SortFields,pivotP, rightP) > 0) then 
                  _SwapRecords(pivotP, rightP); 
              end; 
            exit; 
          end; 
        rightP := (nElem -1) + pivotP; 
        leftP :=  (nElem shr 1) + pivotP; 
        { sort pivot, left, and right elements for "median of 3" } 
        if (_CompareRecords(SortFields,leftP, rightP) > 0) then _SwapRecords(leftP, rightP); 
        if (_CompareRecords(SortFields,leftP, pivotP) > 0) then _SwapRecords(leftP, pivotP) 
 
        else if (_CompareRecords(SortFields,pivotP, rightP) > 0) then _SwapRecords(pivotP, rightP); 
        if (nElem = 3) then 
          begin 
            _SwapRecords(pivotP, leftP); 
            exit; 
          end; 
        { now for the classic Horae algorithm } 
        pivotEnd := pivotP + 1; 
        leftP := pivotEnd; 
        repeat 
          retval := _CompareRecords(SortFields,leftP, pivotP); 
          while (retval <= 0) do 
            begin 
              if (retval = 0) then 
                begin 
                  _SwapRecords(LeftP, PivotEnd); 
                  Inc(PivotEnd); 
                end; 
              if (leftP < rightP) then 
                Inc(leftP) 
              else 
                goto qBreak; 
              retval := _CompareRecords(SortFields,leftP, pivotP); 
            end; {while} 
          while (leftP < rightP) do 
            begin 
 
              retval := _CompareRecords(SortFields,pivotP, rightP); 
              if (retval < 0) then 
                Dec(rightP) 
              else 
                begin 
                  _SwapRecords(leftP, rightP); 
                  if (retval <> 0) then 
                    begin 
                      Inc(leftP); 
                      Dec(rightP); 
                    end; 
                  break; 
                end; 
            end; {while} 
 
        until (leftP >= rightP); 
      qBreak: 
        if (_CompareRecords(SortFields,leftP, pivotP) <= 0) then Inc(leftP); 
        leftTemp := leftP -1; 
        pivotTemp := pivotP; 
        while ((pivotTemp < pivotEnd) and (leftTemp >= pivotEnd)) do 
          begin 
            _SwapRecords(pivotTemp, leftTemp); 
            Inc(pivotTemp); 
            Dec(leftTemp); 
          end; {while} 
        lNum := (leftP - pivotEnd); 
        nElem := ((nElem + pivotP) -leftP); 
 
        if (nElem < lNum) then 
          begin 
            qSortHelp(leftP, nElem); 
            nElem := lNum; 
          end 
        else 
          begin 
            qSortHelp(pivotP, lNum); 
            pivotP := leftP; 
          end; 
        goto TailRecursion; 
      end; {qSortHelp } 
 
  begin 
    if (uNElem < 2) then  exit; { nothing to sort } 
    qSortHelp(1, uNElem); 
  end; { QSort } 
 
 
BEGIN 
  CheckActive; 
  if fReadOnly then 
    raise eDBFError.Create ('Dataset must be opened for read/write to perform sort.'); 
//  if fDataFileHeader.DeletedCount > 0 then 
//    BEGIN 
//      Close; 
//      PackTable; 
//      Open; 
//    END; 
  QSort(FRecordCount {+ fDeletedCount}); 
  First; 
END; 
 
// ____________________________________________________________________________ 
// TDBF.UnsortTable 
// Used to help test the sort routine.  Attempts to generate a random 
// dispersment of the records in the dataset. 
Procedure TDBF.UnsortTable; 
Var 
  IX : Integer; 
BEGIN 
  First; 
  Randomize; 
  for IX := 0 to RecordCOunt do 
    BEGIN 
      _SwapRecords(IX,Random(RecordCount+1)); 
    END; 
  First; 
END; 
 
//////////////////////////////////////// 
////// Part II: 
////// Bookmarks management and movement 
//////////////////////////////////////// 
 
// ____________________________________________________________________________ 
// TDBF.InternalGotoBookmark 
// II: set the requested bookmark as current record 
procedure TDBF.InternalGotoBookmark (Bookmark: Pointer); 
var 
  ReqBookmark: Integer; 
begin 
  ReqBookmark := PInteger (Bookmark)^; 
//  ShowMessage ('InternalGotoBookmark: ' + 
//    IntToStr (ReqBookmark)); 
  if (ReqBookmark >= 0) and (ReqBookmark < FRecordCount {+ fDeletedCount}) then 
    FCurrentRecord := ReqBookmark 
  else 
    raise eDBFError.Create ('Bookmark ' + 
      IntToStr (ReqBookmark) + ' not found'); 
end; 
 
// ____________________________________________________________________________ 
// TDBF.InternalSetToRecord 
// II: same as above (but passes a buffer) 
procedure TDBF.InternalSetToRecord (Buffer: PChar); 
var 
  ReqBookmark: Integer; 
begin 
//  ShowMessage ('InternalSetToRecord'); 
  ReqBookmark := PRecInfo(Buffer + FRecordInfoOffset).Bookmark; 
  InternalGotoBookmark (@ReqBookmark); 
end; 
 
// ____________________________________________________________________________ 
// TDBF.GetBookmarkFlag 
// II: retrieve bookmarks flags from buffer 
function TDBF.GetBookmarkFlag ( 
  Buffer: PChar): TBookmarkFlag; 
begin 
//  ShowMessage ('GetBookmarkFlag'); 
  Result := PRecInfo(Buffer + FRecordInfoOffset).BookmarkFlag; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.SetBookmarkFlag 
// II: change the bookmark flags in the buffer 
procedure TDBF.SetBookmarkFlag (Buffer: PChar; 
  Value: TBookmarkFlag); 
begin 
//  ShowMessage ('SetBookmarkFlag'); 
  PRecInfo(Buffer + FRecordInfoOffset).BookmarkFlag := Value; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.GetBookmarkData 
// II: read the bookmark data from record buffer 
procedure TDBF.GetBookmarkData ( 
  Buffer: PChar; Data: Pointer); 
begin 
//  ShowMessage ('GetBookmarkData: ' + IntToStr (PRecInfo(Buffer + FRecordInfoOffset).Bookmark)); 
  PInteger(Data)^ := 
    PRecInfo(Buffer + FRecordInfoOffset).Bookmark; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.SetBookmarkData 
// II: set the bookmark data in the buffer 
procedure TDBF.SetBookmarkData ( 
  Buffer: PChar; Data: Pointer); 
begin 
//  ShowMessage ('SetBookmarkData: ' + IntToStr (PInteger(Data)^)); 
  PRecInfo(Buffer + FRecordInfoOffset).Bookmark := 
    PInteger(Data)^; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.InternalFirst 
// II: Go to a special position before the first record 
procedure TDBF.InternalFirst; 
begin 
  FCurrentRecord := BofCrack; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.InternalLast 
// II: Go to a special position after the last record 
procedure TDBF.InternalLast; 
begin 
  EofCrack := FRecordCount {+ fDeletedCount}; 
  FCurrentRecord := EofCrack; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.GetRecordCount 
// II (optional): Record count 
function TDBF.GetRecordCount: Longint; 
begin 
  CheckActive; 
  Result := FRecordCount; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.GetRecNo 
// II (optional): Get the number of the current record 
function TDBF.GetRecNo: Longint; 
begin 
  UpdateCursorPos; 
  if FCurrentRecord < 0 then 
    Result := 1 
  else 
    Result := FCurrentRecord + 1; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.SetRecNo 
// II (optional): Move to the given record number 
procedure TDBF.SetRecNo(Value: Integer); 
begin 
  CheckBrowseMode; 
  if (Value > 1) and (Value <= (FRecordCount{+FDeletedCount})) then 
  begin 
    FCurrentRecord := Value - 1; 
    Resync([]); 
  end; 
end; 
 
////////////////////////////////////////// 
////// Part III: 
////// Record buffers and field management 
////////////////////////////////////////// 
 
// ____________________________________________________________________________ 
// TDBF.GetRecordSize 
/// III: Determine the size of each record buffer in memory 
function TDBF.GetRecordSize: Word; 
begin 
  Result := FRecordSize; // data only 
end; 
 
// ____________________________________________________________________________ 
// TDBF.AllocRecordBuffer 
/// III: Allocate a buffer for the record 
function TDBF.AllocRecordBuffer: PChar; 
begin 
  Result := StrAlloc(FRecordBufferSize+1); 
end; 
 
// ____________________________________________________________________________ 
// TDBF.InternalInitRecord 
// III: Initialize the record (set to zero) 
procedure TDBF.InternalInitRecord(Buffer: PChar); 
(*var 
  Field : TField; 
  i : integer; 
  FieldOffset : integer; 
  S : string; *) 
begin 
  FillChar(Buffer^, FRecordBufferSize, 32); 
(*  for i := 0 to FieldCount-1 do 
    begin 
      Field := Fields[i]; 
      FieldOffset := Integer(FFileOffset[Field.FieldNo-1])+FRecordHeaderSize; 
      if Field.DataType = ftString then 
        begin 
          pChar(Buffer+FieldOffset)^ := #0; 
        end 
      else if Field.DataType = ftFloat then 
        begin 
          pChar(Buffer+FieldOffset)^ := '0'; 
          pChar(Buffer+FieldOffset+1)^ := #0; 
        end 
      else if Field.DataType = ftDate then 
        begin 
          S := '19900101'; 
          CopyMemory(PChar(Buffer+FieldOffset),PChar(S),8); 
        end 
      else if Field.DataType = ftBoolean then 
        begin 
          pChar(Buffer+FieldOffset)^ := 'F'; 
        end; 
    end; *) 
end; 
 
// ____________________________________________________________________________ 
// TDBF.FreeRecordBuffer 
// III: Free the buffer 
procedure TDBF.FreeRecordBuffer (var Buffer: PChar); 
begin 
  StrDispose(Buffer); 
end; 
 
// ____________________________________________________________________________ 
// TDBF.GetRecord 
// III: Retrieve data for current, previous, or next record 
// (eventually moving to it) and return the status 
function TDBF.GetRecord(Buffer: PChar; 
  GetMode: TGetMode; DoCheck: Boolean): TGetResult; 
var 
  Acceptable : Boolean; 
begin 
  result := grOk; 
  if FRecordCount < 1 then 
    Result := grEOF 
  else 
    repeat 
      case GetMode of 
        gmCurrent : 
          begin 
            // ShowMessage ('GetRecord Current'); 
            if (FCurrentRecord >= FRecordCount{+fDeletedCount}) or 
                (FCurrentRecord < 0) then 
              Result := grError; 
          end; 
        gmNext : 
          begin 
            if (fCurrentRecord < (fRecordCount{+fDeletedCount})-1) then 
              Inc (FCurrentRecord) 
            else 
              Result := grEOF; 
          end; 
        gmPrior : 
          begin 
           if (fCurrentRecord > 0) then 
              Dec(fCurrentRecord) 
           else 
              Result := grBOF; 
          end; 
      end; 
      // fill record data area of buffer 
      if Result = grOK then 
        begin 
          _ReadRecord(Buffer, fCurrentRecord ); 
          {FStream.Position := FDataFileHeader.StartData + 
          FRecordSize * FCurrentRecord; 
          FStream.ReadBuffer (Buffer^, FRecordSize);} 
          ClearCalcFields(Buffer); 
          GetCalcFields(Buffer); 
          with PRecInfo(Buffer + FRecordInfoOffset)^ do 
            begin 
              BookmarkFlag := bfCurrent; 
              Bookmark := FCurrentRecord; 
            end; 
        end 
      else 
        if (Result = grError) and DoCheck then 
          raise eDBFError.Create('GetRecord: Invalid record'); 
      Acceptable := pRecordHeader(Buffer)^.DeletedFlag <> '*'; 
      if Filtered then 
        Acceptable := Acceptable and (_ProcessFilter(Buffer)); 
      if (GetMode=gmCurrent) and Not Acceptable then 
        Result := grError; 
    until (Result <> grOK) or Acceptable; 
  if ((Result=grEOF)or(Result=grBOF)) and Filtered and not (_ProcessFilter(Buffer)) then 
    Result := grError; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.InternalPost 
// III: Write the current data to the file 
procedure TDBF.InternalPost; 
begin 
  CheckActive; 
  if State = dsEdit then 
    begin 
      // replace data with new data 
      {FStream.Position := FDataFileHeader.StartData + (FRecordSize * FCurrentRecord); 
      FStream.WriteBuffer (ActiveBuffer^, FRecordSize);} 
      _WriteRecord (ActiveBuffer, fCurrentRecord); 
    end 
  else 
    begin 
      // always append 
      InternalLast; 
      {FStream.Seek (0, soFromEnd); 
      FStream.WriteBuffer (ActiveBuffer^, FRecordSize);} 
      pRecordHeader(ActiveBuffer)^.DeletedFlag := ' '; 
      _AppendRecord(ActiveBuffer); 
      Inc (FRecordCount); 
    end; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.InternalAddRecord 
// III: Add the current data to the file 
procedure TDBF.InternalAddRecord(Buffer:Pointer; Append:Boolean); 
begin 
  // always append 
  InternalLast; 
  // add record at the end of the file 
  {FStream.Seek (0, soFromEnd);} 
  pRecordHeader(ActiveBuffer)^.DeletedFlag := ' '; 
  _AppendRecord(ActiveBuffer); 
  {FStream.WriteBuffer (ActiveBuffer^, FRecordSize);} 
  Inc (FRecordCount); 
end; 
 
// ____________________________________________________________________________ 
// TDBF.InternalDelete 
// III: Delete the current record 
procedure TDBF.InternalDelete; 
begin 
  CheckActive; 
  // not supported in this version 
{  raise eBinaryDataSetError.Create ( 
    'Delete: Operation not supported');} 
//  pRecordHeader(ActiveBuffer)^.DeletedFlag := fDataFileHeader.LastDeleted; 
  PChar(ActiveBuffer)^ := '*'; 
  _WriteRecord(ActiveBuffer,fCurrentRecord); 
  {FStream.Position := FDataFileHeader.StartData + (FRecordSize * FCurrentRecord); 
  FStream.WriteBuffer (ActiveBuffer^, FRecordSize);} 
//  fDBFHeader.LastDeleted := GetRecNo; 
//  Inc(fDeletedCount); 
//  Dec(fRecordCount); 
//  fDBFHeader.NumberOfRecords := fRecordCount; 
//  WriteHeader; 
  Resync([]); 
end; 
 
// ____________________________________________________________________________ 
// TDBF.GetFieldData 
// III: Move data from record buffer to field 
function TDBF.GetFieldData(Field:TField; Buffer:Pointer):Boolean; 
var 
  FieldOffset: Integer; 
  S : string; 
  Buf2 : PChar; 
  i,l : integer; 
  D : Double; 
  n : integer; 
  T : TDateTime; 
  j : integer; 
  OldDateFormat : string; 
begin 
  Result := False; 
  Buf2 := ActiveBuffer; 
  if (FRecordCount>0) and (Field.FieldNo > 0) and (Assigned(Buffer)) and (Assigned(Buf2))  then 
    begin 
      FieldOffset := Integer(FFileOffset[Field.FieldNo-1])+FRecordHeaderSize; 
      if Field.DataType = ftString then 
        begin 
          l := Integer(FFileWidth[Field.FieldNo-1]); 
          S := ''; 
          i := 0; 
          While (Buf2[FieldOffset+i] <> #0) and (i 0) and (DecimalSeparator <> '.') then 
                S[Pos('.',S)] := DecimalSeparator; 
              Result := True; 
             try 
              D := StrToFloat(S); 
             except 
              D := 0; 
              Result := False; 
             end; 
              PDouble(Buffer)^ := D; 
            end; 
        end 
      else if Field.DataType = ftDate then 
        begin 
          S := ''; 
          for j := 0 to 7 do 
            S := S + pChar(Buf2+FieldOffset+j); 
          SetLength(S,8); 
          if (trim(S) = '') or (S='00000000') then 
            Result := false 
          else 
            begin 
              S := Copy(S,7,2)+DateSeparator+Copy(S,5,2)+DateSeparator+Copy(S,1,4); 
              OldDateFormat := ShortDateFormat; 
              ShortDateFormat := 'dd/mm/yyyy'; 
              t := StrToDate(S); 
              ShortDateFormat := OldDateFormat; 
              j := Trunc(pDouble(@t)^)+693594; 
              pInteger(Buffer)^ := j; 
              result := True; 
            end; 
        end 
      else if Field.DataType = ftBoolean then 
        begin 
          Result := True; 
          if PChar(Buf2+FieldOffset)^ in ['S','T','Y'] then 
            pBoolean(Buffer)^ := True 
          else if PChar(Buf2+FieldOffset)^ in ['N','F'] then 
            pBoolean(Buffer)^ := False 
          else 
            Result := False; 
        end 
      else 
        begin 
          ShowMessage ('very bad error in get field data'); 
          Result := False; 
        end; 
    end; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.SetFieldData 
// III: Move data from field to record buffer 
procedure TDBF.SetFieldData(Field: TField; Buffer: Pointer); 
var 
  FieldOffset: Integer; 
  Buf2 : PChar; 
  l,i,n:integer; 
  S : string; 
  D : TDateTime; 
  j : integer; 
begin 
  Buf2 := ActiveBuffer; 
  if (Field.FieldNo >= 0) and (Assigned(Buffer)) and (Assigned(Buf2)) then 
    begin 
      FieldOffset := Integer(FFileOffset[Field.FieldNo-1])+FRecordHeaderSize; 
      if Field.DataType = ftString then 
        begin 
          l := Integer(FFileWidth[Field.FieldNo-1]); 
          S := ''; 
          i := 0; 
          While (PChar(Buffer)[i] <> #0) and (i 0) and (DecimalSeparator <> '.') then 
            S[Pos(DecimalSeparator,S)] := '.'; 
          CopyMemory(Pchar(Buf2+FieldOffset),PChar(S),n); 
        end 
      else if Field.DataType = ftDate then 
        begin 
          j := pInteger(Buffer)^-693594; 
          pDouble(@d)^ := j; 
          S := FormatDateTime('yyyymmdd',d); 
          StrLCopy(pChar(Buf2+FieldOffset),pChar(S),8); 
        end 
      else if Field.DataType = ftBoolean then 
        begin 
          if pBoolean(Buffer)^ then 
            PChar(Buf2+FieldOffset)^ := 'T' 
          else 
            PChar(Buf2+FieldOffset)^ := 'F'; 
        end 
      else 
        ShowMessage ('very bad error in setfield data'); 
      DataEvent (deFieldChange, Longint(Field)); 
    end; 
end; 
 
// ____________________________________________________________________________ 
// TDBF.InternalHandleException 
// default exception handling 
procedure TDBF.InternalHandleException; 
begin 
  // standard exception handling 
  Application.HandleException(Self); 
end; 
 
Function TDBF._ProcessFilter(Buffer:PChar):boolean; 
var 
  FilterExpresion : string; 
  PosComp : integer; 
  FName : string; 
  FieldPos : integer; 
  FieldOffset : integer; 
  FieldValue : Variant; 
  TestValue : Variant; 
  FieldText : string; 
  OldShortDateFormat : string; 
begin 
  FilterExpresion := Filter; 
  PosComp := Pos('>',FilterExpresion); 
  if PosComp=0 then 
    PosComp := Pos('<',FilterExpresion); 
  if PosComp=0 then 
    PosComp := Pos('=',FilterExpresion); 
  if PosComp=0 then 
    begin 
      _ProcessFilter := True; 
      Exit; 
    end; 
  FName := Trim(Copy(FilterExpresion,1,PosComp-1)); 
  FieldPos := FieldDefs.IndexOf(FName); 
  FieldOffset := integer(FFileOffset[FieldPos]); 
  if FieldPos < 0 then 
    _ProcessFilter := True 
  else if FieldDefs.Items[FieldPos].DataType = ftString then 
    begin // STRING 
     try 
      FieldValue := ''; 
      FieldOffset := FieldOffset+1; 
      While (Buffer[FieldOffset]<>#0) and (Length(FieldValue)=Copy(TestValue,2,(Length(TestValue)-1))) 
          else 
            _ProcessFilter := (FieldValue>TestValue); 
        end 
      else if FilterExpresion[PosComp]='<' then 
        begin 
          if FilterExpresion[PosComp+1]='=' then 
            _ProcessFilter := (FieldValue<=Copy(TestValue,2,(Length(TestValue)-1))) 
          else 
            _ProcessFilter := (FieldValue#0) and (Length(FieldText)0 then 
        FieldText[Pos('.',FieldText)] := DecimalSeparator; 
      FieldValue := StrToFloat(FieldText); 
      if FilterExpresion[PosComp+1]='='then 
        FieldText := Trim(Copy(FilterExpresion,PosComp+2,Length(FilterExpresion)-PosComp-1)) 
      else 
        FieldText := Trim(Copy(FilterExpresion,PosComp+1,Length(FilterExpresion)-PosComp)); 
      if Pos('.',FieldText)>0 then 
        FieldText[Pos('.',FieldText)] := DecimalSeparator; 
      TestValue := StrToFloat(FieldText); 
      if FilterExpresion[PosComp]='=' then 
        _ProcessFilter := (FieldValue=TestValue) 
      else if FilterExpresion[PosComp]='>'then 
        begin 
          if FilterExpresion[PosComp+1]='='then 
            _ProcessFilter := (FieldValue>=TestValue) 
          else 
            _ProcessFilter := (FieldValue>TestValue); 
        end 
      else if FilterExpresion[PosComp]='<'then 
        begin 
          if FilterExpresion[PosComp+1]='='then 
            _ProcessFilter := (FieldValue<=TestValue) 
          else 
            _ProcessFilter := (FieldValue#0) and (Length(FieldText)=TestValue) 
          else 
            _ProcessFilter := (FieldValue>TestValue); 
        end 
      else if FilterExpresion[PosComp]='<' then 
        begin 
          if FilterExpresion[PosComp+1]='='then 
            _ProcessFilter := (FieldValue<=TestValue) 
          else 
            _ProcessFilter := (FieldValue