I am trying to save the state of a component and later restore it. I have made a little class helper to do so but the order of the way Delphi restores the class is causing a problem. It restores the Delimiter correctly but then sets RecordFormat which in turn over writes the Delimiter to be a semi colon. So I end up with the wrong value. Do you have any suggestions how I could get round this? I liked the simplicity of using a class helper but it will get convoluted if I have to handle it afterwards.
interface
type
TFDBatchMoveTextReaderHelper = class helper for TFDBatchMoveTextReader
private
function GetState: String;
procedure SetState( const Value: String );
published
property State: String read GetState write SetState;
end;
implementation
function ComponentToStringProc( Component: TComponent ): string;
var
BinStream: TMemoryStream;
StrStream: TStringStream;
s : string;
begin
BinStream := TMemoryStream.Create;
try
StrStream := TStringStream.Create( s );
try
BinStream.WriteComponent( Component );
BinStream.Seek( 0, soFromBeginning );
ObjectBinaryToText( BinStream, StrStream );
StrStream.Seek( 0, soFromBeginning );
Result := StrStream.DataString;
finally
StrStream.Free;
end;
finally
BinStream.Free
end;
end;
procedure StrtoComp( Value: string; AComponent: TComponent );
var
StrStream: TStringStream;
BinStream: TMemoryStream;
begin
StrStream := TStringStream.Create( Value );
try
BinStream := TMemoryStream.Create;
try
ObjectTextToBinary( StrStream, BinStream );
BinStream.Seek( 0, soFromBeginning );
BinStream.ReadComponent( AComponent );
finally
BinStream.Free;
end;
finally
StrStream.Free;
end;
end;
{ TFDBatchMoveTextReaderHelper }
function TFDBatchMoveTextReaderHelper.GetState: String;
begin
Result := ComponentToStringProc( Self );
end;
procedure TFDBatchMoveTextReaderHelper.SetState( const Value: String );
begin
StrtoComp( Value, Self );
end;
initialization
RegisterClass( TFDBatchMoveTextReader );
end.
And here are my results (ignore the difference in the filename, I changed that before logging the state again. It's the DataDef values I am having problems with)
Text Compare
Produced: 27/03/2020 07:56:58
Mode: All
Left file: Clipboard at 27/03/2020 07:54:09 Right file: Clipboard at 27/03/2020 07:54:18
AReaderState from the database = object TextReader: TFDBatchMoveTextReader <> lReader.State after applied to the object = object TextReader: TFDBatchMoveTextReader
FileName = 'c:\molen\impexp\import\adm_loads_foynes.csv' FileName =
'C:\Users\stevesinclair\AppData\Local\Temp\96FFA4BD1C064E77972398' +
'7AD6E15495\adm_loads_foynes.csv'
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
DataDef.Fields = < = DataDef.Fields = <
item item
FieldName = 'LOAD_ID' FieldName = 'LOAD_ID'
DataType = atLongInt DataType = atLongInt
FieldSize = 9 FieldSize = 9
end end
item item
FieldName = 'SITE_ID' FieldName = 'SITE_ID'
DataType = atLongInt DataType = atLongInt
FieldSize = 5 FieldSize = 5
end end
item item
FieldName = 'VEHICLE_REG' FieldName = 'VEHICLE_REG'
DataType = atString DataType = atString
FieldSize = 32 FieldSize = 32
end end
item item
FieldName = 'CREATED_BY' FieldName = 'CREATED_BY'
DataType = atString DataType = atString
FieldSize = 32 FieldSize = 32
end end
item item
FieldName = 'UPDATED_BY' FieldName = 'UPDATED_BY'
DataType = atString DataType = atString
FieldSize = 32 FieldSize = 32
end end
item item
FieldName = 'LDD_ID' FieldName = 'LDD_ID'
DataType = atLongInt DataType = atLongInt
FieldSize = 9 FieldSize = 9
end end
item item
FieldName = 'TRANS_TYPE_ID' FieldName = 'TRANS_TYPE_ID'
DataType = atLongInt DataType = atLongInt
FieldSize = 3 FieldSize = 3
end end
item item
FieldName = 'STAGE_ID' FieldName = 'STAGE_ID'
DataType = atLongInt DataType = atLongInt
FieldSize = 1 FieldSize = 1
end end
item item
FieldName = 'ACCOUNT_ID' FieldName = 'ACCOUNT_ID'
DataType = atLongInt DataType = atLongInt
FieldSize = 9 FieldSize = 9
end end
item item
FieldName = 'COMMODITY_ID' FieldName = 'COMMODITY_ID'
DataType = atLongInt DataType = atLongInt
FieldSize = 9 FieldSize = 9
end end
item item
FieldName = 'FLN_ID' FieldName = 'FLN_ID'
DataType = atLongInt DataType = atLongInt
FieldSize = 9 FieldSize = 9
end end
item item
FieldName = 'TLN_ID' FieldName = 'TLN_ID'
DataType = atLongInt DataType = atLongInt
FieldSize = 9 FieldSize = 9
end end
item item
FieldName = 'NET_WEIGHT' FieldName = 'NET_WEIGHT'
DataType = atLongInt DataType = atLongInt
FieldSize = 5 FieldSize = 5
end end
item item
FieldName = 'CREATED_DATE' FieldName = 'CREATED_DATE'
DataType = atDateTime DataType = atDateTime
FieldSize = 19 FieldSize = 19
end end
item item
FieldName = 'UPDATED_DATE' FieldName = 'UPDATED_DATE'
DataType = atDate DataType = atDate
FieldSize = 10 FieldSize = 10
end end
item item
FieldName = 'UPDATED_TIME' FieldName = 'UPDATED_TIME'
DataType = atTime DataType = atTime
FieldSize = 8 FieldSize = 8
end end
item item
FieldName = 'TRAILER_REG' FieldName = 'TRAILER_REG'
DataType = atString DataType = atString
FieldSize = 6 FieldSize = 6
end end
item item
FieldName = 'TRANSACTION_DATE' FieldName = 'TRANSACTION_DATE'
DataType = atDateTime DataType = atDateTime
FieldSize = 19 FieldSize = 19
end end
item item
FieldName = 'IOX' FieldName = 'IOX'
DataType = atString DataType = atString
FieldSize = 1 FieldSize = 1
end end
item item
FieldName = 'SHIP_ID' FieldName = 'SHIP_ID'
DataType = atLongInt DataType = atLongInt
FieldSize = 9 FieldSize = 9
end> end>
DataDef.Delimiter = '"' DataDef.Delimiter = '"'
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
DataDef.Separator = ',' <> DataDef.Separator = ';'
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
DataDef.RecordFormat = rfCustom = DataDef.RecordFormat = rfCustom
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
DataDef.FormatSettings.DateSeparator = '/' +-
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
DataDef.FormatSettings.ShortDateFormat = 'DD/MM/YYYY' = DataDef.FormatSettings.ShortDateFormat = 'DD/MM/YYYY'
DataDef.FormatSettings.ShortTimeFormat = 'HH:mm:ss' DataDef.FormatSettings.ShortTimeFormat = 'HH:mm:ss'
Left = 430 Left = 430
Top = 20 Top = 20
end end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Here is a test that I wrote for this which passes.
procedure TMyTestObject.TestReaderStaterfCustom;
var
LOriginalReader: TFDBatchMoveTextReader;
LRestoredReader: TFDBatchMoveTextReader;
const
LDelimiter = '|';
LSeparator = ',';
begin
LOriginalReader := TFDBatchMoveTextReader.Create( nil );
LRestoredReader := TFDBatchMoveTextReader.Create( nil );
try
LOriginalReader.DataDef.RecordFormat := rfCustom;
LOriginalReader.DataDef.Delimiter := LDelimiter;
LOriginalReader.DataDef.Separator := LSeparator;
LRestoredReader.State := LOriginalReader.State;
Assert.AreEqual( LOriginalReader.State, LRestoredReader.State, 'LOriginalReader.State, LRestoredReader.State (after setting)' );
Assert.AreEqual( LOriginalReader.DataDef.Delimiter, LRestoredReader.DataDef.Delimiter );
Assert.AreEqual( LOriginalReader.DataDef.Separator, LRestoredReader.DataDef.Separator );
Assert.AreEqual( LDelimiter, LRestoredReader.DataDef.Delimiter );
Assert.AreEqual( LSeparator, LRestoredReader.DataDef.Separator );
finally
LOriginalReader.Free;
LRestoredReader.Free;
end;
end;
But when I try to use it in my application it fails. I've put some logging round the setter as follows:
procedure TFDBatchMoveTextReaderHelper.SetState( const Value: String );
begin
CodeSite.Send( 'TFDBatchMoveTextReaderHelper.SetState', Value );
StrtoComp( Value, Self );
CodeSite.Send( 'self.DataDef.Separator', self.DataDef.Separator );
end;
Which logs the separator as self.DataDef.Separator = ; which makes no sense as the value in the state string string is:
DataDef.Delimiter = '"'
DataDef.Separator = ','
DataDef.RecordFormat = rfCustom
DataDef.FormatSettings.DateSeparator = '/'
DataDef.FormatSettings.ShortDateFormat = 'DD/MM/YYYY'
DataDef.FormatSettings.ShortTimeFormat = 'HH:mm:ss'
And this is how I am actually using it in the application:
lConn := TFDConnection.Create( nil );
try
lConn.ConnectionDefName := AConnectionDefName;
lMover := TFDBatchMove.Create( lConn );
lMemTable := TFDMemTable.Create( lConn );
lReader := TFDBatchMoveTextReader.Create( lConn );
lReader.State := AReaderState;
lReader.FileName := lTempFileName;
lWriter := TFDBatchMoveDataSetWriter.Create( lConn );
lWriter.Dataset := lMemTable;
lQuery := TFDQuery.Create( lConn );
lQuery.Connection := lConn;
lQuery.SQL.Text := ATargetSQL;
try
lQuery.Prepare;
except
on E: Exception do
begin
CodeSite.SendException( Format( 'Error perparing query: %s', [ ATargetSQL ] ), E );
raise;
end
end;
lMover.Reader := lReader;
lMover.Writer := lWriter;
I have worked round this setting the separator and delimiter after applying the state string. In my rests I found that the order of how the state is saved was causing the problem. If DataDef.RecordFormat is before DataDef.Delimiter and DataDef.Separator it worked. However I have no control of how that is saved so I have hacked it when reading it back.
procedure TFDBatchMoveTextReaderHelper.SetState( const Value: String );
var
RegEx: TRegEx;
Match: TMatch;
const
PatternRecordFormat = 'DataDef\.RecordFormat *= *(rfCustom)';
PatternSeparator = 'DataDef\.Separator *= *''(.)''';
PatternDelimiter = 'DataDef\.Delimiter *= *''(.)''';
begin
StrtoComp( Value, Self );
RegEx := TRegEx.Create( PatternRecordFormat, [ roIgnoreCase, roMultiLine ] );
Match := RegEx.Match( Value );
if Match.Success and ( Match.Groups.Count > 1 ) then
begin
RegEx := TRegEx.Create( PatternSeparator, [ roIgnoreCase, roMultiLine ] );
Match := RegEx.Match( Value );
if Match.Success and ( Match.Groups.Count > 1 ) then
Self.DataDef.Separator := ( Match.Groups[ 1 ].Value + ',' )[ 1 ];
RegEx := TRegEx.Create( PatternDelimiter, [ roIgnoreCase, roMultiLine ] );
Match := RegEx.Match( Value );
if Match.Success and ( Match.Groups.Count > 1 ) then
Self.DataDef.Delimiter := ( Match.Groups[ 1 ].Value + '"' )[ 1 ];
end;
end;
Not the best answer I know but it's all I could come up with. Here's my test to confirm it worked.
[ Test ]
[ TestCase( 'Comma Double Quote', 'rfCommaDoubleQuote' ) ]
[ TestCase( 'Semicolon Double Quote', 'rfSemicolonDoubleQuote' ) ]
[ TestCase( 'Tab Double Quote', 'rfTabDoubleQuote' ) ]
[ TestCase( 'Fixed Length', 'rfFixedLength' ) ]
[ TestCase( 'Field Per Line', 'rfFieldPerLine' ) ]
[ TestCase( 'Bar Double Quote', 'rfCustom' ) ]
procedure TestReaderState( const ARecordFormat: TFDTextRecordFormat );
procedure TMyTestObject.TestReaderState( const ARecordFormat: TFDTextRecordFormat );
var
LOriginalReader: TFDBatchMoveTextReader;
LRestoredReader: TFDBatchMoveTextReader;
LDelimiter : Char;
LSeparator : Char;
LWithFieldNames: Boolean;
begin
LOriginalReader := TFDBatchMoveTextReader.Create( nil );
LRestoredReader := TFDBatchMoveTextReader.Create( nil );
try
case ARecordFormat of
rfCommaDoubleQuote:
begin
LDelimiter := #34;
LSeparator := #44;
end;
rfSemicolonDoubleQuote:
begin
LDelimiter := #34;
LSeparator := #59;
end;
rfTabDoubleQuote:
begin
LDelimiter := #34;
LSeparator := #9;
end;
rfFixedLength:
begin
LDelimiter := #0;
LSeparator := #0;
LWithFieldNames := False;
end;
rfFieldPerLine:
begin
LDelimiter := #0;
LSeparator := #0;
LWithFieldNames := False;
end;
rfCustom:
begin
LDelimiter := #124;
LSeparator := #44;
end;
else
begin
LDelimiter := #34;
LSeparator := #59;
end;
end;
LOriginalReader.DataDef.RecordFormat := ARecordFormat;
if LOriginalReader.DataDef.RecordFormat = ARecordFormat then
begin
LOriginalReader.DataDef.Delimiter := LDelimiter;
LOriginalReader.DataDef.Separator := LSeparator;
end;
LRestoredReader.State := LOriginalReader.State;
Assert.AreEqual( LOriginalReader.State, LRestoredReader.State, 'LOriginalReader.State, LRestoredReader.State (after setting)' );
Assert.AreEqual( LOriginalReader.DataDef.Delimiter, LRestoredReader.DataDef.Delimiter );
Assert.AreEqual( LOriginalReader.DataDef.Separator, LRestoredReader.DataDef.Separator );
Assert.AreEqual( LDelimiter, LRestoredReader.DataDef.Delimiter );
Assert.AreEqual( LSeparator, LRestoredReader.DataDef.Separator );
Assert.AreEqual( LOriginalReader.DataDef.WithFieldNames, LRestoredReader.DataDef.WithFieldNames )
finally
LOriginalReader.Free;
LRestoredReader.Free;
end;
end;
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.