csvdataset.pp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 1999-2014 by Michael Van Canneyt, member of the
  4. Free Pascal development team
  5. CSV Dataset implementation.
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. unit csvdataset;
  13. {$mode objfpc}{$H+}
  14. interface
  15. uses
  16. Classes, SysUtils, bufdataset, csvreadwrite, db;
  17. Type
  18. { TCSVOptions }
  19. TCSVOptions = Class(TCSVHandler)
  20. private
  21. FDefaultFieldLength: Word;
  22. FFirstLineAsFieldNames: Boolean;
  23. Public
  24. Constructor Create; override;
  25. Procedure Assign(Source : TPersistent); override;
  26. Published
  27. // Does first line of the file contain the field names to use ?
  28. property FirstLineAsFieldNames : Boolean Read FFirstLineAsFieldNames Write FFirstLineAsFieldNames;
  29. // Default is to create all fields as strings with the same length. Default string field length.
  30. // If the CSV dataset has field defs prior to loading, this is ignored.
  31. property DefaultFieldLength : Word Read FDefaultFieldLength Write FDefaultFieldLength;
  32. // Field delimiter
  33. property Delimiter;
  34. // Character used to quote "problematic" data
  35. // (e.g. with delimiters or spaces in them)
  36. // A common quotechar is "
  37. property QuoteChar;
  38. // String at the end of the line of data (e.g. CRLF)
  39. property LineEnding;
  40. // Ignore whitespace between delimiters and field data
  41. property IgnoreOuterWhitespace;
  42. // Use quotes when outer whitespace is found
  43. property QuoteOuterWhitespace;
  44. end;
  45. { TCSVDataPacketReader }
  46. TCSVDataPacketReader = class(TDataPacketReader)
  47. private
  48. FOptions: TCSVOptions;
  49. FOwnsOptions: Boolean;
  50. FParser : TCSVParser;
  51. FBuilder : TCSVBuilder;
  52. FLine : TStringList;
  53. FCurrentRow : Integer;
  54. FEOF : Boolean;
  55. FCreateFieldDefs : TFieldDefs;
  56. // Read next row in Fline
  57. Protected
  58. Procedure ReadNextRow;virtual;
  59. procedure SetCreateFieldDefs(AValue: TFieldDefs);virtual;
  60. public
  61. constructor Create(ADataSet: TCustomBufDataset; AStream : TStream); override;
  62. constructor Create(ADataSet: TCustomBufDataset; AStream : TStream; AOptions : TCSVOptions);
  63. Destructor Destroy; override;
  64. procedure LoadFieldDefs(var AnAutoIncValue : integer); override;
  65. procedure StoreFieldDefs(AnAutoIncValue : integer); override;
  66. function GetRecordRowState(out AUpdOrder : Integer) : TRowState; override;
  67. procedure FinalizeStoreRecords; override;
  68. function GetCurrentRecord : boolean; override;
  69. procedure GotoNextRecord; override;
  70. procedure InitLoadRecords; override;
  71. procedure RestoreRecord; override;
  72. procedure StoreRecord(ARowState : TRowState; AUpdOrder : integer = 0); override;
  73. class function RecognizeStream(AStream : TStream) : boolean; override;
  74. Property Options : TCSVOptions Read FOptions;
  75. Property CreateFieldDefs : TFieldDefs read FCreateFieldDefs Write SetCreateFieldDefs;
  76. end;
  77. { TCustomCSVDataset }
  78. TCustomCSVDataset = Class(TBufDataset)
  79. private
  80. FCSVOptions: TCSVOptions;
  81. procedure SetCSVOptions(AValue: TCSVOptions);
  82. Protected
  83. class function DefaultReadFileFormat : TDataPacketFormat; override;
  84. class function DefaultWriteFileFormat : TDataPacketFormat; override;
  85. class function DefaultPacketClass : TDataPacketReaderClass ; override;
  86. function CreateDefaultPacketReader(aStream : TStream): TDataPacketReader ; override;
  87. function GetPacketReader(const Format: TDataPacketFormat; const AStream: TStream): TDataPacketReader; override;
  88. procedure LoadBlobIntoBuffer(FieldDef: TFieldDef;ABlobBuf: PBufBlobField); override;
  89. procedure InternalInitFieldDefs; override;
  90. Public
  91. Constructor Create(AOwner : TComponent); override;
  92. Destructor Destroy; override;
  93. { If FieldDefs is filled prior to calling one of the load functions,
  94. the fielddefs definitions will be checked against file contents
  95. as far as possible: count and names if names are on first line}
  96. procedure LoadFromCSVStream(AStream : TStream);
  97. procedure LoadFromCSVFile(Const AFileName: string);
  98. procedure SaveToCSVStream(AStream : TStream);
  99. procedure SaveToCSVFile(AFileName: string = '');
  100. Protected
  101. Property CSVOptions : TCSVOptions Read FCSVOptions Write SetCSVOptions;
  102. end;
  103. TCSVDataset = Class(TCustomCSVDataset)
  104. Published
  105. Property CSVOptions;
  106. end;
  107. implementation
  108. { TCSVDataPacketReader }
  109. procedure TCSVDataPacketReader.ReadNextRow;
  110. begin
  111. FLine.Clear;
  112. if not FEOF then
  113. begin
  114. if (FCurrentRow>0) then
  115. FLine.Add(FParser.CurrentCellText);
  116. Repeat
  117. FEOF:=Not FParser.ParseNextCell;
  118. if (not FEOF) and (FParser.CurrentRow=FCurrentRow) then
  119. FLine.Add(FParser.CurrentCellText);
  120. until FEOF or (FParser.CurrentRow>FCurrentRow);
  121. end;
  122. FCurrentRow:=FParser.CurrentRow;
  123. end;
  124. procedure TCSVDataPacketReader.SetCreateFieldDefs(AValue: TFieldDefs);
  125. begin
  126. if FCreateFieldDefs=AValue then Exit;
  127. if (FCreateFieldDefs=Nil) then
  128. FCreateFieldDefs:=TFieldDefs.Create(AValue.Dataset);
  129. FCreateFieldDefs.Assign(AValue);
  130. end;
  131. constructor TCSVDataPacketReader.Create(ADataSet: TCustomBufDataset; AStream: TStream);
  132. begin
  133. inherited Create(ADataSet,AStream);
  134. if FOptions=Nil then
  135. begin
  136. FOptions:=TCSVOptions.Create;
  137. FOptions.FFirstLineAsFieldNames:=True;
  138. FOwnsOptions:=True;
  139. end;
  140. FLine:=TStringList.Create;
  141. end;
  142. constructor TCSVDataPacketReader.Create(ADataSet: TCustomBufDataset; AStream: TStream; AOptions: TCSVOptions);
  143. begin
  144. FOptions:=AOptions;
  145. Create(ADataset,AStream);
  146. FOwnsOptions:=AOptions=Nil;
  147. end;
  148. destructor TCSVDataPacketReader.Destroy;
  149. begin
  150. FreeAndNil(FCreateFieldDefs);
  151. If FOwnsOptions then
  152. FreeAndNil(FOPtions);
  153. FreeAndNil(Fline);
  154. FreeAndNil(FParser);
  155. FreeAndNil(FBuilder);
  156. inherited Destroy;
  157. end;
  158. procedure TCSVDataPacketReader.LoadFieldDefs(var AnAutoIncValue: integer);
  159. Var
  160. FN : String;
  161. I : Integer;
  162. begin
  163. FParser:=TCSVParser.Create;
  164. FParser.Assign(FOptions);
  165. FParser.SetSource(Stream);
  166. FCurrentRow:=0;
  167. ReadNextRow;
  168. If Assigned(CreateFieldDefs) then
  169. begin
  170. if (CreateFieldDefs.Count<>Fline.Count) then
  171. DatabaseErrorFmt('CSV File Field count (%d) does not match dataset field count (%d).',[Fline.Count,CreateFieldDefs.Count],Dataset.FieldDefs.Dataset);
  172. If FOptions.FirstLineAsFieldNames then
  173. For I:=0 to FLine.Count-1 do
  174. If (CompareText(FLine[i],CreateFieldDefs[i].Name)<>0) then
  175. DatabaseErrorFmt('CSV File field %d: name "%s" does not match dataset field name "%s".',[I,FLine[i],CreateFieldDefs[i].Name],Dataset.FieldDefs.Dataset);
  176. Dataset.FieldDefs.Assign(CreateFieldDefs);
  177. end
  178. else if (FLine.Count>0) then
  179. For I:=0 to FLine.Count-1 do
  180. begin
  181. If FOptions.FirstLineAsFieldNames then
  182. FN:=FLine[i]
  183. else
  184. FN:=Format('Column%d',[i+1]);
  185. Dataset.FieldDefs.Add(FN,ftString,Foptions.DefaultFieldLength);
  186. end;
  187. if FOptions.FirstLineAsFieldNames then
  188. ReadNextRow;
  189. end;
  190. procedure TCSVDataPacketReader.StoreFieldDefs(AnAutoIncValue: integer);
  191. Var
  192. I : Integer;
  193. begin
  194. FBuilder:=TCSVBuilder.Create;
  195. FBuilder.Assign(FOptions);
  196. FBuilder.SetOutput(Stream);
  197. if FOptions.FirstLineAsFieldNames then
  198. begin
  199. For I:=0 to Dataset.FieldDefs.Count-1 do
  200. FBuilder.AppendCell(Dataset.FieldDefs[i].Name);
  201. FBuilder.AppendRow;
  202. end;
  203. end;
  204. function TCSVDataPacketReader.GetRecordRowState(out AUpdOrder: Integer
  205. ): TRowState;
  206. begin
  207. AUpdOrder:=0;
  208. Result:=[];
  209. end;
  210. procedure TCSVDataPacketReader.FinalizeStoreRecords;
  211. begin
  212. end;
  213. function TCSVDataPacketReader.GetCurrentRecord: boolean;
  214. begin
  215. Result:=Fline.Count>0;
  216. end;
  217. procedure TCSVDataPacketReader.GotoNextRecord;
  218. begin
  219. ReadNextRow;
  220. end;
  221. procedure TCSVDataPacketReader.InitLoadRecords;
  222. begin
  223. // Do nothing
  224. end;
  225. procedure TCSVDataPacketReader.RestoreRecord;
  226. Var
  227. I : integer;
  228. begin
  229. For I:=0 to Fline.Count-1 do
  230. Dataset.Fields[i].AsString:=Copy(FLine[i],1,Dataset.Fields[i].Size)
  231. end;
  232. procedure TCSVDataPacketReader.StoreRecord(ARowState: TRowState; AUpdOrder: integer);
  233. Var
  234. I : integer;
  235. begin
  236. For I:=0 to Dataset.Fields.Count-1 do
  237. FBuilder.AppendCell(Dataset.Fields[i].AsString);
  238. FBuilder.AppendRow;
  239. end;
  240. class function TCSVDataPacketReader.RecognizeStream(AStream: TStream): boolean;
  241. begin
  242. Result:=False;
  243. end;
  244. { TCSVOptions }
  245. Constructor TCSVOptions.Create;
  246. begin
  247. inherited Create;
  248. DefaultFieldLength:=255;
  249. end;
  250. Procedure TCSVOptions.Assign(Source: TPersistent);
  251. begin
  252. if (Source is TCSVOptions) then
  253. begin
  254. FFirstLineAsFieldNames:=TCSVOptions(Source).FirstLineAsFieldNames;
  255. FDefaultFieldLength:=TCSVOptions(Source).FDefaultFieldLength
  256. end;
  257. inherited Assign(Source);
  258. end;
  259. { TCustomCSVDataset }
  260. procedure TCustomCSVDataset.SetCSVOptions(AValue: TCSVOptions);
  261. begin
  262. if (FCSVOptions=AValue) then Exit;
  263. FCSVOptions.Assign(AValue);
  264. end;
  265. class function TCustomCSVDataset.DefaultReadFileFormat: TDataPacketFormat;
  266. begin
  267. Result:=dfDefault;
  268. end;
  269. class function TCustomCSVDataset.DefaultWriteFileFormat: TDataPacketFormat;
  270. begin
  271. Result:=dfDefault;
  272. end;
  273. class function TCustomCSVDataset.DefaultPacketClass: TDataPacketReaderClass;
  274. begin
  275. Result:=TCSVDataPacketReader;
  276. end;
  277. function TCustomCSVDataset.CreateDefaultPacketReader(aStream: TStream): TDataPacketReader;
  278. begin
  279. Result:=TCSVDataPacketReader.Create(Self,AStream,FCSVOptions)
  280. end;
  281. function TCustomCSVDataset.GetPacketReader(const Format: TDataPacketFormat;
  282. const AStream: TStream): TDataPacketReader;
  283. begin
  284. If (Format in [dfAny,dfDefault]) then
  285. Result:=CreateDefaultPacketReader(AStream)
  286. else
  287. Result:=Inherited GetPacketReader(Format,AStream);
  288. end;
  289. procedure TCustomCSVDataset.LoadBlobIntoBuffer(FieldDef: TFieldDef;
  290. ABlobBuf: PBufBlobField);
  291. begin
  292. // Do nothing
  293. end;
  294. procedure TCustomCSVDataset.InternalInitFieldDefs;
  295. begin
  296. // Do nothing
  297. end;
  298. constructor TCustomCSVDataset.Create(AOwner: TComponent);
  299. begin
  300. inherited Create(AOwner);
  301. FCSVOptions:=TCSVOptions.Create;
  302. end;
  303. destructor TCustomCSVDataset.Destroy;
  304. begin
  305. // We must close here, before freeing the options.
  306. Active:=False;
  307. FreeAndNil(FCSVOptions);
  308. inherited Destroy;
  309. end;
  310. procedure TCustomCSVDataset.LoadFromCSVStream(AStream: TStream);
  311. Var
  312. P : TCSVDataPacketReader;
  313. begin
  314. CheckInactive;
  315. P:=TCSVDataPacketReader.Create(Self,AStream,FCSVOptions);
  316. try
  317. if FieldDefs.Count>0 then
  318. P.CreateFieldDefs:=FieldDefs;
  319. SetDatasetPacket(P);
  320. finally
  321. P.Free;
  322. end;
  323. end;
  324. procedure TCustomCSVDataset.LoadFromCSVFile(const AFileName: string);
  325. Var
  326. F : TFileStream;
  327. begin
  328. F:=TFileStream.Create(AFileName,fmOpenRead or fmShareDenyWrite);
  329. try
  330. LoadFromCSVStream(F);
  331. finally
  332. F.Free;
  333. end;
  334. end;
  335. procedure TCustomCSVDataset.SaveToCSVStream(AStream: TStream);
  336. Var
  337. P : TCSVDataPacketReader;
  338. begin
  339. First;
  340. MergeChangeLog;
  341. P:=TCSVDataPacketReader.Create(Self,AStream,FCSVOptions);
  342. try
  343. GetDatasetPacket(P);
  344. finally
  345. P.Free;
  346. end;
  347. end;
  348. procedure TCustomCSVDataset.SaveToCSVFile(AFileName: string);
  349. Var
  350. F : TFileStream;
  351. begin
  352. F:=TFileStream.Create(AFileName, fmCreate);
  353. try
  354. SaveToCSVStream(F);
  355. finally
  356. F.Free;
  357. end;
  358. end;
  359. end.