From a4ddc64e789cdc5fe2d66c28bedd426ef2cb33a3 Mon Sep 17 00:00:00 2001 From: michael Date: Sat, 4 Apr 2015 10:15:14 +0000 Subject: [PATCH] * Changed CSV export to use CSVBuilder to be more RFC 4180 compliant git-svn-id: trunk@30418 - --- packages/fcl-db/src/export/fpcsvexport.pp | 119 ++++++---------------- packages/fcl-db/tests/testdbexport.pas | 1 - 2 files changed, 31 insertions(+), 89 deletions(-) diff --git a/packages/fcl-db/src/export/fpcsvexport.pp b/packages/fcl-db/src/export/fpcsvexport.pp index 269f1b5e9c..c5bc2dc094 100644 --- a/packages/fcl-db/src/export/fpcsvexport.pp +++ b/packages/fcl-db/src/export/fpcsvexport.pp @@ -5,7 +5,7 @@ unit fpcsvexport; interface uses - Classes, SysUtils, DB, fpDBExport; + Classes, SysUtils, fpDBExport, csvreadwrite; Type { TCSVFormatSettings } @@ -14,35 +14,40 @@ Type Private FDelimiter: String; FHeaderRow: Boolean; - FQuoteStrings: TQuoteStrings; + FIgnoreOuterWhiteSpace: Boolean; FRowDelimiter: String; - FStringQuoteChar: String; + FQuoteChar: Char; Public Constructor Create(DoInitSettings : Boolean); override; Procedure Assign(Source : TPersistent); override; + // Kept for compatibility with older versions; please replace with QuoteChar + Property StringQuoteChar : Char Read FQuoteChar Write FQuoteChar; deprecated 'Please replace with QuoteChar'; Published // Properties + // Delimiter between fields/columns. Traditionally , for CSV. Property FieldDelimiter : String Read FDelimiter Write FDelimiter; + //If no, CSV is RFC 4180 compliant; if yes, it matches the unofficial Creativyst specification + Property IgnoreOuterWhitespace : Boolean Read FIgnoreOuterWhiteSpace write FIgnoreOuterWhiteSpace; + // Line ending to be used between rows of data (e.g. #13#10 for standard CSV) Property RowDelimiter : String Read FRowDelimiter Write FRowDelimiter; + // Whether or not the file should have a header row with field names Property HeaderRow : Boolean Read FHeaderRow Write FHeaderRow default true; - Property QuoteStrings : TQuoteStrings Read FQuoteStrings Write FQuoteStrings; - Property StringQuoteChar : String Read FStringQuoteChar Write FStringQuoteChar; + // If fields need to be surrounded by quotes, use this character (e.g. ") + Property QuoteChar : Char Read FQuoteChar Write FQuoteChar; end; { TCustomCSVExporter } TCustomCSVExporter = Class(TCustomFileExporter) private - FCurrentRow:String; + FCSVOut: TCSVBuilder; function GetCSVFormatsettings: TCSVFormatSettings; - procedure OutputRow(const ARow: String); procedure SetCSVFormatSettings(const AValue: TCSVFormatSettings); Protected Function CreateFormatSettings : TCustomExportFormatSettings; override; Procedure DoBeforeExecute; override; Procedure DoAfterExecute; override; Procedure DoDataHeader; override; - Procedure DoDataRowStart; override; Procedure ExportField(EF : TExportFieldItem); override; Procedure DoDataRowEnd; override; Public @@ -82,27 +87,23 @@ implementation procedure TCustomCSVExporter.DoBeforeExecute; begin inherited DoBeforeExecute; + FCSVOut:=TCSVBuilder.Create; + if (FormatSettings.FieldDelimiter<>'') then + FCSVOut.Delimiter:=FormatSettings.FieldDelimiter[1]; + FCSVOut.IgnoreOuterWhitespace:=FormatSettings.IgnoreOuterWhitespace; + FCSVOut.LineEnding:=FormatSettings.RowDelimiter; + FCSVOut.QuoteChar:=FormatSettings.QuoteChar; OpenTextFile; + FCSVOut.SetOutput(Stream); //output to the export stream end; procedure TCustomCSVExporter.DoAfterExecute; begin + FCSVOut.Free; CloseTextFile; inherited DoAfterExecute; end; -procedure TCustomCSVExporter.OutputRow(Const ARow : String); - -Var - RD : String; - -begin - RD:=FormatSettings.RowDelimiter; - If (RD='') then - Writeln(TextFile,ARow) - else - Write(TextFile,ARow,RD) -end; function TCustomCSVExporter.GetCSVFormatsettings: TCSVFormatSettings; begin @@ -124,84 +125,29 @@ end; procedure TCustomCSVExporter.DoDataHeader; Var - S : String; I : Integer; begin If FormatSettings.HeaderRow then begin - S:=''; For I:=0 to ExportFields.Count-1 do begin - If (S<>'') then - S:=S+FormatSettings.FieldDelimiter; - S:=S+ExportFields[i].ExportedName; + FCSVOut.AppendCell(ExportFields[i].ExportedName); end; - OutputRow(S); + FCSVOut.AppendRow; //close off with line ending end; inherited DoDataHeader; end; -procedure TCustomCSVExporter.DoDataRowStart; -begin - FCurrentRow:=''; -end; - procedure TCustomCSVExporter.ExportField(EF: TExportFieldItem); - - Function HaveSpace(Const S : String;QS : TQuoteStrings) : Boolean; - - begin - Result:=(qsSpace in QS) and (Pos(' ',S)<>0) - end; - - Function HaveDelimiter(Const S : String;QS : TQuoteStrings) : Boolean; - - Var - FD : String; - - begin - Result:=(qsDelimiter in QS); - If Result then - begin - FD:=FormatSettings.FieldDelimiter; - Result:=(FD<>'') and (Pos(FD,S)<>0); - end; - end; - -Var - S,C : String; - QS : TQuoteStrings; - begin - S:=FormatField(EF.Field); - QS:=FormatSettings.QuoteStrings; - {If specified, quote everything that can contain delimiters; - leave numeric, date fields alone:} - If ( - (EF.Field.DataType in StringFieldTypes) or - (EF.Field.DataType in MemoFieldTypes) or - (EF.Field.DataType in BlobFieldTypes) - ) - and (QS<>[]) then - begin - If (qsAlways in QS) or HaveSpace(S,QS) or HaveDelimiter(S,QS) then - begin - C:=FormatSettings.StringQuoteChar; - S:=C+S+C; - end; - end; - If (FCurrentRow<>'') then - FCurrentRow:=FCurrentRow+FormatSettings.FieldDelimiter; - FCurrentRow:=FCurrentRow+S; + FCSVOut.AppendCell(FormatField(EF.Field)); end; - procedure TCustomCSVExporter.DoDataRowEnd; begin - OutputRow(FCurrentRow); - FCurrentRow:=''; + FCSVOut.AppendRow; //Line ending end; constructor TCustomCSVExporter.Create(Aowner: TComponent); @@ -213,14 +159,12 @@ end; constructor TCSVFormatSettings.Create(DoInitSettings: Boolean); begin + // These defaults are meant to be Excel CSV compatible inherited Create(DoInitSettings); FHeaderRow:=True; FDelimiter:=','; - FStringQuoteChar:='"'; - FQuoteStrings:=[qsSpace, qsDelimiter]; - {Sensible defaults as reading unquoted strings with delimiters/spaces will - either fail by creating phantom fields (qsDelimiter) or delete leading or - trailing data/spaces (qsSpace)} + FQuoteChar:='"'; + FRowDelimiter:=LineEnding; end; procedure TCSVFormatSettings.Assign(Source: TPersistent); @@ -233,10 +177,9 @@ begin begin FS:=Source as TCSVFormatSettings; FDelimiter:=FS.FDelimiter; - FHeaderRow:=FS.FHEaderRow; - FQuoteStrings:=FS.FQuoteStrings; + FHeaderRow:=FS.FHeaderRow; FRowDelimiter:=FS.FRowDelimiter; - FStringQuoteChar:=FS.FStringQuoteChar; + FQuoteChar:=FS.FQuoteChar; end; inherited Assign(Source); end; @@ -250,8 +193,8 @@ end; Procedure UnRegisterCSVExportFormat; begin + ExportFormats.UnRegisterExportFormat(SCSVExport); end; end. - diff --git a/packages/fcl-db/tests/testdbexport.pas b/packages/fcl-db/tests/testdbexport.pas index a7a40952fb..39e1865683 100644 --- a/packages/fcl-db/tests/testdbexport.pas +++ b/packages/fcl-db/tests/testdbexport.pas @@ -517,7 +517,6 @@ begin ExportSettings:=TCSVFormatSettings.Create(true); try ExportSettings.FieldDelimiter:=';'; - ExportSettings.QuoteStrings:=[qsAlways,qsSpace,qsDelimiter]; //quote everything we can ExportSettings.StringQuoteChar:='"'; //try explicit assignment ExportSettings.RowDelimiter:=#10; //Unix/Linux format ExportSettings.BooleanFalse:='onwaar'; //why not a Dutch output format?