lazarus-ccr/components/fpspreadsheet/source/export/fpsexport.pas

381 lines
12 KiB
ObjectPascal

unit fpsexport;
{
Exports dataset to spreadsheet/tabular format
either XLS (Excel), XLSX (Excel), ODS (OpenOffice/LibreOffice)
or wikitable
}
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, db,
{%H-}fpsallformats, fpspreadsheet, fpstypes, fpsstrings, fpdbexport;
Type
{ TFPSExportFieldItem }
TFPSExportFieldItem = Class(TExportFieldItem)
private
FDestField: TField;
protected
property DestField : TField read FDestField;
end;
TExportFormat = (efXLS {BIFF8},efXLSX,efODS,efWikiTable);
{ TFPSExportFormatSettings }
{@@ Specific export settings that apply to spreadsheet export}
TFPSExportFormatSettings = class(TExportFormatSettings)
private
FExportFormat: TExportFormat;
FHeaderRow: boolean;
FSheetName: String;
public
procedure Assign(Source : TPersistent); override;
procedure InitSettings; override;
published
{@@ File format for the export }
property ExportFormat: TExportFormat read FExportFormat write FExportFormat;
{@@ Flag that determines whether to write the field list to the first
row of the spreadsheet }
property HeaderRow: boolean read FHeaderRow write FHeaderRow default false;
{@@ Sheet name }
property SheetName: String read FSheetName write FSheetName;
end;
{ TGetSheetNameEvent }
TsGetSheetNameEvent = procedure (Sender: TObject; ASheetIndex: Integer;
var ASheetName: String) of object;
{ TCustomFPSExport }
TCustomFPSExport = Class(TCustomDatasetExporter)
private
FRow: cardinal; //current row in exported spreadsheet
FSpreadsheet: TsWorkbook;
FSheet: TsWorksheet;
FFileName: string;
FMultipleSheets: Boolean;
FOnGetSheetName: TsGetSheetNameEvent;
function CalcSheetNameMask(const AMask: String): String;
function CalcUniqueSheetName(const AMask: String): String;
function GetSettings: TFPSExportFormatSettings;
procedure SaveWorkbook;
procedure SetSettings(const AValue: TFPSExportFormatSettings);
protected
function CreateExportFields: TExportFields; override;
function CreateFormatSettings: TCustomExportFormatSettings; override;
procedure DoBeforeExecute; override;
procedure DoAfterExecute; override;
procedure DoDataHeader; override;
procedure DoDataRowEnd; override;
function DoGetSheetName: String; virtual;
procedure ExportField(EF : TExportFieldItem); override;
property FileName: String read FFileName write FFileName;
property Workbook: TsWorkbook read FSpreadsheet;
property RestorePosition default true;
property OnGetSheetName: TsGetSheetNameEvent read FOnGetSheetName write FOnGetSheetName;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure WriteExportFile;
{@@ Settings for the export. Note: a lot of generic settings are preent
that are not relevant for this export, e.g. decimal point settings }
property FormatSettings: TFPSExportFormatSettings read GetSettings write SetSettings;
{@@ MultipleSheets: export several datasets to multiple sheets in
the sasme file. Otherwise a single-sheet workbook is created. }
property MultipleSheets: Boolean read FMultipleSheets write FMultipleSheets default false;
end;
{ TFPSExport }
{@@ Export class allowing dataset export to spreadsheet(like) file }
TFPSExport = Class(TCustomFPSExport)
published
{@@ Destination filename }
property FileName;
{@@ Source dataset }
property Dataset;
{@@ Fields to be exported }
property ExportFields;
{@@ Settings - e.g. export format - to be used }
property FormatSettings;
{@@ Export starting from current record or beginning. }
property FromCurrent;
{@@ Flag indicating whether to return to current dataset position after export }
property RestorePosition;
{@@ Procedure to run when exporting a row }
property OnExportRow;
{@@ Determines the name of the worksheet }
property OnGetSheetName;
end;
{@@ Register export format with fpsdbexport so it can be dynamically used }
procedure RegisterFPSExportFormat;
{@@ Remove registration. Opposite to RegisterFPSExportFormat }
procedure UnRegisterFPSExportFormat;
const
SFPSExport = 'xls';
SPFSExtension = '.xls'; //Add others? Doesn't seem to fit other dxport units
implementation
{ TCustomFPSExport }
constructor TCustomFPSExport.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
RestorePosition := true;
end;
destructor TCustomFPSExport.Destroy;
begin
// Last chance to save file if calling WriteExportFile has been forgotten
// in case of multiple sheets.
if FMultipleSheets and (FSpreadsheet <> nil) then
begin
SaveWorkbook;
FreeAndNil(FSpreadsheet);
end;
inherited;
end;
function TCustomFPSExport.GetSettings: TFPSExportFormatSettings;
begin
result:=TFPSExportFormatSettings(Inherited FormatSettings);
end;
procedure TCustomFPSExport.SetSettings
(const AValue: TFPSExportFormatSettings);
begin
Inherited FormatSettings.Assign(AValue);
end;
function TCustomFPSExport.CreateFormatSettings: TCustomExportFormatSettings;
begin
result:=TFPSExportFormatSettings.Create(True);
end;
function TCustomFPSExport.CreateExportFields: TExportFields;
begin
result:=TExportFields.Create(TFPSExportFieldItem);
end;
procedure TCustomFPSExport.DoBeforeExecute;
begin
Inherited;
if FFileName='' then
Raise EDataExporter.Create(rsExportFileIsRequired);
if (not RestorePosition) and FMultipleSheets then
Raise EDataExporter.Create(rsMultipleSheetsOnlyWithRestorePosition);
if (not FMultipleSheets) or (FSpreadsheet = nil) then
begin
FSpreadsheet:=TsWorkbook.Create;
FSpreadsheet.Options:=FSpreadsheet.Options+[boBufStream];
// For extra performance. Note that virtual mode is not an option
// due to the data export determining flow of the program.
end;
FSheet:=FSpreadsheet.AddWorksheet(DoGetSheetName);
FRow:=0;
end;
procedure TCustomFPSExport.DoDataHeader;
var
i: integer;
begin
if FormatSettings.FHeaderRow then
begin
for i:=0 to ExportFields.Count-1 do
begin
FSheet.WriteText(FRow,i,ExportFields[i].ExportedName);
end;
inc(FRow);
end;
inherited DoDataHeader;
end;
{ Writes the workbook populated during the export process to file }
procedure TCustomFPSExport.SaveWorkbook;
begin
FRow:=0;
// Overwrite existing file similar to how dbf export does it
case Formatsettings.ExportFormat of
efXLS:
FSpreadSheet.WriteToFile(FFileName,sfExcel8,true);
efXLSX:
FSpreadsheet.WriteToFile(FFilename,sfOOXML,true);
efODS:
FSpreadSheet.WriteToFile(FFileName,sfOpenDocument,true);
efWikiTable:
FSpreadSheet.WriteToFile(FFileName,sfWikitable_wikimedia,true);
else
raise Exception.Create('[TCustomFPSExport.SaveWorkbook] ExportFormat unknown');
end;
end;
procedure TCustomFPSExport.DoAfterExecute;
begin
if not FMultipleSheets then
begin
SaveWorkbook;
FreeAndNil(FSpreadsheet); // Don't free FSheet; done by FSpreadsheet
end;
// Multi-sheet workbooks are written when WriteExportFile is called.
inherited;
end;
procedure TCustomFPSExport.DoDataRowEnd;
begin
FRow:=FRow+1;
end;
function TCustomFPSExport.CalcSheetNameMask(const AMask: String): String;
begin
Result := AMask;
// No %d in the mask string
if pos('%d', Result) = 0 then
begin
// If the mask string is already used we'll add a number to the sheet name
if not FSpreadsheet.ValidWorksheetName(Result) then
begin
Result := AMask + '%d';
exit;
end;
end;
end;
function TCustomFPSExport.CalcUniqueSheetName(const AMask: String): String;
var
i: Integer;
begin
if pos('%d', AMask) > 0 then
begin
i := 0;
repeat
inc(i);
Result := Format(AMask, [i]);
until (FSpreadsheet.GetWorksheetByName(Result) = nil);
end else
Result := AMask;
if not FSpreadsheet.ValidWorksheetName(Result) then
Raise EDataExporter.CreateFmt(rsInvalidWorksheetName, [Result]);
end;
{ Method which provides the name of the worksheet into which the dataset is to
be exported. There are several cases:
(1) Use the name defined in the FormatSettings.
(2) Provide the name in an event handler for OnGetSheetname.
The name provided from these sources can contain a %d placeholder which will
be replaced by a number such that the sheet name is unique.
If it does not contain a %d then a %d may be added if needed to get a unique
sheet name. }
function TCustomFPSExport.DoGetSheetName: String;
var
mask: String;
begin
mask := CalcSheetNameMask(FormatSettings.SheetName);
Result := CalcUniqueSheetName(mask);
if Assigned(FOnGetSheetName) then
begin
FOnGetSheetName(Self, FSpreadsheet.GetWorksheetCount, mask);
Result := CalcUniqueSheetName(mask);
end;
end;
procedure TCustomFPSExport.ExportField(EF: TExportFieldItem);
var
F : TFPSExportFieldItem;
dt: TDateTime;
begin
F := EF as TFPSExportFieldItem;
with F do
begin
// Export depending on field datatype;
// Fall back to string if unknown datatype
If Field.IsNull then
FSheet.WriteBlank(FRow, EF.Index)
else if Field.Datatype in (IntFieldTypes+[ftAutoInc,ftLargeInt]) then
FSheet.WriteNumber(FRow, EF.Index,Field.AsInteger)
else if Field.Datatype in [ftBCD,ftFloat,ftFMTBcd] then
FSheet.WriteNumber(FRow, EF.Index, Field.AsFloat)
else if Field.Datatype in [ftCurrency] then
FSheet.WriteCurrency(FRow, EF.Index, Field.AsFloat)
else if Field.DataType in [ftString,ftFixedChar] then
FSheet.WriteText(FRow, EF.Index, Field.AsString)
else if (Field.DataType in ([ftWideMemo,ftWideString,ftFixedWideChar]+BlobFieldTypes)) then
FSheet.WriteText(FRow, EF.Index, UTF8Encode(Field.AsWideString))
{ Note: we test for the wide text fields before the MemoFieldTypes, in order to
let ftWideMemo end up at the right place }
else if Field.DataType in MemoFieldTypes then
FSheet.WriteText(FRow, EF.Index, Field.AsString)
else if Field.DataType=ftBoolean then
FSheet.WriteBoolValue(FRow, EF.Index, Field.AsBoolean)
else if Field.DataType in DateFieldTypes then
case Field.DataType of
ftDate: FSheet.WriteDateTime(FRow, EF.Index, Field.AsDateTime, nfShortDate);
ftTime: FSheet.WriteDateTime(FRow, EF.Index, Field.AsDatetime, nfLongTime);
else // try to guess best format if Field.DataType is ftDateTime
dt := Field.AsDateTime;
if dt < 1.0 then
FSheet.WriteDateTime(FRow, EF.Index, Field.AsDateTime, nfLongTime)
else if frac(dt) = 0 then
FSheet.WriteDateTime(FRow, EF.Index, Field.AsDateTime, nfShortDate)
else
FSheet.WriteDateTime(FRow, EF.Index, Field.AsDateTime, nfShortDateTime);
end
else //fallback to string
FSheet.WriteText(FRow, EF.Index, Field.AsString);
end;
end;
procedure TCustomFPSExport.WriteExportFile;
begin
if FMultipleSheets then begin
SaveWorkbook;
FreeAndNil(FSpreadsheet);
// Don't free FSheet; done by FSpreadsheet
end;
end;
procedure RegisterFPSExportFormat;
begin
ExportFormats.RegisterExportFormat(SFPSExport,rsFPSExportDescription,SPFSExtension,TFPSExport);
end;
procedure UnRegisterFPSExportFormat;
begin
ExportFormats.UnregisterExportFormat(SFPSExport);
end;
{ TFPSExportFormatSettings }
procedure TFPSExportFormatSettings.Assign(Source: TPersistent);
var
FS : TFPSExportFormatSettings;
begin
If Source is TFPSExportFormatSettings then
begin
FS:=Source as TFPSExportFormatSettings;
HeaderRow := FS.HeaderRow;
ExportFormat := FS.ExportFormat;
SheetName := FS.SheetName;
end;
inherited Assign(Source);
end;
procedure TFPSExportFormatSettings.InitSettings;
begin
inherited InitSettings;
FExportFormat := efXLS; //often used format
FSheetName := 'Sheet';
end;
end.