fpspreadsheet: Add import of spreadsheet data to a database to the dbexport project (will be renamed to db_export_import). Functional, but not fully complete.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3433 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-08-05 23:13:39 +00:00
parent 74f5bf26f5
commit 8409952071
3 changed files with 322 additions and 82 deletions

View File

@ -9,7 +9,6 @@
<Title Value="dbexport"/>
<ResourceType Value="res"/>
<UseXPManifest Value="True"/>
<Icon Value="0"/>
</General>
<i18n>
<EnableI18N LFM="False"/>

View File

@ -1,32 +1,34 @@
object Form1: TForm1
Left = 340
Height = 229
Height = 236
Top = 154
Width = 404
Caption = 'Form1'
ClientHeight = 229
ClientWidth = 404
Width = 450
Caption = 'db_Export_Import'
ClientHeight = 236
ClientWidth = 450
OnCreate = FormCreate
OnDestroy = FormDestroy
LCLVersion = '1.3'
object PageControl: TPageControl
Left = 4
Height = 221
Height = 228
Top = 4
Width = 396
ActivePage = TabSheet1
Width = 442
ActivePage = TabImport
Align = alClient
BorderSpacing.Around = 4
TabIndex = 0
TabIndex = 2
TabOrder = 0
object TabSheet1: TTabSheet
OnChange = PageControlChange
object TabDataGenerator: TTabSheet
Caption = '1 - Create database'
ClientHeight = 193
ClientWidth = 388
ClientHeight = 200
ClientWidth = 434
object Label2: TLabel
Left = 4
Height = 15
Top = 4
Width = 380
Width = 426
Align = alTop
BorderSpacing.Around = 4
Caption = 'Create a database with random records'
@ -36,13 +38,13 @@ object Form1: TForm1
end
object Panel1: TPanel
Left = 0
Height = 170
Height = 177
Top = 23
Width = 388
Width = 434
Align = alClient
BevelOuter = bvNone
ClientHeight = 170
ClientWidth = 388
ClientHeight = 177
ClientWidth = 434
TabOrder = 0
object HeaderLabel1: TLabel
Left = 8
@ -62,9 +64,9 @@ object Form1: TForm1
Text = '10000'
end
object BtnCreateDbf: TButton
Left = 280
Left = 326
Height = 25
Top = 116
Top = 123
Width = 99
Anchors = [akRight, akBottom]
Caption = 'Run'
@ -75,15 +77,15 @@ object Form1: TForm1
Left = 0
Height = 3
Top = 0
Width = 388
Width = 434
Align = alTop
Shape = bsTopLine
end
object InfoLabel1: TLabel
Left = 4
Height = 15
Top = 151
Width = 380
Top = 158
Width = 426
Align = alBottom
BorderSpacing.Around = 4
Caption = 'InfoLabe1'
@ -99,15 +101,15 @@ object Form1: TForm1
end
end
end
object TabSheet2: TTabSheet
Caption = '2 - Write to spreadsheet'
ClientHeight = 193
ClientWidth = 388
object TabExport: TTabSheet
Caption = '2 - Export to spreadsheet'
ClientHeight = 200
ClientWidth = 434
object HeaderLabel2: TLabel
Left = 4
Height = 15
Top = 4
Width = 380
Width = 426
Align = alTop
BorderSpacing.Around = 4
Caption = 'Export database table to spreadsheet file'
@ -119,15 +121,15 @@ object Form1: TForm1
Left = 0
Height = 3
Top = 23
Width = 388
Width = 434
Align = alTop
Shape = bsTopLine
end
object InfoLabel2: TLabel
Left = 4
Height = 15
Top = 174
Width = 380
Top = 181
Width = 426
Align = alBottom
BorderSpacing.Around = 4
Caption = 'InfoLabel2'
@ -160,9 +162,9 @@ object Form1: TForm1
TabOrder = 0
end
object BtnExport: TButton
Left = 280
Left = 326
Height = 25
Top = 140
Top = 147
Width = 99
Anchors = [akRight, akBottom]
Caption = 'Run'
@ -170,5 +172,66 @@ object Form1: TForm1
TabOrder = 1
end
end
object TabImport: TTabSheet
Caption = '3 - Import from spreadsheet'
ClientHeight = 200
ClientWidth = 434
object HeaderLabel3: TLabel
Left = 4
Height = 15
Top = 4
Width = 426
Align = alTop
BorderSpacing.Around = 4
Caption = 'Import spreadsheet file in database table'
Font.Style = [fsBold]
ParentColor = False
ParentFont = False
end
object Bevel3: TBevel
Left = 0
Height = 3
Top = 23
Width = 434
Align = alTop
Shape = bsTopLine
end
object InfoLabel3: TLabel
Left = 4
Height = 15
Top = 181
Width = 426
Align = alBottom
BorderSpacing.Around = 4
Caption = 'InfoLabel3'
ParentColor = False
end
object BtnImport: TButton
Left = 326
Height = 25
Top = 147
Width = 99
Anchors = [akRight, akBottom]
Caption = 'Run'
OnClick = BtnImportClick
TabOrder = 0
end
object FileList: TListBox
Left = 6
Height = 121
Top = 56
Width = 292
ItemHeight = 0
TabOrder = 1
end
object Label3: TLabel
Left = 5
Height = 15
Top = 33
Width = 221
Caption = 'Select the spreadsheet file to be imported:'
ParentColor = False
end
end
end
end

