program dbftool; { Reads and exports DBF files. Can create a demo DBF file to test with. Demonstrates creating DBF tables, filling it with data, and exporting datasets. } {$mode objfpc}{$H+} uses {$IFDEF UNIX} {$IFDEF UseCThreads} cthreads, {$ENDIF} {$ENDIF} Classes, SysUtils, CustApp, DB, dbf, dbf_fields, dbf_common, dateutils, fpdbexport, fpcsvexport, fpdbfexport, fpfixedexport, fprtfexport, fpsimplejsonexport, fpsimplexmlexport, fpsqlexport, fptexexport, fpxmlxsdexport; type { TDBFTool } TDBFTool = class(TCustomApplication) private procedure ExportDBF(var MyDbf: TDbf); protected procedure DoRun; override; public constructor Create(TheOwner: TComponent); override; destructor Destroy; override; procedure WriteHelp; virtual; end; procedure CreateDemoDBFs(Directory: string; TableLevel: integer); // Creates 2 demonstration DBFs in Directory with dbase compatibility level // TableLevel // and specified codepage (if not CODEPAGE_NOT_SPECIFIED) var NewDBF: TDBF; i: integer; begin NewDBF := TDBF.Create(nil); try if Directory = '' then NewDBF.FilePath := '' { application directory} else NewDBF.FilePathFull := ExpandFileName(Directory) {full absolute path}; if TableLevel <= 0 then NewDBF.TableLevel := 4 {default to DBase IV} else NewDBF.TableLevel := TableLevel; NewDBF.TableName := 'CUSTOMER.DBF'; writeln('Creating ', NewDBF.TableName, ' with table level ', NewDBF.TableLevel); if TableLevel >= 30 then begin NewDBF.FieldDefs.Add('CUST_NO', ftAutoInc); end else NewDBF.FieldDefs.Add('CUST_NO', ftInteger); NewDBF.FieldDefs.Add('CUSTOMER', ftString, 25); NewDBF.FieldDefs.Add('CITY', ftString, 25); NewDBF.FieldDefs.Add('COUNTRY', ftString, 15); NewDBF.CreateTable; NewDBF.Open; for i := 1 to 5 do //keep size manageable until we have working files begin NewDBF.Append; if (NewDBF.FieldDefs.Find('CUST_NO').DataType <> ftAutoInc) then NewDBF.FieldByName('CUST_NO').AsInteger := i; case i of 1: begin NewDBF.FieldByName('CUSTOMER').AsString := 'Michael Design'; NewDBF.FieldByName('CITY').AsString := 'San Diego'; NewDBF.FieldByName('COUNTRY').AsString := 'USA'; end; 2: begin NewDBF.FieldByName('CUSTOMER').AsString := 'Michael Design'; NewDBF.FieldByName('CITY').AsString := 'San Diego'; NewDBF.FieldByName('COUNTRY').AsString := 'USA'; end; 3: begin NewDBF.FieldByName('CUSTOMER').AsString := 'VC Technologies'; NewDBF.FieldByName('CITY').AsString := 'Dallas'; NewDBF.FieldByName('COUNTRY').AsString := 'USA'; end; 4: begin NewDBF.FieldByName('CUSTOMER').AsString := 'Klämpfl, Van Canneyt'; NewDBF.FieldByName('CITY').AsString := 'Boston'; NewDBF.FieldByName('COUNTRY').AsString := 'USA'; end; 5: begin NewDBF.FieldByName('CUSTOMER').AsString := 'Felipe Bank'; NewDBF.FieldByName('CITY').AsString := 'Manchester'; NewDBF.FieldByName('COUNTRY').AsString := 'England'; end; end; NewDBF.Post; end; NewDBF.Close; finally NewDBF.Free; end; NewDBF := TDBF.Create(nil); try if Directory = '' then NewDBF.FilePath := '' { application directory} else NewDBF.FilePathFull := ExpandFileName(Directory) {full absolute path}; if TableLevel <= 0 then NewDBF.TableLevel := 4 {default to DBase IV} else NewDBF.TableLevel := TableLevel; NewDBF.TableName := 'EMPLOYEE.DBF'; writeln('Creating ', NewDBF.TableName, ' with table level ', NewDBF.TableLevel); if TableLevel >= 30 then begin NewDBF.FieldDefs.Add('EMP_NO', ftAutoInc); end else NewDBF.FieldDefs.Add('EMP_NO', ftInteger); NewDBF.FieldDefs.Add('FIRST_NAME', ftString, 15); NewDBF.FieldDefs.Add('LAST_NAME', ftString, 20); NewDBF.FieldDefs.Add('PHONE_EXT', ftString, 4); NewDBF.FieldDefs.Add('JOB_CODE', ftString, 5); NewDBF.FieldDefs.Add('JOB_GRADE', ftInteger); NewDBF.FieldDefs.Add('JOB_COUNTR', ftString, 15); //Note 10 character limit for table/field names in most DBases NewDBF.FieldDefs.Add('SALARY', ftFloat); NewDBF.CreateTable; NewDBF.Open; for i := 1 to 5 do //keep size manageable until we have working files begin NewDBF.Append; if (NewDBF.FieldDefs.Find('EMP_NO').DataType <> ftAutoInc) then NewDBF.FieldByName('EMP_NO').AsInteger := i; case i of 1: begin NewDBF.FieldByName('FIRST_NAME').AsString := 'William'; NewDBF.FieldByName('LAST_NAME').AsString := 'Shatner'; NewDBF.FieldByName('PHONE_EXT').AsString := '1702'; NewDBF.FieldByName('JOB_CODE').AsString := 'CEO'; NewDBF.FieldByName('JOB_GRADE').AsInteger := 1; NewDBF.FieldByName('JOB_COUNTR').AsString := 'USA'; NewDBF.FieldByName('SALARY').AsFloat := 48000; end; 2: begin NewDBF.FieldByName('FIRST_NAME').AsString := 'Ivan'; NewDBF.FieldByName('LAST_NAME').AsString := 'Ishenin'; NewDBF.FieldByName('PHONE_EXT').AsString := '9802'; NewDBF.FieldByName('JOB_CODE').AsString := 'Eng'; NewDBF.FieldByName('JOB_GRADE').AsInteger := 2; NewDBF.FieldByName('JOB_COUNTR').AsString := 'Russia'; NewDBF.FieldByName('SALARY').AsFloat := 38000; end; 3: begin NewDBF.FieldByName('FIRST_NAME').AsString := 'Erin'; NewDBF.FieldByName('LAST_NAME').AsString := 'Powell'; NewDBF.FieldByName('PHONE_EXT').AsString := '1703'; NewDBF.FieldByName('JOB_CODE').AsString := 'Admin'; NewDBF.FieldByName('JOB_GRADE').AsInteger := 2; NewDBF.FieldByName('JOB_COUNTR').AsString := 'USA'; NewDBF.FieldByName('SALARY').AsFloat := 45368; end; 4: begin NewDBF.FieldByName('FIRST_NAME').AsString := 'Margaret'; NewDBF.FieldByName('LAST_NAME').AsString := 'Tetchy'; NewDBF.FieldByName('PHONE_EXT').AsString := '3804'; NewDBF.FieldByName('JOB_CODE').AsString := 'Eng'; NewDBF.FieldByName('JOB_GRADE').AsInteger := 3; NewDBF.FieldByName('JOB_COUNTR').AsString := 'England'; NewDBF.FieldByName('SALARY').AsFloat := 28045; end; 5: begin NewDBF.FieldByName('FIRST_NAME').AsString := 'Sergey'; NewDBF.FieldByName('LAST_NAME').AsString := 'Bron'; NewDBF.FieldByName('PHONE_EXT').AsString := '3807'; NewDBF.FieldByName('JOB_CODE').AsString := 'Admin'; NewDBF.FieldByName('JOB_GRADE').AsInteger := 3; NewDBF.FieldByName('JOB_COUNTR').AsString := 'England'; NewDBF.FieldByName('SALARY').AsFloat := 24468; end; end; NewDBF.Post; end; NewDBF.Close; finally NewDBF.Free; end; end; procedure GetDBFList(Results: TStringList); // Gets list of all .dbf files in a directory and its subdirectories. var r: TSearchRec; begin results.Clear; if FindFirst('*.dbf', faAnyFile - {$WARNINGS OFF} faVolumeID - faSymLink {$WARNINGS ON} , r) = 0 then begin repeat if (r.Attr and faDirectory) <> faDirectory then begin results.add(expandfilename(r.Name)); end; until (FindNext(r) <> 0); findclose(r); end; end; function BinFieldToHex(BinarySource: TField): string; // Convert binary field contents to strings with hexadecimal representation. // Useful for displaying binary field contents. var HexValue: PChar; begin Result := ''; HexValue := StrAlloc(Length(BinarySource.AsBytes)); try try BinToHex(PChar(BinarySource.AsBytes), HexValue, Length(BinarySource.AsBytes)); Result := 'size: ' + IntToStr(Length(BinarySource.AsBytes)) + '; hex: ' + HexValue; except on E: Exception do begin Result := 'exception: ' + E.ClassName + '/' + E.Message; end; end; finally StrDispose(HexValue); end; end; procedure PrintRecord(DBf: TDBf; RecordNumber: integer); // Prints contents of a record to screen var i: integer; begin writeln('Record ' + IntToStr(RecordNumber)); for i := 0 to DBf.Fields.Count - 1 do begin if DBF.fields[i].IsNull then writeln('Field ', DBf.Fields[i].FieldName, ' is ***NULL***') else if DBF.Fields[i].DataType in [ftVarBytes, ftBytes] then writeln('Field ', DBF.Fields[i].FieldName, ' has value: binary ' + BinFieldToHex(DBF.Fields[i])) else writeln('Field ', DBf.Fields[i].FieldName, ' has value: ' + DBf.fields[i].AsString); end; end; { TDBFTool } procedure TDBFTool.ExportDBF(var MyDbf: TDbf); // Exports recordset to another format depending on user selection var ExportFormatText: string; ExportSettings: TCustomExportFormatSettings; Exporter: TCustomFileExporter; begin ExportFormatText := UpperCase(GetOptionValue('exportformat')); try case ExportFormatText of 'ACCESS', 'MSACCESS': begin Exporter := TXMLXSDExporter.Create(nil); ExportSettings := TXMLXSDFormatSettings.Create(true); (ExportSettings as TXMLXSDFormatSettings).CreateXSD := true; (ExportSettings as TXMLXSDFormatSettings).ExportFormat := AccessCompatible; (ExportSettings as TXMLXSDFormatSettings).DecimalSeparator := '.'; Exporter.FileName := MyDBF.FilePathFull + ChangeFileExt(MyDBF.TableName, '.xml'); end; 'ADO', 'ADONET', 'ADO.NET': begin Exporter := TXMLXSDExporter.Create(nil); ExportSettings := TXMLXSDFormatSettings.Create(true); (ExportSettings as TXMLXSDFormatSettings).CreateXSD := true; (ExportSettings as TXMLXSDFormatSettings).ExportFormat := ADONETCompatible; (ExportSettings as TXMLXSDFormatSettings).DecimalSeparator := '.'; Exporter.FileName := MyDBF.FilePathFull + ChangeFileExt(MyDBF.TableName, '.xml'); end; 'CSVEXCEL', 'EXCELCSV', 'CREATIVYST': begin Exporter := TCSVExporter.Create(nil); ExportSettings := TCSVFormatSettings.Create(true); (ExportSettings as TCSVFormatSettings).RowDelimiter:=LineEnding; //todo: delimiter? Exporter.FileName := MyDBF.FilePathFull + ChangeFileExt(MyDBF.TableName, '.csv'); end; 'CSV', 'CSVRFC4180', 'CSVLIBRE', 'CSVLIBREOFFICE': begin Exporter := TCSVExporter.Create(nil); ExportSettings := TCSVFormatSettings.Create(true); (ExportSettings as TCSVFormatSettings).DecimalSeparator := '.'; (ExportSettings as TCSVFormatSettings).StringQuoteChar := '"'; Exporter.FileName := MyDBF.FilePathFull + ChangeFileExt(MyDBF.TableName, '.csv'); end; 'DATASET', 'DELPHI': begin Exporter := TXMLXSDExporter.Create(nil); ExportSettings := TXMLXSDFormatSettings.Create(true); (ExportSettings as TXMLXSDFormatSettings).ExportFormat := DelphiClientDataset; (ExportSettings as TXMLXSDFormatSettings).DecimalSeparator := '.'; Exporter.FileName := MyDBF.FilePathFull + ChangeFileExt(MyDBF.TableName, '.xml'); end; 'EXCEL', 'EXCELXML': begin Exporter := TXMLXSDExporter.Create(nil); ExportSettings := TXMLXSDFormatSettings.Create(true); (ExportSettings as TXMLXSDFormatSettings).ExportFormat := ExcelCompatible; (ExportSettings as TXMLXSDFormatSettings).DecimalSeparator := '.'; Exporter.FileName := MyDBF.FilePathFull + ChangeFileExt(MyDBF.TableName, '.xml'); end; 'JSON': begin Exporter := TSimpleJSONExporter.Create(nil); ExportSettings := TSimpleJSONFormatSettings.Create(true); Exporter.FileName := MyDBF.FilePathFull + ChangeFileExt(MyDBF.TableName, '.json'); end; 'SIMPLEXML', 'XML': begin Exporter := TSimpleXMLExporter.Create(nil); ExportSettings := TSimpleXMLFormatSettings.Create(true); Exporter.FileName := MyDBF.FilePathFull + ChangeFileExt(MyDBF.TableName, '.xml'); end; 'RTF': begin Exporter := TRTFExporter.Create(nil); ExportSettings := TSimpleXMLFormatSettings.Create(true); Exporter.FileName := MyDBF.FilePathFull + ChangeFileExt(MyDBF.TableName, '.rtf'); end; 'SQL': begin Exporter := TSQLExporter.Create(nil); ExportSettings := TSQLFormatSettings.Create(true); (ExportSettings as TSQLFormatSettings).QuoteChar := '"'; (ExportSettings as TSQLFormatSettings).DecimalSeparator := '.'; (ExportSettings as TSQLFormatSettings).TableName := ChangeFileExt(MyDBF.TableName,''); (ExportSettings as TSQLFormatSettings).DateFormat := 'yyyy"-"mm"-"dd'; //ISO 8601, yyyy-mm-dd (ExportSettings as TSQLFormatSettings).TimeFormat := 'hh":"nn":"ss'; //ISO 8601, hh:mm:ss; (ExportSettings as TSQLFormatSettings).DateTimeFormat := (ExportSettings as TSQLFormatSettings).DateFormat + '"T"' + (ExportSettings as TSQLFormatSettings).TimeFormat; //ISO 8601 Exporter.FileName := MyDBF.FilePathFull + ChangeFileExt(MyDBF.TableName, '.sql'); end; 'TEX', 'LATEX': begin Exporter := TTeXExporter.Create(nil); ExportSettings := TTeXExportFormatSettings.Create(true); Exporter.FileName := MyDBF.FilePathFull + ChangeFileExt(MyDBF.TableName, '.tex'); end; 'TEXT', 'FIXED', 'FIXEDTEXT': begin Exporter := TFixedLengthExporter.Create(nil); ExportSettings := nil; Exporter.FileName := MyDBF.FilePathFull + ChangeFileExt(MyDBF.TableName, '.txt'); end else begin writeln('***Error: Unknown export format ' + ExportFormatText + ' specified' + '. Aborting'); Exporter := nil; ExportSettings := nil; Terminate; Exit; end; end; if assigned(ExportSettings) then Exporter.FormatSettings := ExportSettings; Exporter.Dataset := MyDBF; MyDBF.First; // we've just read the last record - make sure export starts at beginning Exporter.Execute; writeln('Completed export to ' + Exporter.FileName); finally if assigned(Exporter) then Exporter.Free; if assigned(ExportSettings) then ExportSettings.Free; end; end; procedure TDBFTool.DoRun; var DBFs: TStringList; Demo: boolean; ErrorMsg: string; FileNo: integer; MyDbf: TDbf; RecCount: integer; TableLevel: integer; //todo: use it begin // quick check parameters ErrorMsg := CheckOptions('h', 'codepage: createdemo exportformat: help tablelevel:'); if ErrorMsg <> '' then begin ShowException(Exception.Create(ErrorMsg)); Terminate; Exit; end; // parse parameters if HasOption('h', 'help') then begin WriteHelp; Terminate; Exit; end; DBFs := TStringList.Create; try Demo := false; if HasOption('createdemo') then Demo := true; TableLevel := 4; //DBF if HasOption('tablelevel') then TableLevel := StrToIntDef(GetOptionValue('tablelevel'), 4); if Demo then begin try CreateDemoDBFs('', TableLevel); except on E: Exception do begin writeln('*** Error creating demo databases: ' + E.Message); Terminate; Exit; end; end; end; // Process all dbfs if no files specified if DBFs.Count = 0 then GetDBFList(DBFs); if DBFs.Count = 0 then writeln('Could not find any dbf files'); for FileNo := 0 to DBFs.Count - 1 do begin if not (fileexists(DBFs[FileNo])) then begin // for some reason, fpc trunk suddenly returns the directory as well... //writeln('Sorry, file ',DBFs[FileNo],' does not exist.'); break; end; MyDbf := TDbf.Create(nil); try try MyDbf.FilePath := ExtractFilePath(DBFs[FileNo]); MyDbf.TableName := ExtractFileName(DBFs[FileNo]); MyDbf.ReadOnly := true; writeln('*** Opening: ' + DBFs[FileNo]); MyDbf.Open; writeln('Database tablelevel: ' + IntToStr(MyDbf.TableLevel)); writeln('Database codepage: ' + IntToStr(MyDBF.CodePage)); RecCount := 1; while not (MyDbf.EOF) do begin PrintRecord(MyDBF, RecCount); MyDBF.Next; RecCount := RecCount + 1; writeln(''); end; if HasOption('exportformat') then begin try ExportDBF(MyDbf); except on E: Exception do begin writeln('*** Problem exporting file ', FileNo, ': ', E.Message); end; end; end; MyDbf.Close; except on E: Exception do begin writeln('*** Error reading file ', FileNo, ': ', E.Message); end; end; finally MyDbf.Free; end; end; finally DBFs.Free; end; // stop program loop Terminate; end; constructor TDBFTool.Create(TheOwner: TComponent); begin inherited Create(TheOwner); StopOnException := true; end; destructor TDBFTool.Destroy; begin inherited Destroy; end; procedure TDBFTool.WriteHelp; begin writeln('Usage: ', ExeName, ' -h'); writeln(' --createdemo create demo database'); writeln(' --tablelevel= optional: desired tablelevel for demo db'); writeln(' 3 DBase III'); writeln(' 4 DBase IV'); writeln(' 7 Visual DBase 7'); writeln(' 25 FoxPro 2.x'); writeln(' 30 Visual FoxPro'); writeln(' --exportformat= export dbfs to format. Format can be:'); writeln(' access Microsoft Access XML'); writeln(' adonet ADO.Net dataset'); writeln(' csvexcel Excel/Creativyst format CSV text file (with locale dependent output)'); writeln(' csvRFC4180 LibreOffice/RFC4180 format CSV text file'); writeln(' dataset Delphi dataset XML'); writeln(' excel Microsoft Excel XML'); writeln(' fixedtext Fixed length text file'); writeln(' json JSON file'); writeln(' rtf Rich Text Format'); writeln(' simplexml Simple XML'); writeln(' sql SQL insert statements'); writeln(' tex LaTeX file'); end; var Application: TDBFTool; begin Application := TDBFTool.Create(nil); Application.Title := 'DBFTool'; Application.Run; Application.Free; end.