fpspreadsheet: Redo CSVParams for CSV reader and writer

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3652 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-10-13 22:28:38 +00:00
parent b38a85d511
commit 05b3519063
3 changed files with 194 additions and 107 deletions

View File

@@ -29,7 +29,8 @@ begin
WriteLn('Opening input file ', InputFilename); WriteLn('Opening input file ', InputFilename);
CSVParams.ColDelimiter := #9; CSVParams.Delimiter := #9;
CSVParams.QuoteChar := '''';
// Create the spreadsheet // Create the spreadsheet
MyWorkbook := TsWorkbook.Create; MyWorkbook := TsWorkbook.Create;

View File

@@ -309,9 +309,8 @@ begin
lRow.Height := 2; // 2 lines lRow.Height := 2; // 2 lines
MyWorksheet.WriteRowInfo(6, lRow); MyWorksheet.WriteRowInfo(6, lRow);
CSVParams.ColDelimiter := #9; CSVParams.Delimiter := #9;
CSVParams.DecimalSeparator := '.'; CSVParams.FormatSettings.DecimalSeparator := '.';
CSVParams.DateTimeFormat := 'YYYYMMDD-HHNNSS';
CSVParams.NumberFormat := '%.9f'; CSVParams.NumberFormat := '%.9f';
CSVParams.QuoteChar := ''''; CSVParams.QuoteChar := '''';

View File

@@ -11,12 +11,12 @@ uses
type type
TsCSVReader = class(TsCustomSpreadReader) TsCSVReader = class(TsCustomSpreadReader)
private private
FFormatSettings: TFormatSettings;
FRow, FCol: Cardinal;
FCellValue: String;
FWorksheetName: String; FWorksheetName: String;
function IsDateTime(AText: String; out ADateTime: TDateTime): Boolean;
function IsNumber(AText: String; out ANumber: Double): Boolean;
function IsQuotedText(var AText: String): Boolean;
procedure ReadCellValue(ARow, ACol: Cardinal; AText: String);
protected protected
procedure ProcessCellValue(AStream: TStream);
procedure ReadBlank(AStream: TStream); override; procedure ReadBlank(AStream: TStream); override;
procedure ReadFormula(AStream: TStream); override; procedure ReadFormula(AStream: TStream); override;
procedure ReadLabel(AStream: TStream); override; procedure ReadLabel(AStream: TStream); override;
@@ -30,8 +30,7 @@ type
TsCSVWriter = class(TsCustomSpreadWriter) TsCSVWriter = class(TsCustomSpreadWriter)
private private
FFormatSettings: TFormatSettings; FLineEnding: String;
protected protected
procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal;
ACell: PCell); override; ACell: PCell); override;
@@ -43,7 +42,6 @@ type
const AValue: string; ACell: PCell); override; const AValue: string; ACell: PCell); override;
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
const AValue: double; ACell: PCell); override; const AValue: double; ACell: PCell); override;
procedure WriteSheet(AStream: TStream; AWorksheet: TsWorksheet); procedure WriteSheet(AStream: TStream; AWorksheet: TsWorksheet);
public public
@@ -52,70 +50,195 @@ type
procedure WriteToStrings(AStrings: TStrings); override; procedure WriteToStrings(AStrings: TStrings); override;
end; end;
TsCSVLineEnding = (leSystem, leCRLF, leCR, leLF);
TsCSVParams = record TsCSVParams = record
LineDelimiter: String; // LineEnding SheetIndex: Integer;
ColDelimiter: Char; // ';', ',', TAB (#9) LineEnding: TsCSVLineEnding;
QuoteChar: Char; // use #0 if strings are not quoted Delimiter: Char;
NumberFormat: String; // if empty, numbers are formatted as in sheet QuoteChar: Char;
DateTimeFormat: String; // if empty, date/times are formatted as in sheet NumberFormat: String;
DecimalSeparator: Char; // '.', ',', #0 if using workbook's formatsetting FormatSettings: TFormatSettings;
SheetIndex: Integer; // -1 for all sheets
end; end;
var var
CSVParams: TsCSVParams = ( CSVParams: TsCSVParams = (
LineDelimiter: ''; // is replaced by LineEnding at runtime SheetIndex: 0; // Store sheet #0 by default
ColDelimiter: ';'; LineEnding: leSystem; // Write system lineending, read any
QuoteChar: '"'; Delimiter: ';'; // Column delimiter
NumberFormat: ''; // Use number format of worksheet QuoteChar: '"'; // for quoted strings
DateTimeFormat: ''; // Use DateTime format of worksheet NumberFormat: ''; // if empty write numbers like in sheet, otherwise use this format
DecimalSeparator: '.';
SheetIndex: 0; // Store sheet #0
); );
implementation implementation
uses uses
StrUtils, DateUtils, fpsutils; StrUtils, DateUtils, fpsutils;
{ Initializes the FormatSettings of the CSVParams to default values which
can be replaced by the FormatSettings of the workbook's FormatSettings }
procedure InitCSVFormatSettings;
var
i: Integer;
begin
with CSVParams.FormatSettings do
begin
CurrencyFormat := Byte(-1);
NegCurrFormat := Byte(-1);
ThousandSeparator := #0;
DecimalSeparator := #0;
CurrencyDecimals := Byte(-1);
DateSeparator := #0;
TimeSeparator := #0;
ListSeparator := #0;
CurrencyString := '';
ShortDateFormat := '';
LongDateFormat := '';
TimeAMString := '';
TimePMString := '';
ShortTimeFormat := '';
LongTimeFormat := '';
for i:=1 to 12 do
begin
ShortMonthNames[i] := '';
LongMonthNames[i] := '';
end;
for i:=1 to 7 do
begin
ShortDayNames[i] := '';
LongDayNames[i] := '';
end;
TwoDigitYearCenturyWindow := Word(-1);
end;
end;
procedure ReplaceFormatSettings(var AFormatSettings: TFormatSettings;
const ADefaultFormats: TFormatSettings);
var
i: Integer;
begin
if AFormatSettings.CurrencyFormat = Byte(-1) then
AFormatSettings.CurrencyFormat := ADefaultFormats.CurrencyFormat;
if AFormatSettings.NegCurrFormat = Byte(-1) then
AFormatSettings.NegCurrFormat := ADefaultFormats.NegCurrFormat;
if AFormatSettings.ThousandSeparator = #0 then
AFormatSettings.ThousandSeparator := ADefaultFormats.ThousandSeparator;
if AFormatSettings.DecimalSeparator = #0 then
AFormatSettings.DecimalSeparator := ADefaultFormats.DecimalSeparator;
if AFormatSettings.CurrencyDecimals = Byte(-1) then
AFormatSettings.CurrencyDecimals := ADefaultFormats.CurrencyDecimals;
if AFormatSettings.DateSeparator = #0 then
AFormatSettings.DateSeparator := ADefaultFormats.DateSeparator;
if AFormatSettings.TimeSeparator = #0 then
AFormatSettings.TimeSeparator := ADefaultFormats.TimeSeparator;
if AFormatSettings.ListSeparator = #0 then
AFormatSettings.ListSeparator := ADefaultFormats.ListSeparator;
if AFormatSettings.CurrencyString = '' then
AFormatSettings.CurrencyString := ADefaultFormats.CurrencyString;
if AFormatSettings.ShortDateFormat = '' then
AFormatSettings.ShortDateFormat := ADefaultFormats.ShortDateFormat;
if AFormatSettings.LongDateFormat = '' then
AFormatSettings.LongDateFormat := ADefaultFormats.LongDateFormat;
if AFormatSettings.ShortTimeFormat = '' then
AFormatSettings.ShortTimeFormat := ADefaultFormats.ShortTimeFormat;
if AFormatSettings.LongTimeFormat = '' then
AFormatSettings.LongTimeFormat := ADefaultFormats.LongTimeFormat;
for i:=1 to 12 do
begin
if AFormatSettings.ShortMonthNames[i] = '' then
AFormatSettings.ShortMonthNames[i] := ADefaultFormats.ShortMonthNames[i];
if AFormatSettings.LongMonthNames[i] = '' then
AFormatSettings.LongMonthNames[i] := ADefaultFormats.LongMonthNames[i];
end;
for i:=1 to 7 do
begin
if AFormatSettings.ShortDayNames[i] = '' then
AFormatSettings.ShortDayNames[i] := ADefaultFormats.ShortDayNames[i];
if AFormatSettings.LongDayNames[i] = '' then
AFormatSettings.LongDayNames[i] := ADefaultFormats.LongDayNames[i];
end;
if AFormatSettings.TwoDigitYearCenturyWindow = Word(-1) then
AFormatSettings.TwoDigitYearCenturyWindow := ADefaultFormats.TwoDigitYearCenturyWindow;
end;
{ -----------------------------------------------------------------------------} { -----------------------------------------------------------------------------}
{ TsCSVReader } { TsCSVReader }
{------------------------------------------------------------------------------} {------------------------------------------------------------------------------}
constructor TsCSVReader.Create(AWorkbook: TsWorkbook); constructor TsCSVReader.Create(AWorkbook: TsWorkbook);
begin begin
inherited Create(AWorkbook); inherited Create(AWorkbook);
FFormatSettings := AWorkbook.FormatSettings; ReplaceFormatSettings(CSVParams.FormatSettings, AWorkbook.FormatSettings);
FWorksheetName := 'Sheet1'; FWorksheetName := 'Sheet1'; // will be replaced by filename
end; end;
procedure TsCSVReader.ProcessCellValue(AStream: TStream); function TsCSVReader.IsDateTime(AText: String; out ADateTime: TDateTime): Boolean;
begin begin
if FCellValue = '' then Result := TryStrToDateTime(AText, ADateTime, CSVParams.FormatSettings);
ReadBlank(AStream) end;
else
if (Length(FCellValue) > 1) and ( function TsCSVReader.IsNumber(AText: String; out ANumber: Double): Boolean;
((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 begin
Delete(FCellValue, Length(FCellValue), 1); Result := TryStrToFloat(AText, ANumber, CSVParams.FormatSettings);
Delete(FCellValue, 1, 1); end;
ReadLabel(AStream);
function TsCSVReader.IsQuotedText(var AText: String): Boolean;
begin
if (Length(AText) > 1) and (CSVParams.QuoteChar <> #0) and
(AText[1] = CSVParams.QuoteChar) and
(AText[Length(AText)] = CSVParams.QuoteChar) then
begin
Delete(AText, 1, 1);
Delete(AText, Length(AText), 1);
Result := true;
end else end else
ReadNumber(AStream); Result := false;
end; end;
procedure TsCSVReader.ReadBlank(AStream: TStream); procedure TsCSVReader.ReadBlank(AStream: TStream);
begin begin
// We could write a blank cell, but since CSV does not support formatting Unused(AStream);
// this would be a waste of memory. --> Just do nothing end;
procedure TsCSVReader.ReadCellValue(ARow, ACol: Cardinal; AText: String);
var
dbl: Double;
dt: TDateTime;
begin
// Empty strings are blank cells -- nothing to do
if AText = '' then
exit;
// Quoted text is a TEXT cell
if IsQuotedText(AText) then
begin
FWorksheet.WriteUTF8Text(ARow, ACol, AText);
exit;
end;
// Check for a NUMBER cell
if IsNumber(AText, dbl) then
begin
FWorksheet.WriteNumber(ARow, ACol, dbl);
exit;
end;
// Check for a DATE/TIME cell
if IsDateTime(AText, dt) then
begin
FWorksheet.WriteDateTime(ARow, ACol, dt);
exit;
end;
// What is left is handled as a TEXT cell
FWorksheet.WriteUTF8Text(ARow, ACol, AText);
end; end;
procedure TsCSVReader.ReadFormula(AStream: TStream); procedure TsCSVReader.ReadFormula(AStream: TStream);
begin begin
// Nothing to do - CSV does not support formulas Unused(AStream);
end; end;
procedure TsCSVReader.ReadFromFile(AFileName: String; AData: TsWorkbook); procedure TsCSVReader.ReadFromFile(AFileName: String; AData: TsWorkbook);
@@ -129,27 +252,29 @@ var
n: Int64; n: Int64;
ch: Char; ch: Char;
nextch: Char; nextch: Char;
cellValue: String;
r, c: Cardinal;
begin begin
FWorkbook := AData; FWorkbook := AData;
FWorksheet := AData.AddWorksheet(FWorksheetName); FWorksheet := AData.AddWorksheet(FWorksheetName);
n := AStream.Size; n := AStream.Size;
FCellValue := ''; cellValue := '';
FRow := 0; r := 0;
FCol := 0; c := 0;
while AStream.Position < n do begin while AStream.Position < n do begin
ch := char(AStream.ReadByte); ch := char(AStream.ReadByte);
if ch = CSVParams.ColDelimiter then begin if ch = CSVParams.Delimiter then begin
// End of column reached // End of column reached
ProcessCellValue(AStream); ReadCellValue(r, c, cellValue);
inc(FCol); inc(c);
FCellValue := ''; cellValue := '';
end else end else
if (ch = #13) or (ch = #10) then begin if (ch = #13) or (ch = #10) then begin
// End of row reached // End of row reached
ProcessCellValue(AStream); ReadCellValue(r, c, cellValue);
inc(FRow); inc(r);
FCol := 0; c := 0;
FCellValue := ''; cellValue := '';
// look for CR+LF: if true, skip next byte // look for CR+LF: if true, skip next byte
if AStream.Position+1 < n then begin if AStream.Position+1 < n then begin
@@ -158,7 +283,7 @@ begin
AStream.Position := AStream.Position - 1; // re-read nextchar in next loop AStream.Position := AStream.Position - 1; // re-read nextchar in next loop
end; end;
end else end else
FCellValue := FCellValue + ch; cellValue := cellValue + ch;
end; end;
end; end;
@@ -177,47 +302,11 @@ end;
procedure TsCSVReader.ReadLabel(AStream: TStream); procedure TsCSVReader.ReadLabel(AStream: TStream);
begin begin
Unused(AStream); Unused(AStream);
FWorksheet.WriteUTF8Text(FRow, FCol, FCellValue);
end; end;
procedure TsCSVReader.ReadNumber(AStream: TStream); procedure TsCSVReader.ReadNumber(AStream: TStream);
var
dbl: Double;
dt: TDateTime;
fs: TFormatSettings;
begin begin
Unused(AStream); 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; end;
@@ -227,11 +316,13 @@ end;
constructor TsCSVWriter.Create(AWorkbook: TsWorkbook); constructor TsCSVWriter.Create(AWorkbook: TsWorkbook);
begin begin
inherited Create(AWorkbook); inherited Create(AWorkbook);
FFormatSettings := AWorkbook.FormatSettings; ReplaceFormatSettings(CSVParams.FormatSettings, FWorkbook.FormatSettings);
if CSVParams.DecimalSeparator <> #0 then case CSVParams.LineEnding of
FFormatSettings.DecimalSeparator := CSVParams.DecimalSeparator; leSystem : FLineEnding := LineEnding;
if CSVParams.LineDelimiter = '' then leCRLF : FLineEnding := #13#10;
CSVParams.LineDelimiter := LineEnding; leCR : FLineEnding := #13;
leLF : FLineEnding := #10;
end;
end; end;
procedure TsCSVWriter.WriteBlank(AStream: TStream; const ARow, ACol: Cardinal; procedure TsCSVWriter.WriteBlank(AStream: TStream; const ARow, ACol: Cardinal;
@@ -242,17 +333,12 @@ begin
// nothing to do // nothing to do
end; end;
{ Write date/time values in the same way they are displayed in the sheet }
procedure TsCSVWriter.WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal; procedure TsCSVWriter.WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal;
const AValue: TDateTime; ACell: PCell); const AValue: TDateTime; ACell: PCell);
var
s: String;
begin begin
Unused(ARow, ACol); Unused(ARow, ACol);
if CSVParams.DateTimeFormat <> '' then AppendToStream(AStream, FWorksheet.ReadAsUTF8Text(ACell));
s := FormatDateTime(CSVParams.DateTimeFormat, AValue, FFormatSettings)
else
s := FWorksheet.ReadAsUTF8Text(ACell);
AppendToStream(AStream, s);
end; end;
procedure TsCSVWriter.WriteFormula(AStream: TStream; const ARow, ACol: Cardinal; procedure TsCSVWriter.WriteFormula(AStream: TStream; const ARow, ACol: Cardinal;
@@ -287,7 +373,7 @@ begin
if ACell = nil then if ACell = nil then
exit; exit;
if CSVParams.NumberFormat <> '' then if CSVParams.NumberFormat <> '' then
s := Format(CSVParams.NumberFormat, [AValue], FFormatSettings) s := Format(CSVParams.NumberFormat, [AValue], CSVParams.FormatSettings)
else else
s := FWorksheet.ReadAsUTF8Text(ACell); s := FWorksheet.ReadAsUTF8Text(ACell);
AppendToStream(AStream, s); AppendToStream(AStream, s);
@@ -308,9 +394,9 @@ begin
if cell <> nil then if cell <> nil then
WriteCellCallback(cell, AStream); WriteCellCallback(cell, AStream);
if c = lastCol then if c = lastCol then
AppendToStream(AStream, CSVParams.LineDelimiter) AppendToStream(AStream, FLineEnding)
else else
AppendToStream(AStream, CSVParams.ColDelimiter); AppendToStream(AStream, CSVParams.Delimiter);
end; end;
end; end;
@@ -340,6 +426,7 @@ end;
initialization initialization
InitCSVFormatSettings;
RegisterSpreadFormat(TsCSVReader, TsCSVWriter, sfCSV); RegisterSpreadFormat(TsCSVReader, TsCSVWriter, sfCSV);
end. end.