浏览代码

* Implemented CSVDataset

git-svn-id: trunk@30417 -
michael 10 年之前
父节点
当前提交
6b130438eb

+ 2 - 0
.gitattributes

@@ -2101,6 +2101,7 @@ packages/fcl-db/src/base/Makefile svneol=native#text/plain
 packages/fcl-db/src/base/Makefile.fpc svneol=native#text/plain
 packages/fcl-db/src/base/bufdataset.pas svneol=native#text/plain
 packages/fcl-db/src/base/bufdataset_parser.pp svneol=native#text/plain
+packages/fcl-db/src/base/csvdataset.pp svneol=native#text/plain
 packages/fcl-db/src/base/database.inc svneol=native#text/plain
 packages/fcl-db/src/base/dataset.inc svneol=native#text/plain
 packages/fcl-db/src/base/datasource.inc svneol=native#text/plain
@@ -2314,6 +2315,7 @@ packages/fcl-db/tests/memdstoolsunit.pas svneol=native#text/plain
 packages/fcl-db/tests/reruntest.sh svneol=native#text/plain
 packages/fcl-db/tests/sdfdstoolsunit.pas svneol=native#text/plain
 packages/fcl-db/tests/sqldbtoolsunit.pas svneol=native#text/plain
+packages/fcl-db/tests/tccsvdataset.pp svneol=native#text/plain
 packages/fcl-db/tests/tcgensql.pas svneol=native#text/plain
 packages/fcl-db/tests/tcparser.pas svneol=native#text/plain
 packages/fcl-db/tests/tcsdfdata.pp svneol=native#text/plain

+ 7 - 0
packages/fcl-db/fpmake.pp

@@ -92,6 +92,13 @@ begin
           AddUnit('dbconst');
         end;
 
+    T:=P.Targets.AddUnit('csvdataset.pp');
+      with T.Dependencies do
+        begin
+        AddUnit('db');
+        AddUnit('bufdataset');
+        end;
+
     T:=P.Targets.AddUnit('bufdataset_parser.pp');
       with T.Dependencies do
         begin

+ 389 - 0
packages/fcl-db/src/base/csvdataset.pp

