You've already forked lazarus-ccr
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3643 8e941d3f-bd1b-0410-a28a-d453659cc2b4
347 lines
9.7 KiB
ObjectPascal
347 lines
9.7 KiB
ObjectPascal
unit fpscsv;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils,
|
|
fpspreadsheet;
|
|
|
|
type
|
|
TsCSVReader = class(TsCustomSpreadReader)
|
|
private
|
|
FFormatSettings: TFormatSettings;
|
|
FRow, FCol: Cardinal;
|
|
FCellValue: String;
|
|
FWorksheetName: String;
|
|
protected
|
|
procedure ProcessCellValue(AStream: TStream);
|
|
procedure ReadBlank(AStream: TStream); override;
|
|
procedure ReadFormula(AStream: TStream); override;
|
|
procedure ReadLabel(AStream: TStream); override;
|
|
procedure ReadNumber(AStream: TStream); override;
|
|
public
|
|
constructor Create(AWorkbook: TsWorkbook); override;
|
|
procedure ReadFromFile(AFileName: String; AData: TsWorkbook); override;
|
|
procedure ReadFromStream(AStream: TStream; AData: TsWorkbook); override;
|
|
procedure ReadFromStrings(AStrings: TStrings; AData: TsWorkbook); override;
|
|
end;
|
|
|
|
TsCSVWriter = class(TsCustomSpreadWriter)
|
|
private
|
|
FFormatSettings: TFormatSettings;
|
|
|
|
protected
|
|
procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal;
|
|
ACell: PCell); override;
|
|
procedure WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: TDateTime; ACell: PCell); override;
|
|
procedure WriteFormula(AStream: TStream; const ARow, ACol: Cardinal;
|
|
ACell: PCell); override;
|
|
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: string; ACell: PCell); override;
|
|
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: double; ACell: PCell); override;
|
|
|
|
procedure WriteSheet(AStream: TStream; AWorksheet: TsWorksheet);
|
|
|
|
public
|
|
constructor Create(AWorkbook: TsWorkbook); override;
|
|
procedure WriteToStream(AStream: TStream); override;
|
|
procedure WriteToStrings(AStrings: TStrings); override;
|
|
end;
|
|
|
|
TsCSVParams = record
|
|
LineDelimiter: String; // LineEnding
|
|
ColDelimiter: Char; // ';', ',', TAB (#9)
|
|
QuoteChar: Char; // use #0 if strings are not quoted
|
|
NumberFormat: String; // if empty, numbers are formatted as in sheet
|
|
DateTimeFormat: String; // if empty, date/times are formatted as in sheet
|
|
DecimalSeparator: Char; // '.', ',', #0 if using workbook's formatsetting
|
|
SheetIndex: Integer; // -1 for all sheets
|
|
end;
|
|
|
|
var
|
|
CSVParams: TsCSVParams = (
|
|
LineDelimiter: ''; // is replaced by LineEnding at runtime
|
|
ColDelimiter: ';';
|
|
QuoteChar: '"';
|
|
NumberFormat: ''; // Use number format of worksheet
|
|
DateTimeFormat: ''; // Use DateTime format of worksheet
|
|
DecimalSeparator: '.';
|
|
SheetIndex: 0; // Store sheet #0
|
|
);
|
|
|
|
implementation
|
|
|
|
uses
|
|
StrUtils, DateUtils, fpsutils;
|
|
|
|
{ -----------------------------------------------------------------------------}
|
|
{ TsCSVReader }
|
|
{------------------------------------------------------------------------------}
|
|
constructor TsCSVReader.Create(AWorkbook: TsWorkbook);
|
|
begin
|
|
inherited Create(AWorkbook);
|
|
FFormatSettings := AWorkbook.FormatSettings;
|
|
FWorksheetName := 'Sheet1';
|
|
end;
|
|
|
|
procedure TsCSVReader.ProcessCellValue(AStream: TStream);
|
|
begin
|
|
if FCellValue = '' then
|
|
ReadBlank(AStream)
|
|
else
|
|
if (Length(FCellValue) > 1) and (
|
|
((FCellValue[1] = '"') and (FCellValue[Length(FCellValue)] = '"'))
|
|
or
|
|
(not (CSVParams.QuoteChar in [#0, '"']) and (FCellValue[1] = CSVParams.QuoteChar)
|
|
and (FCellValue[Length(FCellValue)] = CSVParams.QuoteChar))
|
|
) then
|
|
begin
|
|
Delete(FCellValue, Length(FCellValue), 1);
|
|
Delete(FCellValue, 1, 1);
|
|
ReadLabel(AStream);
|
|
end else
|
|
ReadNumber(AStream);
|
|
end;
|
|
|
|
procedure TsCSVReader.ReadBlank(AStream: TStream);
|
|
begin
|
|
// We could write a blank cell, but since CSV does not support formatting
|
|
// this would be a waste of memory. --> Just do nothing
|
|
end;
|
|
|
|
procedure TsCSVReader.ReadFormula(AStream: TStream);
|
|
begin
|
|
// Nothing to do - CSV does not support formulas
|
|
end;
|
|
|
|
procedure TsCSVReader.ReadFromFile(AFileName: String; AData: TsWorkbook);
|
|
begin
|
|
FWorksheetName := ChangeFileExt(ExtractFileName(AFileName), '');
|
|
inherited;
|
|
end;
|
|
|
|
procedure TsCSVReader.ReadFromStream(AStream: TStream; AData: TsWorkbook);
|
|
var
|
|
n: Int64;
|
|
ch: Char;
|
|
nextch: Char;
|
|
begin
|
|
FWorkbook := AData;
|
|
FWorksheet := AData.AddWorksheet(FWorksheetName);
|
|
n := AStream.Size;
|
|
FCellValue := '';
|
|
FRow := 0;
|
|
FCol := 0;
|
|
while AStream.Position < n do begin
|
|
ch := char(AStream.ReadByte);
|
|
if ch = CSVParams.ColDelimiter then begin
|
|
// End of column reached
|
|
ProcessCellValue(AStream);
|
|
inc(FCol);
|
|
FCellValue := '';
|
|
end else
|
|
if (ch = #13) or (ch = #10) then begin
|
|
// End of row reached
|
|
ProcessCellValue(AStream);
|
|
inc(FRow);
|
|
FCol := 0;
|
|
FCellValue := '';
|
|
|
|
// look for CR+LF: if true, skip next byte
|
|
if AStream.Position+1 < n then begin
|
|
nextch := char(AStream.ReadByte);
|
|
if ((ch = #13) and (nextch <> #10)) then
|
|
AStream.Position := AStream.Position - 1; // re-read nextchar in next loop
|
|
end;
|
|
end else
|
|
FCellValue := FCellValue + ch;
|
|
end;
|
|
end;
|
|
|
|
procedure TsCSVReader.ReadFromStrings(AStrings: TStrings; AData: TsWorkbook);
|
|
var
|
|
stream: TStringStream;
|
|
begin
|
|
stream := TStringStream.Create(AStrings.Text);
|
|
try
|
|
ReadFromStream(stream, AData);
|
|
finally
|
|
stream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TsCSVReader.ReadLabel(AStream: TStream);
|
|
begin
|
|
Unused(AStream);
|
|
FWorksheet.WriteUTF8Text(FRow, FCol, FCellValue);
|
|
end;
|
|
|
|
procedure TsCSVReader.ReadNumber(AStream: TStream);
|
|
var
|
|
dbl: Double;
|
|
dt: TDateTime;
|
|
fs: TFormatSettings;
|
|
begin
|
|
Unused(AStream);
|
|
|
|
// Try as float
|
|
fs := FFormatSettings;
|
|
if CSVParams.DecimalSeparator <> #0 then
|
|
fs.DecimalSeparator := CSVParams.DecimalSeparator;
|
|
if TryStrToFloat(FCellValue, dbl, fs) then
|
|
begin
|
|
FWorksheet.WriteNumber(FRow, FCol, dbl);
|
|
FWorkbook.FormatSettings.DecimalSeparator := fs.DecimalSeparator;
|
|
exit;
|
|
end;
|
|
if fs.DecimalSeparator = '.'
|
|
then fs.DecimalSeparator := ','
|
|
else fs.DecimalSeparator := '.';
|
|
if TryStrToFloat(FCellValue, dbl, fs) then
|
|
begin
|
|
FWorksheet.WriteNumber(FRow, FCol, dbl);
|
|
FWorkbook.FormatSettings.DecimalSeparator := fs.DecimalSeparator;
|
|
exit;
|
|
end;
|
|
|
|
// Try as date/time
|
|
fs := FFormatSettings;
|
|
if TryStrToDateTime(FCellValue, dt, fs) then
|
|
begin
|
|
FWorksheet.WriteDateTime(FRow, FCol, dt);
|
|
exit;
|
|
end;
|
|
|
|
// Could not convert to float or date/time. Show at least as label.
|
|
FWorksheet.WriteUTF8Text(FRow, FCol, FCellValue);
|
|
end;
|
|
|
|
|
|
{ -----------------------------------------------------------------------------}
|
|
{ TsCSVWriter }
|
|
{------------------------------------------------------------------------------}
|
|
constructor TsCSVWriter.Create(AWorkbook: TsWorkbook);
|
|
begin
|
|
inherited Create(AWorkbook);
|
|
FFormatSettings := AWorkbook.FormatSettings;
|
|
if CSVParams.DecimalSeparator <> #0 then
|
|
FFormatSettings.DecimalSeparator := CSVParams.DecimalSeparator;
|
|
if CSVParams.LineDelimiter = '' then
|
|
CSVParams.LineDelimiter := LineEnding;
|
|
end;
|
|
|
|
procedure TsCSVWriter.WriteBlank(AStream: TStream; const ARow, ACol: Cardinal;
|
|
ACell: PCell);
|
|
begin
|
|
Unused(AStream);
|
|
Unused(ARow, ACol, ACell);
|
|
// nothing to do
|
|
end;
|
|
|
|
procedure TsCSVWriter.WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: TDateTime; ACell: PCell);
|
|
var
|
|
s: String;
|
|
begin
|
|
Unused(ARow, ACol);
|
|
if CSVParams.DateTimeFormat <> '' then
|
|
s := FormatDateTime(CSVParams.DateTimeFormat, AValue, FFormatSettings)
|
|
else
|
|
s := FWorksheet.ReadAsUTF8Text(ACell);
|
|
AppendToStream(AStream, s);
|
|
end;
|
|
|
|
procedure TsCSVWriter.WriteFormula(AStream: TStream; const ARow, ACol: Cardinal;
|
|
ACell: PCell);
|
|
begin
|
|
// no formulas in CSV
|
|
Unused(AStream);
|
|
Unused(ARow, ACol, AStream);
|
|
end;
|
|
|
|
procedure TsCSVWriter.WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: string; ACell: PCell);
|
|
var
|
|
s: String;
|
|
begin
|
|
Unused(ARow, ACol);
|
|
if ACell = nil then
|
|
exit;
|
|
s := ACell^.UTF8StringValue;
|
|
if CSVParams.QuoteChar <> #0 then
|
|
s := CSVParams.QuoteChar + s + CSVParams.QuoteChar;
|
|
AppendToStream(AStream, s);
|
|
end;
|
|
|
|
procedure TsCSVWriter.WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: double; ACell: PCell);
|
|
var
|
|
s: String;
|
|
mask: String;
|
|
begin
|
|
Unused(ARow, ACol);
|
|
if ACell = nil then
|
|
exit;
|
|
if CSVParams.NumberFormat <> '' then
|
|
s := Format(CSVParams.NumberFormat, [AValue], FFormatSettings)
|
|
else
|
|
s := FWorksheet.ReadAsUTF8Text(ACell);
|
|
AppendToStream(AStream, s);
|
|
end;
|
|
|
|
procedure TsCSVWriter.WriteSheet(AStream: TStream; AWorksheet: TsWorksheet);
|
|
var
|
|
r, c: Cardinal;
|
|
lastRow, lastCol: Cardinal;
|
|
cell: PCell;
|
|
begin
|
|
FWorksheet := AWorksheet;
|
|
lastRow := FWorksheet.GetLastOccupiedRowIndex;
|
|
lastCol := FWorksheet.GetLastOccupiedColIndex;
|
|
for r := 0 to lastRow do
|
|
for c := 0 to lastCol do begin
|
|
cell := FWorksheet.FindCell(r, c);
|
|
if cell <> nil then
|
|
WriteCellCallback(cell, AStream);
|
|
if c = lastCol then
|
|
AppendToStream(AStream, CSVParams.LineDelimiter)
|
|
else
|
|
AppendToStream(AStream, CSVParams.ColDelimiter);
|
|
end;
|
|
end;
|
|
|
|
procedure TsCSVWriter.WriteToStream(AStream: TStream);
|
|
var
|
|
n: Integer;
|
|
begin
|
|
if (CSVParams.SheetIndex >= 0) and (CSVParams.SheetIndex < FWorkbook.GetWorksheetCount)
|
|
then n := CSVParams.SheetIndex
|
|
else n := 0;
|
|
WriteSheet(AStream, FWorkbook.GetWorksheetByIndex(n));
|
|
end;
|
|
|
|
procedure TsCSVWriter.WriteToStrings(AStrings: TStrings);
|
|
var
|
|
stream: TStream;
|
|
begin
|
|
stream := TStringStream.Create('');
|
|
try
|
|
WriteToStream(stream);
|
|
stream.Position := 0;
|
|
AStrings.LoadFromStream(stream);
|
|
finally
|
|
stream.Free;
|
|
end;
|
|
end;
|
|
|
|
|
|
initialization
|
|
RegisterSpreadFormat(TsCSVReader, TsCSVWriter, sfCSV);
|
|
|
|
end.
|
|
|