View File

@ -15,30 +15,46 @@ type
TForm1 = class(TForm)
Bevel1: TBevel;
Bevel2: TBevel;
Bevel3: TBevel;
BtnCreateDbf: TButton;
BtnExport: TButton;
BtnImport: TButton;
EdRecordCount: TEdit;
HeaderLabel3: TLabel;
InfoLabel2: TLabel;
HeaderLabel1: TLabel;
InfoLabel1: TLabel;
InfoLabel3: TLabel;
Label1: TLabel;
Label2: TLabel;
HeaderLabel2: TLabel;
FileList: TListBox;
Label3: TLabel;
PageControl: TPageControl;
Panel1: TPanel;
RgFileFormat: TRadioGroup;
TabSheet1: TTabSheet;
TabSheet2: TTabSheet;
TabDataGenerator: TTabSheet;
TabExport: TTabSheet;
TabImport: TTabSheet;
procedure BtnCreateDbfClick(Sender: TObject);
procedure BtnExportClick(Sender: TObject);
procedure BtnImportClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure PageControlChange(Sender: TObject);
private
{ private declarations }
FDataset: TDbf;
FExportDataset: TDbf;
FImportDataset: TDbf;
FWorkbook: TsWorkbook;
FHeaderTemplateCell: PCell;
FDateTemplateCell: PCell;
// All data for the cells is generated here (out of the .dbf file)
FImportedFieldNames: TStringList;
FImportedFieldTypes: Array of TFieldType;
// For reading: all data for the database is generated here out of the spreadsheet file
procedure ReadCellDataHandler(Sender: TObject; ARow, ACol: Cardinal;
const ADataCell: PCell);
// For writing: all data for the cells is generated here (out of the .dbf file)
procedure WriteCellDataHandler(Sender: TObject; ARow, ACol: Cardinal;
var AValue: variant; var AStyleCell: PCell);
public
@ -65,66 +81,70 @@ const
CITIES: array[0..NUM_CITIES-1] of string = (
'New York', 'Los Angeles', 'San Francisco', 'Chicago', 'Miami',
'New Orleans', 'Washington', 'Boston', 'Seattle', 'Las Vegas');
TABLENAME = 'people.dbf'; //name for the dbf table
DATADIR = 'data'; //subdirectory where .dbf is stored
// File formats corresponding to the items of the RgFileFormat radiogroup
FILE_FORMATS: array[0..4] of TsSpreadsheetFormat = (
sfExcel2, sfExcel5, sfExcel8, sfOOXML, sfOpenDocument
);
// Spreadsheet files will get the TABLENAME and have one of these extensions.
FILE_EXT: array[0..4] of string = (
'_excel2.xls', '_excel5.xls', '.xls', '.xlsx', '.ods');
{ TForm1 }
{ This procedure creates a test database table with random data for us to play with }
{ This procedure creates a test dbf table with random data for us to play with }
procedure TForm1.BtnCreateDbfClick(Sender: TObject);
var
i: Integer;
startDate: TDate;
maxAge: Integer = 80 * 365;
begin
if FDataset <> nil then
FDataset.Free;
if FExportDataset <> nil then
FExportDataset.Free;
ForceDirectories(DATADIR);
startDate := EncodeDate(2010, 8, 1);
FDataset := TDbf.Create(self);
FDataset.FilePathFull := DATADIR + DirectorySeparator;
FDataset.TableName := TABLENAME;
FDataset.TableLevel := 4; //DBase IV; most widely used.
FDataset.FieldDefs.Add('Last name', ftString);
FDataset.FieldDefs.Add('First name', ftString);
FDataset.FieldDefs.Add('City', ftString);
FDataset.FieldDefs.Add('Birthday', ftDateTime);
DeleteFile(FDataset.FilePathFull + FDataset.TableName);
FDataset.CreateTable;
FExportDataset := TDbf.Create(self);
FExportDataset.FilePathFull := DATADIR + DirectorySeparator;
FExportDataset.TableName := TABLENAME;
FExportDataset.TableLevel := 4; //DBase IV; most widely used.
FExportDataset.FieldDefs.Add('Last name', ftString);
FExportDataset.FieldDefs.Add('First name', ftString);
FExportDataset.FieldDefs.Add('City', ftString);
FExportDataset.FieldDefs.Add('Birthday', ftDateTime);
DeleteFile(FExportDataset.FilePathFull + FExportDataset.TableName);
FExportDataset.CreateTable;
FDataset.Open;
FExportDataset.Open;
for i:=1 to StrToInt(EdRecordCount.Text) do begin
if (i mod 25) = 0 then
begin
InfoLabel1.Caption := Format('Adding record %d...', [i]);
Application.ProcessMessages;
end;
FDataset.Insert;
FDataset.FieldByName('Last name').AsString := LAST_NAMES[Random(NUM_LAST_NAMES)];
FDataset.FieldByName('First name').AsString := FIRST_NAMES[Random(NUM_FIRST_NAMES)];
FDataset.FieldByName('City').AsString := CITIES[Random(NUM_CITIES)];
FDataset.FieldByName('Birthday').AsDateTime := startDate - random(maxAge);
FExportDataset.Insert;
FExportDataset.FieldByName('Last name').AsString := LAST_NAMES[Random(NUM_LAST_NAMES)];
FExportDataset.FieldByName('First name').AsString := FIRST_NAMES[Random(NUM_FIRST_NAMES)];
FExportDataset.FieldByName('City').AsString := CITIES[Random(NUM_CITIES)];
FExportDataset.FieldByName('Birthday').AsDateTime := startDate - random(maxAge);
// creates a random date between "startDate" and "maxAge" days back
FDataset.Post;
FExportDataset.Post;
end;
FDataset.Close;
FExportDataset.Close;
InfoLabel1.Caption := Format('Done. Created file "%s" in folder "data".', [
FDataset.TableName, FDataset.FilePathFull
FExportDataset.TableName, FExportDataset.FilePathFull
]);
InfoLabel2.Caption := '';
InfoLabel3.Caption := '';
end;
procedure TForm1.BtnExportClick(Sender: TObject);
const
FILE_FORMATS: array[0..4] of TsSpreadsheetFormat = (
sfExcel2, sfExcel5, sfExcel8, sfOOXML, sfOpenDocument
);
EXT: array[0..4] of string = (
'_excel2.xls', '_excel5.xls', '.xls', '.xlsx', '.ods');
var
DataFileName: String;
worksheet: TsWorksheet;
@ -132,14 +152,14 @@ begin
InfoLabel2.Caption := '';
Application.ProcessMessages;
if FDataset = nil then
if FExportDataset = nil then
begin
FDataset := TDbf.Create(self);
FDataset.FilePathFull := DATADIR + DirectorySeparator;
FDataset.TableName := TABLENAME;
FExportDataset := TDbf.Create(self);
FExportDataset.FilePathFull := DATADIR + DirectorySeparator;
FExportDataset.TableName := TABLENAME;
end;
DataFileName := FDataset.FilePathFull + FDataset.TableName;
DataFileName := FExportDataset.FilePathFull + FExportDataset.TableName;
if not FileExists(DataFileName) then
begin
MessageDlg(Format('Database file "%s" not found. Please run "Create database" first.',
@ -147,11 +167,11 @@ begin
exit;
end;
FDataset.Open;
FExportDataset.Open;
FWorkbook := TsWorkbook.Create;
try
worksheet := FWorkbook.AddWorksheet(FDataset.TableName);
worksheet := FWorkbook.AddWorksheet(FExportDataset.TableName);
// Make header line frozen
worksheet.Options := worksheet.Options + [soHasFrozenPanes];
@ -176,45 +196,203 @@ begin
// FWorkbook.Options := FWorkbook.Options + [boVirtualMode, boBufStream];
FWorkbook.Options := FWorkbook.Options + [boVirtualMode];
FWorkbook.OnWriteCellData := @WriteCellDataHandler;
FWorkbook.VirtualRowCount := FDataset.RecordCount + 1; // +1 for the header line
FWorkbook.VirtualColCount := FDataset.FieldCount;
FWorkbook.VirtualRowCount := FExportDataset.RecordCount + 1; // +1 for the header line
FWorkbook.VirtualColCount := FExportDataset.FieldCount;
// Write
DataFileName := ChangeFileExt(DataFileName, EXT[RgFileFormat.ItemIndex]);
DataFileName := ChangeFileExt(DataFileName, FILE_EXT[RgFileFormat.ItemIndex]);
FWorkbook.WriteToFile(DataFileName, FILE_FORMATS[RgFileFormat.ItemIndex], true);
finally
FreeAndNil(FWorkbook);
end;
InfoLabel2.Caption := Format('Done. Database exported to file "%s" in folder "%s"',
[ChangeFileExt(FDataset.TableName, EXT[RgFileFormat.ItemIndex]), FDataset.FilePathFull]);
InfoLabel2.Caption := Format('Done. Database exported to file "%s" in folder "%s"', [
ChangeFileExt(FExportDataset.TableName, FILE_EXT[RgFileFormat.ItemIndex]),
FExportDataset.FilePathFull
]);
end;
{ This procedure imports the contents of the selected spreadsheet file into a
new dbf database file using virtual mode. }
procedure TForm1.BtnImportClick(Sender: TObject);
var
DataFileName: String;
fmt: TsSpreadsheetFormat;
ext: String;
begin
if FileList.ItemIndex = -1 then begin
MessageDlg('Please select a file in the listbox.', mtInformation, [mbOK], 0);
exit;
end;
// Determine the file format from the filename - just to avoid the annoying
// exceptions that occur for Excel2 and Excel5.
DataFileName := FileList.Items[FileList.ItemIndex];
ext := lowercase(ExtractFileExt(DataFileName));
if ext = '.xls' then begin
if pos(FILE_EXT[0], DataFileName) > 0 then
fmt := sfExcel2
else
if pos(FILE_EXT[1], DataFileName) > 0 then
fmt := sfExcel5
else
fmt := sfExcel8;
end else
if ext = '.xlsx' then
fmt := sfOOXML
else
if ext = '.ods' then
fmt := sfOpenDocument
else begin
MessageDlg('Unknown spreadsheet file format.', mtError, [mbOK], 0);
exit;
end;
DataFileName := DATADIR + DirectorySeparator + DataFileName;
// Prepare dbf table for the spreadsheet data to be imported
if FImportDataset <> nil then
FImportDataset.Free;
FImportDataset := TDbf.Create(self);
FImportDataset.FilePathFull := DATADIR + DirectorySeparator;
FImportDataset.TableName := 'imported_' + TABLENAME;
FImportDataset.TableLevel := 4; //DBase IV; most widely used.
DeleteFile(FImportDataset.FilePathFull + FImportDataset.TableName);
// The stringlist will temporarily store the field names ...
if FImportedFieldNames = nil then
FImportedFieldNames := TStringList.Create;
FImportedFieldNames.Clear;
// ... and this array stores the field types until we have all information
// to create the dbf table.
SetLength(FImportedFieldTypes, 0);
// Create the workbook and activate virtual mode
FWorkbook := TsWorkbook.Create;
try
FWorkbook.Options := FWorkbook.Options + [boVirtualMode];
FWorkbook.OnReadCellData := @ReadCellDataHandler;
// Read the data from the spreadsheet file transparently into the dbf file
// The data are not permanently available in the worksheet and do occupy
// memory there - this is virtual mode.
FWorkbook.ReadFromFile(DataFilename, fmt);
finally
FWorkbook.Free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
InfoLabel1.Caption := '';
InfoLabel2.Caption := '';
InfoLabel3.Caption := '';
PageControl.ActivePageIndex := 0;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(FImportedFieldNames);
end;
{ When we activate the "Import" page of the pagecontrol we read the data
folder and collect all spreadsheet files available in a list box. The user
will have to select the one to be converted to dbf. }
procedure TForm1.PageControlChange(Sender: TObject);
var
sr: TSearchRec;
ext: String;
begin
if PageControl.ActivePage = TabImport then begin
FileList.Clear;
if FindFirst(DATADIR + DirectorySeparator + ChangeFileExt(TABLENAME, '') + '*.*', faAnyFile, sr) = 0
then begin
repeat
if (sr.Name = '.') or (sr.Name = '..') then
Continue;
ext := lowercase(ExtractFileExt(sr.Name));
if (ext = '.xls') or (ext = '.xlsx') or (ext = '.ods') then
FileList.Items.Add(sr.Name);
until FindNext(sr) <> 0;
FindClose(sr);
end;
end;
end;
{ This is the event handler for reading a spreadsheet file in virtual mode.
The data are not stored in the worksheet and exist only temporarily.
This event handler picks the data and posts them to the database table.
Note that we do not make many assumptions on the data structure here. Therefore
we have to buffer the first two rows of the spreadsheet file until the
structure of the table is clear. }
procedure TForm1.ReadCellDataHandler(Sender: TObject; ARow, Acol: Cardinal;
const ADataCell: PCell);
var
i: Integer;
begin
// The first row (index 0) holds the field names. We temporarily store the
// field names in a string list because we don't know the data types of the
// cell before we have not read the second row (index 1).
if ARow = 0 then begin
// We know that the first row contains string cells -> no further checks.
FImportedFieldNames.Add(ADataCell^.UTF8StringValue);
end
else
// We have to buffer the second row (index 1) as well. When it is fully read
// we can put everything together and create the dfb table.
if ARow = 1 then begin
if Length(FImportedFieldTypes) = 0 then
SetLength(FImportedFieldTypes, FImportedFieldNames.Count);
case ADataCell^.ContentType of
cctNumber : FImportedFieldTypes[ACol] := ftFloat;
cctUTF8String: FImportedFieldTypes[ACol] := ftString;
cctDateTime : FImportedFieldTypes[ACol] := ftDate;
end;
// All field types are known --> we create the table
if ACol = High(FImportedFieldTypes) then begin
for i:=0 to High(FImportedFieldTypes) do
FImportDataset.FieldDefs.Add(FImportedFieldNames[i], FImportedFieldTypes[i]);
DeleteFile(FImportDataset.FilePathFull + FImportDataset.TableName);
FImportDataset.CreateTable;
FImportDataset.Open;
end;
end
else
begin
// Now that we know everything we can add the data to the table
if ACol = 0 then
FImportDataset.Insert;
case ADataCell^.ContentType of
cctNumber : FImportDataSet.Fields[Acol].AsFloat := ADataCell^.NumberValue;
cctUTF8String: FImportDataset.Fields[Acol].AsString := ADataCell^.UTF8StringValue;
cctDateTime : FImportDataset.Fields[ACol].AsDateTime := ADataCell^.DateTimeValue;
end;
if ACol = High(FImportedFieldTypes) then
FImportDataset.Post;
end;
end;
{ This is the event handler for exporting a database file to spreadsheet format
in virtual mode. Data are not written into the worksheet, they exist only
temporarily. }
procedure TForm1.WriteCellDataHandler(Sender: TObject; ARow, ACol: Cardinal;
var AValue: variant; var AStyleCell: PCell);
begin
// Header line: we want to show the field names here.
if ARow = 0 then
begin
AValue := FDataset.Fields[ACol].FieldName;
AValue := FExportDataset.Fields[ACol].FieldName;
AStyleCell := FHeaderTemplateCell;
FDataset.First;
FExportDataset.First;
end
else
begin
AValue := FDataset.Fields[ACol].Value;
if FDataset.Fields[ACol].DataType = ftDate then
AValue := FExportDataset.Fields[ACol].Value;
if FExportDataset.Fields[ACol].DataType = ftDate then
AStyleCell := FDateTemplateCell;
if ACol = FWorkbook.VirtualColCount-1 then
begin
FDataset.Next;
FExportDataset.Next;
if (ARow-1) mod 25 = 0 then
begin
InfoLabel1.Caption := Format('Writing record %d...', [ARow-1]);