You've already forked lazarus-ccr
aarre
applications
bindings
components
ZVDateTimeCtrls
aboutcomponent
acs
beepfp
callite
chelper
cmdline
cmdlinecfg
colorpalette
cryptini
csvdocument
epiktimer
flashfiler
fpsound
fpspreadsheet
docs
examples
images
languages
reference
source
common
fpolebasic.pas
fpolestorage.pas
fpsallformats.pas
fpscell.pas
fpsclasses.pas
fpscsv.pas
fpscsvdocument.pas
fpscurrency.pas
fpsexprparser.pas
fpsfunc.pas
fpsheaderfooterparser.pas
fpshtml.pas
fpshtmlutils.pas
fpsimages.pas
fpsnumformat.pas
fpsopendocument.pas
fpspagelayout.pas
fpspalette.pas
fpspatches.pas
fpspreadsheet.pas
fpsreaderwriter.pas
fpsrpn.pas
fpssearch.pas
fpsstreams.pas
fpsstrings.pas
fpstypes.pas
fpsutils.pas
fpsxmlcommon.pas
fpszipper.pp
uvirtuallayer.pas
uvirtuallayer_ole.pas
uvirtuallayer_ole_helpers.pas
uvirtuallayer_ole_types.pas
uvirtuallayer_stream.pas
uvirtuallayer_types.pas
wikitable.pas
xlsbiff2.pas
xlsbiff5.pas
xlsbiff8.pas
xlscommon.pas
xlsconst.pas
xlsescher.pas
xlsxml.pas
xlsxooxml.pas
export
visual
fps.inc
install.txt
laz_fpspreadsheet.lpk
laz_fpspreadsheet_visual.lpk
laz_fpspreadsheetexport_visual.lpk
tests
README.txt
fractions
freetypepascal
geckoport
gradcontrols
industrialstuff
iosdesigner
iphonelazext
jujiboutils
jvcllaz
kcontrols
lazautoupdate
lazbarcodes
lclextensions
longtimer
manualdock
mbColorLib
mplayer
multithreadprocs
nvidia-widgets
onguard
orpheus
playsoundpackage
poweredby
powerpdf
rgbgraphics
richmemo
richview
rtfview
rx
scrolltext
smnetgradient
spktoolbar
svn
tdi
thtmlport
tparadoxdataset
tvplanit
virtualtreeview
virtualtreeview-new
xdev_toolkit
zlibar
examples
lclbindings
wst
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@5282 8e941d3f-bd1b-0410-a28a-d453659cc2b4
469 lines
14 KiB
ObjectPascal
469 lines
14 KiB
ObjectPascal
unit fpscsv;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils,
|
|
fpstypes, fpspreadsheet, fpsReaderWriter, fpsCsvDocument;
|
|
|
|
type
|
|
TsCSVReader = class(TsCustomSpreadReader)
|
|
private
|
|
FWorksheetName: String;
|
|
FFormatSettings: TFormatSettings;
|
|
function IsQuotedText(var AText: String): Boolean;
|
|
procedure ReadCellValue(ARow, ACol: Cardinal; AText: String);
|
|
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;
|
|
procedure ReadFromFile(AFileName: String; AParams: TsStreamParams = []); override;
|
|
procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); override;
|
|
procedure ReadFromStrings(AStrings: TStrings; AParams: TsStreamParams = []); override;
|
|
end;
|
|
|
|
TsCSVWriter = class(TsCustomSpreadWriter)
|
|
private
|
|
FCSVBuilder: TCSVBuilder;
|
|
FEncoding: String;
|
|
FFormatSettings: TFormatSettings;
|
|
FClipboardMode: Boolean;
|
|
protected
|
|
procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal;
|
|
ACell: PCell); override;
|
|
procedure WriteBool(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: Boolean; 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; AParams: TsStreamParams = []); override;
|
|
procedure WriteToStrings(AStrings: TStrings; AParams: TsStreamParams = []); override;
|
|
end;
|
|
|
|
TsCSVLineEnding = (leSystem, leCRLF, leCR, leLF);
|
|
|
|
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
|
|
Encoding: String; // RW: Encoding of file (code page, such as "utf8", "cp1252" etc)
|
|
DetectContentType: Boolean; // R: try to convert strings to content types
|
|
NumberFormat: String; // W: if empty write numbers like in sheet, otherwise use this format
|
|
AutoDetectNumberFormat: Boolean; // R: automatically detects decimal/thousand separator used in numbers
|
|
TrueText: String; // RW: String for boolean TRUE
|
|
FalseText: String; // RW: String for boolean FALSE
|
|
FormatSettings: TFormatSettings; // RW: add'l parameters for conversion
|
|
end;
|
|
|
|
var
|
|
CSVParams: TsCSVParams = (
|
|
SheetIndex: 0;
|
|
LineEnding: leSystem;
|
|
Delimiter: ';';
|
|
QuoteChar: '"';
|
|
Encoding: ''; // '' = auto-detect when reading, UTF8 when writing
|
|
DetectContentType: true;
|
|
NumberFormat: '';
|
|
AutoDetectNumberFormat: true;
|
|
TrueText: 'TRUE';
|
|
FalseText: 'FALSE';
|
|
{%H-});
|
|
|
|
sfidCSV: TsSpreadFormatID;
|
|
|
|
function LineEndingAsString(ALineEnding: TsCSVLineEnding): String;
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
DateUtils, LConvEncoding, Math,
|
|
fpsUtils, fpsNumFormat;
|
|
|
|
function LineEndingAsString(ALineEnding: TsCSVLineEnding): String;
|
|
begin
|
|
case ALineEnding of
|
|
leSystem: Result := LineEnding;
|
|
leCR : Result := #13;
|
|
leLF : Result := #10;
|
|
leCRLF : Result := #13#10;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ -----------------------------------------------------------------------------}
|
|
{ TsCSVReader }
|
|
{------------------------------------------------------------------------------}
|
|
|
|
constructor TsCSVReader.Create(AWorkbook: TsWorkbook);
|
|
begin
|
|
inherited Create(AWorkbook);
|
|
FWorksheetName := 'Sheet1'; // will be replaced by filename
|
|
FFormatSettings := CSVParams.FormatSettings;
|
|
ReplaceFormatSettings(FFormatSettings, FWorkbook.FormatSettings);
|
|
end;
|
|
|
|
{ Checks if text is quoted; strips any starting and ending quotes }
|
|
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
|
|
Result := false;
|
|
end;
|
|
|
|
procedure TsCSVReader.ReadBlank(AStream: TStream);
|
|
begin
|
|
Unused(AStream);
|
|
end;
|
|
|
|
{ Determines content types from/for the text read from the csv file and writes
|
|
the corresponding data to the worksheet. }
|
|
procedure TsCSVReader.ReadCellValue(ARow, ACol: Cardinal; AText: String);
|
|
var
|
|
dblValue: Double;
|
|
dtValue: TDateTime;
|
|
boolValue: Boolean;
|
|
currSym: string;
|
|
warning: String;
|
|
nf: TsNumberFormat;
|
|
decs: Integer;
|
|
cell: PCell;
|
|
begin
|
|
// Empty strings are blank cells -- nothing to do
|
|
if AText = '' then
|
|
exit;
|
|
|
|
cell := FWorksheet.AddCell(ARow, ACol);
|
|
|
|
// Do not try to interpret the strings. --> everything is a LABEL cell.
|
|
if not CSVParams.DetectContentType then
|
|
begin
|
|
FWorksheet.WriteText(cell, AText);
|
|
exit;
|
|
end;
|
|
|
|
// Check for a NUMBER or CURRENCY cell
|
|
if IsNumberValue(AText, CSVParams.AutoDetectNumberFormat, FFormatSettings,
|
|
dblValue, nf, decs, currSym, warning) then
|
|
begin
|
|
if currSym <> '' then
|
|
FWorksheet.WriteCurrency(cell, dblValue, nfCurrency, decs, currSym)
|
|
else
|
|
FWorksheet.WriteNumber(cell, dblValue, nf, decs);
|
|
if warning <> '' then
|
|
FWorkbook.AddErrorMsg('Cell %s: %s', [GetCellString(ARow, ACol), warning]);
|
|
exit;
|
|
end;
|
|
|
|
// Check for a DATE/TIME cell
|
|
// No idea how to apply the date/time formatsettings here...
|
|
if IsDateTimeValue(AText, FFormatSettings, dtValue, nf) then
|
|
begin
|
|
FWorksheet.WriteDateTime(cell, dtValue, nf);
|
|
exit;
|
|
end;
|
|
|
|
// Check for a BOOLEAN cell
|
|
if IsBoolValue(AText, CSVParams.TrueText, CSVParams.FalseText, boolValue) then
|
|
begin
|
|
FWorksheet.WriteBoolValue(cell, boolValue);
|
|
exit;
|
|
end;
|
|
|
|
// What is left is handled as a TEXT cell
|
|
FWorksheet.WriteText(cell, AText);
|
|
end;
|
|
|
|
procedure TsCSVReader.ReadFormula(AStream: TStream);
|
|
begin
|
|
Unused(AStream);
|
|
end;
|
|
|
|
procedure TsCSVReader.ReadFromFile(AFileName: String;
|
|
AParams: TsStreamParams = []);
|
|
begin
|
|
FWorksheetName := ChangeFileExt(ExtractFileName(AFileName), '');
|
|
inherited ReadFromFile(AFilename, AParams);
|
|
end;
|
|
|
|
procedure TsCSVReader.ReadFromStream(AStream: TStream;
|
|
AParams: TsStreamParams = []);
|
|
var
|
|
Parser: TCSVParser;
|
|
s: String;
|
|
encoding: String;
|
|
begin
|
|
Unused(AParams);
|
|
|
|
// 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;
|
|
|
|
// Create worksheet
|
|
FWorksheet := FWorkbook.AddWorksheet(FWorksheetName, true);
|
|
|
|
// Create csv parser, read file and store in worksheet
|
|
Parser := TCSVParser.Create;
|
|
try
|
|
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);
|
|
while Parser.ParseNextCell do begin
|
|
// Convert string to UTF8
|
|
s := Parser.CurrentCellText;
|
|
s := ConvertEncoding(s, encoding, EncodingUTF8);
|
|
ReadCellValue(Parser.CurrentRow, Parser.CurrentCol, s);
|
|
end;
|
|
finally
|
|
Parser.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TsCSVReader.ReadFromStrings(AStrings: TStrings;
|
|
AParams: TsStreamParams = []);
|
|
var
|
|
Stream: TStringStream;
|
|
begin
|
|
Stream := TStringStream.Create(AStrings.Text);
|
|
try
|
|
ReadFromStream(Stream, AParams);
|
|
finally
|
|
Stream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TsCSVReader.ReadLabel(AStream: TStream);
|
|
begin
|
|
Unused(AStream);
|
|
end;
|
|
|
|
procedure TsCSVReader.ReadNumber(AStream: TStream);
|
|
begin
|
|
Unused(AStream);
|
|
end;
|
|
|
|
|
|
{ -----------------------------------------------------------------------------}
|
|
{ TsCSVWriter }
|
|
{------------------------------------------------------------------------------}
|
|
|
|
constructor TsCSVWriter.Create(AWorkbook: TsWorkbook);
|
|
begin
|
|
inherited Create(AWorkbook);
|
|
FFormatSettings := CSVParams.FormatSettings;
|
|
ReplaceFormatSettings(FFormatSettings, FWorkbook.FormatSettings);
|
|
if CSVParams.Encoding = '' then
|
|
FEncoding := 'utf8'
|
|
else
|
|
FEncoding := CSVParams.Encoding;
|
|
end;
|
|
|
|
procedure TsCSVWriter.WriteBlank(AStream: TStream; const ARow, ACol: Cardinal;
|
|
ACell: PCell);
|
|
begin
|
|
Unused(AStream);
|
|
Unused(ARow, ACol, ACell);
|
|
FCSVBuilder.AppendCell('');
|
|
end;
|
|
|
|
{ Write boolean cell to stream formatted as string }
|
|
procedure TsCSVWriter.WriteBool(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: Boolean; ACell: PCell);
|
|
var
|
|
s: String;
|
|
begin
|
|
Unused(AStream);
|
|
Unused(ARow, ACol, ACell);
|
|
if AValue then
|
|
s := CSVParams.TrueText
|
|
else
|
|
s := CSVParams.FalseText;
|
|
s := ConvertEncoding(s, EncodingUTF8, FEncoding);
|
|
FCSVBuilder.AppendCell(s);
|
|
end;
|
|
|
|
{ Write date/time values in the same way they are displayed in the sheet }
|
|
procedure TsCSVWriter.WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: TDateTime; ACell: PCell);
|
|
var
|
|
s: String;
|
|
begin
|
|
Unused(AStream);
|
|
Unused(ARow, ACol, AValue);
|
|
s := FWorksheet.ReadAsText(ACell);
|
|
s := ConvertEncoding(s, EncodingUTF8, FEncoding);
|
|
FCSVBuilder.AppendCell(s);
|
|
end;
|
|
|
|
{ CSV does not support formulas, but we can write the formula results to
|
|
to stream. }
|
|
procedure TsCSVWriter.WriteFormula(AStream: TStream; const ARow, ACol: Cardinal;
|
|
ACell: PCell);
|
|
begin
|
|
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;
|
|
end;
|
|
|
|
{ Writes a LABEL cell to the stream. }
|
|
procedure TsCSVWriter.WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: string; ACell: PCell);
|
|
var
|
|
s: String;
|
|
begin
|
|
Unused(AStream);
|
|
Unused(ARow, ACol, AValue);
|
|
if ACell = nil then
|
|
exit;
|
|
s := ACell^.UTF8StringValue;
|
|
s := ConvertEncoding(s, EncodingUTF8, FEncoding);
|
|
// No need to quote; csvdocument will do that for us...
|
|
FCSVBuilder.AppendCell(s);
|
|
end;
|
|
|
|
{ Writes a number cell to the stream. }
|
|
procedure TsCSVWriter.WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: double; ACell: PCell);
|
|
var
|
|
s: String;
|
|
begin
|
|
Unused(AStream);
|
|
Unused(ARow, ACol);
|
|
if ACell = nil then
|
|
exit;
|
|
if CSVParams.NumberFormat <> '' then
|
|
s := Format(CSVParams.NumberFormat, [AValue], FFormatSettings)
|
|
else
|
|
s := FWorksheet.ReadAsText(ACell, FFormatSettings);
|
|
s := ConvertEncoding(s, EncodingUTF8, FEncoding);
|
|
FCSVBuilder.AppendCell(s);
|
|
end;
|
|
|
|
procedure TsCSVWriter.WriteSheet(AStream: TStream; AWorksheet: TsWorksheet);
|
|
var
|
|
r, c: Cardinal;
|
|
firstRow, lastRow: Cardinal;
|
|
firstCol, lastCol: Cardinal;
|
|
cell: PCell;
|
|
n: Integer;
|
|
begin
|
|
FWorksheet := AWorksheet;
|
|
|
|
FCSVBuilder := TCSVBuilder.Create;
|
|
try
|
|
FCSVBuilder.Delimiter := CSVParams.Delimiter;
|
|
FCSVBuilder.LineEnding := LineEndingAsString(CSVParams.LineEnding);
|
|
FCSVBuilder.QuoteChar := CSVParams.QuoteChar;
|
|
FCSVBuilder.SetOutput(AStream);
|
|
|
|
n := FWorksheet.GetCellCount;
|
|
if FClipboardMode and (n = 1) then
|
|
begin
|
|
cell := FWorksheet.Cells.GetFirstCell;
|
|
WriteCellToStream(AStream, cell);
|
|
end else
|
|
begin
|
|
if FClipboardMode then
|
|
begin
|
|
firstRow := FWorksheet.GetFirstRowIndex;
|
|
firstCol := FWorksheet.GetFirstColIndex;
|
|
end else
|
|
begin
|
|
firstRow := 0;
|
|
firstCol := 0;
|
|
end;
|
|
lastRow := FWorksheet.GetLastOccupiedRowIndex;
|
|
lastCol := FWorksheet.GetLastOccupiedColIndex;
|
|
for r := firstRow to lastRow do
|
|
begin
|
|
for c := firstCol to lastCol do
|
|
begin
|
|
cell := FWorksheet.FindCell(r, c);
|
|
if cell = nil then
|
|
FCSVBuilder.AppendCell('')
|
|
else
|
|
WriteCellToStream(AStream, cell);
|
|
end;
|
|
FCSVBuilder.AppendRow;
|
|
end;
|
|
end;
|
|
finally
|
|
FreeAndNil(FCSVBuilder);
|
|
end;
|
|
end;
|
|
|
|
procedure TsCSVWriter.WriteToStream(AStream: TStream;
|
|
AParams: TsStreamParams = []);
|
|
var
|
|
n: Integer;
|
|
begin
|
|
FClipboardMode := (spClipboard in AParams);
|
|
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;
|
|
AParams: TsStreamParams = []);
|
|
var
|
|
Stream: TStream;
|
|
begin
|
|
Stream := TStringStream.Create('');
|
|
try
|
|
WriteToStream(Stream, AParams);
|
|
Stream.Position := 0;
|
|
AStrings.LoadFromStream(Stream);
|
|
finally
|
|
Stream.Free;
|
|
end;
|
|
end;
|
|
|
|
|
|
initialization
|
|
InitFormatSettings(CSVParams.FormatSettings);
|
|
|
|
// Registers this reader / writer in fpSpreadsheet
|
|
sfidCSV := RegisterSpreadFormat(sfCSV,
|
|
TsCSVReader, TsCSVWriter,
|
|
STR_FILEFORMAT_CSV, 'CSV', [STR_COMMA_SEPARATED_EXTENSION, '.txt']
|
|
);
|
|
|
|
end.
|
|
|