@@ -0,0 +1,389 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 1999-2014 by Michael Van Canneyt, member of the
+    Free Pascal development team
+
+    CSV Dataset implementation.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit csvdataset;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, bufdataset, csvreadwrite, db, sqldb;
+
+Type
+
+
+  { TCSVOptions }
+
+  TCSVOptions = Class(TCSVHandler)
+  private
+    FDefaultFieldLength: Word;
+    FFirstLineAsFieldNames: Boolean;
+  Public
+    Constructor Create; override;
+    Procedure Assign(Source : TPersistent); override;
+  Published
+    // Does first line of the file contain the field names to use ?
+    property FirstLineAsFieldNames : Boolean Read FFirstLineAsFieldNames Write FFirstLineAsFieldNames;
+    // Default is to create all fields as strings with the same length. Default string field length.
+    // If the CSV dataset has field defs prior to loading, this is ignored.
+    property DefaultFieldLength : Word Read FDefaultFieldLength Write FDefaultFieldLength;
+    // Field delimiter
+    property Delimiter;
+    // Character used to quote "problematic" data
+    // (e.g. with delimiters or spaces in them)
+    // A common quotechar is "
+    property QuoteChar;
+    // String at the end of the line of data (e.g. CRLF)
+    property LineEnding;
+    // Ignore whitespace between delimiters and field data
+    property IgnoreOuterWhitespace;
+    // Use quotes when outer whitespace is found
+    property QuoteOuterWhitespace;
+  end;
+
+  { TCSVDataPacketReader }
+
+  TCSVDataPacketReader = class(TDataPacketReader)
+  private
+    FOptions: TCSVOptions;
+    FOwnsOptions: Boolean;
+    FParser : TCSVParser;
+    FBuilder : TCSVBuilder;
+    FLine : TStringList;
+    FCurrentRow : Integer;
+    FEOF : Boolean;
+    FCreateFieldDefs : TFieldDefs;
+    // Read next row in Fline
+  Protected
+    Procedure ReadNextRow;virtual;
+    procedure SetCreateFieldDefs(AValue: TFieldDefs);virtual;
+  public
+    constructor Create(ADataSet: TCustomBufDataset; AStream : TStream); override;
+    constructor Create(ADataSet: TCustomBufDataset; AStream : TStream; AOptions : TCSVOptions);
+    Destructor Destroy; override;
+    procedure LoadFieldDefs(var AnAutoIncValue : integer); override;
+    procedure StoreFieldDefs(AnAutoIncValue : integer); override;
+    function GetRecordRowState(out AUpdOrder : Integer) : TRowState; override;
+    procedure FinalizeStoreRecords; override;
+    function GetCurrentRecord : boolean; override;
+    procedure GotoNextRecord; override;
+    procedure InitLoadRecords; override;
+    procedure RestoreRecord override;
+    procedure StoreRecord(ARowState : TRowState; AUpdOrder : integer = 0); override;
+    class function RecognizeStream(AStream : TStream) : boolean; override;
+    Property Options : TCSVOptions Read FOptions;
+    Property CreateFieldDefs : TFieldDefs read FCreateFieldDefs Write SetCreateFieldDefs;
+  end;
+
+  { TCustomCSVDataset }
+
+  TCustomCSVDataset = Class(TBufDataset)
+  private
+    FCSVOptions: TCSVOptions;
+    procedure SetCSVOptions(AValue: TCSVOptions);
+  Protected
+    procedure LoadBlobIntoBuffer(FieldDef: TFieldDef;ABlobBuf: PBufBlobField); override;
+    procedure InternalInitFieldDefs; override;
+  Public
+    Constructor Create(AOwner : TComponent); override;
+    Destructor Destroy; override;
+    { If FieldDefs is filled prior to calling one of the load functions,
+      the fielddefs definitions will be checked against file contents
+      as far as possible: count and names if names are on first line}
+    procedure LoadFromCSVStream(AStream : TStream);
+    procedure LoadFromCSVFile(Const AFileName: string);
+    procedure SaveToCSVStream(AStream : TStream);
+    procedure SaveToCSVFile(AFileName: string = '');
+  Protected
+    Property CSVOptions : TCSVOptions Read FCSVOptions Write SetCSVOptions;
+  end;
+
+  TCSVDataset = Class(TCustomCSVDataset)
+  Published
+    Property CSVOptions;
+  end;
+
+implementation
+
+{ TCSVDataPacketReader }
+
+Procedure TCSVDataPacketReader.ReadNextRow;
+
+
+begin
+  FLine.Clear;
+  if not FEOF then
+    begin
+    if (FCurrentRow>0) then
+      FLine.Add(FParser.CurrentCellText);
+    Repeat
+      FEOF:=Not FParser.ParseNextCell;
+      if (not FEOF) and (FParser.CurrentRow=FCurrentRow) then
+        FLine.Add(FParser.CurrentCellText);
+    until FEOF or (FParser.CurrentRow>FCurrentRow);
+    end;
+  FCurrentRow:=FParser.CurrentRow;
+end;
+
+procedure TCSVDataPacketReader.SetCreateFieldDefs(AValue: TFieldDefs);
+begin
+  if FCreateFieldDefs=AValue then Exit;
+  if (FCreateFieldDefs=Nil) then
+    begin
+    FCreateFieldDefs:=TFieldDefs.Create(AValue.Dataset);
+    FCreateFieldDefs.Assign(AValue);
+    end;
+end;
+
+constructor TCSVDataPacketReader.Create(ADataSet: TCustomBufDataset; AStream: TStream);
+begin
+  inherited Create(ADataSet,AStream);
+  if FOptions=Nil then
+    begin
+    FOptions:=TCSVOptions.Create;
+    FOptions.FFirstLineAsFieldNames:=True;
+    FOwnsOptions:=True;
+    end;
+  FLine:=TStringList.Create;
+end;
+
+constructor TCSVDataPacketReader.Create(ADataSet: TCustomBufDataset; AStream: TStream; AOptions: TCSVOptions);
+begin
+  FOptions:=AOptions;
+  Create(ADataset,AStream);
+  FOwnsOptions:=AOptions=Nil;
+end;
+
+Destructor TCSVDataPacketReader.Destroy;
+begin
+  If FOwnsOptions then
+    FreeAndNil(FOPtions);
+  FreeAndNil(Fline);
+  inherited Destroy;
+end;
+
+procedure TCSVDataPacketReader.LoadFieldDefs(var AnAutoIncValue: integer);
+Var
+  FN : String;
+  I : Integer;
+
+begin
+  FParser:=TCSVParser.Create;
+  FParser.SetSource(Stream);
+  FCurrentRow:=0;
+  ReadNextRow;
+  If Assigned(CreateFieldDefs) then
+   begin
+   if (CreateFieldDefs.Count<>Fline.Count) then
+     DatabaseErrorFmt('CSV File Field count (%d) does not match dataset field count (%d).',[Fline.Count,CreateFieldDefs.Count],Dataset.FieldDefs.Dataset);
+   If FOptions.FirstLineAsFieldNames then
+     For I:=0 to FLine.Count-1 do
+       If (CompareText(FLine[i],CreateFieldDefs[i].Name)<>0) then
+         DatabaseErrorFmt('CSV File field %d: name "%s" does not match dataset field name "%s".',[I,FLine[i],CreateFieldDefs[i].Name],Dataset.FieldDefs.Dataset);
+   Dataset.FieldDefs.Assign(CreateFieldDefs);
+   end
+  else if (FLine.Count>0) then
+    For I:=0 to FLine.Count-1 do
+      begin
+      If FOptions.FirstLineAsFieldNames then
+        FN:=FLine[i]
+      else
+        FN:=Format('Column%d',[i+1]);
+      Dataset.FieldDefs.Add(FN,ftString,Foptions.DefaultFieldLength);
+      end;
+  if FOptions.FirstLineAsFieldNames then
+   ReadNextRow;
+end;
+
+procedure TCSVDataPacketReader.StoreFieldDefs(AnAutoIncValue: integer);
+
+Var
+  I : Integer;
+
+begin
+  FBuilder:=TCSVBuilder.Create;
+  FBuilder.SetOutput(Stream);
+  if FOptions.FirstLineAsFieldNames then
+    begin
+    For I:=0 to Dataset.FieldDefs.Count-1 do
+      FBuilder.AppendCell(Dataset.FieldDefs[i].Name);
+    FBuilder.AppendRow;
+    end;
+end;
+
+function TCSVDataPacketReader.GetRecordRowState(out AUpdOrder: Integer
+  ): TRowState;
+begin
+  AUpdOrder:=0;
+  Result:=[];
+end;
+
+procedure TCSVDataPacketReader.FinalizeStoreRecords;
+begin
+
+end;
+
+function TCSVDataPacketReader.GetCurrentRecord: boolean;
+begin
+  Result:=Fline.Count>0;
+end;
+
+procedure TCSVDataPacketReader.GotoNextRecord;
+begin
+  ReadNextRow;
+end;
+
+procedure TCSVDataPacketReader.InitLoadRecords;
+begin
+   // Do nothing
+end;
+
+procedure TCSVDataPacketReader.RestoreRecord;
+
+Var
+  I : integer;
+
+begin
+  For I:=0 to Fline.Count-1 do
+    Dataset.Fields[i].AsString:=Copy(FLine[i],1,Dataset.Fields[i].Size)
+end;
+
+procedure TCSVDataPacketReader.StoreRecord(ARowState: TRowState; AUpdOrder: integer);
+Var
+  I : integer;
+
+begin
+  For I:=0 to Dataset.Fields.Count-1 do
+    FBuilder.AppendCell(Dataset.Fields[i].AsString);
+  FBuilder.AppendRow;
+end;
+
+class function TCSVDataPacketReader.RecognizeStream(AStream: TStream): boolean;
+begin
+  Result:=False;
+end;
+
+{ TCSVOptions }
+
+Constructor TCSVOptions.Create;
+begin
+  inherited Create;
+  DefaultFieldLength:=255;
+end;
+
+Procedure TCSVOptions.Assign(Source: TPersistent);
+begin
+  if (Source is TCSVOptions) then
+    begin
+    FFirstLineAsFieldNames:=TCSVOptions(Source).FirstLineAsFieldNames;
+    FDefaultFieldLength:=TCSVOptions(Source).FDefaultFieldLength
+    end;
+  inherited Assign(Source);
+end;
+
+{ TCustomCSVDataset }
+
+procedure TCustomCSVDataset.SetCSVOptions(AValue: TCSVOptions);
+begin
+  if (FCSVOptions=AValue) then Exit;
+  FCSVOptions.Assign(AValue);
+end;
+
+procedure TCustomCSVDataset.LoadBlobIntoBuffer(FieldDef: TFieldDef;
+  ABlobBuf: PBufBlobField);
+begin
+  // Do nothing
+end;
+
+procedure TCustomCSVDataset.InternalInitFieldDefs;
+begin
+  // Do nothing
+end;
+
+Constructor TCustomCSVDataset.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FCSVOptions:=TCSVOptions.Create;
+end;
+
+Destructor TCustomCSVDataset.Destroy;
+begin
+  FreeAndNil(FCSVOptions);
+  inherited Destroy;
+end;
+
+procedure TCustomCSVDataset.LoadFromCSVStream(AStream: TStream);
+
+Var
+  P : TCSVDataPacketReader;
+
+begin
+  CheckInactive;
+  P:=TCSVDataPacketReader.Create(Self,AStream,FCSVOptions);
+  try
+    if FieldDefs.Count>0 then
+     P.CreateFieldDefs:=FieldDefs;
+    SetDatasetPacket(P);
+  finally
+    P.Free;
+  end;
+end;
+
+procedure TCustomCSVDataset.LoadFromCSVFile(Const AFileName: string);
+
+Var
+  F : TFileStream;
+
+begin
+  F:=TFileStream.Create(AFileName,fmOpenRead or fmShareDenyWrite);
+  try
+    LoadFromCSVStream(F);
+  finally
+    F.Free;
+  end;
+end;
+
+procedure TCustomCSVDataset.SaveToCSVStream(AStream: TStream);
+
+Var
+  P : TCSVDataPacketReader;
+
+begin
+  First;
+  MergeChangeLog;
+  P:=TCSVDataPacketReader.Create(Self,AStream,FCSVOPtions);
+  try
+    GetDatasetPacket(P);
+  finally
+    P.Free;
+  end;
+end;
+
+procedure TCustomCSVDataset.SaveToCSVFile(AFileName: string);
+Var
+  F : TFileStream;
+
+begin
+  F:=TFileStream.Create(AFileName,fmOpenRead or fmShareDenyWrite);
+  try
+    SaveToCSVStream(F);
+  finally
+    F.Free;
+  end;
+end;
+
+end.
+

