home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / pascal / library / dos / database / dbf / dbf3.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1989-01-13  |  28.0 KB  |  852 lines

  1. {$R-}    {Range checking off}
  2. {$B+}    {Boolean complete evaluation on}
  3. {$S+}    {Stack checking on}
  4. {$I+}    {I/O checking on}
  5. {$N-}    {No numeric coprocessor}
  6. {$M 65500,16384,655360} {Turbo 3 default stack and heap}
  7.  
  8. USES CRT,DOS;
  9.  
  10. CONST
  11.   DB2File = 2;
  12.   DB3File = 3;
  13.   DB3WithMemo = $83;
  14.   ValidTypes : SET OF Char = ['C', 'N', 'L', 'M', 'D'];
  15.   MAX_HEADER = 4129;          { = maximum length of dBASE III header }
  16.   MAX_BYTES_IN_RECORD = 4000; { dBASE III record limit }
  17.   MAX_FIELDS_IN_RECORD = 128; { dBASE III field limit  }
  18.   BYTES_IN_MEMO_RECORD = 512; { dBASE III memo field record size }
  19.  
  20.                       { Special Error codes for .DBF files }
  21.   NOT_DB_FILE = $80;  { first byte was not a $3 or $83 or a $2 (dBASE II)}
  22.   INVALID_FIELD = $81;{ invalid field type was found }
  23.   REC_TOO_HIGH = $82; { tried to read a record beyond the correct range }
  24.   PARTIAL_READ = $83; { only a partial record was read }
  25.  
  26.   (*
  27.   Although there are some declarations for memo files, the routines to access
  28.   them have not yet been implemented.
  29.   *)
  30.  
  31. TYPE
  32.   _HeaderType = ARRAY[0..MAX_HEADER] OF Byte;
  33.   _HeaderPrologType = ARRAY[0..31] OF Byte;
  34.   _FieldDescType = ARRAY[0..31] OF Byte;
  35.   _dRec = ^_DataRecord;
  36.   _DataRecord = ARRAY[0..MAX_BYTES_IN_RECORD] OF Byte;
  37.   _Str255 = STRING[255];
  38.   _Str80 = STRING[80];
  39.   _Str64 = STRING[64];
  40.   _Str10 = STRING[10];
  41.   _Str8 = STRING[8];
  42.   _Str2 = STRING[2];
  43.   _dbfFile = FILE;
  44.   _FieldRecord = RECORD
  45.                   Name : _Str10;
  46.                   Typ : Char;
  47.                   Len : Byte;
  48.                   Dec : Byte;
  49.                   Off : Integer;
  50.                 END;
  51.   _FieldArray = ARRAY[1..MAX_FIELDS_IN_RECORD] OF _FieldRecord;
  52.   _dFields = ^_FieldArray;
  53.   _MemoRecord = ARRAY[1..BYTES_IN_MEMO_RECORD] OF Byte;
  54.   _MemoFile = FILE OF _MemoRecord;
  55.   _StatusType = (NotOpen, NotUpdated, Updated);
  56.   dbfRecord = RECORD
  57.                 FileName : _Str64;
  58.                 dFile : _dbfFile;
  59.                 HeadProlog : _HeaderPrologType;
  60.                 dStatus : _StatusType;
  61.                 WithMemo : Boolean;
  62.                 DateOfUpdate : _Str8;
  63.                 NumRecs : LongInt;
  64.                 HeadLen : Integer;
  65.                 RecLen : Integer;
  66.                 NumFields : Integer;
  67.                 Fields : _dFields;
  68.                 CurRecord : _dRec;
  69.               END;
  70.  
  71.   VAR
  72.     dbfError : Integer; { global error indicators }
  73.     dbfOK  : Boolean;
  74.  
  75.   FUNCTION MakeInt(VAR b) : Integer;
  76.   VAR
  77.     i : Integer ABSOLUTE b;
  78.  
  79.   BEGIN
  80.   MakeInt := i;
  81.   END;
  82.  
  83.   FUNCTION MakeStr(b : Byte) : _Str2;
  84.   VAR
  85.     i : Integer;
  86.     s : _Str2;
  87.   BEGIN
  88.   i := b;
  89.   Str(i:2, s);
  90.   IF s[1] = ' ' THEN s[1] := '0';
  91.   MakeStr := s;
  92.   END;
  93.  
  94.   PROCEDURE GetDbfRecord(VAR D : dbfRecord; RecNum : LongInt);
  95.  
  96.   VAR
  97.     Result : Integer;
  98.  
  99.   BEGIN
  100.   IF RecNum > D.NumRecs THEN
  101.     BEGIN
  102.     dbfError := REC_TOO_HIGH;
  103.     dbfOK := FALSE;
  104.     Exit;
  105.     END;
  106.   {$I-} Seek(D.dFile, D.HeadLen+(RecNum-1)*D.RecLen); {$I+}
  107.   dbfError := IOResult;
  108.   IF dbfError = 0 THEN
  109.     BEGIN
  110.     {$I-} BlockRead(D.dFile, D.CurRecord^, D.RecLen, Result); {$I+}
  111.     dbfError := IOResult;
  112.     IF (dbfError = 0) AND (Result < D.RecLen) THEN
  113.       dbfError := PARTIAL_READ;
  114.     END;
  115.   dbfOK := (dbfError = 0);
  116.   END;                        {GetDbfRecord}
  117.  
  118.   PROCEDURE PutDbfRecord(VAR D : dbfRecord; RecNum : LongInt);
  119.  
  120.   VAR
  121.     Result : Integer;
  122.  
  123.   BEGIN
  124.   IF RecNum > D.NumRecs THEN
  125.     BEGIN
  126.     RecNum := D.NumRecs+1;
  127.     D.NumRecs := RecNum;
  128.     END;
  129.   {$I-} Seek(D.dFile, D.HeadLen+(RecNum-1)*D.RecLen); {$I+}
  130.   dbfError := IOResult;
  131.   IF dbfError = 0 THEN
  132.     BEGIN
  133.     {$I-} BlockWrite(D.dFile, D.CurRecord^, D.RecLen, Result); {$I+}
  134.     dbfError := IOResult;
  135.     END;
  136.   IF dbfError = 0 THEN D.dStatus := Updated;
  137.   dbfOK := (dbfError = 0);
  138.   END;                        {PutDbfRecord}
  139.  
  140.   PROCEDURE AppendDbf(VAR D : dbfRecord);
  141.  
  142.   BEGIN
  143.   PutDbfRecord(D, D.NumRecs+1);
  144.   END;
  145.  
  146.   PROCEDURE CloseDbf(VAR D : dbfRecord);
  147.  
  148.     PROCEDURE UpdateHeader(VAR D : dbfRecord);
  149.  
  150.     VAR
  151.       Reg : Registers;
  152.       r : Real;
  153.  
  154.     BEGIN                     { UpdateHeader }
  155.     r := D.NumRecs;
  156.     Reg.AX := $2A00;          { Get DOS Date }
  157.     Intr($21, Dos.Registers(Reg));
  158. {! 6. Paramete^r to Intr must be of the type Registers defined in DOS unit.}
  159.     IF D.HeadProlog[0] = DB2File THEN
  160.       BEGIN
  161.       D.HeadProlog[5] := Reg.CX-1900; {Year}
  162.       D.HeadProlog[3] := Reg.DH; {Month}
  163.       D.HeadProlog[4] := Reg.DL; {Day}
  164.       D.HeadProlog[2] := Trunc(r/256.0);
  165.       r := r-(D.HeadProlog[5]*256.0);
  166.       D.HeadProlog[1] := Trunc(r);
  167.       END
  168.     ELSE
  169.       BEGIN
  170.       D.HeadProlog[1] := Reg.CX-1900; {Year}
  171.       D.HeadProlog[2] := Reg.DH; {Month}
  172.       D.HeadProlog[3] := Reg.DL; {Day}
  173.       D.HeadProlog[7] := Trunc(r/16777216.0);
  174.       r := r-(D.HeadProlog[7]*16777216.0);
  175.       D.HeadProlog[6] := Trunc(r/65536.0);
  176.       r := r-(D.HeadProlog[6]*65536.0);
  177.       D.HeadProlog[5] := Trunc(r/256);
  178.       r := r-(D.HeadProlog[5]*256);
  179.       D.HeadProlog[4] := Trunc(r);
  180.       END;
  181.     {$I-}Seek(D.dFile, 0);{$I+}
  182.     dbfError := IOResult;
  183.     IF dbfError = 0 THEN
  184.       BEGIN
  185.       {$I-} BlockWrite(D.dFile, D.HeadProlog, 8); {$I+}
  186.       dbfError := IOResult;
  187.       END;
  188.     dbfOK := (dbfError = 0);
  189.     END;                      { UpdateHeader }
  190.  
  191.   CONST
  192.     EofMark : Byte = $1A;
  193.  
  194.   BEGIN                       { CloseDbf }
  195.   dbfError := 0;
  196.   IF D.dStatus = Updated THEN
  197.     BEGIN
  198.     UpdateHeader(D);
  199.     IF dbfError = 0 THEN
  200.       BEGIN
  201.       {$I-} Seek(D.dFile, D.HeadLen+D.NumRecs*D.RecLen); {$I+}
  202.       dbfError := IOResult;
  203. {! 9. IOResult now^ returns different values corresponding to DOS error codes.}
  204.       END;
  205.     IF dbfError = 0 THEN
  206.       BEGIN
  207.       {$I-} BlockWrite(D.dFile, EofMark, 1); {$I+} {Put EOF marker }
  208.       dbfError := IOResult;
  209. {! 10. IOResult no^w returns different values corresponding to DOS error codes.}
  210.       END;
  211.     END;   { IF Updated }
  212.   IF dbfError = 0 THEN
  213.     BEGIN
  214.     {$I-} Close(D.dFile);     {$I+}
  215.     dbfError := IOResult;
  216. {! 11. IOResult ^now returns different values corresponding to DOS error codes.}
  217.     END;
  218.   IF dbfError = 0 THEN
  219.     BEGIN
  220.       D.dStatus := NotOpen;
  221.       FreeMem(D.CurRecord, D.RecLen);
  222.       FreeMem(D.Fields, D.NumFields*SizeOf(_FieldRecord));
  223.     END;
  224.   dbfOK := (dbfError = 0);
  225.   END;                        { CloseDbf }
  226.  
  227.   PROCEDURE OpenDbf(VAR D : dbfRecord);
  228.  
  229.     PROCEDURE ProcessHeader(VAR Header : _HeaderType; NumBytes : Integer);
  230.  
  231.       PROCEDURE GetOneFieldDesc(VAR F; VAR Field : _FieldRecord;
  232.                                 VAR Offset : Integer);
  233.  
  234.       VAR
  235.         i : Integer;
  236.         FD : _FieldDescType ABSOLUTE F;
  237.  
  238.       BEGIN                   { GetOneFieldDesc }
  239.       i := 0;
  240.       Field.Name := '';
  241.       REPEAT
  242.         Field.Name[Succ(i)] := Chr(FD[i]);
  243.         i := Succ(i);
  244.       UNTIL FD[i] = 0;
  245.       Field.Name[0] := Chr(i);
  246.       Field.Typ := Char(FD[11]);
  247.       IF D.HeadProlog[0] = DB2File THEN
  248.         BEGIN
  249.         Field.Len := FD[12];
  250.         Field.Dec := FD[15];
  251.         END
  252.       ELSE
  253.         BEGIN
  254.         Field.Len := FD[16];
  255.         Field.Dec := FD[17];
  256.         END;
  257.       Field.Off := Offset;
  258.       Offset := Offset+Field.Len;
  259.       IF NOT(Field.Typ IN ValidTypes) THEN
  260.         dbfError := INVALID_FIELD;
  261.       END;                    { GetOneFieldDesc }
  262.  
  263.       PROCEDURE ProcessDB2Header;
  264.  
  265.       VAR
  266.         o, i, tFieldsLen : Integer;
  267.         tempFields : _FieldArray;
  268.  
  269.       BEGIN   { ProcessDB2Header }
  270.       D.DateOfUpdate := MakeStr(Header[3])+'/'+MakeStr(Header[4])+'/'+MakeStr(Header[5]);
  271.       D.NumRecs := Header[1];
  272.       D.HeadLen := 521;
  273.       IF NumBytes < D.HeadLen THEN
  274.         BEGIN
  275.         dbfError := NOT_DB_FILE;
  276.         Close(D.dFile);
  277.         Exit;
  278.         END;
  279.       D.RecLen := MakeInt(Header[6]); { Includes the Deleted Record Flag }
  280.       GetMem(D.CurRecord, D.RecLen); { Allocate some memory for a buffer  }
  281.       D.dStatus := NotUpdated;
  282.       D.NumFields := 0;
  283.       Move(Header, D.HeadProlog, SizeOf(D.HeadProlog));
  284.       o := 1;                   {Offset within dbf record of current field }
  285.       i := 8;                   {Index for Header }
  286.       WHILE Header[i] <> $0D DO
  287.         BEGIN
  288.         D.NumFields := Succ(D.NumFields);
  289.         GetOneFieldDesc(Header[i], tempFields[D.NumFields], o);
  290.         IF dbfError <> 0 THEN
  291.           BEGIN
  292.           Close(D.dFile);
  293.           Exit;
  294.           END;
  295.         i := i+16;
  296.         END;                    { While Header[i] <> $0D }
  297.       tFieldsLen := D.NumFields*SizeOf(_FieldRecord);
  298.       GetMem(D.Fields, tFieldsLen);
  299.       Move(tempFields, D.Fields^, tFieldsLen);
  300.       D.WithMemo := FALSE;
  301.       END;                      {ProcessDB2Header}
  302.  
  303.     VAR
  304.       o, i : Integer;
  305.       tempFields : _FieldArray;
  306.  
  307.     BEGIN                     {ProcessHeader}
  308.     CASE Header[0] OF
  309.       DB2File : BEGIN
  310.                   ProcessDB2Header;
  311.                   Exit;
  312.                 END;
  313.       DB3File : D.WithMemo := False;
  314.       DB3WithMemo : D.WithMemo := True;
  315.       ELSE
  316.         BEGIN
  317.         dbfError := NOT_DB_FILE;
  318.         Close(D.dFile);
  319.         Exit;
  320.         END;
  321.       END;                      {CASE}
  322.     D.DateOfUpdate := MakeStr(Header[2])+'/'+MakeStr(Header[3])+'/'+MakeStr(Header[1]);
  323.     D.NumRecs := Header[4];
  324.     D.HeadLen := MakeInt(Header[8]);
  325.     IF NumBytes < D.HeadLen THEN
  326.       BEGIN
  327.       dbfError := NOT_DB_FILE;
  328.       Close(D.dFile);
  329.       Exit;
  330.       END;
  331.     D.RecLen := MakeInt(Header[10]); { Includes the Deleted Record Flag }
  332.     GetMem(D.CurRecord, D.RecLen); { Allocate some memory for a buffer  }
  333.     D.dStatus := NotUpdated;
  334.     D.NumFields := 0;
  335.     Move(Header, D.HeadProlog, SizeOf(D.HeadProlog));
  336.     o := 1;                   {Offset within dbf record of current field }
  337.     i := 32;                  {Index for Header }
  338.     WHILE Header[i] <> $0D DO
  339.       BEGIN
  340.       D.NumFields := Succ(D.NumFields);
  341.       GetOneFieldDesc(Header[i], tempFields[D.NumFields], o);
  342.       IF dbfError <> 0 THEN
  343.         BEGIN
  344.         Close(D.dFile);
  345.         Exit;
  346.         END;
  347.       i := i+32;
  348.       END;                    { While Header[i] <> $0D }
  349.     i := D.NumFields*SizeOf(_FieldRecord);
  350.     GetMem(D.Fields,i) ;
  351.     Move(tempFields, D.Fields^, i);
  352.     END;                      {ProcessHeader}
  353.  
  354.     PROCEDURE GetHeader;
  355.  
  356.     VAR
  357.       Result : Integer;
  358.       H : _HeaderType;
  359.  
  360.     BEGIN                     { GetHeader }
  361.     {$I-} BlockRead(D.dFile, H, MAX_HEADER, Result); {$I+}
  362.     dbfError := IOResult;
  363. {! 12. IOResult ^now returns different values corresponding to DOS error codes.}
  364.     IF dbfError = 0 THEN ProcessHeader(H, Result);
  365.     END;                      { GetHeader }
  366.  
  367.   BEGIN                       { OpenDbf }
  368.   Assign(D.dFile, D.FileName);
  369.   {$I-} Reset(D.dFile, 1); {$I+} {the '1' parameter sets the record size}
  370.   dbfError := IOResult;
  371. {! 13. IOResul^t now returns different values corresponding to DOS error codes.}
  372.   IF dbfError = 0 THEN GetHeader;
  373.   dbfOK := (dbfError = 0);
  374.   END;                        { OpenDbf }
  375.  
  376.   PROCEDURE CreateDbf(VAR D : dbfRecord; fn : _Str64; n : Integer;
  377.                       flds : _dFields);
  378.     {
  379.     Call this procedure with the full pathname of the file that you want
  380.     to create (fn), the number of fields in a record (n), and a pointer
  381.     to an array of _FieldRecord (flds).  The procedure will initialize all
  382.     the data structures in the dbfRecord (D).
  383.     }
  384.  
  385.   VAR
  386.     tHeader : _HeaderType;
  387.  
  388.     PROCEDURE MakeFieldDescs;
  389.  
  390.       PROCEDURE MakeOneFieldDesc(VAR F; VAR Field : _FieldRecord);
  391.  
  392.       VAR
  393.         FD : _FieldDescType ABSOLUTE F;
  394.  
  395.       BEGIN                   { MakeOneFieldDesc }
  396.       Move(Field.Name[1],FD,Ord(Field.Name[0]));
  397.       FD[11] := Ord(Field.Typ);
  398.       FD[16] := Field.Len;
  399.       IF Field.Typ <> 'N' THEN Field.Dec := 0;
  400.       FD[17] := Field.Dec;
  401.       Field.Off := D.RecLen;
  402.       D.RecLen := D.RecLen+Field.Len;
  403.       IF NOT(Field.Typ IN ValidTypes) THEN dbfError := INVALID_FIELD;
  404.       IF Field.Typ = 'M' THEN D.WithMemo := TRUE;
  405.       END;                    { MakeOneFieldDesc }
  406.  
  407.     VAR
  408.       i : Integer;
  409.  
  410.     BEGIN                     {MakeFieldDescs}
  411.     D.RecLen := 1;
  412.     FOR i := 1 TO D.NumFields DO
  413.       BEGIN
  414.       MakeOneFieldDesc(tHeader[i*32],flds^[i]);
  415.       IF dbfError <> 0 THEN Exit;
  416.       END;
  417.     END;                      {MakeFieldDescs}
  418.  
  419.     PROCEDURE MakeHeader;
  420.  
  421.     VAR
  422.       Result : Integer;
  423.  
  424.     BEGIN                     { MakeHeader }
  425.     FillChar(tHeader,SizeOf(tHeader),#0);
  426.     D.WithMemo := FALSE;
  427.     D.HeadLen := Succ(D.NumFields) * 32;
  428.     tHeader[D.HeadLen] := $0D;
  429.     D.HeadLen := Succ(D.HeadLen);
  430.     tHeader[8] := Lo(D.HeadLen);
  431.     tHeader[9] := Hi(D.HeadLen);
  432.     MakeFieldDescs;
  433.     IF D.WithMemo THEN
  434.       tHeader[0] := DB3WithMemo
  435.     ELSE
  436.       tHeader[0] := DB3File;
  437.     tHeader[10] := Lo(D.RecLen);
  438.     tHeader[11] := Hi(D.RecLen);
  439.     END;                      { MakeHeader }
  440.  
  441.   VAR
  442.     i : Integer;
  443.  
  444.   BEGIN            { CreateDbf }
  445.   D.NumFields := n;
  446.   MakeHeader;
  447.   D.FileName := fn;
  448.   Assign(D.dFile, D.FileName);
  449.   {$I-} Rewrite(D.dFile, 1); {$I+} {Will overwrite if file exists!}
  450.   dbfError := IOResult;
  451.   IF dbfError = 0 THEN
  452.     BEGIN
  453.     {$I-} BlockWrite(D.dFile,tHeader,Succ(D.HeadLen));{$I+}
  454.     dbfError := IOResult;
  455.     END;
  456.   IF dbfError = 0 THEN
  457.     BEGIN
  458.     D.dStatus := Updated;
  459.     D.NumRecs := 0;
  460.     Move(tHeader,D.HeadProlog,SizeOf(D.HeadProlog));
  461.     D.DateOfUpdate := '  /  /  ';
  462.     GetMem(D.CurRecord, D.RecLen); { Allocate some memory for a buffer  }
  463.     FillChar(D.CurRecord^,D.RecLen,' ');
  464.     i := D.NumFields*SizeOf(_FieldRecord);
  465.     GetMem(D.Fields,i);
  466.     Move(flds, D.Fields^,i);
  467.     END;
  468.   dbfOK := (dbfError = 0);
  469.   END;                        { CreateDbf }
  470.  
  471. (* To enable the Demo program, delete the next line. *)
  472.  
  473.   PROCEDURE ErrorHalt(errorCode : Integer);
  474.     { a VERY crude error handler }
  475.   VAR
  476.     errorMsg : _Str80;
  477.  
  478.   BEGIN
  479.   CASE errorCode OF
  480.      00 : Exit;                { no error occurred }
  481.     $01 : errorMsg := 'Not found';
  482.     $02 : errorMsg := 'Not open for input';
  483.     $03 : errorMsg := 'Not open for output';
  484.     $04 : errorMsg := 'Just not open';
  485.     $91 : errorMsg := 'Seek beyond EOF';
  486.     $99 : errorMsg := 'Unexpected EOF';
  487.     $F0 : errorMsg := 'Disk write error';
  488.     $F1 : errorMsg := 'Directory full';
  489.     $F3 : errorMsg := 'Too many files';
  490.     $FF : errorMsg := 'Where did that file go?';
  491.     NOT_DB_FILE : errorMsg := 'Not a dBASE data file';
  492.     INVALID_FIELD : errorMsg := 'Invalid field type encountered';
  493.     REC_TOO_HIGH  : errorMsg := 'Requested record beyond range';
  494.     PARTIAL_READ  : errorMsg := 'Tried to read beyon EOF';
  495.     ELSE
  496.      errorMsg := 'Undefined error';
  497.     END;
  498.   WriteLn;
  499.   WriteLn(errorCode:3, ': ',errorMsg);
  500.   Halt(1);
  501.   END;
  502.  
  503. TYPE
  504.   PseudoStr = ARRAY[1..255] OF Char;
  505.  
  506. VAR
  507.   Demo : dbfRecord;
  508.   j, i : Integer;
  509.   blanks : _Str255;
  510.   SizeOfFile, r : LongInt;
  511.   fn : _Str64;
  512.  
  513.   PROCEDURE Wait;
  514.   VAR
  515.     c : Char;
  516.  
  517.   BEGIN
  518.     Write('Press any key to continue . . .');
  519.     C:=ReadKey;
  520.   END;
  521.  
  522.  
  523.   PROCEDURE List(VAR D : dbfRecord);
  524.  
  525.     PROCEDURE ShowField(VAR a; VAR F : _FieldRecord);
  526.  
  527.     VAR
  528.       Data : PseudoStr ABSOLUTE a;
  529.  
  530.     BEGIN
  531.     WITH F DO
  532.       BEGIN
  533.       CASE Typ OF
  534.         'C', 'N', 'L' : Write(Copy(Data, 1, Len));
  535.         'M' : Write('Memo      ');
  536.         'D' : Write(Copy(Data, 5, 2), '/',
  537.               Copy(Data, 7, 2), '/',
  538.               Copy(Data, 1, 2));
  539.       END;                    {CASE}
  540.       IF Len <= Length(Name) THEN
  541.         Write(Copy(blanks, 1, Length(Name)-Pred(Len)))
  542.       ELSE
  543.         Write(' ');
  544.       END;                    {WITH F}
  545.     END;                      {ShowField}
  546.  
  547.   BEGIN                       {List}
  548.   WriteLn;
  549.   Write('Rec Num  ');
  550.   WITH D DO
  551.     BEGIN
  552.     FOR i := 1 TO NumFields DO
  553.       WITH Fields^[i] DO
  554.         IF Len >= Length(Name) THEN
  555.           Write(Name, Copy(blanks, 1, Succ(Len-Length(Name))))
  556.         ELSE
  557.           Write(Name, ' ');
  558.     WriteLn;
  559.     r := 1;
  560.     WHILE r <= NumRecs DO
  561.       BEGIN
  562.       GetDbfRecord(Demo, r);
  563.       IF NOT dbfOK THEN ErrorHalt(dbfError);
  564.       WriteLn;
  565.       Write(r:7, ' ');
  566.       Write(Chr(CurRecord^[0])); { the 'deleted' indicator }
  567.       FOR i := 1 TO NumFields DO
  568.         ShowField(CurRecord^[Fields^[i].Off], Fields^[i]);
  569.       r := r+1;
  570.       END;                    {WHILE r }
  571.     END;                      {WITH D }
  572.   END;                        {List}
  573.  
  574.   PROCEDURE DisplayStructure(VAR D : dbfRecord);
  575.  
  576.   VAR
  577.     i : Integer;
  578.  
  579.   BEGIN
  580.   WITH D DO
  581.     BEGIN
  582.     ClrScr;
  583.     Write(' #  Field Name   Type  Length  Decimal');
  584.     FOR i := 1 TO NumFields DO
  585.       BEGIN
  586.       WITH Fields^[i] DO
  587.         BEGIN
  588.         IF i MOD 20 = 0 THEN
  589.           BEGIN
  590.           WriteLn;
  591.           Wait;
  592.           ClrScr;
  593.           Write(' #  Field Name   Type  Length  Decimal');
  594.           END;
  595.         GoToXY(1, Succ(WhereY));
  596.         Write(i:2, Name:12, Typ:5, Len:9);
  597.         IF Typ = 'N' THEN Write(Dec:5);
  598.         END;                  {WITH Fields^}
  599.       END;                    {FOR}
  600.     WriteLn;
  601.     Wait;
  602.     END;                      {WITH D}
  603.   END;                        { DisplayStructure }
  604.  
  605.   PROCEDURE CopyDbf(fnDB2,fnDB3 : _Str64);
  606.     {
  607.     Copies a .DBF file to another file.  The SOURCE file may be a
  608.     II, III, or III Plus file.  The DESTINATION file will be a III Plus
  609.     file (although III will be able to use it with no problems).
  610.     }
  611.  
  612.   VAR
  613.     dOrg,dDest : dbfRecord;
  614.     recCount : LongInt;
  615.     x,y : Integer;
  616.     dummyPtr : _dRec;
  617.  
  618.   BEGIN             { CopyDbf }
  619.   dOrg.FileName := fnDB2;
  620.   OpenDbf(dOrg);
  621.   IF NOT dbfOK THEN ErrorHalt(dbfError);
  622.   CreateDbf(dDest,fnDB3,dOrg.NumFields,dOrg.Fields);
  623.   IF NOT dbfOK THEN ErrorHalt(dbfError);
  624.   dummyPtr := dDest.CurRecord;
  625.   dDest.CurRecord := dOrg.CurRecord;     { a dirty trick! }
  626.   recCount := 0;
  627.   WriteLn;
  628.   x := WhereX; y := WhereY;
  629.   Write(recCount:8,' Records Converted.');
  630.   WHILE recCount < dOrg.NumRecs DO
  631.     BEGIN
  632.     recCount := recCount + 1;
  633.     GetDbfRecord(dOrg,recCount);
  634.     IF NOT dbfOK THEN ErrorHalt(dbfError);
  635.     AppendDbf(dDest);     { go right into the append because both CurRecords }
  636.                           {  point to the same place }
  637.     IF NOT dbfOK THEN ErrorHalt(dbfError);
  638.     GotoXY(x,y);
  639.     Write(recCount:8);
  640.     END;
  641.   WriteLn;
  642.   CloseDbf(dOrg);
  643.   IF NOT dbfOK THEN ErrorHalt(dbfError);
  644.   dDest.CurRecord := dummyPtr;      { It is important to undo the dirty work! }
  645.   CloseDbf(dDest);
  646.   IF NOT dbfOK THEN ErrorHalt(dbfError);
  647.   END;              { CopyDbf }
  648.  
  649. VAR
  650.   fn1,fn2,p : _Str64;
  651.  
  652. BEGIN                         {Demonstration of DBF routines}
  653. WITH Demo DO
  654.   BEGIN
  655.   FillChar(blanks, SizeOf(blanks), $20);
  656.   blanks[0] := Chr(255);
  657.   ClrScr;
  658.   GoToXY(10, 10);
  659.   Write('Name of dBASE file (.DBF assumed): ');
  660.   Read(FileName);
  661.   IF Pos('.', FileName) = 0 THEN FileName := FileName+'.DBF';
  662.   OpenDbf(Demo);
  663.   IF NOT dbfOK THEN ErrorHalt(dbfError);
  664.   ClrScr;
  665.   SizeOfFile := FileSize(dFile);
  666.   WriteLn('File Name: ', FileName);
  667.   WriteLn('Date Of Last Update: ', DateOfUpdate);
  668.   WriteLn('Number of Records: ', NumRecs:10);
  669.   WriteLn('Size of File: ', SizeOfFile:15);
  670.   WriteLn('Length of Header: ', HeadLen:11);
  671.   WriteLn('Length of One Record: ', RecLen:7);
  672.   IF WithMemo THEN WriteLn('This file contains Memo fields.');
  673.   IF HeadProlog[0] = DB2File THEN WriteLn('dBASE 2.4 file');
  674.   Wait;
  675.   ClrScr;
  676.   DisplayStructure(Demo);
  677.   ClrScr;
  678.   List(Demo);
  679.   WriteLn;
  680.   Wait;
  681.   CloseDbf(Demo);
  682.   IF NOT dbfOK THEN ErrorHalt(dbfError);
  683.   END;                        {WITH}
  684. ClrScr;
  685. WriteLn('Enter the name of a dBASE file (II, III, or III +) to copy.');
  686. Write('Enter a blank name to exit: ');
  687. ReadLn(fn1);
  688. IF fn1 = '' THEN Halt;
  689. IF Pos('.', fn1) = 0 THEN fn1 := fn1+'.DBF';
  690. Write('Enter destination file name: ');
  691. ReadLn(fn2);
  692. IF Pos('.', fn2) = 0 THEN fn2 := fn2+'.DBF';
  693. CopyDbf(fn1,fn2);
  694. END.                          {of Demo program }
  695.  
  696.  
  697. (*
  698.   DBF.PAS version 1.3
  699.   Copyright (C) 1986 By James Troutman
  700.   CompuServe PPN 74746,1567
  701.   Permission is granted to use these routines for non-commercial purposes.
  702.   For commercial use, please request permission via EasyPlex.
  703.  
  704.   Revision history
  705.    1.1  - 5/6/86 - update header when modifying the .DBF file; write the
  706.    End Of File marker; simplify use.
  707.  
  708.    1.2  - 5/27/86 - removed (some of) the absurdities from the code;
  709.    allocate the current record buffer on the heap rather than in the data
  710.    segment; symbol names changed to avoid conflicts; some error checking
  711.    added.
  712.  
  713.    1.3  - 6/5/86 - added support for dBASE II files; new procedure CreateDbf.
  714.  
  715.                     !!!!ATTENTION!!!!
  716.   If you have downloaded an earlier version of this file, please note that
  717.   several of the TYPEs and VARs have been changed.  You may have to make
  718.   some adjustments to any existing programs you have that use these routines.
  719.  
  720.   The routines in this file present some tools for accessing dBASE II, III, and
  721.   III Plus files from within a Turbo Pascal program.  There is MUCH
  722.   room for improvement: the error checking is simplistic, there is no support
  723.   for memo files, no buffering of data, no support for index files,
  724.   etc. The main routines are:
  725.  
  726.          PROCEDURE OpenDbf(VAR D : dbfRecord;) : Integer;
  727.          PROCEDURE CloseDbf(VAR D : dbfRecord) : Integer;
  728.          PROCEDURE GetDbfRecord(VAR D : dbfRecord; RecNum : Real);
  729.          PROCEDURE PutDbfRecord(VAR D : dbfRecord; RecNum : Real);
  730.          PROCEDURE AppendDbf(VAR D : dbfRecord);
  731.          PROCEDURE CreateDbf(VAR D : dbfRecord; fn : _Str64; n : Integer;
  732.                              flds : _dFields);
  733.  
  734.   After calling one of the procedures, check the status of the Boolean variable
  735.   dbfOK to determine the success or failure of the operation.  If it failed,
  736.   dbfError will contain a value corresponding to the IOResult value or
  737.   to a specially assigned value for several special conditions.  Notice in
  738.   particular that an unsuccessful call to CloseDbf will leave the file status
  739.   unchanged and the memory still allocated.  It is your program's
  740.   responsibility to take appropriate action.
  741.  
  742.   A skeletal program might go something like:
  743.     {$I Dbf.PAS}
  744.     VAR
  745.       D : dbfRecord; { declare your dBASE file variable }
  746.     BEGIN
  747.     D.FileName := 'MyFile.DBF'; { get filename of .dbf file into FileName field
  748.                                   of D variable ...  }
  749.     OpenDbf(D);        { to open the file }
  750.     IF NOT dbfOK THEN { check dbfError and process error };
  751.     {... the rest of your program including calls to
  752.      GetDbfRecord, PutDbfRecord, AppendDbf as needed,
  753.      always remembering to interrogate the two global status
  754.      variables after each procedure call   }
  755.     CloseDbf(D);      { to close the file  }
  756.     IF NOT dbfOK THEN { check dbfError and process error };
  757.     END.
  758.  
  759.   Upon exit from the GetDbfRecord Procedure, the CurRecord of the
  760.   dbfRecord variable points to the current record contents.  Each field
  761.   can be accessed using its offset into the CurRecord^ with the variable
  762.   Off in the Fields^ array.
  763.   Upon entry to the PutDbfRecord Procedure, the CurRecord^ should contain
  764.   the data that you want to write.
  765.   AppendDbf automatically adds a record to the end of the file (the
  766.   CurRecord^ should contain the data that you want to write).
  767.  
  768.   Notice that the OpenDbf routine does allocate a buffer on the heap for
  769.   the current record.  You can, of course, override this by pointing
  770.   CurRecord to any data structure that you wish; HOWEVER, since CloseDbf
  771.   deallocates the buffer, you must repoint CurRecord to its original buffer
  772.   before calling CloseDbf.
  773.  
  774.   See the demo program for some examples.
  775.   If you have any problems with these routines, please
  776.   let me know.  Suggestions for improvements gratefully accepted.
  777.   *)
  778.  
  779. (*
  780. dBASE III Database File Structure
  781. The structure of a dBASE III database file is composed of a
  782. header and data records.  The layout is given below.
  783. dBASE III DATABASE FILE HEADER:
  784. +---------+-------------------+---------------------------------+
  785. |  BYTE   |     CONTENTS      |          MEANING                |
  786. +---------+-------------------+---------------------------------+
  787. |  0      |  1 byte           | dBASE III version number        |
  788. |         |                   |  (03H without a .DBT file)      |
  789. |         |                   |  (83H with a .DBT file)         |
  790. +---------+-------------------+---------------------------------+
  791. |  1-3    |  3 bytes          | date of last update             |
  792. |         |                   |  (YY MM DD) in binary format    |
  793. +---------+-------------------+---------------------------------+
  794. |  4-7    |  32 bit number    | number of records in data file  |
  795. +---------+-------------------+---------------------------------+
  796. |  8-9    |  16 bit number    | length of header structure      |
  797. +---------+-------------------+---------------------------------+
  798. |  10-11  |  16 bit number    | length of the record            |
  799. +---------+-------------------+---------------------------------+
  800. |  12-31  |  20 bytes         | reserved bytes (version 1.00)   |
  801. +---------+-------------------+---------------------------------+
  802. |  32-n   |  32 bytes each    | field descriptor array          |
  803. |         |                   |  (see below)                    | --+
  804. +---------+-------------------+---------------------------------+   |
  805. |  n+1    |  1 byte           | 0DH as the field terminator     |   |
  806. +---------+-------------------+---------------------------------+   |
  807. |
  808. |
  809. A FIELD DESCRIPTOR:      <------------------------------------------+
  810. +---------+-------------------+---------------------------------+
  811. |  BYTE   |     CONTENTS      |          MEANING                |
  812. +---------+-------------------+---------------------------------+
  813. |  0-10   |  11 bytes         | field name in ASCII zero-filled |
  814. +---------+-------------------+---------------------------------+
  815. |  11     |  1 byte           | field type in ASCII             |
  816. |         |                   |  (C N L D or M)                 |
  817. +---------+-------------------+---------------------------------+
  818. |  12-15  |  32 bit number    | field data address              |
  819. |         |                   |  (address is set in memory)     |
  820. +---------+-------------------+---------------------------------+
  821. |  16     |  1 byte           | field length in binary          |
  822. +---------+-------------------+---------------------------------+
  823. |  17     |  1 byte           | field decimal count in binary   |
  824. +---------+-------------------+--------------------------------
  825. |  18-31  |  14 bytes         | reserved bytes (version 1.00)   |
  826. +---------+-------------------+---------------------------------+
  827. The data records are layed out as follows:
  828. 1. Data records are preceeded by one byte that is a
  829. space (20H) if the record is not deleted and an
  830. asterisk (2AH) if it is deleted.
  831. 2. Data fields are packed into records with no field
  832. separators or record terminators.
  833. 3. Data types are stored in ASCII format as follows:
  834. DATA TYPE      DATA RECORD STORAGE
  835. ---------      --------------------------------------------
  836. Character      (ASCII characters)
  837. Numeric        - . 0 1 2 3 4 5 6 7 8 9
  838. Logical        ? Y y N n T t F f  (? when not initialized)
  839. Memo           (10 digits representing a .DBT block number)
  840. Date           (8 digits in YYYYMMDD format, such as
  841. 19840704 for July 4, 1984)
  842.  
  843. This information came directly from the Ashton-Tate Forum.
  844. It can also be found in the Advanced Programmer's Guide available
  845. from Ashton-Tate.
  846.  
  847. One slight difference occurs between files created by dBASE III and those
  848. created by dBASE III Plus.  In the earlier files, there is an ASCII NUL
  849. character between the $0D end of header indicator and the start of the data.
  850. This NUL is no longer present in Plus, making a Plus header one byte smaller
  851. than an identically structured III file.
  852. *)