sdfdata.pp 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093
  1. unit SdfData;
  2. {$mode objfpc}
  3. {$h+}
  4. //-----------------------------------------------------------------------------
  5. { Unit Name : SdfData Application : TSdfDataSet TFixedFormatDataSet Components
  6. Version : 2.05
  7. Author : Orlando Arrocha email: [email protected]
  8. Purpose : This components are designed to access directly text files as
  9. database tables. The files may be limited (SDF) or fixed size
  10. columns.
  11. ---------------
  12. Modifications
  13. ---------------
  14. 14/Jul/11 BigChimp:
  15. Added AllowMultiLine property so user can use fields that have line endings
  16. (Carriage Return and/or Line Feed) embedded in their fields (fields need to be
  17. quoted). Enabled by default; will break compatibility with earlier versions of
  18. SdfData, but using multilines would have resulted in corrupted import anyway.
  19. 12/Mar/04 Lazarus version (Sergey Smirnov AKA SSY)
  20. Locate and CheckString functions are removed because of Variant data type.
  21. Many things are changed for FPC/Lazarus compatibility.
  22. 02/Jun/02 Version 2.05 (Doriano Biondelli)
  23. TrimSpace property added for those cases where you need to retrieve the
  24. field with spaces.
  25. 01/Jan/02 Version 2.04 (Orlando Arrocha)
  26. FieldList is now populated.
  27. Locate was changed to improve speed and some bug fixing too. Thanks for
  28. asking and testing Marcelo Castro
  29. 16/Dec/01 Version 2.03 (Orlando Arrocha)
  30. Fixed some bugs and added some recomentdations. Here is a list:
  31. Quotations on the last field was not removed properly. Special thanks to
  32. Daniel Nakasone for helping with the solution.
  33. Appending first record to empty files was failing. Thanks again Daniel
  34. Nakasone for the report
  35. GetFieldData now trims the trailing spaces of the field, so users doesn't
  36. needs to do it by themselves anymore. Thanks for the recomendation
  37. Juergen Gehrke.
  38. FieldDefs is now available from the designer. Recomended by Leslie Drewery.
  39. ****** THANKS TO ALL & KEEP SENDING RECOMENDATIONS *****
  40. 05/Oct/01 Version 2.02 (Ben Hay)
  41. Locate function : implement the virtual tdataset method "Locate".
  42. ****** THANKS BEN *****
  43. 11/Sep/01 Version 2.01 (Leslie Drewery)
  44. Added additional logic to handle Corrupt Data by making sure the
  45. Quotes are closed and the delimiter/<CR>/<LF> are the next
  46. characters.
  47. Altered buffer method to create on constructor and cleared when opened.
  48. New Resource File. Nice Icons
  49. SavetoStream method included
  50. LoadFromStream method included
  51. ****** THANKS LESLIE *****
  52. 14/Ago/01 Version 2.00 (Orlando Arrocha)
  53. John Dung Nguyen showed me how to make this compatible with C-Builder
  54. and encouraged me to include a filter.
  55. Dimitry V. Borko says that russian CSV files used other delimiters,
  56. so now you can change it.
  57. OnFilter and other events included.
  58. Delimiter property added to TSdfDataSet. No more dependency on CommaText
  59. methodology -- choose your own delimiter.
  60. BufToStore/StoreToBuf methods lets you translate data records to and from
  61. your propietary storage format.
  62. TTextDataSet removed dependencies.
  63. TBaseTextDataSet class removed. // TBaseTextDataSet = TFixedFormatDataSet;
  64. ****** THANKS JOHN ****** ***** THANKS DIMMY *****
  65. 19/Jul/01 Version 1.03 (Orlando Arrocha)
  66. TBaseTextDataSet class introduced.
  67. FileName property changed datatype to TFileName and removed the property
  68. editor to segregate design-time code from runtime units.
  69. *** To add file browsing functionality please install
  70. *** TFileNamePropertyEditor -- also freeware.
  71. ********** THANKS WAYNE *********
  72. 18/Jun/01 Version 1.02 (Wayne Brantley)
  73. Schema replaces SchemaFileName property. Same as SchemaFileName, except
  74. you can define the schema inside the component. If you still need an
  75. external file, just use Schema.LoadFromFile()
  76. TFixedFormatDataSet class introduced. Use this class for a Fixed length
  77. format file (instead of delimited). The full schema definition
  78. (including lengths) is obviously required.
  79. Bug Fixed - When FirstLineSchema is true and there were no records, it
  80. would display garbage.
  81. 30/Mar/01 Version 1.01 (Orlando Arrocha)
  82. Ligia Maria Pimentel suggested to use the first line of the file to
  83. define the field names. ****** THANKS LIGIA ******
  84. FileMustExist property. You must put this property to FALSE if you want to
  85. create a new file.
  86. FirstLineSchema property. You can define the field names on the first line
  87. of your file. Fields have to be defined with this format
  88. <field_name1> [= field_size1] , <field_name2> [= field_size2] ...
  89. SchemaFileName property. (Changed to Schema by 1.02 Wayne)
  90. Lets you define the fields attributes (only supports field name and
  91. size). Have to be defined in this format (one field per line) :
  92. <field_name> [= field_size]
  93. NOTE: fields that doesn't define the length get the record size.
  94. RemoveBlankRecords procedure. Removes all the blank records from the file.
  95. RemoveExtraColumns procedure. If the file have more columns than the
  96. scheme or the field definition at design time, it remove the extra
  97. values from the file.
  98. SaveFileAs. Let you save the file to another filename.
  99. NOTE: This component save changes on closing the table, so you can use
  100. this to save data before that event.
  101. Jan 2001 Version 1.0 TSdfDataSet introduced.
  102. ---------
  103. TERMS
  104. ---------
  105. This component is provided AS-IS without any warranty of any kind, either
  106. express or implied. This component is freeware and can be used in any software
  107. product. Credits on applications will be welcomed.
  108. If you find it useful, improve it or have a wish list... please drop me a mail,
  109. I'll be glad to hear your comments.
  110. ----------------
  111. How to Install
  112. ----------------
  113. 1. Copy this SDFDATA.PAS and the associated SDFDATA.DCR to the folder from
  114. where you wish to install the component. This will probably be $(DELPHI)\lib
  115. or a sub-folder.
  116. 2. Install the TSdfDataSet and TFixedFormatDataSet components by choosing the
  117. Component | Install Component menu option.
  118. 3. Select the "Into exisiting package" page of the Install Components dialogue.
  119. 4. Browse to the folder where you saved this file and select it.
  120. 5. Ensure that the "Package file name" edit box contains $(DELPHI)\DCLUSR??.DPK
  121. or the one you prefer for DB related objects.
  122. 6. Accept that the package will be rebuilt.
  123. }
  124. //-----------------------------------------------------------------------------
  125. interface
  126. uses
  127. DB, Classes, SysUtils, DBConst;
  128. type
  129. //-----------------------------------------------------------------------------
  130. // TRecInfo
  131. PRecInfo = ^TRecInfo;
  132. TRecInfo = packed record
  133. RecordNumber: PtrInt;
  134. BookmarkFlag: TBookmarkFlag;
  135. end;
  136. //-----------------------------------------------------------------------------
  137. // TBaseTextDataSet
  138. TFixedFormatDataSet = class(TDataSet)
  139. private
  140. FSchema :TStringList;
  141. FFileName :TFileName;
  142. FFilterBuffer :TRecordBuffer;
  143. FFileMustExist :Boolean;
  144. FReadOnly :Boolean;
  145. FLoadfromStream :Boolean;
  146. FTrimSpace :Boolean;
  147. procedure SetSchema(const Value: TStringList);
  148. procedure SetFileName(Value : TFileName);
  149. procedure SetFileMustExist(Value : Boolean);
  150. procedure SetTrimSpace(Value : Boolean);
  151. procedure SetReadOnly(Value : Boolean);
  152. procedure RemoveWhiteLines(List : TStrings; IsFileRecord : Boolean);
  153. procedure LoadFieldScheme(List : TStrings; MaxSize : Integer);
  154. function GetActiveRecBuf(var RecBuf: TRecordBuffer): Boolean;
  155. procedure SetFieldPos(var Buffer : TRecordBuffer; FieldNo : Integer);
  156. protected
  157. FData :TStringlist;
  158. FCurRec :Integer;
  159. FRecBufSize :Integer;
  160. FRecordSize :Integer;
  161. FLastBookmark :PtrInt;
  162. FRecInfoOfs :Integer;
  163. FBookmarkOfs :Integer;
  164. FSaveChanges :Boolean;
  165. FDefaultRecordLength:Cardinal;
  166. FDataOffset : Integer;
  167. protected
  168. function AllocRecordBuffer: TRecordBuffer; override;
  169. procedure FreeRecordBuffer(var Buffer: TRecordBuffer); override;
  170. procedure InternalAddRecord(Buffer: Pointer; DoAppend: Boolean); override;
  171. procedure InternalClose; override;
  172. procedure InternalDelete; override;
  173. procedure InternalFirst; override;
  174. procedure InternalGotoBookmark(ABookmark: Pointer); override;
  175. procedure InternalHandleException; override;
  176. procedure InternalInitFieldDefs; override;
  177. procedure InternalInitRecord(Buffer: TRecordBuffer); override;
  178. procedure InternalLast; override;
  179. procedure InternalOpen; override;
  180. procedure InternalPost; override;
  181. procedure InternalEdit; override;
  182. procedure InternalSetToRecord(Buffer: TRecordBuffer); override;
  183. function IsCursorOpen: Boolean; override;
  184. procedure GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer); override;
  185. function GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag; override;
  186. function GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
  187. function GetRecordSize: Word; override;
  188. procedure SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag); override;
  189. procedure SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer); override;
  190. procedure SetFieldData(Field: TField; Buffer: Pointer); override;
  191. procedure ClearCalcFields(Buffer: TRecordBuffer); override;
  192. function GetRecordCount: Integer; override;
  193. function GetRecNo: Integer; override;
  194. procedure SetRecNo(Value: Integer); override;
  195. function GetCanModify: boolean; override;
  196. function TxtGetRecord(Buffer : TRecordBuffer; GetMode: TGetMode): TGetResult;
  197. function RecordFilter(RecBuf: Pointer; ARecNo: Integer): Boolean;
  198. function BufToStore(Buffer: TRecordBuffer): String; virtual;
  199. function StoreToBuf(Source: String): String; virtual;
  200. public
  201. property DefaultRecordLength: Cardinal read FDefaultRecordLength
  202. write FDefaultRecordLength default 250;
  203. constructor Create(AOwner: TComponent); override;
  204. destructor Destroy; override;
  205. function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
  206. procedure RemoveBlankRecords; dynamic;
  207. procedure RemoveExtraColumns; dynamic;
  208. procedure SaveFileAs(strFileName : String); dynamic;
  209. property CanModify;
  210. procedure LoadFromStream(Stream :TStream);
  211. procedure SavetoStream(Stream :TStream);
  212. published
  213. property FileMustExist: Boolean read FFileMustExist write SetFileMustExist;
  214. property ReadOnly: Boolean read FReadOnly write SetReadOnly;
  215. property FileName : TFileName read FFileName write SetFileName;
  216. property Schema: TStringList read FSchema write SetSchema;
  217. property TrimSpace: Boolean read FTrimSpace write SetTrimSpace default True;
  218. property FieldDefs;
  219. property Active;
  220. property AutoCalcFields;
  221. property Filtered;
  222. property BeforeOpen;
  223. property AfterOpen;
  224. property BeforeClose;
  225. property AfterClose;
  226. property BeforeInsert;
  227. property AfterInsert;
  228. property BeforeEdit;
  229. property AfterEdit;
  230. property BeforePost;
  231. property AfterPost;
  232. property BeforeCancel;
  233. property AfterCancel;
  234. property BeforeDelete;
  235. property AfterDelete;
  236. property BeforeScroll;
  237. property AfterScroll;
  238. // property BeforeRefresh;
  239. // property AfterRefresh;
  240. property OnCalcFields;
  241. property OnDeleteError;
  242. property OnEditError;
  243. property OnFilterRecord;
  244. property OnNewRecord;
  245. property OnPostError;
  246. end;
  247. //-----------------------------------------------------------------------------
  248. // TSdfDataSet
  249. TSdfDataSet = class(TFixedFormatDataSet)
  250. private
  251. FDelimiter : Char;
  252. FFirstLineAsSchema : Boolean;
  253. FFMultiLine :Boolean;
  254. procedure SetMultiLine(const Value: Boolean);
  255. procedure SetFirstLineAsSchema(Value : Boolean);
  256. procedure SetDelimiter(Value : Char);
  257. protected
  258. procedure InternalInitFieldDefs; override;
  259. function GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode; DoCheck: Boolean)
  260. : TGetResult; override;
  261. function BufToStore(Buffer: TRecordBuffer): String; override;
  262. function StoreToBuf(Source: String): String; override;
  263. public
  264. constructor Create(AOwner: TComponent); override;
  265. published
  266. property AllowMultiLine: Boolean read FFMultiLine write SetMultiLine default True; //Whether or not to allow fields containing CR and/or LF
  267. property Delimiter: Char read FDelimiter write SetDelimiter;
  268. property FirstLineAsSchema: Boolean read FFirstLineAsSchema write SetFirstLineAsSchema;
  269. end;
  270. procedure Register;
  271. implementation
  272. //{$R *.Res}
  273. //-----------------------------------------------------------------------------
  274. // TFixedFormatDataSet
  275. //-----------------------------------------------------------------------------
  276. constructor TFixedFormatDataSet.Create(AOwner : TComponent);
  277. begin
  278. FDefaultRecordLength := 250;
  279. FFileMustExist := TRUE;
  280. FLoadfromStream := False;
  281. FRecordSize := 0;
  282. FTrimSpace := TRUE;
  283. FSchema := TStringList.Create;
  284. FData := TStringList.Create; // Load the textfile into a stringlist
  285. inherited Create(AOwner);
  286. end;
  287. destructor TFixedFormatDataSet.Destroy;
  288. begin
  289. inherited Destroy;
  290. FData.Free;
  291. FSchema.Free;
  292. end;
  293. procedure TFixedFormatDataSet.SetSchema(const Value: TStringList);
  294. begin
  295. CheckInactive;
  296. FSchema.Assign(Value);
  297. end;
  298. procedure TFixedFormatDataSet.SetFileMustExist(Value : Boolean);
  299. begin
  300. CheckInactive;
  301. FFileMustExist := Value;
  302. end;
  303. procedure TFixedFormatDataSet.SetTrimSpace(Value : Boolean);
  304. begin
  305. CheckInactive;
  306. FTrimSpace := Value;
  307. end;
  308. procedure TFixedFormatDataSet.SetReadOnly(Value : Boolean);
  309. begin
  310. CheckInactive;
  311. FReadOnly := Value;
  312. end;
  313. procedure TFixedFormatDataSet.SetFileName(Value : TFileName);
  314. begin
  315. CheckInactive;
  316. FFileName := Value;
  317. end;
  318. procedure TFixedFormatDataSet.InternalInitFieldDefs;
  319. var
  320. i, len, Maxlen :Integer;
  321. LstFields :TStrings;
  322. begin
  323. if not Assigned(FData) then
  324. exit;
  325. FRecordSize := 0;
  326. Maxlen := 0;
  327. FieldDefs.Clear;
  328. for i := FData.Count - 1 downto 0 do // Find out the longest record
  329. begin
  330. len := Length(FData[i]);
  331. if len > Maxlen then
  332. Maxlen := len;
  333. FData.Objects[i] := TObject(Pointer(i+1)); // Fabricate Bookmarks
  334. end;
  335. if (Maxlen = 0) then
  336. Maxlen := FDefaultRecordLength;
  337. LstFields := TStringList.Create;
  338. try
  339. LoadFieldScheme(LstFields, Maxlen);
  340. for i := 0 to LstFields.Count -1 do // Add fields
  341. begin
  342. len := StrToIntDef(LstFields.Values[LstFields.Names[i]], Maxlen);
  343. FieldDefs.Add(Trim(LstFields.Names[i]), ftString, len, False);
  344. Inc(FRecordSize, len);
  345. end;
  346. finally
  347. LstFields.Free;
  348. end;
  349. end;
  350. procedure TFixedFormatDataSet.InternalOpen;
  351. var
  352. Stream : TStream;
  353. begin
  354. FCurRec := -1;
  355. FSaveChanges := FALSE;
  356. if not Assigned(FData) then
  357. FData := TStringList.Create;
  358. if (not FileMustExist) and (not FileExists(FileName)) then
  359. begin
  360. Stream := TFileStream.Create(FileName, fmCreate);
  361. Stream.Free;
  362. end;
  363. if not FLoadfromStream then
  364. FData.LoadFromFile(FileName);
  365. FRecordSize := FDefaultRecordLength;
  366. InternalInitFieldDefs;
  367. if DefaultFields then
  368. CreateFields;
  369. BindFields(TRUE);
  370. if FRecordSize = 0 then
  371. FRecordSize := FDefaultRecordLength;
  372. BookmarkSize := SizeOf(PtrInt);
  373. FRecInfoOfs := FRecordSize + CalcFieldsSize; // Initialize the offset for TRecInfo in the buffer
  374. FBookmarkOfs := FRecInfoOfs + SizeOf(TRecInfo);
  375. FRecBufSize := FBookmarkOfs + BookmarkSize;
  376. FLastBookmark := FData.Count;
  377. end;
  378. procedure TFixedFormatDataSet.InternalClose;
  379. begin
  380. if (not FReadOnly) and (FSaveChanges) then // Write any edits to disk
  381. FData.SaveToFile(FileName);
  382. FLoadfromStream := False;
  383. FData.Clear;
  384. BindFields(FALSE);
  385. if DefaultFields then // Destroy the TField
  386. DestroyFields;
  387. FCurRec := -1; // Reset these internal flags
  388. FLastBookmark := 0;
  389. FRecordSize := 0;
  390. end;
  391. function TFixedFormatDataSet.IsCursorOpen: Boolean;
  392. begin
  393. Result := Assigned(FData) and (FRecordSize > 0);
  394. end;
  395. procedure TFixedFormatDataSet.InternalHandleException;
  396. begin
  397. {$ifndef fpc}
  398. Application.HandleException(Self);
  399. {$else}
  400. inherited;
  401. {$endif}
  402. end;
  403. // Loads Data from a stream.
  404. procedure TFixedFormatDataSet.LoadFromStream(Stream: TStream);
  405. begin
  406. if assigned(stream) then
  407. begin
  408. Active := False; //Make sure the Dataset is Closed.
  409. Stream.Position := 0; //Make sure you are at the top of the Stream.
  410. FLoadfromStream := True;
  411. if not Assigned(FData) then
  412. raise Exception.Create('Data buffer unassigned');
  413. FData.LoadFromStream(Stream);
  414. Active := True;
  415. end
  416. else
  417. raise exception.Create('Invalid Stream Assigned (Load From Stream');
  418. end;
  419. // Saves Data as text to a stream.
  420. procedure TFixedFormatDataSet.SavetoStream(Stream: TStream);
  421. begin
  422. if assigned(stream) then
  423. FData.SaveToStream(Stream)
  424. else
  425. raise exception.Create('Invalid Stream Assigned (Save To Stream');
  426. end;
  427. // Record Functions
  428. function TFixedFormatDataSet.AllocRecordBuffer: TRecordBuffer;
  429. begin
  430. if FRecBufSize > 0 then
  431. Result := AllocMem(FRecBufSize)
  432. else
  433. Result := nil;
  434. end;
  435. procedure TFixedFormatDataSet.FreeRecordBuffer(var Buffer: TRecordBuffer);
  436. begin
  437. if Buffer <> nil then
  438. FreeMem(Buffer);
  439. end;
  440. procedure TFixedFormatDataSet.InternalInitRecord(Buffer: TRecordBuffer);
  441. begin
  442. FillChar(Buffer[0], FRecordSize, 0);
  443. end;
  444. procedure TFixedFormatDataSet.ClearCalcFields(Buffer: TRecordBuffer);
  445. begin
  446. FillChar(Buffer[RecordSize], CalcFieldsSize, 0);
  447. end;
  448. function TFixedFormatDataSet.GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode;
  449. DoCheck: Boolean): TGetResult;
  450. begin
  451. if (FData.Count < (1+FDataOffset)) then
  452. Result := grEOF
  453. else
  454. Result := TxtGetRecord(Buffer, GetMode);
  455. if Result = grOK then
  456. begin
  457. if (CalcFieldsSize > 0) then
  458. GetCalcFields(Buffer);
  459. with PRecInfo(Buffer + FRecInfoOfs)^ do
  460. begin
  461. BookmarkFlag := bfCurrent;
  462. RecordNumber := PtrInt(FData.Objects[FCurRec]);
  463. end;
  464. end
  465. else
  466. if (Result = grError) and DoCheck then
  467. DatabaseError('No Records');
  468. end;
  469. function TFixedFormatDataSet.GetRecordCount: Longint;
  470. begin
  471. Result := FData.Count;
  472. end;
  473. function TFixedFormatDataSet.GetRecNo: Longint;
  474. var
  475. BufPtr: TRecordBuffer;
  476. begin
  477. Result := -1;
  478. if GetActiveRecBuf(BufPtr) then
  479. Result := PRecInfo(BufPtr + FRecInfoOfs)^.RecordNumber;
  480. end;
  481. procedure TFixedFormatDataSet.SetRecNo(Value: Integer);
  482. begin
  483. CheckBrowseMode;
  484. if (Value >= 0) and (Value < FData.Count) and (Value <> RecNo) then
  485. begin
  486. DoBeforeScroll;
  487. FCurRec := Value - 1;
  488. Resync([]);
  489. DoAfterScroll;
  490. end;
  491. end;
  492. function TFixedFormatDataSet.GetRecordSize: Word;
  493. begin
  494. Result := FRecordSize;
  495. end;
  496. function TFixedFormatDataSet.GetActiveRecBuf(var RecBuf: TRecordBuffer): Boolean;
  497. begin
  498. case State of
  499. dsBrowse: if IsEmpty then RecBuf := nil else RecBuf := ActiveBuffer;
  500. dsEdit, dsInsert: RecBuf := ActiveBuffer;
  501. dsCalcFields: RecBuf := CalcBuffer;
  502. dsFilter: RecBuf := FFilterBuffer;
  503. else
  504. RecBuf := nil;
  505. end;
  506. Result := RecBuf <> nil;
  507. end;
  508. function TFixedFormatDataSet.TxtGetRecord(Buffer : TRecordBuffer; GetMode: TGetMode): TGetResult;
  509. var
  510. Accepted : Boolean;
  511. begin
  512. Result := grOK;
  513. repeat
  514. Accepted := TRUE;
  515. case GetMode of
  516. gmNext:
  517. if FCurRec >= RecordCount - 1 then
  518. Result := grEOF
  519. else
  520. Inc(FCurRec);
  521. gmPrior:
  522. if FCurRec <= FDataOffset then
  523. Result := grBOF
  524. else
  525. Dec(FCurRec);
  526. gmCurrent:
  527. if (FCurRec < FDataOffset) or (FCurRec >= RecordCount) then
  528. Result := grError;
  529. end;
  530. if (Result = grOk) then
  531. begin
  532. Move(PChar(StoreToBuf(FData[FCurRec]))^, Buffer[0], FRecordSize);
  533. if Filtered then
  534. begin
  535. Accepted := RecordFilter(Buffer, FCurRec +1);
  536. if not Accepted and (GetMode = gmCurrent) then
  537. Inc(FCurRec);
  538. end;
  539. end;
  540. until Accepted;
  541. end;
  542. function TFixedFormatDataSet.RecordFilter(RecBuf: Pointer; ARecNo: Integer): Boolean;
  543. var
  544. Accept: Boolean;
  545. SaveState: TDataSetState;
  546. begin // Returns true if accepted in the filter
  547. SaveState := SetTempState(dsFilter);
  548. FFilterBuffer := RecBuf;
  549. PRecInfo(FFilterBuffer + FRecInfoOfs)^.RecordNumber := ARecNo;
  550. Accept := TRUE;
  551. if Accept and Assigned(OnFilterRecord) then
  552. OnFilterRecord(Self, Accept);
  553. RestoreState(SaveState);
  554. Result := Accept;
  555. end;
  556. function TFixedFormatDataSet.GetCanModify: boolean;
  557. begin
  558. Result := not FReadOnly;
  559. end;
  560. // Field Related
  561. procedure TFixedFormatDataSet.LoadFieldScheme(List : TStrings; MaxSize : Integer);
  562. var
  563. tmpFieldName : string;
  564. tmpSchema : TStrings;
  565. i : Integer;
  566. begin
  567. tmpSchema := TStringList.Create;
  568. try // Load Schema Structure
  569. if (Schema.Count > 0) then
  570. begin
  571. tmpSchema.Assign(Schema);
  572. RemoveWhiteLines(tmpSchema, FALSE);
  573. end
  574. else
  575. tmpSchema.Add('Line');
  576. for i := 0 to tmpSchema.Count -1 do // Interpret Schema
  577. begin
  578. tmpFieldName := tmpSchema.Names[i];
  579. if (tmpFieldName = '') then
  580. tmpFieldName := Format('%s=%d', [tmpSchema[i], MaxSize])
  581. else
  582. tmpFieldName := tmpSchema[i];
  583. List.Add(tmpFieldName);
  584. end;
  585. finally
  586. tmpSchema.Free;
  587. end;
  588. end;
  589. function TFixedFormatDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
  590. var
  591. TempPos, recbuf : PChar;
  592. begin
  593. Result := GetActiveRecBuf(TRecordBuffer(RecBuf));
  594. if Result then
  595. begin
  596. if Field.FieldNo > 0 then
  597. begin
  598. TempPos := RecBuf;
  599. SetFieldPos(TRecordBuffer(RecBuf), Field.FieldNo);
  600. Result := (RecBuf < StrEnd(TempPos));
  601. end
  602. else
  603. if (State in [dsBrowse, dsEdit, dsInsert, dsCalcFields]) then
  604. begin
  605. Inc(RecBuf, FRecordSize + Field.Offset);
  606. Result := Boolean(Byte(RecBuf[0]));
  607. end;
  608. end;
  609. if Result and (Buffer <> nil) then
  610. begin
  611. StrLCopy(Buffer, RecBuf, Field.Size);
  612. if FTrimSpace then
  613. begin
  614. TempPos := StrEnd(Buffer);
  615. repeat
  616. Dec(TempPos);
  617. if (TempPos[0] = ' ') then
  618. TempPos[0]:= #0
  619. else
  620. break;
  621. until (TempPos = Buffer);
  622. end;
  623. end;
  624. end;
  625. procedure TFixedFormatDataSet.SetFieldData(Field: TField; Buffer: Pointer);
  626. var
  627. RecBuf: PChar;
  628. BufEnd: PChar;
  629. p : Integer;
  630. begin
  631. if not (State in dsWriteModes) then
  632. DatabaseError(SNotEditing, Self);
  633. GetActiveRecBuf(TRecordBuffer(RecBuf));
  634. if Field.FieldNo > 0 then
  635. begin
  636. if State = dsCalcFields then
  637. DatabaseError('Dataset not in edit or insert mode', Self);
  638. if Field.ReadOnly and not (State in [dsSetKey, dsFilter]) then
  639. DatabaseErrorFmt(SReadOnlyField, [Field.DisplayName]);
  640. if State in [dsEdit, dsInsert, dsNewValue] then
  641. Field.Validate(Buffer);
  642. if Field.FieldKind <> fkInternalCalc then
  643. begin
  644. SetFieldPos(TRecordBuffer(RecBuf), Field.FieldNo);
  645. BufEnd := StrEnd(pansichar(ActiveBuffer)); // Fill with blanks when necessary
  646. if BufEnd > RecBuf then
  647. BufEnd := RecBuf;
  648. FillChar(BufEnd[0], Field.Size + PtrInt(RecBuf) - PtrInt(BufEnd), Ord(' '));
  649. p := StrLen(Buffer);
  650. if p > Field.Size then
  651. p := Field.Size;
  652. Move(Buffer^, RecBuf[0], p);
  653. end;
  654. end
  655. else // fkCalculated, fkLookup
  656. begin
  657. Inc(RecBuf, FRecordSize + Field.Offset);
  658. Move(Buffer^, RecBuf[0], Field.Size);
  659. end;
  660. if not (State in [dsCalcFields, dsFilter, dsNewValue]) then
  661. DataEvent(deFieldChange, Ptrint(Field));
  662. end;
  663. procedure TFixedFormatDataSet.SetFieldPos(var Buffer : TRecordBuffer; FieldNo : Integer);
  664. var
  665. i : Integer;
  666. begin
  667. i := 1;
  668. while (i < FieldNo) and (i < FieldDefs.Count) do
  669. begin
  670. Inc(Buffer, FieldDefs.Items[i-1].Size);
  671. Inc(i);
  672. end;
  673. end;
  674. // Navigation / Editing
  675. procedure TFixedFormatDataSet.InternalFirst;
  676. begin
  677. FCurRec := -1;
  678. end;
  679. procedure TFixedFormatDataSet.InternalLast;
  680. begin
  681. FCurRec := FData.Count;
  682. end;
  683. procedure TFixedFormatDataSet.InternalPost;
  684. var
  685. i: Longint;
  686. begin
  687. FSaveChanges := TRUE;
  688. inherited UpdateRecord;
  689. if (State = dsEdit) then // just update the data in the string list
  690. begin
  691. FData[FCurRec] := BufToStore(ActiveBuffer);
  692. end
  693. else
  694. InternalAddRecord(ActiveBuffer, FALSE);
  695. end;
  696. procedure TFixedFormatDataSet.InternalEdit;
  697. begin
  698. end;
  699. procedure TFixedFormatDataSet.InternalDelete;
  700. begin
  701. FSaveChanges := TRUE;
  702. FData.Delete(FCurRec);
  703. if FCurRec >= FData.Count then
  704. Dec(FCurRec);
  705. end;
  706. procedure TFixedFormatDataSet.InternalAddRecord(Buffer: Pointer; DoAppend: Boolean);
  707. begin
  708. FSaveChanges := TRUE;
  709. Inc(FLastBookmark);
  710. if DoAppend then
  711. InternalLast;
  712. if (FCurRec >=0) then
  713. FData.InsertObject(FCurRec, BufToStore(Buffer), TObject(Pointer(FLastBookmark)))
  714. else
  715. FData.AddObject(BufToStore(Buffer), TObject(Pointer(FLastBookmark)));
  716. end;
  717. procedure TFixedFormatDataSet.InternalGotoBookmark(ABookmark: Pointer);
  718. var
  719. Index: Integer;
  720. begin
  721. Index := FData.IndexOfObject(TObject(PPtrInt(ABookmark)^));
  722. if Index <> -1 then
  723. FCurRec := Index
  724. else
  725. DatabaseError('Bookmark not found');
  726. end;
  727. procedure TFixedFormatDataSet.InternalSetToRecord(Buffer: TRecordBuffer);
  728. begin
  729. if (State <> dsInsert) then
  730. InternalGotoBookmark(@PRecInfo(Buffer + FRecInfoOfs)^.RecordNumber);
  731. end;
  732. function TFixedFormatDataSet.GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag;
  733. begin
  734. Result := PRecInfo(Buffer + FRecInfoOfs)^.BookmarkFlag;
  735. end;
  736. procedure TFixedFormatDataSet.SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag);
  737. begin
  738. PRecInfo(Buffer + FRecInfoOfs)^.BookmarkFlag := Value;
  739. end;
  740. procedure TFixedFormatDataSet.GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
  741. begin
  742. Move(Buffer[FRecInfoOfs], Data^, BookmarkSize);
  743. end;
  744. procedure TFixedFormatDataSet.SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
  745. begin
  746. Move(Data^, Buffer[FRecInfoOfs], BookmarkSize);
  747. end;
  748. procedure TFixedFormatDataSet.RemoveWhiteLines(List : TStrings; IsFileRecord : Boolean);
  749. var
  750. i : integer;
  751. begin
  752. for i := List.Count -1 downto 0 do
  753. begin
  754. if (Trim(List[i]) = '' ) then
  755. if IsFileRecord then
  756. begin
  757. FCurRec := i;
  758. InternalDelete;
  759. end
  760. else
  761. List.Delete(i);
  762. end;
  763. end;
  764. procedure TFixedFormatDataSet.RemoveBlankRecords;
  765. begin
  766. RemoveWhiteLines(FData, TRUE);
  767. end;
  768. procedure TFixedFormatDataSet.RemoveExtraColumns;
  769. var
  770. i : Integer;
  771. begin
  772. for i := FData.Count -1 downto 0 do
  773. FData[i] := BufToStore(trecordbuffer(StoreToBuf(FData[i])));
  774. FData.SaveToFile(FileName);
  775. end;
  776. procedure TFixedFormatDataSet.SaveFileAs(strFileName : String);
  777. begin
  778. FData.SaveToFile(strFileName);
  779. FFileName := strFileName;
  780. FSaveChanges := FALSE;
  781. end;
  782. function TFixedFormatDataSet.StoreToBuf(Source: String): String;
  783. begin
  784. Result := Source;
  785. end;
  786. function TFixedFormatDataSet.BufToStore(Buffer: TRecordBuffer): String;
  787. begin
  788. Result := Copy(pansichar(Buffer), 1, FRecordSize);
  789. end;
  790. //-----------------------------------------------------------------------------
  791. // TSdfDataSet
  792. //-----------------------------------------------------------------------------
  793. constructor TSdfDataSet.Create(AOwner: TComponent);
  794. begin
  795. inherited Create(AOwner);
  796. FDelimiter := ',';
  797. FFirstLineAsSchema := FALSE;
  798. FFMultiLine :=False;
  799. end;
  800. procedure TSdfDataSet.InternalInitFieldDefs;
  801. var
  802. pStart, pEnd, len : Integer;
  803. begin
  804. if not IsCursorOpen then
  805. exit;
  806. if (FData.Count = 0) or (Trim(FData[0]) = '') then
  807. begin
  808. FirstLineAsSchema := FALSE;
  809. FDataOffset:=0;
  810. end
  811. else if (Schema.Count = 0) or (FirstLineAsSchema) then
  812. begin
  813. Schema.Clear;
  814. len := Length(FData[0]);
  815. pEnd := 1;
  816. repeat
  817. while (pEnd <= len) and (FData[0][pEnd] in [#1..' ']) do
  818. Inc(pEnd);
  819. if (pEnd > len) then
  820. break;
  821. pStart := pEnd;
  822. if (FData[0][pStart] = '"') then
  823. begin
  824. repeat
  825. Inc(pEnd);
  826. until (pEnd > len) or (FData[0][pEnd] = '"');
  827. if (FData[0][pEnd] = '"') then
  828. Inc(pStart);
  829. end
  830. else
  831. while (pEnd <= len) and (FData[0][pEnd] <> Delimiter) do
  832. Inc(pEnd);
  833. if (FirstLineAsSchema) then
  834. Schema.Add(Copy(FData[0], pStart, pEnd - pStart))
  835. else
  836. Schema.Add(Format('Field%d', [Schema.Count + 1]));
  837. if (FData[0][pEnd] = '"') then
  838. while (pEnd <= len) and (FData[0][pEnd] <> Delimiter) do
  839. Inc(pEnd);
  840. if (FData[0][pEnd] = Delimiter) then
  841. Inc(pEnd);
  842. until (pEnd > len);
  843. end;
  844. inherited;
  845. end;
  846. function TSdfDataSet.GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode;
  847. DoCheck: Boolean): TGetResult;
  848. begin
  849. if FirstLineAsSchema then
  850. begin
  851. if (FData.Count < 2) then
  852. begin
  853. if GetMode=gmPrior then
  854. Result := grBOF
  855. else
  856. Result := grEOF
  857. end
  858. else
  859. begin
  860. If (FCurrec=-1) and (GetMode=gmNext) then
  861. inc(FCurrec);
  862. Result := inherited GetRecord(Buffer, GetMode, DoCheck);
  863. end;
  864. end
  865. else
  866. Result := inherited GetRecord(Buffer, GetMode, DoCheck);
  867. end;
  868. function TSdfDataSet.StoreToBuf(Source: String): String;
  869. const
  870. CR :char = #13;
  871. LF :char = #10;
  872. var
  873. i,
  874. p :Integer;
  875. pRet,
  876. pStr,
  877. pStrEnd :PChar;
  878. Ret :String;
  879. begin
  880. SetLength(Ret, FRecordSize);
  881. FillChar(PChar(Ret)^, FRecordSize, Ord(' '));
  882. PStrEnd := PChar(Source);
  883. pRet := PChar(Ret);
  884. for i := 0 to FieldDefs.Count - 1 do
  885. begin
  886. while Boolean(Byte(pStrEnd[0])) and (pStrEnd[0] in [#1..' ']) do
  887. begin
  888. if FFMultiLine=true then
  889. begin
  890. if ((pStrEnd[0]=CR) or (pStrEnd[0]=LF)) then
  891. begin
  892. //view this as text, not control characters, so do nothing
  893. //todo: check if this is really necessary, probably revert
  894. //to original code as quoted case is handled below
  895. end;
  896. end
  897. else
  898. begin
  899. Inc(pStrEnd);
  900. end;
  901. end;
  902. if not Boolean(Byte(pStrEnd[0])) then
  903. break;
  904. pStr := pStrEnd;
  905. if (pStr[0] = '"') then
  906. begin
  907. if FFMultiLine=true then
  908. begin
  909. repeat
  910. Inc(pStrEnd);
  911. until not Boolean(Byte(pStrEnd[0])) or
  912. ((pStrEnd[0] = '"') and ((pStrEnd + 1)[0] in [Delimiter,#0]));
  913. end
  914. else
  915. begin
  916. // No multiline, so treat cr/lf as end of record
  917. repeat
  918. Inc(pStrEnd);
  919. until not Boolean(Byte(pStrEnd[0])) or
  920. ((pStrEnd[0] = '"') and ((pStrEnd + 1)[0] in [Delimiter,CR,LF, #0]));
  921. end;
  922. if (pStrEnd[0] = '"') then
  923. Inc(pStr);
  924. end
  925. else
  926. while Boolean(Byte(pStrEnd[0])) and (pStrEnd[0] <> Delimiter) do
  927. Inc(pStrEnd);
  928. p := pStrEnd - pStr;
  929. if (p > FieldDefs[i].Size) then
  930. p := FieldDefs[i].Size;
  931. Move(pStr[0], pRet[0], p);
  932. Inc(pRet, FieldDefs[i].Size);
  933. if (pStrEnd[0] = '"') then
  934. while Boolean(Byte(pStrEnd[0])) and (pStrEnd[0] <> Delimiter) do
  935. Inc(pStrEnd);
  936. if (pStrEnd[0] = Delimiter) then
  937. Inc(pStrEnd);
  938. end;
  939. Result := Ret;
  940. end;
  941. function TSdfDataSet.BufToStore(Buffer: TRecordBuffer): String;
  942. const
  943. QuoteDelimiter='"';
  944. var
  945. Str : String;
  946. p, i : Integer;
  947. QuoteMe: boolean;
  948. begin
  949. Result := '';
  950. p := 1;
  951. QuoteMe:=false;
  952. for i := 0 to FieldDefs.Count - 1 do
  953. begin
  954. Str := Trim(Copy(pansichar(Buffer), p, FieldDefs[i].Size));
  955. Inc(p, FieldDefs[i].Size);
  956. if FFMultiLine=true then
  957. begin
  958. // If multiline enabled, quote whenever we find carriage return or linefeed
  959. if ((QuoteMe=False) and (StrScan(PChar(Str), #10) <> nil)) then QuoteMe:=true;
  960. if ((QuoteMe=False) and (StrScan(PChar(Str), #13) <> nil)) then QuoteMe:=true;
  961. end
  962. else
  963. begin
  964. // If we don't allow multiline, remove all CR and LF because they mess with the record ends:
  965. StringReplace(Str, #10, '', [rfReplaceAll]);
  966. StringReplace(Str, #13, '', [rfReplaceAll]);
  967. end;
  968. // Check for any delimiters occurring in field text
  969. if ((QuoteMe=False) and (StrScan(PChar(Str), FDelimiter) <> nil)) then QuoteMe:=true;
  970. if (QuoteMe=True) then
  971. Str := QuoteDelimiter + Str + QuoteDelimiter;
  972. Result := Result + Str + FDelimiter;
  973. end;
  974. p := Length(Result);
  975. while (p > 0) and (Result[p] = FDelimiter) do
  976. begin
  977. System.Delete(Result, p, 1);
  978. Dec(p);
  979. end;
  980. end;
  981. procedure TSdfDataSet.SetDelimiter(Value : Char);
  982. begin
  983. CheckInactive;
  984. FDelimiter := Value;
  985. end;
  986. procedure TSdfDataSet.SetFirstLineAsSchema(Value : Boolean);
  987. begin
  988. CheckInactive;
  989. FFirstLineAsSchema := Value;
  990. FDataOffset:=Ord(FFirstLineAsSchema);
  991. end;
  992. procedure TSdfDataSet.SetMultiLine(const Value: Boolean);
  993. begin
  994. FFMultiLine:=Value;
  995. end;
  996. //-----------------------------------------------------------------------------
  997. // This procedure is used to register this component on the component palette
  998. //-----------------------------------------------------------------------------
  999. procedure Register;
  1000. begin
  1001. RegisterComponents('Data Access', [TFixedFormatDataSet]);
  1002. RegisterComponents('Data Access', [TSdfDataSet]);
  1003. end;
  1004. end.