+ 1 - 1
packages/fcl-db/tests/dbtestframework.pas

@@ -28,7 +28,7 @@ uses
   TestSpecificTBufDataset,
   TestSpecificTDBF,
   TestSpecificTMemDataset,
-  TestDBExport,
+  TestDBExport, tccsvdataset,
   consoletestrunner;
 
 Procedure LegacyOutput;

+ 6 - 28
packages/fcl-db/tests/dbtestframework_gui.lpi

@@ -27,12 +27,6 @@
             <IncludeFiles Value="$(ProjOutDir)"/>
             <OtherUnitFiles Value="../src/base;../src/sqldb/odbc;../src/sqldb/mssql;../src/sqldb/sqlite;../src/sqldb/postgres;../src/sqldb/oracle;../src/memds;../src/sqldb;../src/sqldb/interbase;../src/sqldb/mysql;../src/dbase;../src/sdf"/>
           </SearchPaths>
-          <Other>
-            <CompilerMessages>
-              <UseMsgFile Value="True"/>
-            </CompilerMessages>
-            <CompilerPath Value="$(CompPath)"/>
-          </Other>
         </CompilerOptions>
       </Item2>
       <Item3 Name="Default_no_local_ppus">
@@ -46,12 +40,6 @@
               <GenerateDebugInfo Value="False"/>
             </Debugging>
           </Linking>
