mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-10-19 15:11:50 +02:00
1023 lines
30 KiB
ObjectPascal
1023 lines
30 KiB
ObjectPascal
unit SdfData;
|
|
|
|
{$mode objfpc}
|
|
{$h+}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
{ Unit Name : SdfData Application : TSdfDataSet TFixedFormatDataSet Components
|
|
Version : 2.05
|
|
Author : Orlando Arrocha email: oarrocha@hotmail.com
|
|
Purpose : This components are designed to access directly text files as
|
|
database tables. The files may be limited (SDF) or fixed size
|
|
columns.
|
|
---------------
|
|
Modifications
|
|
---------------
|
|
12/Mar/04 Lazarus version (Sergey Smirnov AKA SSY)
|
|
Locate and CheckString functions are removed because of Variant data type.
|
|
Many things are changed for FPC/Lazarus compatibility.
|
|
02/Jun/02 Version 2.05 (Doriano Biondelli)
|
|
TrimSpace property added for those cases where you need to retrieve the
|
|
field with spaces.
|
|
01/Jan/02 Version 2.04 (Orlando Arrocha)
|
|
FieldList is now populated.
|
|
Locate was changed to improve speed and some bug fixing too. Thanks for
|
|
asking and testing Marcelo Castro
|
|
16/Dec/01 Version 2.03 (Orlando Arrocha)
|
|
Fixed some bugs and added some recomentdations. Here is a list:
|
|
Quotations on the last field was not removed properly. Special thanks to
|
|
Daniel Nakasone for helping with the solution.
|
|
Appending first record to empty files was failing. Thanks again Daniel
|
|
Nakasone for the report
|
|
GetFieldData now trims the trailing spaces of the field, so users doesn't
|
|
needs to do it by themselves anymore. Thanks for the recomendation
|
|
Juergen Gehrke.
|
|
FieldDefs is now available from the designer. Recomended by Leslie Drewery.
|
|
****** THANKS TO ALL & KEEP SENDING RECOMENDATIONS *****
|
|
05/Oct/01 Version 2.02 (Ben Hay)
|
|
Locate function : implement the virtual tdataset method "Locate".
|
|
****** THANKS BEN *****
|
|
11/Sep/01 Version 2.01 (Leslie Drewery)
|
|
Added additional logic to handle Corrupt Data by making sure the
|
|
Quotes are closed and the delimiter/<CR>/<LF> are the next
|
|
characters.
|
|
Altered buffer method to create on constructor and cleared when opened.
|
|
New Resource File. Nice Icons
|
|
SavetoStream method included
|
|
LoadFromStream method included
|
|
****** THANKS LESLIE *****
|
|
14/Ago/01 Version 2.00 (Orlando Arrocha)
|
|
John Dung Nguyen showed me how to make this compatible with C-Builder
|
|
and encouraged me to include a filter.
|
|
Dimitry V. Borko says that russian CSV files used other delimiters,
|
|
so now you can change it.
|
|
OnFilter and other events included.
|
|
Delimiter property added to TSdfDataSet. No more dependency on CommaText
|
|
methodology -- choose your own delimiter.
|
|
BufToStore/StoreToBuf methods lets you translate data records to and from
|
|
your propietary storage format.
|
|
TTextDataSet removed dependencies.
|
|
TBaseTextDataSet class removed. // TBaseTextDataSet = TFixedFormatDataSet;
|
|
****** THANKS JOHN ****** ***** THANKS DIMMY *****
|
|
19/Jul/01 Version 1.03 (Orlando Arrocha)
|
|
TBaseTextDataSet class introduced.
|
|
FileName property changed datatype to TFileName and removed the property
|
|
editor to segregate design-time code from runtime units.
|
|
*** To add file browsing functionality please install
|
|
*** TFileNamePropertyEditor -- also freeware.
|
|
********** THANKS WAYNE *********
|
|
18/Jun/01 Version 1.02 (Wayne Brantley)
|
|
Schema replaces SchemaFileName property. Same as SchemaFileName, except
|
|
you can define the schema inside the component. If you still need an
|
|
external file, just use Schema.LoadFromFile()
|
|
TFixedFormatDataSet class introduced. Use this class for a Fixed length
|
|
format file (instead of delimited). The full schema definition
|
|
(including lengths) is obviously required.
|
|
Bug Fixed - When FirstLineSchema is true and there were no records, it
|
|
would display garbage.
|
|
|
|
30/Mar/01 Version 1.01 (Orlando Arrocha)
|
|
Ligia Maria Pimentel suggested to use the first line of the file to
|
|
define the field names. ****** THANKS LIGIA ******
|
|
FileMustExist property. You must put this property to FALSE if you want to
|
|
create a new file.
|
|
FirstLineSchema property. You can define the field names on the first line
|
|
of your file. Fields have to be defined with this format
|
|
<field_name1> [= field_size1] , <field_name2> [= field_size2] ...
|
|
SchemaFileName property. (Changed to Schema by 1.02 Wayne)
|
|
Lets you define the fields attributes (only supports field name and
|
|
size). Have to be defined in this format (one field per line) :
|
|
<field_name> [= field_size]
|
|
NOTE: fields that doesn't define the length get the record size.
|
|
RemoveBlankRecords procedure. Removes all the blank records from the file.
|
|
RemoveExtraColumns procedure. If the file have more columns than the
|
|
scheme or the field definition at design time, it remove the extra
|
|
values from the file.
|
|
SaveFileAs. Let you save the file to another filename.
|
|
NOTE: This component save changes on closing the table, so you can use
|
|
this to save data before that event.
|
|
Jan 2001 Version 1.0 TSdfDataSet introduced.
|
|
---------
|
|
TERMS
|
|
---------
|
|
This component is provided AS-IS without any warranty of any kind, either
|
|
express or implied. This component is freeware and can be used in any software
|
|
product. Credits on applications will be welcomed.
|
|
If you find it useful, improve it or have a wish list... please drop me a mail,
|
|
I'll be glad to hear your comments.
|
|
----------------
|
|
How to Install
|
|
----------------
|
|
1. Copy this SDFDATA.PAS and the associated SDFDATA.DCR to the folder from
|
|
where you wish to install the component. This will probably be $(DELPHI)\lib
|
|
or a sub-folder.
|
|
2. Install the TSdfDataSet and TFixedFormatDataSet components by choosing the
|
|
Component | Install Component menu option.
|
|
3. Select the "Into exisiting package" page of the Install Components dialogue.
|
|
4. Browse to the folder where you saved this file and select it.
|
|
5. Ensure that the "Package file name" edit box contains $(DELPHI)\DCLUSR??.DPK
|
|
or the one you prefer for DB related objects.
|
|
6. Accept that the package will be rebuilt.
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
interface
|
|
|
|
uses
|
|
DB, Classes, SysUtils;
|
|
|
|
const
|
|
MAXSTRLEN = 250;
|
|
|
|
type
|
|
//-----------------------------------------------------------------------------
|
|
// TRecInfo
|
|
PRecInfo = ^TRecInfo;
|
|
TRecInfo = packed record
|
|
RecordNumber: PtrInt;
|
|
BookmarkFlag: TBookmarkFlag;
|
|
end;
|
|
//-----------------------------------------------------------------------------
|
|
// TBaseTextDataSet
|
|
TFixedFormatDataSet = class(TDataSet)
|
|
private
|
|
FSchema :TStringList;
|
|
FFileName :TFileName;
|
|
FFilterBuffer :PChar;
|
|
FFileMustExist :Boolean;
|
|
FReadOnly :Boolean;
|
|
FLoadfromStream :Boolean;
|
|
FTrimSpace :Boolean;
|
|
procedure SetSchema(const Value: TStringList);
|
|
procedure SetFileName(Value : TFileName);
|
|
procedure SetFileMustExist(Value : Boolean);
|
|
procedure SetTrimSpace(Value : Boolean);
|
|
procedure SetReadOnly(Value : Boolean);
|
|
procedure RemoveWhiteLines(List : TStrings; IsFileRecord : Boolean);
|
|
procedure LoadFieldScheme(List : TStrings; MaxSize : Integer);
|
|
function GetActiveRecBuf(var RecBuf: PChar): Boolean;
|
|
procedure SetFieldPos(var Buffer : PChar; FieldNo : Integer);
|
|
protected
|
|
FData :TStringlist;
|
|
FCurRec :Integer;
|
|
FRecBufSize :Integer;
|
|
FRecordSize :Integer;
|
|
FLastBookmark :PtrInt;
|
|
FRecInfoOfs :Word;
|
|
FBookmarkOfs :Word;
|
|
FSaveChanges :Boolean;
|
|
protected
|
|
function AllocRecordBuffer: PChar; override;
|
|
procedure FreeRecordBuffer(var Buffer: PChar); override;
|
|
procedure InternalAddRecord(Buffer: Pointer; DoAppend: Boolean); override;
|
|
procedure InternalClose; override;
|
|
procedure InternalDelete; override;
|
|
procedure InternalFirst; override;
|
|
procedure InternalGotoBookmark(ABookmark: Pointer); override;
|
|
procedure InternalHandleException; override;
|
|
procedure InternalInitFieldDefs; override;
|
|
procedure InternalInitRecord(Buffer: PChar); override;
|
|
procedure InternalLast; override;
|
|
procedure InternalOpen; override;
|
|
procedure InternalPost; override;
|
|
procedure InternalEdit; override;
|
|
procedure InternalSetToRecord(Buffer: PChar); override;
|
|
function IsCursorOpen: Boolean; override;
|
|
procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
|
|
function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
|
|
function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
|
|
function GetRecordSize: Word; override;
|
|
procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
|
|
procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
|
|
procedure SetFieldData(Field: TField; Buffer: Pointer); override;
|
|
procedure ClearCalcFields(Buffer: PChar); override;
|
|
function GetRecordCount: Integer; override;
|
|
function GetRecNo: Integer; override;
|
|
procedure SetRecNo(Value: Integer); override;
|
|
function GetCanModify: boolean; override;
|
|
function TxtGetRecord(Buffer : PChar; GetMode: TGetMode): TGetResult;
|
|
function RecordFilter(RecBuf: Pointer; ARecNo: Integer): Boolean;
|
|
function BufToStore(Buffer: PChar): String; virtual;
|
|
function StoreToBuf(Source: String): String; virtual;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
|
|
procedure RemoveBlankRecords; dynamic;
|
|
procedure RemoveExtraColumns; dynamic;
|
|
procedure SaveFileAs(strFileName : String); dynamic;
|
|
property CanModify;
|
|
procedure LoadFromStream(Stream :TStream);
|
|
procedure SavetoStream(Stream :TStream);
|
|
published
|
|
property FileMustExist: Boolean read FFileMustExist write SetFileMustExist;
|
|
property ReadOnly: Boolean read FReadOnly write SetReadOnly;
|
|
property FileName : TFileName read FFileName write SetFileName;
|
|
property Schema: TStringList read FSchema write SetSchema;
|
|
property TrimSpace: Boolean read FTrimSpace write SetTrimSpace default True;
|
|
property FieldDefs;
|
|
property Active;
|
|
property AutoCalcFields;
|
|
property Filtered;
|
|
property BeforeOpen;
|
|
property AfterOpen;
|
|
property BeforeClose;
|
|
property AfterClose;
|
|
property BeforeInsert;
|
|
property AfterInsert;
|
|
property BeforeEdit;
|
|
property AfterEdit;
|
|
property BeforePost;
|
|
property AfterPost;
|
|
property BeforeCancel;
|
|
property AfterCancel;
|
|
property BeforeDelete;
|
|
property AfterDelete;
|
|
property BeforeScroll;
|
|
property AfterScroll;
|
|
// property BeforeRefresh;
|
|
// property AfterRefresh;
|
|
property OnCalcFields;
|
|
property OnDeleteError;
|
|
property OnEditError;
|
|
property OnFilterRecord;
|
|
property OnNewRecord;
|
|
property OnPostError;
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// TSdfDataSet
|
|
TSdfDataSet = class(TFixedFormatDataSet)
|
|
private
|
|
FDelimiter : Char;
|
|
FFirstLineAsSchema : Boolean;
|
|
procedure SetFirstLineAsSchema(Value : Boolean);
|
|
procedure SetDelimiter(Value : Char);
|
|
protected
|
|
procedure InternalInitFieldDefs; override;
|
|
function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean)
|
|
: TGetResult; override;
|
|
function BufToStore(Buffer: PChar): String; override;
|
|
function StoreToBuf(Source: String): String; override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
published
|
|
property Delimiter: Char read FDelimiter write SetDelimiter;
|
|
property FirstLineAsSchema: Boolean read FFirstLineAsSchema write SetFirstLineAsSchema;
|
|
end;
|
|
procedure Register;
|
|
|
|
implementation
|
|
//{$R *.Res}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// TFixedFormatDataSet
|
|
//-----------------------------------------------------------------------------
|
|
constructor TFixedFormatDataSet.Create(AOwner : TComponent);
|
|
begin
|
|
FFileMustExist := TRUE;
|
|
FLoadfromStream := False;
|
|
FRecordSize := 0;
|
|
FTrimSpace := TRUE;
|
|
FSchema := TStringList.Create;
|
|
FData := TStringList.Create; // Load the textfile into a stringlist
|
|
inherited Create(AOwner);
|
|
end;
|
|
|
|
destructor TFixedFormatDataSet.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
FData.Free;
|
|
FSchema.Free;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.SetSchema(const Value: TStringList);
|
|
begin
|
|
CheckInactive;
|
|
FSchema.Assign(Value);
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.SetFileMustExist(Value : Boolean);
|
|
begin
|
|
CheckInactive;
|
|
FFileMustExist := Value;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.SetTrimSpace(Value : Boolean);
|
|
begin
|
|
CheckInactive;
|
|
FTrimSpace := Value;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.SetReadOnly(Value : Boolean);
|
|
begin
|
|
CheckInactive;
|
|
FReadOnly := Value;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.SetFileName(Value : TFileName);
|
|
begin
|
|
CheckInactive;
|
|
FFileName := Value;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.InternalInitFieldDefs;
|
|
var
|
|
i, len, Maxlen :Integer;
|
|
LstFields :TStrings;
|
|
begin
|
|
if not Assigned(FData) then
|
|
exit;
|
|
FRecordSize := 0;
|
|
Maxlen := 0;
|
|
FieldDefs.Clear;
|
|
for i := FData.Count - 1 downto 0 do // Find out the longest record
|
|
begin
|
|
len := Length(FData[i]);
|
|
if len > Maxlen then
|
|
Maxlen := len;
|
|
FData.Objects[i] := TObject(Pointer(i+1)); // Fabricate Bookmarks
|
|
end;
|
|
if (Maxlen = 0) then
|
|
Maxlen := MAXSTRLEN;
|
|
LstFields := TStringList.Create;
|
|
try
|
|
LoadFieldScheme(LstFields, Maxlen);
|
|
for i := 0 to LstFields.Count -1 do // Add fields
|
|
begin
|
|
len := StrToIntDef(LstFields.Values[LstFields.Names[i]], Maxlen);
|
|
FieldDefs.Add(Trim(LstFields.Names[i]), ftString, len, False);
|
|
Inc(FRecordSize, len);
|
|
end;
|
|
finally
|
|
LstFields.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.InternalOpen;
|
|
var
|
|
Stream : TStream;
|
|
begin
|
|
FCurRec := -1;
|
|
FSaveChanges := FALSE;
|
|
if not Assigned(FData) then
|
|
FData := TStringList.Create;
|
|
if (not FileMustExist) and (not FileExists(FileName)) then
|
|
begin
|
|
Stream := TFileStream.Create(FileName, fmCreate);
|
|
Stream.Free;
|
|
end;
|
|
if not FLoadfromStream then
|
|
FData.LoadFromFile(FileName);
|
|
FRecordSize := MAXSTRLEN;
|
|
InternalInitFieldDefs;
|
|
if DefaultFields then
|
|
CreateFields;
|
|
BindFields(TRUE);
|
|
if FRecordSize = 0 then
|
|
FRecordSize := MAXSTRLEN;
|
|
BookmarkSize := SizeOf(Integer);
|
|
FRecInfoOfs := FRecordSize + CalcFieldsSize; // Initialize the offset for TRecInfo in the buffer
|
|
FBookmarkOfs := FRecInfoOfs + SizeOf(TRecInfo);
|
|
FRecBufSize := FBookmarkOfs + BookmarkSize;
|
|
FLastBookmark := FData.Count;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.InternalClose;
|
|
begin
|
|
if (not FReadOnly) and (FSaveChanges) then // Write any edits to disk
|
|
FData.SaveToFile(FileName);
|
|
FLoadfromStream := False;
|
|
FData.Clear;
|
|
BindFields(FALSE);
|
|
if DefaultFields then // Destroy the TField
|
|
DestroyFields;
|
|
FCurRec := -1; // Reset these internal flags
|
|
FLastBookmark := 0;
|
|
FRecordSize := 0;
|
|
end;
|
|
|
|
function TFixedFormatDataSet.IsCursorOpen: Boolean;
|
|
begin
|
|
Result := Assigned(FData) and (FRecordSize > 0);
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.InternalHandleException;
|
|
begin
|
|
{$ifndef fpc}
|
|
Application.HandleException(Self);
|
|
{$else}
|
|
inherited;
|
|
{$endif}
|
|
end;
|
|
|
|
// Loads Data from a stream.
|
|
procedure TFixedFormatDataSet.LoadFromStream(Stream: TStream);
|
|
begin
|
|
if assigned(stream) then
|
|
begin
|
|
Active := False; //Make sure the Dataset is Closed.
|
|
Stream.Position := 0; //Make sure you are at the top of the Stream.
|
|
FLoadfromStream := True;
|
|
if not Assigned(FData) then
|
|
raise Exception.Create('Data buffer unassigned');
|
|
FData.LoadFromStream(Stream);
|
|
Active := True;
|
|
end
|
|
else
|
|
raise exception.Create('Invalid Stream Assigned (Load From Stream');
|
|
end;
|
|
|
|
// Saves Data as text to a stream.
|
|
procedure TFixedFormatDataSet.SavetoStream(Stream: TStream);
|
|
begin
|
|
if assigned(stream) then
|
|
FData.SaveToStream(Stream)
|
|
else
|
|
raise exception.Create('Invalid Stream Assigned (Save To Stream');
|
|
end;
|
|
|
|
// Record Functions
|
|
function TFixedFormatDataSet.AllocRecordBuffer: PChar;
|
|
begin
|
|
if FRecBufSize > 0 then
|
|
Result := AllocMem(FRecBufSize)
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.FreeRecordBuffer(var Buffer: PChar);
|
|
begin
|
|
if Buffer <> nil then
|
|
FreeMem(Buffer);
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.InternalInitRecord(Buffer: PChar);
|
|
begin
|
|
FillChar(Buffer[0], FRecordSize, 0);
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.ClearCalcFields(Buffer: PChar);
|
|
begin
|
|
FillChar(Buffer[RecordSize], CalcFieldsSize, 0);
|
|
end;
|
|
|
|
function TFixedFormatDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode;
|
|
DoCheck: Boolean): TGetResult;
|
|
begin
|
|
if (FData.Count < 1) then
|
|
Result := grEOF
|
|
else
|
|
Result := TxtGetRecord(Buffer, GetMode);
|
|
if Result = grOK then
|
|
begin
|
|
if (CalcFieldsSize > 0) then
|
|
GetCalcFields(Buffer);
|
|
with PRecInfo(Buffer + FRecInfoOfs)^ do
|
|
begin
|
|
BookmarkFlag := bfCurrent;
|
|
RecordNumber := PtrInt(FData.Objects[FCurRec]);
|
|
end;
|
|
end
|
|
else
|
|
if (Result = grError) and DoCheck then
|
|
DatabaseError('No Records');
|
|
end;
|
|
|
|
function TFixedFormatDataSet.GetRecordCount: Longint;
|
|
begin
|
|
Result := FData.Count;
|
|
end;
|
|
|
|
function TFixedFormatDataSet.GetRecNo: Longint;
|
|
var
|
|
BufPtr: PChar;
|
|
begin
|
|
Result := -1;
|
|
if GetActiveRecBuf(BufPtr) then
|
|
Result := PRecInfo(BufPtr + FRecInfoOfs)^.RecordNumber;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.SetRecNo(Value: Integer);
|
|
begin
|
|
CheckBrowseMode;
|
|
if (Value >= 0) and (Value < FData.Count) and (Value <> RecNo) then
|
|
begin
|
|
DoBeforeScroll;
|
|
FCurRec := Value - 1;
|
|
Resync([]);
|
|
DoAfterScroll;
|
|
end;
|
|
end;
|
|
|
|
function TFixedFormatDataSet.GetRecordSize: Word;
|
|
begin
|
|
Result := FRecordSize;
|
|
end;
|
|
|
|
function TFixedFormatDataSet.GetActiveRecBuf(var RecBuf: PChar): Boolean;
|
|
begin
|
|
case State of
|
|
dsBrowse: if IsEmpty then RecBuf := nil else RecBuf := ActiveBuffer;
|
|
dsEdit, dsInsert: RecBuf := ActiveBuffer;
|
|
dsCalcFields: RecBuf := CalcBuffer;
|
|
dsFilter: RecBuf := FFilterBuffer;
|
|
else
|
|
RecBuf := nil;
|
|
end;
|
|
Result := RecBuf <> nil;
|
|
end;
|
|
|
|
function TFixedFormatDataSet.TxtGetRecord(Buffer : PChar; GetMode: TGetMode): TGetResult;
|
|
var
|
|
Accepted : Boolean;
|
|
begin
|
|
Result := grOK;
|
|
repeat
|
|
Accepted := TRUE;
|
|
case GetMode of
|
|
gmNext:
|
|
if FCurRec >= RecordCount - 1 then
|
|
Result := grEOF
|
|
else
|
|
Inc(FCurRec);
|
|
gmPrior:
|
|
if FCurRec <= 0 then
|
|
Result := grBOF
|
|
else
|
|
Dec(FCurRec);
|
|
gmCurrent:
|
|
if (FCurRec < 0) or (FCurRec >= RecordCount) then
|
|
Result := grError;
|
|
end;
|
|
if (Result = grOk) then
|
|
begin
|
|
Move(PChar(StoreToBuf(FData[FCurRec]))^, Buffer[0], FRecordSize);
|
|
if Filtered then
|
|
begin
|
|
Accepted := RecordFilter(Buffer, FCurRec +1);
|
|
if not Accepted and (GetMode = gmCurrent) then
|
|
Inc(FCurRec);
|
|
end;
|
|
end;
|
|
until Accepted;
|
|
end;
|
|
|
|
function TFixedFormatDataSet.RecordFilter(RecBuf: Pointer; ARecNo: Integer): Boolean;
|
|
var
|
|
Accept: Boolean;
|
|
SaveState: TDataSetState;
|
|
begin // Returns true if accepted in the filter
|
|
SaveState := SetTempState(dsFilter);
|
|
FFilterBuffer := RecBuf;
|
|
PRecInfo(FFilterBuffer + FRecInfoOfs)^.RecordNumber := ARecNo;
|
|
Accept := TRUE;
|
|
if Accept and Assigned(OnFilterRecord) then
|
|
OnFilterRecord(Self, Accept);
|
|
RestoreState(SaveState);
|
|
Result := Accept;
|
|
end;
|
|
|
|
function TFixedFormatDataSet.GetCanModify: boolean;
|
|
begin
|
|
Result := not FReadOnly;
|
|
end;
|
|
|
|
// Field Related
|
|
procedure TFixedFormatDataSet.LoadFieldScheme(List : TStrings; MaxSize : Integer);
|
|
var
|
|
tmpFieldName : string;
|
|
tmpSchema : TStrings;
|
|
i : Integer;
|
|
begin
|
|
tmpSchema := TStringList.Create;
|
|
try // Load Schema Structure
|
|
if (Schema.Count > 0) then
|
|
begin
|
|
tmpSchema.Assign(Schema);
|
|
RemoveWhiteLines(tmpSchema, FALSE);
|
|
end
|
|
else
|
|
tmpSchema.Add('Line');
|
|
for i := 0 to tmpSchema.Count -1 do // Interpret Schema
|
|
begin
|
|
tmpFieldName := tmpSchema.Names[i];
|
|
if (tmpFieldName = '') then
|
|
tmpFieldName := Format('%s=%d', [tmpSchema[i], MaxSize])
|
|
else
|
|
tmpFieldName := tmpSchema[i];
|
|
List.Add(tmpFieldName);
|
|
end;
|
|
finally
|
|
tmpSchema.Free;
|
|
end;
|
|
end;
|
|
|
|
function TFixedFormatDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
|
|
var
|
|
TempPos, RecBuf: PChar;
|
|
begin
|
|
Result := GetActiveRecBuf(RecBuf);
|
|
if Result then
|
|
begin
|
|
if Field.FieldNo > 0 then
|
|
begin
|
|
TempPos := RecBuf;
|
|
SetFieldPos(RecBuf, Field.FieldNo);
|
|
Result := (RecBuf < StrEnd(TempPos));
|
|
end
|
|
else
|
|
if (State in [dsBrowse, dsEdit, dsInsert, dsCalcFields]) then
|
|
begin
|
|
Inc(RecBuf, FRecordSize + Field.Offset);
|
|
Result := Boolean(Byte(RecBuf[0]));
|
|
end;
|
|
end;
|
|
if Result and (Buffer <> nil) then
|
|
begin
|
|
StrLCopy(Buffer, RecBuf, Field.Size);
|
|
if FTrimSpace then
|
|
begin
|
|
TempPos := StrEnd(Buffer);
|
|
repeat
|
|
Dec(TempPos);
|
|
if (TempPos[0] = ' ') then
|
|
TempPos[0]:= #0
|
|
else
|
|
break;
|
|
until (TempPos = Buffer);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.SetFieldData(Field: TField; Buffer: Pointer);
|
|
var
|
|
RecBuf, BufEnd: PChar;
|
|
p : Integer;
|
|
begin
|
|
if not (State in [dsEdit, dsInsert]) then
|
|
DatabaseError('Dataset not in edit or insert mode', Self);
|
|
GetActiveRecBuf(RecBuf);
|
|
if Field.FieldNo > 0 then
|
|
begin
|
|
if State = dsCalcFields then
|
|
DatabaseError('Dataset not in edit or insert mode', Self);
|
|
if Field.ReadOnly and not (State in [dsSetKey, dsFilter]) then
|
|
DatabaseErrorFmt('Field ''%s'' cannot be modified', [Field.DisplayName]);
|
|
Field.Validate(Buffer);
|
|
if Field.FieldKind <> fkInternalCalc then
|
|
begin
|
|
SetFieldPos(RecBuf, Field.FieldNo);
|
|
BufEnd := StrEnd(ActiveBuffer); // Fill with blanks when necessary
|
|
if BufEnd > RecBuf then
|
|
BufEnd := RecBuf;
|
|
FillChar(BufEnd[0], Field.Size + PtrInt(RecBuf) - PtrInt(BufEnd), Ord(' '));
|
|
p := StrLen(Buffer);
|
|
if p > Field.Size then
|
|
p := Field.Size;
|
|
Move(Buffer^, RecBuf[0], p);
|
|
ActiveBuffer[RecordSize-1] := #0;
|
|
end;
|
|
end
|
|
else // fkCalculated, fkLookup
|
|
begin
|
|
Inc(RecBuf, FRecordSize + Field.Offset);
|
|
Move(Buffer^, RecBuf[0], Field.Size);
|
|
end;
|
|
if not (State in [dsCalcFields, dsFilter, dsNewValue]) then
|
|
DataEvent(deFieldChange, Ptrint(Field));
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.SetFieldPos(var Buffer : PChar; FieldNo : Integer);
|
|
var
|
|
i : Integer;
|
|
begin
|
|
i := 1;
|
|
while (i < FieldNo) and (i < FieldDefs.Count) do
|
|
begin
|
|
Inc(Buffer, FieldDefs.Items[i-1].Size);
|
|
Inc(i);
|
|
end;
|
|
end;
|
|
|
|
// Navigation / Editing
|
|
procedure TFixedFormatDataSet.InternalFirst;
|
|
begin
|
|
FCurRec := -1;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.InternalLast;
|
|
begin
|
|
FCurRec := FData.Count;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.InternalPost;
|
|
var
|
|
i: Longint;
|
|
begin
|
|
FSaveChanges := TRUE;
|
|
inherited UpdateRecord;
|
|
if (State = dsEdit) then // just update the data in the string list
|
|
begin
|
|
FData[FCurRec] := BufToStore(ActiveBuffer);
|
|
end
|
|
else
|
|
InternalAddRecord(ActiveBuffer, FALSE);
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.InternalEdit;
|
|
begin
|
|
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.InternalDelete;
|
|
begin
|
|
FSaveChanges := TRUE;
|
|
FData.Delete(FCurRec);
|
|
if FCurRec >= FData.Count then
|
|
Dec(FCurRec);
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.InternalAddRecord(Buffer: Pointer; DoAppend: Boolean);
|
|
begin
|
|
FSaveChanges := TRUE;
|
|
Inc(FLastBookmark);
|
|
if DoAppend then
|
|
InternalLast;
|
|
if (FCurRec >=0) then
|
|
FData.InsertObject(FCurRec, BufToStore(Buffer), TObject(Pointer(FLastBookmark)))
|
|
else
|
|
FData.AddObject(BufToStore(Buffer), TObject(Pointer(FLastBookmark)));
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.InternalGotoBookmark(ABookmark: Pointer);
|
|
var
|
|
Index: Integer;
|
|
begin
|
|
Index := FData.IndexOfObject(TObject(PPtrInt(ABookmark)^));
|
|
if Index <> -1 then
|
|
FCurRec := Index
|
|
else
|
|
DatabaseError('Bookmark not found');
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.InternalSetToRecord(Buffer: PChar);
|
|
begin
|
|
if (State <> dsInsert) then
|
|
InternalGotoBookmark(@PRecInfo(Buffer + FRecInfoOfs)^.RecordNumber);
|
|
end;
|
|
|
|
function TFixedFormatDataSet.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
|
|
begin
|
|
Result := PRecInfo(Buffer + FRecInfoOfs)^.BookmarkFlag;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
|
|
begin
|
|
PRecInfo(Buffer + FRecInfoOfs)^.BookmarkFlag := Value;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.GetBookmarkData(Buffer: PChar; Data: Pointer);
|
|
begin
|
|
Move(Buffer[FBookmarkOfs], Data^, BookmarkSize);
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.SetBookmarkData(Buffer: PChar; Data: Pointer);
|
|
begin
|
|
Move(Data^, Buffer[FBookmarkOfs], BookmarkSize);
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.RemoveWhiteLines(List : TStrings; IsFileRecord : Boolean);
|
|
var
|
|
i : integer;
|
|
begin
|
|
for i := List.Count -1 downto 0 do
|
|
begin
|
|
if (Trim(List[i]) = '' ) then
|
|
if IsFileRecord then
|
|
begin
|
|
FCurRec := i;
|
|
InternalDelete;
|
|
end
|
|
else
|
|
List.Delete(i);
|
|
end;
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.RemoveBlankRecords;
|
|
begin
|
|
RemoveWhiteLines(FData, TRUE);
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.RemoveExtraColumns;
|
|
var
|
|
i : Integer;
|
|
begin
|
|
for i := FData.Count -1 downto 0 do
|
|
FData[i] := BufToStore(PChar(StoreToBuf(FData[i])));
|
|
FData.SaveToFile(FileName);
|
|
end;
|
|
|
|
procedure TFixedFormatDataSet.SaveFileAs(strFileName : String);
|
|
begin
|
|
FData.SaveToFile(strFileName);
|
|
FFileName := strFileName;
|
|
FSaveChanges := FALSE;
|
|
end;
|
|
|
|
function TFixedFormatDataSet.StoreToBuf(Source: String): String;
|
|
begin
|
|
Result := Source;
|
|
end;
|
|
|
|
function TFixedFormatDataSet.BufToStore(Buffer: PChar): String;
|
|
begin
|
|
Result := Copy(Buffer, 1, FRecordSize);
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// TSdfDataSet
|
|
//-----------------------------------------------------------------------------
|
|
constructor TSdfDataSet.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FDelimiter := ',';
|
|
FFirstLineAsSchema := FALSE;
|
|
end;
|
|
|
|
procedure TSdfDataSet.InternalInitFieldDefs;
|
|
var
|
|
pStart, pEnd, len : Integer;
|
|
begin
|
|
if not IsCursorOpen then
|
|
exit;
|
|
if (FData.Count = 0) or (Trim(FData[0]) = '') then
|
|
FirstLineAsSchema := FALSE
|
|
else if (Schema.Count = 0) or (FirstLineAsSchema) then
|
|
begin
|
|
Schema.Clear;
|
|
len := Length(FData[0]);
|
|
pEnd := 1;
|
|
repeat
|
|
while (pEnd <= len) and (FData[0][pEnd] in [#1..' ']) do
|
|
Inc(pEnd);
|
|
|
|
if (pEnd > len) then
|
|
break;
|
|
|
|
pStart := pEnd;
|
|
|
|
if (FData[0][pStart] = '"') then
|
|
begin
|
|
repeat
|
|
Inc(pEnd);
|
|
until (pEnd > len) or (FData[0][pEnd] = '"');
|
|
|
|
if (FData[0][pEnd] = '"') then
|
|
Inc(pStart);
|
|
end
|
|
else
|
|
while (pEnd <= len) and (FData[0][pEnd] <> Delimiter) do
|
|
Inc(pEnd);
|
|
|
|
if (FirstLineAsSchema) then
|
|
Schema.Add(Copy(FData[0], pStart, pEnd - pStart))
|
|
else
|
|
Schema.Add(Format('Field%d', [Schema.Count + 1]));
|
|
|
|
if (FData[0][pEnd] = '"') then
|
|
while (pEnd <= len) and (FData[0][pEnd] <> Delimiter) do
|
|
Inc(pEnd);
|
|
|
|
if (FData[0][pEnd] = Delimiter) then
|
|
Inc(pEnd);
|
|
|
|
until (pEnd > len);
|
|
end;
|
|
inherited;
|
|
end;
|
|
|
|
function TSdfDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode;
|
|
DoCheck: Boolean): TGetResult;
|
|
begin
|
|
if FirstLineAsSchema then
|
|
begin
|
|
if (FData.Count < 2) then
|
|
Result := grEOF
|
|
else
|
|
begin
|
|
Result := inherited GetRecord(Buffer, GetMode, DoCheck);
|
|
if (Result = grOk) and (FCurRec = 0) then
|
|
Result := inherited GetRecord(Buffer, GetMode, DoCheck);
|
|
end;
|
|
end
|
|
else
|
|
Result := inherited GetRecord(Buffer, GetMode, DoCheck);
|
|
end;
|
|
|
|
function TSdfDataSet.StoreToBuf(Source: String): String;
|
|
const
|
|
CR :char = #13;
|
|
LF :char = #10;
|
|
var
|
|
i,
|
|
p :Integer;
|
|
pRet,
|
|
pStr,
|
|
pStrEnd :PChar;
|
|
Ret :String;
|
|
begin
|
|
SetLength(Ret, FRecordSize);
|
|
|
|
FillChar(PChar(Ret)^, FRecordSize, Ord(' '));
|
|
PStrEnd := PChar(Source);
|
|
pRet := PChar(Ret);
|
|
|
|
for i := 0 to FieldDefs.Count - 1 do
|
|
begin
|
|
|
|
while Boolean(Byte(pStrEnd[0])) and (pStrEnd[0] in [#1..' ']) do
|
|
Inc(pStrEnd);
|
|
|
|
if not Boolean(Byte(pStrEnd[0])) then
|
|
break;
|
|
|
|
pStr := pStrEnd;
|
|
|
|
if (pStr[0] = '"') then
|
|
begin
|
|
repeat
|
|
Inc(pStrEnd);
|
|
until not Boolean(Byte(pStrEnd[0])) or
|
|
((pStrEnd[0] = '"') and ((pStrEnd + 1)[0] in [Delimiter,CR,LF, #0]));
|
|
|
|
if (pStrEnd[0] = '"') then
|
|
Inc(pStr);
|
|
end
|
|
else
|
|
while Boolean(Byte(pStrEnd[0])) and (pStrEnd[0] <> Delimiter) do
|
|
Inc(pStrEnd);
|
|
|
|
p := pStrEnd - pStr;
|
|
if (p > FieldDefs[i].Size) then
|
|
p := FieldDefs[i].Size;
|
|
|
|
Move(pStr[0], pRet[0], p);
|
|
|
|
Inc(pRet, FieldDefs[i].Size);
|
|
|
|
if (pStrEnd[0] = '"') then
|
|
while Boolean(Byte(pStrEnd[0])) and (pStrEnd[0] <> Delimiter) do
|
|
Inc(pStrEnd);
|
|
|
|
if (pStrEnd[0] = Delimiter) then
|
|
Inc(pStrEnd);
|
|
end;
|
|
Result := Ret;
|
|
end;
|
|
|
|
function TSdfDataSet.BufToStore(Buffer: PChar): String;
|
|
var
|
|
Str : String;
|
|
p, i : Integer;
|
|
begin
|
|
Result := '';
|
|
p := 1;
|
|
for i := 0 to FieldDefs.Count - 1 do
|
|
begin
|
|
Str := Trim(Copy(Buffer, p, FieldDefs[i].Size));
|
|
Inc(p, FieldDefs[i].Size);
|
|
if (StrScan(PChar(Str), FDelimiter) <> nil) then
|
|
Str := '"' + Str + '"';
|
|
Result := Result + Str + FDelimiter;
|
|
end;
|
|
p := Length(Result);
|
|
while (p > 0) and (Result[p] = FDelimiter) do
|
|
begin
|
|
System.Delete(Result, p, 1);
|
|
Dec(p);
|
|
end;
|
|
end;
|
|
|
|
procedure TSdfDataSet.SetDelimiter(Value : Char);
|
|
begin
|
|
CheckInactive;
|
|
FDelimiter := Value;
|
|
end;
|
|
|
|
procedure TSdfDataSet.SetFirstLineAsSchema(Value : Boolean);
|
|
begin
|
|
CheckInactive;
|
|
FFirstLineAsSchema := Value;
|
|
end;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This procedure is used to register this component on the component palette
|
|
//-----------------------------------------------------------------------------
|
|
procedure Register;
|
|
begin
|
|
RegisterComponents('Data Access', [TFixedFormatDataSet]);
|
|
RegisterComponents('Data Access', [TSdfDataSet]);
|
|
end;
|
|
|
|
end.
|