2014-10-10 09:10:43 +00:00
|
|
|
unit fpscsv;
|
|
|
|
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
uses
|
|
|
|
Classes, SysUtils,
|
2015-02-25 09:43:37 +00:00
|
|
|
fpstypes, fpspreadsheet, fpsReaderWriter, fpsCsvDocument;
|
2014-10-10 09:10:43 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
TsCSVReader = class(TsCustomSpreadReader)
|
|
|
|
private
|
|
|
|
FWorksheetName: String;
|
2014-10-30 13:04:37 +00:00
|
|
|
FFormatSettings: TFormatSettings;
|
2014-10-14 15:56:08 +00:00
|
|
|
function IsBool(AText: String; out AValue: Boolean): Boolean;
|
2014-10-30 13:04:37 +00:00
|
|
|
function IsDateTime(AText: String; out ADateTime: TDateTime;
|
|
|
|
out ANumFormat: TsNumberFormat): Boolean;
|
2014-10-20 21:04:20 +00:00
|
|
|
function IsNumber(AText: String; out ANumber: Double; out ANumFormat: TsNumberFormat;
|
|
|
|
out ADecimals: Integer; out ACurrencySymbol, AWarning: String): Boolean;
|
2014-10-13 22:28:38 +00:00
|
|
|
function IsQuotedText(var AText: String): Boolean;
|
|
|
|
procedure ReadCellValue(ARow, ACol: Cardinal; AText: String);
|
2014-10-10 09:10:43 +00:00
|
|
|
protected
|
|
|
|
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;
|
2015-02-04 19:50:50 +00:00
|
|
|
procedure ReadFromFile(AFileName: String); override;
|
|
|
|
procedure ReadFromStream(AStream: TStream); override;
|
|
|
|
procedure ReadFromStrings(AStrings: TStrings); override;
|
2014-10-10 09:10:43 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
TsCSVWriter = class(TsCustomSpreadWriter)
|
|
|
|
private
|
2014-10-23 22:43:30 +00:00
|
|
|
FCSVBuilder: TCSVBuilder;
|
2014-10-24 20:44:37 +00:00
|
|
|
FEncoding: String;
|
2014-10-30 13:04:37 +00:00
|
|
|
FFormatSettings: TFormatSettings;
|
2014-10-10 09:10:43 +00:00
|
|
|
protected
|
|
|
|
procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
ACell: PCell); override;
|
2014-10-14 15:56:08 +00:00
|
|
|
procedure WriteBool(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: Boolean; ACell: PCell); override;
|
2014-10-10 09:10:43 +00:00
|
|
|
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;
|
|
|
|
|
2014-10-13 22:28:38 +00:00
|
|
|
TsCSVLineEnding = (leSystem, leCRLF, leCR, leLF);
|
|
|
|
|
2014-10-14 15:56:08 +00:00
|
|
|
TsCSVParams = record // W = writing, R = reading, RW = reading/writing
|
|
|
|
SheetIndex: Integer; // W: Index of the sheet to be written
|
|
|
|
LineEnding: TsCSVLineEnding; // W: Specification for line ending to be written
|
|
|
|
Delimiter: Char; // RW: Column delimiter
|
|
|
|
QuoteChar: Char; // RW: Character for quoting texts
|
2015-02-04 18:15:19 +00:00
|
|
|
Encoding: String; // RW: Encoding of file (code page, such as "utf8", "cp1252" etc)
|
2014-10-19 21:20:57 +00:00
|
|
|
DetectContentType: Boolean; // R: try to convert strings to content types
|
2014-10-14 15:56:08 +00:00
|
|
|
NumberFormat: String; // W: if empty write numbers like in sheet, otherwise use this format
|
2014-10-19 21:20:57 +00:00
|
|
|
AutoDetectNumberFormat: Boolean; // R: automatically detects decimal/thousand separator used in numbers
|
2014-10-14 15:56:08 +00:00
|
|
|
TrueText: String; // RW: String for boolean TRUE
|
|
|
|
FalseText: String; // RW: String for boolean FALSE
|
|
|
|
FormatSettings: TFormatSettings; // RW: add'l parameters for conversion
|
2014-10-10 09:10:43 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
var
|
|
|
|
CSVParams: TsCSVParams = (
|
2014-10-14 15:56:08 +00:00
|
|
|
SheetIndex: 0;
|
|
|
|
LineEnding: leSystem;
|
|
|
|
Delimiter: ';';
|
|
|
|
QuoteChar: '"';
|
2014-10-24 20:44:37 +00:00
|
|
|
Encoding: ''; // '' = auto-detect when reading, UTF8 when writing
|
2014-10-19 21:20:57 +00:00
|
|
|
DetectContentType: true;
|
2014-10-14 15:56:08 +00:00
|
|
|
NumberFormat: '';
|
2014-10-19 21:20:57 +00:00
|
|
|
AutoDetectNumberFormat: true;
|
2014-10-14 15:56:08 +00:00
|
|
|
TrueText: 'TRUE';
|
|
|
|
FalseText: 'FALSE';
|
2014-10-20 09:22:06 +00:00
|
|
|
{%H-});
|
2014-10-10 09:10:43 +00:00
|
|
|
|
2014-10-23 22:43:30 +00:00
|
|
|
function LineEndingAsString(ALineEnding: TsCSVLineEnding): String;
|
|
|
|
|
2014-10-13 22:28:38 +00:00
|
|
|
|
2014-10-10 09:10:43 +00:00
|
|
|
implementation
|
|
|
|
|
|
|
|
uses
|
2014-11-03 15:34:57 +00:00
|
|
|
//StrUtils,
|
2015-01-17 22:57:23 +00:00
|
|
|
DateUtils, LConvEncoding, Math,
|
|
|
|
fpsutils, fpscurrency;
|
2014-10-10 09:10:43 +00:00
|
|
|
|
2014-10-13 22:28:38 +00:00
|
|
|
{ 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;
|
|
|
|
|
2014-10-23 22:43:30 +00:00
|
|
|
function LineEndingAsString(ALineEnding: TsCSVLineEnding): String;
|
|
|
|
begin
|
|
|
|
case ALineEnding of
|
|
|
|
leSystem: Result := LineEnding;
|
|
|
|
leCR : Result := #13;
|
|
|
|
leLF : Result := #10;
|
|
|
|
leCRLF : Result := #13#10;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-10-13 22:28:38 +00:00
|
|
|
|
2014-10-10 09:10:43 +00:00
|
|
|
{ -----------------------------------------------------------------------------}
|
|
|
|
{ TsCSVReader }
|
|
|
|
{------------------------------------------------------------------------------}
|
2014-10-13 22:28:38 +00:00
|
|
|
|
2014-10-10 09:10:43 +00:00
|
|
|
constructor TsCSVReader.Create(AWorkbook: TsWorkbook);
|
|
|
|
begin
|
|
|
|
inherited Create(AWorkbook);
|
2014-10-13 22:28:38 +00:00
|
|
|
FWorksheetName := 'Sheet1'; // will be replaced by filename
|
2015-02-04 19:50:50 +00:00
|
|
|
FFormatSettings := CSVParams.FormatSettings;
|
|
|
|
ReplaceFormatSettings(FFormatSettings, FWorkbook.FormatSettings);
|
2014-10-10 09:10:43 +00:00
|
|
|
end;
|
|
|
|
|
2014-10-14 15:56:08 +00:00
|
|
|
function TsCSVReader.IsBool(AText: String; out AValue: Boolean): Boolean;
|
|
|
|
begin
|
|
|
|
if SameText(AText, CSVParams.TrueText) then
|
|
|
|
begin
|
|
|
|
AValue := true;
|
|
|
|
Result := true;
|
|
|
|
end else
|
|
|
|
if SameText(AText, CSVParams.FalseText) then
|
|
|
|
begin
|
|
|
|
AValue := false;
|
|
|
|
Result := true;
|
|
|
|
end else
|
|
|
|
Result := false;
|
|
|
|
end;
|
|
|
|
|
2014-10-30 13:04:37 +00:00
|
|
|
function TsCSVReader.IsDateTime(AText: String; out ADateTime: TDateTime;
|
|
|
|
out ANumFormat: TsNumberFormat): Boolean;
|
|
|
|
|
|
|
|
{ Test whether the text is formatted according to a built-in date/time format.
|
|
|
|
Converts the obtained date/time value back to a string and compares. }
|
|
|
|
function TestFormat(lNumFmt: TsNumberFormat): Boolean;
|
|
|
|
var
|
|
|
|
fmt: string;
|
|
|
|
begin
|
|
|
|
fmt := BuildDateTimeFormatString(lNumFmt, FFormatSettings);
|
|
|
|
Result := FormatDateTime(fmt, ADateTime, FFormatSettings) = AText;
|
|
|
|
if Result then ANumFormat := lNumFmt;
|
|
|
|
end;
|
|
|
|
|
2014-10-10 09:10:43 +00:00
|
|
|
begin
|
2014-10-30 13:04:37 +00:00
|
|
|
Result := TryStrToDateTime(AText, ADateTime, FFormatSettings);
|
|
|
|
if Result then
|
|
|
|
begin
|
|
|
|
ANumFormat := nfCustom;
|
|
|
|
if abs(ADateTime) > 1 then // this is most probably a date
|
|
|
|
begin
|
|
|
|
if TestFormat(nfShortDateTime) then
|
|
|
|
exit;
|
|
|
|
if TestFormat(nfLongDate) then
|
|
|
|
exit;
|
|
|
|
if TestFormat(nfShortDate) then
|
|
|
|
exit;
|
|
|
|
end else
|
|
|
|
begin // this case is time-only
|
|
|
|
if TestFormat(nfLongTimeAM) then
|
|
|
|
exit;
|
|
|
|
if TestFormat(nfLongTime) then
|
|
|
|
exit;
|
|
|
|
if TestFormat(nfShortTimeAM) then
|
|
|
|
exit;
|
|
|
|
if TestFormat(nfShortTime) then
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
end;
|
2014-10-13 22:28:38 +00:00
|
|
|
end;
|
|
|
|
|
2014-10-19 21:20:57 +00:00
|
|
|
function TsCSVReader.IsNumber(AText: String; out ANumber: Double;
|
2014-10-20 21:04:20 +00:00
|
|
|
out ANumFormat: TsNumberFormat; out ADecimals: Integer;
|
2014-10-19 21:20:57 +00:00
|
|
|
out ACurrencySymbol, AWarning: String): Boolean;
|
|
|
|
var
|
|
|
|
p: Integer;
|
2014-10-24 10:46:04 +00:00
|
|
|
DecSep, ThousSep: Char;
|
2014-10-13 22:28:38 +00:00
|
|
|
begin
|
2014-10-20 21:04:20 +00:00
|
|
|
Result := false;
|
2014-10-19 21:20:57 +00:00
|
|
|
AWarning := '';
|
|
|
|
|
|
|
|
// To detect whether the text is a currency value we look for the currency
|
|
|
|
// string. If we find it, we delete it and convert the remaining string to
|
|
|
|
// a number.
|
2014-10-30 13:04:37 +00:00
|
|
|
ACurrencySymbol := FFormatSettings.CurrencyString;
|
2014-10-29 16:39:49 +00:00
|
|
|
if RemoveCurrencySymbol(ACurrencySymbol, AText) then
|
|
|
|
begin
|
|
|
|
if IsNegative(AText) then
|
|
|
|
begin
|
|
|
|
if AText = '' then
|
|
|
|
exit;
|
|
|
|
AText := '-' + AText;
|
|
|
|
end;
|
2014-10-19 21:20:57 +00:00
|
|
|
end else
|
|
|
|
ACurrencySymbol := '';
|
|
|
|
|
|
|
|
if CSVParams.AutoDetectNumberFormat then
|
2014-10-24 10:46:04 +00:00
|
|
|
Result := TryStrToFloatAuto(AText, ANumber, DecSep, ThousSep, AWarning)
|
2014-10-20 21:04:20 +00:00
|
|
|
else begin
|
2014-10-30 13:04:37 +00:00
|
|
|
Result := TryStrToFloat(AText, ANumber, FFormatSettings);
|
2014-10-20 21:04:20 +00:00
|
|
|
if Result then
|
|
|
|
begin
|
2014-10-30 13:04:37 +00:00
|
|
|
if pos(FFormatSettings.DecimalSeparator, AText) = 0
|
2014-10-24 10:46:04 +00:00
|
|
|
then DecSep := #0
|
2014-10-30 13:04:37 +00:00
|
|
|
else DecSep := FFormatSettings.DecimalSeparator;
|
2014-10-20 21:04:20 +00:00
|
|
|
if pos(CSVParams.FormatSettings.ThousandSeparator, AText) = 0
|
2014-10-24 10:46:04 +00:00
|
|
|
then ThousSep := #0
|
2014-10-30 13:04:37 +00:00
|
|
|
else ThousSep := FFormatSettings.ThousandSeparator;
|
2014-10-20 21:04:20 +00:00
|
|
|
end;
|
|
|
|
end;
|
2014-10-19 21:20:57 +00:00
|
|
|
|
2014-10-20 21:04:20 +00:00
|
|
|
// Try to determine the number format
|
|
|
|
if Result then
|
|
|
|
begin
|
2014-10-24 10:46:04 +00:00
|
|
|
if ThousSep <> #0 then
|
2014-10-20 21:04:20 +00:00
|
|
|
ANumFormat := nfFixedTh
|
|
|
|
else
|
|
|
|
ANumFormat := nfGeneral;
|
|
|
|
// count number of decimal places and try to catch special formats
|
|
|
|
ADecimals := 0;
|
2014-10-24 10:46:04 +00:00
|
|
|
if DecSep <> #0 then
|
2014-10-20 21:04:20 +00:00
|
|
|
begin
|
|
|
|
// Go to the decimal separator and search towards the end of the string
|
2014-10-24 10:46:04 +00:00
|
|
|
p := pos(DecSep, AText) + 1;
|
2014-10-20 21:04:20 +00:00
|
|
|
while (p <= Length(AText)) do begin
|
|
|
|
// exponential format
|
|
|
|
if AText[p] in ['+', '-', 'E', 'e'] then
|
|
|
|
begin
|
|
|
|
ANumFormat := nfExp;
|
|
|
|
break;
|
|
|
|
end else
|
|
|
|
// percent format
|
|
|
|
if AText[p] = '%' then
|
|
|
|
begin
|
|
|
|
ANumFormat := nfPercentage;
|
|
|
|
break;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
inc(p);
|
|
|
|
inc(ADecimals);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
if (ADecimals > 0) and (ADecimals < 9) and (ANumFormat = nfGeneral) then
|
|
|
|
// "no formatting" assumed if there are "many" decimals
|
|
|
|
ANumFormat := nfFixed;
|
2014-10-20 21:52:53 +00:00
|
|
|
end else
|
|
|
|
begin
|
|
|
|
p := Length(AText);
|
|
|
|
while (p > 0) do begin
|
|
|
|
case AText[p] of
|
|
|
|
'%' : ANumFormat := nfPercentage;
|
|
|
|
'e', 'E': ANumFormat := nfExp;
|
|
|
|
else dec(p);
|
|
|
|
end;
|
|
|
|
break;
|
|
|
|
end;
|
2014-10-20 21:04:20 +00:00
|
|
|
end;
|
|
|
|
end else
|
|
|
|
ACurrencySymbol := '';
|
2014-10-13 22:28:38 +00:00
|
|
|
end;
|
|
|
|
|
2014-10-24 13:21:34 +00:00
|
|
|
{ Checks if text is quoted; strips any starting and ending quotes }
|
2014-10-13 22:28:38 +00:00
|
|
|
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
|
2014-10-10 09:10:43 +00:00
|
|
|
begin
|
2014-10-13 22:28:38 +00:00
|
|
|
Delete(AText, 1, 1);
|
|
|
|
Delete(AText, Length(AText), 1);
|
|
|
|
Result := true;
|
2014-10-10 09:10:43 +00:00
|
|
|
end else
|
2014-10-13 22:28:38 +00:00
|
|
|
Result := false;
|
2014-10-10 09:10:43 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsCSVReader.ReadBlank(AStream: TStream);
|
|
|
|
begin
|
2014-10-13 22:28:38 +00:00
|
|
|
Unused(AStream);
|
|
|
|
end;
|
|
|
|
|
2014-10-23 22:43:30 +00:00
|
|
|
{ Determines content types from/for the text read from the csv file and writes
|
|
|
|
the corresponding data to the worksheet. }
|
2014-10-13 22:28:38 +00:00
|
|
|
procedure TsCSVReader.ReadCellValue(ARow, ACol: Cardinal; AText: String);
|
|
|
|
var
|
2014-10-14 15:56:08 +00:00
|
|
|
dblValue: Double;
|
|
|
|
dtValue: TDateTime;
|
|
|
|
boolValue: Boolean;
|
2014-10-19 21:20:57 +00:00
|
|
|
currSym: string;
|
|
|
|
warning: String;
|
2014-10-20 16:14:59 +00:00
|
|
|
nf: TsNumberFormat;
|
2014-10-20 21:04:20 +00:00
|
|
|
decs: Integer;
|
2014-10-13 22:28:38 +00:00
|
|
|
begin
|
|
|
|
// Empty strings are blank cells -- nothing to do
|
|
|
|
if AText = '' then
|
|
|
|
exit;
|
|
|
|
|
2014-10-19 21:20:57 +00:00
|
|
|
// Do not try to interpret the strings. --> everything is a LABEL cell.
|
|
|
|
if not CSVParams.DetectContentType then
|
|
|
|
begin
|
|
|
|
FWorksheet.WriteUTF8Text(ARow, aCol, AText);
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Check for a NUMBER or CURRENCY cell
|
2014-10-20 21:04:20 +00:00
|
|
|
if IsNumber(AText, dblValue, nf, decs, currSym, warning) then
|
2014-10-13 22:28:38 +00:00
|
|
|
begin
|
2014-10-19 21:20:57 +00:00
|
|
|
if currSym <> '' then
|
2014-10-29 22:36:03 +00:00
|
|
|
FWorksheet.WriteCurrency(ARow, ACol, dblValue, nfCurrency, decs, currSym)
|
2014-10-19 21:20:57 +00:00
|
|
|
else
|
2014-10-20 21:04:20 +00:00
|
|
|
FWorksheet.WriteNumber(ARow, ACol, dblValue, nf, decs);
|
2014-10-19 21:20:57 +00:00
|
|
|
if warning <> '' then
|
|
|
|
FWorkbook.AddErrorMsg('Cell %s: %s', [GetCellString(ARow, ACol), warning]);
|
2014-10-13 22:28:38 +00:00
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Check for a DATE/TIME cell
|
2014-10-20 16:14:59 +00:00
|
|
|
// No idea how to apply the date/time formatsettings here...
|
2014-10-30 13:04:37 +00:00
|
|
|
if IsDateTime(AText, dtValue, nf) then
|
2014-10-14 15:56:08 +00:00
|
|
|
begin
|
2014-10-20 16:14:59 +00:00
|
|
|
FWorksheet.WriteDateTime(ARow, ACol, dtValue, nf);
|
2014-10-14 15:56:08 +00:00
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Check for a BOOLEAN cell
|
2014-10-19 21:20:57 +00:00
|
|
|
if IsBool(AText, boolValue) then
|
2014-10-13 22:28:38 +00:00
|
|
|
begin
|
2014-10-14 15:56:08 +00:00
|
|
|
FWorksheet.WriteBoolValue(ARow, aCol, boolValue);
|
2014-10-13 22:28:38 +00:00
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// What is left is handled as a TEXT cell
|
|
|
|
FWorksheet.WriteUTF8Text(ARow, ACol, AText);
|
2014-10-10 09:10:43 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsCSVReader.ReadFormula(AStream: TStream);
|
|
|
|
begin
|
2014-10-13 22:28:38 +00:00
|
|
|
Unused(AStream);
|
2014-10-10 09:10:43 +00:00
|
|
|
end;
|
|
|
|
|
2015-02-04 19:50:50 +00:00
|
|
|
procedure TsCSVReader.ReadFromFile(AFileName: String);
|
2014-10-10 09:10:43 +00:00
|
|
|
begin
|
|
|
|
FWorksheetName := ChangeFileExt(ExtractFileName(AFileName), '');
|
|
|
|
inherited;
|
|
|
|
end;
|
|
|
|
|
2015-02-04 19:50:50 +00:00
|
|
|
procedure TsCSVReader.ReadFromStream(AStream: TStream);
|
2014-10-23 22:43:30 +00:00
|
|
|
var
|
2014-10-24 10:46:04 +00:00
|
|
|
Parser: TCSVParser;
|
2014-10-24 20:44:37 +00:00
|
|
|
s: String;
|
|
|
|
encoding: String;
|
2014-10-23 22:43:30 +00:00
|
|
|
begin
|
2014-10-24 20:44:37 +00:00
|
|
|
// Try to determine encoding of the input file
|
|
|
|
SetLength(s, Min(1000, AStream.Size));
|
|
|
|
AStream.ReadBuffer(s[1], Length(s));
|
|
|
|
if CSVParams.Encoding = '' then
|
|
|
|
encoding := GuessEncoding(s)
|
|
|
|
else
|
|
|
|
encoding := CSVParams.Encoding;
|
|
|
|
|
2015-02-04 19:50:50 +00:00
|
|
|
// Create worksheet
|
|
|
|
FWorksheet := FWorkbook.AddWorksheet(FWorksheetName, true);
|
2015-02-04 18:15:19 +00:00
|
|
|
|
2014-10-24 20:44:37 +00:00
|
|
|
// Create csv parser, read file and store in worksheet
|
2014-10-24 10:46:04 +00:00
|
|
|
Parser := TCSVParser.Create;
|
2014-10-23 22:43:30 +00:00
|
|
|
try
|
2014-10-24 10:46:04 +00:00
|
|
|
Parser.Delimiter := CSVParams.Delimiter;
|
|
|
|
Parser.LineEnding := LineEndingAsString(CSVParams.LineEnding);
|
|
|
|
Parser.QuoteChar := CSVParams.QuoteChar;
|
|
|
|
// Indicate column counts between rows may differ:
|
|
|
|
Parser.EqualColCountPerRow := false;
|
|
|
|
Parser.SetSource(AStream);
|
2014-10-24 20:44:37 +00:00
|
|
|
while Parser.ParseNextCell do begin
|
|
|
|
// Convert string to UTF8
|
|
|
|
s := Parser.CurrentCellText;
|
|
|
|
s := ConvertEncoding(s, encoding, EncodingUTF8);
|
|
|
|
ReadCellValue(Parser.CurrentRow, Parser.CurrentCol, s);
|
|
|
|
end;
|
2014-10-23 22:43:30 +00:00
|
|
|
finally
|
2014-10-24 10:46:04 +00:00
|
|
|
Parser.Free;
|
2014-10-23 22:43:30 +00:00
|
|
|
end;
|
|
|
|
end;
|
2014-10-24 20:44:37 +00:00
|
|
|
|
2015-02-04 19:50:50 +00:00
|
|
|
procedure TsCSVReader.ReadFromStrings(AStrings: TStrings);
|
2014-10-10 09:10:43 +00:00
|
|
|
var
|
2014-10-24 10:46:04 +00:00
|
|
|
Stream: TStringStream;
|
2014-10-10 09:10:43 +00:00
|
|
|
begin
|
2014-10-24 10:46:04 +00:00
|
|
|
Stream := TStringStream.Create(AStrings.Text);
|
2014-10-10 09:10:43 +00:00
|
|
|
try
|
2015-02-04 19:50:50 +00:00
|
|
|
ReadFromStream(Stream);
|
2014-10-10 09:10:43 +00:00
|
|
|
finally
|
2014-10-24 10:46:04 +00:00
|
|
|
Stream.Free;
|
2014-10-10 09:10:43 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsCSVReader.ReadLabel(AStream: TStream);
|
|
|
|
begin
|
|
|
|
Unused(AStream);
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsCSVReader.ReadNumber(AStream: TStream);
|
|
|
|
begin
|
|
|
|
Unused(AStream);
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
{ -----------------------------------------------------------------------------}
|
|
|
|
{ TsCSVWriter }
|
|
|
|
{------------------------------------------------------------------------------}
|
2014-10-14 15:56:08 +00:00
|
|
|
|
2014-10-10 09:10:43 +00:00
|
|
|
constructor TsCSVWriter.Create(AWorkbook: TsWorkbook);
|
|
|
|
begin
|
|
|
|
inherited Create(AWorkbook);
|
2014-10-30 13:04:37 +00:00
|
|
|
FFormatSettings := CSVParams.FormatSettings;
|
|
|
|
ReplaceFormatSettings(FFormatSettings, FWorkbook.FormatSettings);
|
2014-10-24 20:44:37 +00:00
|
|
|
if CSVParams.Encoding = '' then
|
|
|
|
FEncoding := 'utf8'
|
|
|
|
else
|
|
|
|
FEncoding := CSVParams.Encoding;
|
2014-10-10 09:10:43 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsCSVWriter.WriteBlank(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
ACell: PCell);
|
|
|
|
begin
|
|
|
|
Unused(AStream);
|
|
|
|
Unused(ARow, ACol, ACell);
|
|
|
|
// nothing to do
|
|
|
|
end;
|
|
|
|
|
2014-10-24 10:46:04 +00:00
|
|
|
{ Write boolean cell to stream formatted as string }
|
2014-10-14 15:56:08 +00:00
|
|
|
procedure TsCSVWriter.WriteBool(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: Boolean; ACell: PCell);
|
2014-10-24 20:44:37 +00:00
|
|
|
var
|
|
|
|
s: String;
|
2014-10-14 15:56:08 +00:00
|
|
|
begin
|
2014-10-23 22:43:30 +00:00
|
|
|
Unused(AStream);
|
2014-10-14 15:56:08 +00:00
|
|
|
Unused(ARow, ACol, ACell);
|
2014-10-23 22:43:30 +00:00
|
|
|
if AValue then
|
2014-10-24 20:44:37 +00:00
|
|
|
s := CSVParams.TrueText
|
2014-10-23 22:43:30 +00:00
|
|
|
else
|
2014-10-24 20:44:37 +00:00
|
|
|
s := CSVParams.FalseText;
|
|
|
|
s := ConvertEncoding(s, EncodingUTF8, FEncoding);
|
|
|
|
FCSVBuilder.AppendCell(s);
|
2014-10-14 15:56:08 +00:00
|
|
|
end;
|
|
|
|
|
2014-10-13 22:28:38 +00:00
|
|
|
{ Write date/time values in the same way they are displayed in the sheet }
|
2014-10-10 09:10:43 +00:00
|
|
|
procedure TsCSVWriter.WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: TDateTime; ACell: PCell);
|
2014-10-24 20:44:37 +00:00
|
|
|
var
|
|
|
|
s: String;
|
2014-10-10 09:10:43 +00:00
|
|
|
begin
|
2014-10-23 22:43:30 +00:00
|
|
|
Unused(AStream);
|
2014-10-20 09:22:06 +00:00
|
|
|
Unused(ARow, ACol, AValue);
|
2014-10-24 20:44:37 +00:00
|
|
|
s := FWorksheet.ReadAsUTF8Text(ACell);
|
|
|
|
s := ConvertEncoding(s, EncodingUTF8, FEncoding);
|
|
|
|
FCSVBuilder.AppendCell(s);
|
2014-10-10 09:10:43 +00:00
|
|
|
end;
|
|
|
|
|
2014-10-24 10:46:04 +00:00
|
|
|
{ CSV does not support formulas, but we can write the formula results to
|
2014-10-14 15:56:08 +00:00
|
|
|
to stream. }
|
2014-10-10 09:10:43 +00:00
|
|
|
procedure TsCSVWriter.WriteFormula(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
ACell: PCell);
|
|
|
|
begin
|
2014-10-14 15:56:08 +00:00
|
|
|
if ACell = nil then
|
|
|
|
exit;
|
|
|
|
case ACell^.ContentType of
|
|
|
|
cctBool : WriteBool(AStream, ARow, ACol, ACell^.BoolValue, ACell);
|
|
|
|
cctEmpty : ;
|
|
|
|
cctDateTime : WriteDateTime(AStream, ARow, ACol, ACell^.DateTimeValue, ACell);
|
|
|
|
cctNumber : WriteNumber(AStream, ARow, ACol, ACell^.NumberValue, ACell);
|
|
|
|
cctUTF8String: WriteLabel(AStream, ARow, ACol, ACell^.UTF8StringValue, ACell);
|
|
|
|
cctError : ;
|
|
|
|
end;
|
2014-10-10 09:10:43 +00:00
|
|
|
end;
|
|
|
|
|
2014-10-14 15:56:08 +00:00
|
|
|
{ Writes a LABEL cell to the stream. }
|
2014-10-10 09:10:43 +00:00
|
|
|
procedure TsCSVWriter.WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: string; ACell: PCell);
|
|
|
|
var
|
|
|
|
s: String;
|
|
|
|
begin
|
2014-10-23 22:43:30 +00:00
|
|
|
Unused(AStream);
|
2014-10-20 09:22:06 +00:00
|
|
|
Unused(ARow, ACol, AValue);
|
2014-10-10 09:10:43 +00:00
|
|
|
if ACell = nil then
|
|
|
|
exit;
|
|
|
|
s := ACell^.UTF8StringValue;
|
2014-10-24 20:44:37 +00:00
|
|
|
s := ConvertEncoding(s, EncodingUTF8, FEncoding);
|
2014-10-24 13:21:34 +00:00
|
|
|
// No need to quote; csvdocument will do that for us...
|
2014-10-23 22:43:30 +00:00
|
|
|
FCSVBuilder.AppendCell(s);
|
2014-10-10 09:10:43 +00:00
|
|
|
end;
|
|
|
|
|
2014-10-24 10:46:04 +00:00
|
|
|
{ Writes a number cell to the stream. }
|
2014-10-10 09:10:43 +00:00
|
|
|
procedure TsCSVWriter.WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: double; ACell: PCell);
|
|
|
|
var
|
|
|
|
s: String;
|
|
|
|
begin
|
2014-10-23 22:43:30 +00:00
|
|
|
Unused(AStream);
|
2014-10-10 09:10:43 +00:00
|
|
|
Unused(ARow, ACol);
|
|
|
|
if ACell = nil then
|
|
|
|
exit;
|
|
|
|
if CSVParams.NumberFormat <> '' then
|
2014-10-30 13:04:37 +00:00
|
|
|
s := Format(CSVParams.NumberFormat, [AValue], FFormatSettings)
|
2014-10-10 09:10:43 +00:00
|
|
|
else
|
2014-10-30 13:04:37 +00:00
|
|
|
s := FWorksheet.ReadAsUTF8Text(ACell, FFormatSettings);
|
2014-10-24 20:44:37 +00:00
|
|
|
s := ConvertEncoding(s, EncodingUTF8, FEncoding);
|
2014-10-23 22:43:30 +00:00
|
|
|
FCSVBuilder.AppendCell(s);
|
2014-10-10 09:10:43 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsCSVWriter.WriteSheet(AStream: TStream; AWorksheet: TsWorksheet);
|
|
|
|
var
|
2015-03-06 12:12:45 +00:00
|
|
|
r: Cardinal;
|
|
|
|
LastRow: Cardinal;
|
2015-03-05 10:35:32 +00:00
|
|
|
cell: PCell;
|
2014-10-10 09:10:43 +00:00
|
|
|
begin
|
|
|
|
FWorksheet := AWorksheet;
|
2014-10-23 22:43:30 +00:00
|
|
|
|
|
|
|
FCSVBuilder := TCSVBuilder.Create;
|
|
|
|
try
|
|
|
|
FCSVBuilder.Delimiter := CSVParams.Delimiter;
|
|
|
|
FCSVBuilder.LineEnding := LineEndingAsString(CSVParams.LineEnding);
|
|
|
|
FCSVBuilder.QuoteChar := CSVParams.QuoteChar;
|
|
|
|
FCSVBuilder.SetOutput(AStream);
|
|
|
|
|
2014-10-24 10:46:04 +00:00
|
|
|
LastRow := FWorksheet.GetLastOccupiedRowIndex;
|
|
|
|
for r := 0 to LastRow do
|
2015-03-05 10:35:32 +00:00
|
|
|
begin
|
|
|
|
for cell in FWorksheet.Cells.GetRowEnumerator(r) do
|
|
|
|
WriteCellToStream(AStream, cell);
|
|
|
|
FCSVBuilder.AppendRow;
|
|
|
|
end;
|
2014-10-23 22:43:30 +00:00
|
|
|
finally
|
|
|
|
FreeAndNil(FCSVBuilder);
|
|
|
|
end;
|
2014-10-10 09:10:43 +00:00
|
|
|
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
|
2014-10-24 10:46:04 +00:00
|
|
|
Stream: TStream;
|
2014-10-10 09:10:43 +00:00
|
|
|
begin
|
2014-10-24 10:46:04 +00:00
|
|
|
Stream := TStringStream.Create('');
|
2014-10-10 09:10:43 +00:00
|
|
|
try
|
2014-10-24 10:46:04 +00:00
|
|
|
WriteToStream(Stream);
|
|
|
|
Stream.Position := 0;
|
|
|
|
AStrings.LoadFromStream(Stream);
|
2014-10-10 09:10:43 +00:00
|
|
|
finally
|
2014-10-24 10:46:04 +00:00
|
|
|
Stream.Free;
|
2014-10-10 09:10:43 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
initialization
|
2014-10-13 22:28:38 +00:00
|
|
|
InitCSVFormatSettings;
|
2014-10-10 09:10:43 +00:00
|
|
|
RegisterSpreadFormat(TsCSVReader, TsCSVWriter, sfCSV);
|
|
|
|
|
|
|
|
end.
|
|
|
|
|