-          <Other>
-            <CompilerMessages>
-              <UseMsgFile Value="True"/>
-            </CompilerMessages>
-            <CompilerPath Value="$(CompPath)"/>
-          </Other>
         </CompilerOptions>
       </Item3>
       <Item4 Name="Default_no_local_ppus_debug">
@@ -65,12 +53,6 @@
               <OptimizationLevel Value="0"/>
             </Optimizations>
           </CodeGeneration>
-          <Other>
-            <CompilerMessages>
-              <UseMsgFile Value="True"/>
-            </CompilerMessages>
-            <CompilerPath Value="$(CompPath)"/>
-          </Other>
         </CompilerOptions>
       </Item4>
     </BuildModes>
@@ -82,7 +64,6 @@
     <RunParams>
       <local>
         <FormatVersion Value="1"/>
-        <LaunchingApplication PathPlusParams="/usr/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/>
       </local>
     </RunParams>
     <RequiredPackages Count="4">
@@ -99,17 +80,20 @@
         <PackageName Value="FCL"/>
       </Item4>
     </RequiredPackages>
-    <Units Count="2">
+    <Units Count="3">
       <Unit0>
         <Filename Value="dbtestframework_gui.lpr"/>
         <IsPartOfProject Value="True"/>
-        <UnitName Value="dbtestframework_gui"/>
       </Unit0>
       <Unit1>
         <Filename Value="dbguitestrunner.pas"/>
         <IsPartOfProject Value="True"/>
-        <UnitName Value="DBGuiTestRunner"/>
       </Unit1>
+      <Unit2>
+        <Filename Value="tccsvdataset.pp"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="tccsvdataset"/>
+      </Unit2>
     </Units>
   </ProjectOptions>
   <CompilerOptions>
@@ -123,12 +107,6 @@
         <GenerateDebugInfo Value="False"/>
       </Debugging>
     </Linking>
-    <Other>
-      <CompilerMessages>
-        <UseMsgFile Value="True"/>
-      </CompilerMessages>
-      <CompilerPath Value="$(CompPath)"/>
-    </Other>
   </CompilerOptions>
   <Debugging>
     <Exceptions Count="7">

+ 1 - 1
packages/fcl-db/tests/dbtestframework_gui.lpr

@@ -35,7 +35,7 @@ uses
   TestSpecificTBufDataset,
   TestSpecificTDBF,
   TestSpecificTMemDataset,
