I have a StringList with 2 rows
aOldPriceTerms[0] = 'FuelAddition=336643160'
aOldPriceTerms[1] = 'Freight=336643155'
So it works fine to use
aOldPriceTerms.Values['Freight'] -> '336643155'
I want to have a list of ID's from the list.
So simply
'FuelAddition','Freight'
Currently I use this code where aOldPriceTerms is the actual StringList.
function GetList(aOldPriceTerms: TStringList): String;
var
vOldTerm : string;
vOldTermsList : TStringList;
begin
vOldTermsList := TStringList.Create;
try
for i := aOldPriceTerms.Count - 1 downto 0 do
begin
vOldTerm := Copy(aOldPriceTerms[i], 1, (Pos('=', aOldPriceTerms[i])-1));
vOldTermsList.Add(vOldTerm);
end;
Result := vOldTermsList.CommaText;
finally
FreeAndNil(vOldTermsList);
end;
end;
My question is there a cleaner way to get the ids ?
Example is from the Delphi Basics, but TStringList.Names is also described in the Delphi documentation
// Now display all name and age pair values
for i := 0 to names.Count-1 do
begin
ShowMessage(names.Names[i]+' is '+names.ValueFromIndex[i]);
end;
You can use the TALNVStringList (NV for nameValue) from alcinoe (https://github.com/Zeus64/alcinoe) to handle Name and Value without all the time splitting the string
TALNVStringList is exactly the same as TStringList (nothing to change in the code except replacing TstringList by TALNVStringList) except that it's more efficient in speed because it's store the name in one dedicated field and the value in another dedicated field (no need to do all the time pos('=') and copy() to retrieve the name and the value of the row)
for i := 0 to aOldPriceTerms.Count-1 do
begin
ShowMessage(aOldPriceTerms.Names[i]+' is '+aOldPriceTerms.ValueFromIndex[i]);
end;
Exe demo showing the speed penalty of classic TstringList: https://svn.code.sf.net/p/alcinoe/code/demos/ALSortedListBenchmark/win32/ALSortedListBenchmark.exe
To get all the id's
for name in names.Names do
begin
i := names.IndexOf[name];
end;
or
To get all the Values
for name in vOldTermsList.Names do
begin
Value := vOldTermsList.Value[name];
end;
I am building a SQL query builder for a repository. This builder will find all required fields for a query and create the SQL text. For this, I am using the Format() procedure. But, I am having trouble creating in runtime the TVarRec array that I must pass to the Format procedure.
It's easy to build this array using constants like Format('%s, %s', ['AString', 'AnotherString']);`. But how must we do to create it in runtime ?
Here is a simplified version of my approach :
procedure BuildString;
begin
FStrings := TStringList.Create;
FStrings.Add('String 1');
FStrings.Add('String 2');
FStrings.Add('String 3');
FFormatString := '%0:s, %1:s, %2:s';
SetLength(FFormatStringParams, FStrings.Count);
for I := 0 to FStrings.Count - 1 do
begin
aString := FStrings.Strings[I];
FFormatStringParams[I].VString := Addr(aString);
end;
ShowMessage(Format(FFormatString, FFormatStringParams));
end;
But when I run this, I get the error "The format '%0:s, %1:s, %2:s' is invalid or incompatible with the argument'
I understand that I am incorrectly building the TVarRec array that I must pass to the Format procedure. Can anyone help me in this?
Thank you.
You are passing the address of the string variable rather than its value. Furthermore, you are assigning the VString field, which expects a ShortString. You are supplying a native String instead.
Replace
VString := Addr(aString)
with
VUnicodeString := Pointer(aString)
Furthermore, you need to specify the string type by assigning the VType field.
for I := 0 to FStrings.Count - 1 do
begin
aString := FStrings.Strings[I];
FFormatStringParams[I].VType := vtUnicodeString;
FFormatStringParams[I].VUnicodeString := Pointer(aString);
end;
Want to load name value pairs into a TStringList.
have a text file with data:
a=ant
a=animal
b=bob
d=darn
Function to load it:
LoadData(argPath: string): TStringList;
var
data: TStringList;
begin
data := TStringList.Create(true);
data.Delimiter := '=';
try
data.LoadFromFile(argPath);
except on E : Exception do
begin
Application.MessageBox(PWideChar(E.Message),
'Unable to Load Data', MB_OK or MB_ICONERROR);
Application.Terminate;
end;
end;
Result := data;
end;
Result is currently:
a=ant
a=animal
b=bob
d=darn
want the Result's strings to be:
ant
animal
bob
darn
and the Result's TObjects to be
a
a
b
d
After this I want to be able to display this in a TComboBox.
You'll have to use the built-in functions in the TStringList. I've broken this down into variables just for ease of reading / understanding, but could be compacted:
var
X: Integer;
Name, Value: String;
begin
for X:= 0 to MyList.Count - 1 do begin
Name:= MyList.Names[X];
Value:= MyList.ValueFromIndex[X];
MyCombo.Items.Add(Value);
end;
end;
With that, I'm sure you can figure out the rest (since I'm not sure exactly what you mean by result strings and objects).
It's preferable to pass the string list as a parameter, to ease lifetime management. It's also better to let the function raise exceptions and deal with them later. That allows more flexibility and the possibility of reuse. Like this:
procedure LoadValues(const FileName: string: Values: TStringList);
var
i: Integer;
begin
Values.LoadFromFile(FileName);
for i := 0 to Values.Count - 1 do
if Pos(Values.NameValueSeparator, Values[i]) <> 0 then
Values[i] := Values.ValueFromIndex[i];
end;
You cannot store strings directly in the Objects[] property of a TStrings because strings are managed types. You should probably store the names in a separate string list, or another more capable data structure. It's easy to extend the above code to populate two lists instead of one using the Names[] property.
It sounds like you are trying to stuff all of your data into a visual control. Don't do that. Treat visual controls as things to display data and not to hold and manage it.
Gave up on trying to load the combo box with LoadFromFile. Ended up creating two separate lists.
In FormCreate handler:
LoadData(...)
myComboBox.Items := displayList;
LoadData
procedure MyForm.LoadData(
const path: string; data: TStringList);
var index : Integer;
begin
data.LoadFromFile(path);
for index := 0 to data.Count - 1 do
begin
displayList.Add(data.ValueFromIndex[index]);
valueList.Add(data.Names[index]);
end;
end;
Whenever you need to get the selected value:
valueList[myComboBox.ItemIndex]
and I am tearing my hair out!!
Even something simple like this work:
procedure MyAdoQueryTest();
const MYSQL_CONNECT_STRING='Driver={MySQL ODBC 5.1 Driver};Server=%s;Port=3306;Database=%s;User=%s;Password=%s;Option=3;';
var AdoConnection : TADOConnection;
ADOQuery : TADOQuery;
Param : TParameter;
begin
AdoConnection := TADOConnection.Create(Nil);
AdoConnection.ConnectionString := Format(MYSQL_CONNECT_STRING,['localhost',
'mysql',
'root',
'']);
AdoConnection.LoginPrompt := False;
AdoConnection.Connected := True;
ADOQuery := TADOQuery.Create(Nil);
ADOQuery.Connection := AdoConnection;
ADOQuery.Sql.Clear();
ADOQuery.SQl.Add('SHOW :what_to_show');
Param := ADOQuery.Parameters.ParamByName('what_to_show');
Param.DataType := ftString;
Param.Value := 'databases';
ADOQuery.Prepared := true;
ADOQuery.Active := True;
end;
(btw, do I really need to use the 'Param' variable and 3 statements, or can I just ` ADOQuery.Parameters.ParamByName('what_to_show').Value := 'databases';?)
Anyway, when I run it, I get an exception at ADOQuery.SQl.Add('SHOW :what_to_show'); which says "Arguments are of the wrong type, are out of the acceptable range or are in conflict with one another".
What I am trying to do is to make 2 central functions: one which will accept and execute any SQL statement which will not return any data (such as INSERT INTO) and oen which will (such as SELECT).
I currently have these working with AdoConnection only, but am now trying to use AdoQuery because I want to parametrize my SQL statements to handle strings with quotes in them.
I can has halpz?
The error is here:
ADOQuery.SQl.Add('SHOW :what_to_show');
The :Param can only be used for values, not for dynamic column/keyword/table/database names.
This is because if it worked like that you'd have an SQL-injection risk depending on the contents of your parameter.
In order to fix that you'll have to inject your what_to_show thingy into the SQL-string.
Like so:
var
what_to_show: string;
begin
....
what_to_show:= 'tables';
ADOQuery.SQL.Text:= ('SHOW '+what_to_show);
....
Now it will work.
Warning
Make sure test everything you inject into the SQL to prevent users from being able inject their SQL-code into your queries.
Parameters prevent SQL injection, but because you cannot use them here you need to check them against a list of pre-approved values. e.g. a stringlist holding all the allowed what_to_shows.
Escaping or use of special chars is useless.
Safe injection example code
var
what_to_show: string;
i: integer;
inputapproved: boolean;
begin
....
what_to_show:= lower(trim(someinput));
i:= 0;
inputapproved:= false;
while (i < WhiteList.count) and not(inputapproved) do begin
inputapproved:= ( what_to_show = lower(Whitelist[i]) );
Inc(i);
end; {while}
if inputapproved then ADOQuery.SQL.Text:= ('SHOW '+what_to_show);
....
I have defined a record which has lots of fields with different types (integer, real , string, ... plus dynamic arrays in terms of "array of ...").
I want to save it as a whole to a file and then be able to load it back to my program. I don't want to go through saving each field's value individually.
The file type (binary or ascii or ...) is not important as long Delphi could read it back to a record.
Do you have any suggestions?
You can load and save the memory of a record directly to and from a stream, as long as you don't use dynamic arrays. So if you use strings, you need to make them fixed:
type TTestRecord = record
FMyString : string[20];
end;
var
rTestRecord: TTestRecord;
strm : TMemoryStream;
strm.Write(rTestRecord, Sizeof(TTestRecord) );
You can even load or save an array of record at once!
type TRecordArray = array of TTestRecord;
var ra : TRecordArray;
strm.Write(ra[0], SizeOf(TTestRecord) * Length(ra));
In case you want to write dynamic content:
iCount := Length(aArray);
strm.Write(iCount, Sizeof(iCount) ); //first write our length
strm.Write(aArray[0], SizeOf * iCount); //then write content
After that, you can read it back:
strm.Read(iCount, Sizeof(iCount) ); //first read the length
SetLength(aArray, iCount); //then alloc mem
strm.Read(aArray[0], SizeOf * iCount); //then read content
As promised here it is: https://github.com/KrystianBigaj/kblib
When you defined for example record as:
TTestRecord = record
I: Integer;
D: Double;
U: UnicodeString;
W: WideString;
A: AnsiString;
Options: TKBDynamicOptions;
IA: array[0..2] of Integer;
AI: TIntegerDynArray;
AD: TDoubleDynArray;
AU: array of UnicodeString;
AW: TWideStringDynArray;
AA: array of AnsiString;
R: array of TTestRecord; // record contain dynamic array of itself (D2009+)
end;
You can save whole dynamic record to stream (as binary data) by :
TKBDynamic.WriteTo(lStream, lTestRecord, TypeInfo(TTestRecord));
To load it back:
TKBDynamic.ReadFrom(lStream, lTestRecord, TypeInfo(TTestRecord));
It not need to be a record, you can do same for any dynamic type like:
TKBDynamic.WriteTo(lStream, lStr, TypeInfo(UnicodeString));
TKBDynamic.WriteTo(lStream, lInts, TypeInfo(TIntegerDynArray));
TKBDynamic.WriteTo(lStream, lArrayOfTestRecord, TypeInfo(TArrayOfTestRecord)); // TArrayOfTestRecord = array of TTestRecord;
Tested on Delphi 2006/2009/XE. License: MPL 1.1/GPL 2.0/LGPL 3.0
See readme for information.
Another option which works very well for records (Delphi 2010+) is to use the SuperObject library. For example:
type
TData = record
str: string;
int: Integer;
bool: Boolean;
flt: Double;
end;
var
ctx: TSuperRttiContext;
data: TData;
obj: ISuperObject;
sValue : string;
begin
ctx := TSuperRttiContext.Create;
try
sValue := '{str: "foo", int: 123, bool: true, flt: 1.23}';
data := ctx.AsType<TData>(SO(sValue));
obj := ctx.AsJson<TData>(data);
sValue := Obj.AsJson;
finally
ctx.Free;
end;
end;
I also tested this briefly with a simple TArray<Integer> dynamic array and it did not have a problem storing and loading the array elements.
In addition to the answers that indicate how you do this, please also be aware of these:
You must be aware that writing records out to a file will be Delphi version specific (usually: specific to a series of Delphi versions that share the same memory layout for the underlying data types).
You can only do that if your record does not contain fields of a managed type. Which means that fields cannot be of these managed types: strings, dynamic arrays, variants, and reference types (like pointers, procedural types, method references, interfaces or classes) and file types, or types that contain those manages types. Which basically limits to to these unmanaged types:
A: Simple types (including bytes, integers, floats, enumerations, chars and such)
B: Short strings
C: Sets
D: Static arrays of A, B, C, D and E
E: Records of A, B, C, D and E
In stead of writing out records to/from a file, it might be better to go with class instances and convert them to/from JSON, and them write the JSON string equivalent to a file and read it back in.
You can use this unit to do the JSON conversion for you (should work with Delphi 2010 and up; works for sure with Delphi XE and up) from this location this location.
unit BaseObject;
interface
uses DBXJSON, DBXJSONReflect;
type
TBaseObject = class
public
{ public declarations }
class function ObjectToJSON<T : class>(myObject: T): TJSONValue;
class function JSONToObject<T : class>(json: TJSONValue): T;
end;
implementation
{ TBaseObject }
class function TBaseObject.JSONToObject<T>(json: TJSONValue): T;
var
unm: TJSONUnMarshal;
begin
if json is TJSONNull then
exit(nil);
unm := TJSONUnMarshal.Create;
try
exit(T(unm.Unmarshal(json)))
finally
unm.Free;
end;
end;
class function TBaseObject.ObjectToJSON<T>(myObject: T): TJSONValue;
var
m: TJSONMarshal;
begin
if Assigned(myObject) then
begin
m := TJSONMarshal.Create(TJSONConverter.Create);
try
exit(m.Marshal(myObject));
finally
m.Free;
end;
end
else
exit(TJSONNull.Create);
end;
end.
I hope this helps you getting an overview of things.
--jeroen
You could also define an object instead of a record, so you can use RTTI to save your object to XML or whatever. If you have D2010 or XE, you can use DeHL to serialize it:
Delphi 2010 DeHL Serialization XML and custom attribute : how it work?
But if you "google" you can find other libs with RTTI and serialization (with D2007 etc)
Another solution, working from Delphi 5 up to XE, is available as an OpenSource unit.
In fact, it implements:
some low-level RTTI functions for handling record types: RecordEquals, RecordSave, RecordSaveLength, RecordLoad;
a dedicated TDynArray object, which is a wrapper around any dynamic array, able to expose TList-like methods around any dynamic array, even containing records, strings, or other dynamic arrays. It's able to serialize any dynamic array.
Serialization uses an optimized binary format, and is able to save and load any record or dynamic array as RawByteString.
We use this in our ORM, to store high-level types like dynamic array properties into a database back-end. First step to a DB-Sharding architecture.
If you have dynamic strings or array you can't write the record "as a whole". Instead of using old style-25 characters max strings, I would add methods to the record to be able to "stream" itself to a stream, or better using a TFiler descendant:
TMyRec = record
A: string;
B: Integer;
procedure Read(AReader: TReader);
procedure Writer(AWriter: TWriter);
end;
procedure TMyrec.Read(AReader: TReader);
begin
A := AReader.ReadString;
B := AReader.ReadInteger;
end;
Codes from delphibasics :
type
TCustomer = Record
name : string[20];
age : Integer;
male : Boolean;
end;
var
myFile : File of TCustomer; // A file of customer records
customer : TCustomer; // A customer record variable
begin
// Try to open the Test.cus binary file for writing to
AssignFile(myFile, 'Test.cus');
ReWrite(myFile);
// Write a couple of customer records to the file
customer.name := 'Fred Bloggs';
customer.age := 21;
customer.male := true;
Write(myFile, customer);
customer.name := 'Jane Turner';
customer.age := 45;
customer.male := false;
Write(myFile, customer);
// Close the file
CloseFile(myFile);
// Reopen the file in read only mode
FileMode := fmOpenRead;
Reset(myFile);
// Display the file contents
while not Eof(myFile) do
begin
Read(myFile, customer);
if customer.male
then ShowMessage('Man with name '+customer.name+
' is '+IntToStr(customer.age))
else ShowMessage('Lady with name '+customer.name+
' is '+IntToStr(customer.age));
end;
// Close the file for the last time
CloseFile(myFile);
end;
The problem with saving a record containing dynamic array or real strings (or other "managed" types for that matter) is, it's not an big blob of memory containing everything, it's more like a tree. Someone or something needs to go over everything and save it to storage, somehow. Other languages (Python for example) include all sorts of facilities to transform most objects to text (serialize it), save it to disk and reload it (deserialize it).
Even though a Embarcadero-provided solution doesn't exist for Delphi, one can be implemented using the extended RTTI available in Delphi 2010. A ready-made implementation is available in the DeHL library (here's a blog post about it) - but I can't say much about the implementation, I never used DeHL.
An other option is the one you want to avoid: manually serialize the record to an TStream; It's actually not half difficult. Here's the kind of code I usually use to read/write objects to a file stream:
procedure SaveToFile(FileName:string);
var F:TFileStream;
W:TWriter;
i:Integer;
begin
F := TFileStream.Create(FileName, fmCreate);
try
W := TWriter.Create(F, 128);
try
// For every field that needs saving:
W.WriteString(SomeStr);
W.WriteInteger(TheNumber);
// Dynamic arrays? Save the length first, then save
// every item. The length is needed when reading.
W.WriteInteger(Length(DArray));
for i:=0 to High(DArray) do
W.WriteString(DArray[i]);
finally W.Free;
end;
finally F.Free;
end;
end;
procedure ReadFromFile(FileName:string);
var F:TFileStream;
R:TReader;
i,n:Integer;
begin
F := TFileStream.Create(FileName, fmOpenRead);
try
R := TReader.Create(F, 128);
try
SomeStr := R.ReadString;
TheNumber := R.ReadInteger;
// Reading the dynamic-array. We first get the length:
n := R.ReadInteger;
SetLength(DArray, n);
// And item-by-item
for i:=0 to n-1 do
DArray[i] := R.ReadString;
finally R.Free;
end;
finally F.Free;
end;
end;