Files
lazarus-ccr/components/fpspreadsheet/fpscsv.pas

347 lines
9.7 KiB
ObjectPascal
Raw Normal View History

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.