-  TestDBExport;
+  TestDBExport, tccsvdataset;
 
 {$R *.res}
 

+ 404 - 0
packages/fcl-db/tests/tccsvdataset.pp

@@ -0,0 +1,404 @@
+unit tccsvdataset;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, db, SysUtils, fpcunit, testutils, testregistry, csvdataset;
+
+type
+
+  { TTestCSVDataset }
+
+  TTestCSVDataset= class(TTestCase)
+  private
+    FCSVDataset: TCSVDataset;
+    // Load CSVDataset from CSV stream containing lines
+    Procedure LoadFromLines(Const Lines: Array of string);
+    // Save CSVDataset to CSV stream, transform to lines
+    Procedure SaveToLines(Const Lines: TStrings);
+    // Save CSVDataset to CSV stream, transform to lines, compare with given lines
+    Procedure AssertLines(Const Lines: Array of string);
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+    Property CSVDataset : TCSVDataset Read FCSVDataset;
+  published
+    procedure TestEmpty;
+    procedure TestDefaults;
+    Procedure TestLoadEmptyDefault;
+    Procedure TestLoadEmptyFirstLineAsNames;
+    Procedure TestLoad2fieldsFirstLineAsNames;
+    Procedure TestLoad2fields;
+    Procedure TestLoad2Records2fields;
+    Procedure TestSaveEmptyDefault;
+    Procedure TestSaveEmptyFirstLineAsNames;
+    Procedure TestSaveOneRecordDefault;
+    Procedure TestSaveOneRecordFirstLineAsNames;
+    Procedure TestSaveTwoRecordsDefault;
+    Procedure TestSaveTwoRecordsFirstLineAsNames;
+    Procedure TestSaveOneRecord2FieldsDefault;
+    Procedure TestSaveOneRecord2FieldsFirstLineAsNames;
+    Procedure TestLoadPriorFieldDefs;
+    Procedure TestLoadPriorFieldDefsNoFieldNames;
+    Procedure TestLoadPriorFieldDefsNoFieldNamesWrongCount;
+    Procedure TestLoadPriorFieldDefsFieldNamesWrongCount;
+    Procedure TestLoadPriorFieldDefsFieldNamesWrongNames;
+  end;
+
+implementation
+
+procedure TTestCSVDataset.TestEmpty;
+begin
+  AssertNotNull('Have CSV dataset',CSVDataset);
+  AssertFalse('Not open',CSVDataset.Active);
+  AssertEquals('No fielddefs',0,CSVDataset.FieldDefs.Count);
+  AssertEquals('Name','DS',CSVDataset.Name);
+end;
+
+procedure TTestCSVDataset.TestDefaults;
+begin
+  With CSVDataset.CSVOptions do
+    begin
+    AssertEquals('DefaultFieldLength',255,DefaultFieldLength);
+    AssertEquals('FirstLineAsFieldNames',False,FirstLineAsFieldNames);
+    AssertEquals('Delimiter',',',Delimiter);
+    AssertEquals('QuoteChar','"',QuoteChar);
+    AssertEquals('LineEnding',sLineBreak,LineEnding);
+    AssertEquals('IgnoreOuterWhitespace',False,IgnoreOuterWhitespace);
+    AssertEquals('QuoteOuterWhitespace',True,QuoteOuterWhitespace);
+    AssertEquals('EqualColCountPerRow',True,EqualColCountPerRow);
+    end;
+end;
+
+Procedure TTestCSVDataset.LoadFromLines(Const Lines : Array of string);
+
+Var
+  L : TStringList;
+  s : TStream;
+begin
+  S:=Nil;
+  L:=TStringList.Create;
+  try
+    L.AddStrings(Lines);
+    S:=TStringStream.Create(L.Text);
+    CSVDataset.LoadFromCSVStream(S);
+  finally
+    S.Free;
+    L.Free;
+  end;
+end;
+
+Procedure TTestCSVDataset.SaveToLines(Const Lines: TStrings);
+
+Var
+  S : TStringStream;
+
+begin
+  S:=TStringStream.Create('');
+  try
+    CSVDataset.SaveToCSVStream(S);
+    Lines.Text:=S.DataString;
+    {
+    Writeln('----');
+    Writeln(S.DataString);
+    Writeln('----');
+    }
+  finally
+    S.Free;
+  end;
+end;
+
+Procedure TTestCSVDataset.AssertLines(Const Lines: Array of string);
+
+Var
+  L : TStrings;
+  I : Integer;
+begin
+  L:=TStringList.Create;
+  try
+    SaveToLines(L);
+    AssertEquals('Number of lines',Length(Lines),L.Count);
+    For I:=0 to L.Count-1 do
+      AssertEquals('Correct line '+IntToStr(0),Lines[I],L[i]);
+  finally
+    L.Free;
+  end;
+end;
+
+Procedure TTestCSVDataset.TestLoadEmptyDefault;
+begin
+  LoadFromLines(['a']);
+  AssertEquals('Active',True,CSVDataset.Active);
+  AssertEquals('field count',1,CSVDataset.FieldDefs.Count);
+  AssertEquals('field name','Column1',CSVDataset.FieldDefs[0].Name);
+  AssertEquals('field size',CSVDataset.CSVOptions.DefaultFieldLength,CSVDataset.FieldDefs[0].Size);
+  AssertEquals('Not Empty',False,CSVDataset.EOF and CSVDataset.BOF);
+  AssertEquals('field contents','a',CSVDataset.Fields[0].AsString);
+end;
+
+Procedure TTestCSVDataset.TestLoadEmptyFirstLineAsNames;
+
+begin
+  CSVDataset.CSVOptions.FirstLineAsFieldNames:=True;
+  CSVDataset.CSVOptions.DefaultFieldLength:=128;
+  LoadFromLines(['a']);
+  AssertEquals('Active',True,CSVDataset.Active);
+  AssertEquals('field count',1,CSVDataset.FieldDefs.Count);
+  AssertEquals('field name','a',CSVDataset.FieldDefs[0].Name);
+  AssertEquals('field size',CSVDataset.CSVOptions.DefaultFieldLength,CSVDataset.FieldDefs[0].Size);
+  AssertEquals('Empty',True,CSVDataset.EOF and CSVDataset.BOF);
+end;
+
+Procedure TTestCSVDataset.TestLoad2fieldsFirstLineAsNames;
+begin
+  CSVDataset.CSVOptions.FirstLineAsFieldNames:=True;
+  CSVDataset.CSVOptions.DefaultFieldLength:=128;
+  LoadFromLines(['a,b']);
+  AssertEquals('Active',True,CSVDataset.Active);
+  AssertEquals('field count',2,CSVDataset.FieldDefs.Count);
+  AssertEquals('field 0 name','a',CSVDataset.FieldDefs[0].Name);
+  AssertEquals('field 0 size',CSVDataset.CSVOptions.DefaultFieldLength,CSVDataset.FieldDefs[0].Size);
+  AssertEquals('field 1 name','b',CSVDataset.FieldDefs[1].Name);
+  AssertEquals('field 1 size',CSVDataset.CSVOptions.DefaultFieldLength,CSVDataset.FieldDefs[1].Size);
+  AssertEquals('Empty',True,CSVDataset.EOF and CSVDataset.BOF);
+end;
+
+Procedure TTestCSVDataset.TestLoad2fields;
+
+begin
+  CSVDataset.CSVOptions.DefaultFieldLength:=128;
+  LoadFromLines(['a,b']);
+  AssertEquals('Active',True,CSVDataset.Active);
+  AssertEquals('field count',2,CSVDataset.FieldDefs.Count);
+  AssertEquals('field 0 name','Column1',CSVDataset.FieldDefs[0].Name);
+  AssertEquals('field 0 size',CSVDataset.CSVOptions.DefaultFieldLength,CSVDataset.FieldDefs[0].Size);
+  AssertEquals('field 1 name','Column2',CSVDataset.FieldDefs[1].Name);
+  AssertEquals('field 1 size',CSVDataset.CSVOptions.DefaultFieldLength,CSVDataset.FieldDefs[1].Size);
+  AssertEquals('Empty',False,CSVDataset.EOF and CSVDataset.BOF);
+  AssertEquals('Not Empty',False,CSVDataset.EOF and CSVDataset.BOF);
+  AssertEquals('field 0 contents','a',CSVDataset.Fields[0].AsString);
+  AssertEquals('field 1 contents','b',CSVDataset.Fields[1].AsString);
+end;
+
+Procedure TTestCSVDataset.TestLoad2Records2fields;
+begin
+  CSVDataset.CSVOptions.DefaultFieldLength:=128;
+  LoadFromLines(['a,b','c,d']);
+  AssertEquals('Active',True,CSVDataset.Active);
+  AssertEquals('field count',2,CSVDataset.FieldDefs.Count);
+  AssertEquals('field 0 name','Column1',CSVDataset.FieldDefs[0].Name);
+  AssertEquals('field 0 size',CSVDataset.CSVOptions.DefaultFieldLength,CSVDataset.FieldDefs[0].Size);
+  AssertEquals('field 1 name','Column2',CSVDataset.FieldDefs[1].Name);
+  AssertEquals('field 1 size',CSVDataset.CSVOptions.DefaultFieldLength,CSVDataset.FieldDefs[1].Size);
+  AssertEquals('Empty',False,CSVDataset.EOF and CSVDataset.BOF);
+  AssertEquals('Not Empty',False,CSVDataset.EOF and CSVDataset.BOF);
+  AssertEquals('field 0 contents','a',CSVDataset.Fields[0].AsString);
+  AssertEquals('field 1 contents','b',CSVDataset.Fields[1].AsString);
+  CSVDataset.Next;
+  AssertEquals('not At EOF',False,CSVDataset.EOF);
+  AssertEquals('field 0 contents','c',CSVDataset.Fields[0].AsString);
+  AssertEquals('field 1 contents','d',CSVDataset.Fields[1].AsString);
+  CSVDataset.Next;
+  AssertEquals('At EOF',True,CSVDataset.EOF);
+end;
+
+Procedure TTestCSVDataset.TestSaveEmptyDefault;
+begin
+  CSVDataset.FieldDefs.Add('a',ftString);
+  CSVDataset.CreateDataset;
+  AssertLines([]);
+end;
+
+Procedure TTestCSVDataset.TestSaveEmptyFirstLineAsNames;
+begin
+  CSVDataset.CSVOptions.FirstLineAsFieldNames:=True;
+  CSVDataset.FieldDefs.Add('a',ftString);
+  CSVDataset.CreateDataset;
+  AssertLines(['a']);
+end;
+
+Procedure TTestCSVDataset.TestSaveOneRecordDefault;
+begin
+//  CSVDataset.CSVOptions.FirstLineAsFieldNames:=True;
+  CSVDataset.FieldDefs.Add('a',ftString,20);
+  CSVDataset.CreateDataset;
+  CSVDataset.Append;
+  CSVDataset.Fields[0].AsString:='b';
+  CSVDataset.Post;
+  AssertLines(['b']);
+end;
+
+Procedure TTestCSVDataset.TestSaveOneRecordFirstLineAsNames;
+begin
+  CSVDataset.CSVOptions.FirstLineAsFieldNames:=True;
+  CSVDataset.FieldDefs.Add('a',ftString,20);
+  CSVDataset.CreateDataset;
+  CSVDataset.Append;
+  CSVDataset.Fields[0].AsString:='b';
+  CSVDataset.Post;
+  AssertLines(['a','b']);
+end;
+
+Procedure TTestCSVDataset.TestSaveTwoRecordsDefault;
+begin
+  CSVDataset.FieldDefs.Add('a',ftString,20);
+  CSVDataset.CreateDataset;
+  CSVDataset.Append;
+  CSVDataset.Fields[0].AsString:='b';
+  CSVDataset.Post;
+  CSVDataset.Append;
+  CSVDataset.Fields[0].AsString:='c';
+  CSVDataset.Post;
+  AssertLines(['b','c']);
+end;
+
+Procedure TTestCSVDataset.TestSaveTwoRecordsFirstLineAsNames;
+begin
+  CSVDataset.CSVOptions.FirstLineAsFieldNames:=True;
+  CSVDataset.FieldDefs.Add('a',ftString,20);
+  CSVDataset.CreateDataset;
+  CSVDataset.Append;
+  CSVDataset.Fields[0].AsString:='b';
+  CSVDataset.Post;
+  CSVDataset.Append;
+  CSVDataset.Fields[0].AsString:='c';
+  CSVDataset.Post;
+  AssertLines(['a','b','c']);
+end;
+
+Procedure TTestCSVDataset.TestSaveOneRecord2FieldsDefault;
+begin
+  CSVDataset.FieldDefs.Add('a',ftString,20);
+  CSVDataset.FieldDefs.Add('b',ftString,20);
+  CSVDataset.CreateDataset;
+  CSVDataset.Append;
+  CSVDataset.Fields[0].AsString:='c';
+  CSVDataset.Fields[1].AsString:='d';
+  CSVDataset.Post;
+  AssertLines(['c,d']);
+end;
+
+Procedure TTestCSVDataset.TestSaveOneRecord2FieldsFirstLineAsNames;
+begin
+  CSVDataset.CSVOptions.FirstLineAsFieldNames:=True;
+  CSVDataset.FieldDefs.Add('a',ftString,20);
+  CSVDataset.FieldDefs.Add('b',ftString,20);
+  CSVDataset.CreateDataset;
+  CSVDataset.Append;
+  CSVDataset.Fields[0].AsString:='c';
+  CSVDataset.Fields[1].AsString:='d';
+  CSVDataset.Post;
+  AssertLines(['a,b','c,d']);
+end;
+
+Procedure TTestCSVDataset.TestLoadPriorFieldDefs;
+begin
+  CSVDataset.CSVOptions.FirstLineAsFieldNames:=True;
+  CSVDataset.FieldDefs.Add('a',ftString,20);
+  CSVDataset.FieldDefs.Add('b',ftInteger,4);
+  LoadFromLines(['a,b','1,2']);
+  AssertEquals('field count',2,CSVDataset.FieldDefs.Count);
+  AssertEquals('field 0 name','a',CSVDataset.FieldDefs[0].Name);
+  AssertEquals('field 0 size',20,CSVDataset.FieldDefs[0].Size);
+  AssertEquals('field 1 name','b',CSVDataset.FieldDefs[1].Name);
+  AssertEquals('field 1 size',4,CSVDataset.FieldDefs[1].Size);
+  AssertEquals('field 1 typee',Ord(ftInteger),Ord(CSVDataset.FieldDefs[1].DataType));
+  AssertEquals('Not Empty',False,CSVDataset.EOF and CSVDataset.BOF);
+  AssertEquals('field 0 contents','1',CSVDataset.Fields[0].AsString);
+  AssertEquals('field 1 contents',2,CSVDataset.Fields[1].AsInteger);
+end;
+
+Procedure TTestCSVDataset.TestLoadPriorFieldDefsNoFieldNames;
+begin
+  CSVDataset.FieldDefs.Add('a',ftString,20);
+  CSVDataset.FieldDefs.Add('b',ftInteger,4);
+  LoadFromLines(['1,2']);
+  AssertEquals('field count',2,CSVDataset.FieldDefs.Count);
+  AssertEquals('field 0 name','a',CSVDataset.FieldDefs[0].Name);
+  AssertEquals('field 0 size',20,CSVDataset.FieldDefs[0].Size);
+  AssertEquals('field 1 name','b',CSVDataset.FieldDefs[1].Name);
+  AssertEquals('field 1 size',4,CSVDataset.FieldDefs[1].Size);
+  AssertEquals('field 1 typee',Ord(ftInteger),Ord(CSVDataset.FieldDefs[1].DataType));
+  AssertEquals('Not Empty',False,CSVDataset.EOF and CSVDataset.BOF);
+  AssertEquals('field 0 contents','1',CSVDataset.Fields[0].AsString);
+  AssertEquals('field 1 contents',2,CSVDataset.Fields[1].AsInteger);
+end;
+
+Procedure TTestCSVDataset.TestLoadPriorFieldDefsNoFieldNamesWrongCount;
+
+Var
+  OK : Boolean;
+begin
+  CSVDataset.FieldDefs.Add('a',ftString,20);
+  CSVDataset.FieldDefs.Add('b',ftInteger,4);
+  try
+    LoadFromLines(['1']);
+    OK:=False;
+  except
+    OK:=true;
+  end;
+  if not OK then
+    Fail('Expected exception, but none raised');
+end;
+
+Procedure TTestCSVDataset.TestLoadPriorFieldDefsFieldNamesWrongCount;
+
+const
+  EM = 'DS : CSV File Field count (1) does not match dataset field count (2).';
+Var
+  OK : String;
+begin
+  CSVDataset.CSVOptions.FirstLineAsFieldNames:=True;
+  CSVDataset.FieldDefs.Add('a',ftString,20);
+  CSVDataset.FieldDefs.Add('b',ftInteger,4);
+  try
+    LoadFromLines(['A']);
+    OK:='Expected exception, but none raised';
+  except
+    on E : Exception do
+      if  (E.Message<>EM) then
+        OK:=ComparisonMsg(EM,E.Message);
+  end;
+  if (OK<>'') then
+    Fail(OK);
+end;
+
+Procedure TTestCSVDataset.TestLoadPriorFieldDefsFieldNamesWrongNames;
+const
+  EM = 'DS : CSV File field 1: name "c" does not match dataset field name "b".';
+Var
+  OK : String;
+begin
+  CSVDataset.CSVOptions.FirstLineAsFieldNames:=True;
+  CSVDataset.FieldDefs.Add('a',ftString,20);
+  CSVDataset.FieldDefs.Add('b',ftInteger,4);
+  try
+    LoadFromLines(['a,c']);
+    OK:='No exception raised';
+  except
+    on E : Exception do
+      if  (E.Message<>EM) then
+        OK:=ComparisonMsg(EM,E.Message)
+  end;
+  if (OK<>'') then
+    Fail(OK);
+end;
+
+procedure TTestCSVDataset.SetUp;
+begin
+  FCSVDataset:=TCSVDataset.Create(Nil);
+  FCSVDataset.Name:='DS';
+end;
+
+procedure TTestCSVDataset.TearDown;
+begin
+  FreeAndNil(FCSVDataset);
+end;
+
+Initialization
+
+  RegisterTest(TTestCSVDataset);
+end.
+