I have an AnsiString as follows :
UID,CardNo,Pin,Password,Group,StartTime,EndTime,Name,SuperAuthorize
1,0,1,,0,0,0,Johnny Test,0
2,0,2,,0,0,0,Harry Potter,0
3,0,3,,0,0,0,Scooby Doo,0
4,0,4,,0,0,0,Baby Lonney Toons,0
How do I add the delimiter for new line in a TStringlist named Users so that I get :
Users[1] : 1,0,1,,0,0,0,Johnny Test,0
Users[2] : 2,0,2,,0,0,0,Harry Potter,0
Users[3] : 3,0,3,,0,0,0,Scooby Doo,0
Here's my code :
procedure TFRTest.GetInfo(sbuffer : AnsiString);
var
userIndex : Integer;;
Users : TStringList;
begin
Users := TStringList.Create;
Users.Delimiter := Char(10) ;
try
Users.DelimitedText := sbuffer;
for userIndex := 0 to Users.Count -1 do
begin
ShowMessage(Users[userIndex]);
end;
finally
Users.Free;
end;
You do not need to use DelimitedText for this. You can simply assign directly to Text and that will treat line breaks as delimiters.
{$APPTYPE CONSOLE}
uses
Classes;
const
Str =
'UID,CardNo,Pin,Password,Group,StartTime,EndTime,Name,SuperAuthorize' + sLineBreak +
'1,0,1,,0,0,0,Johnny Test,0' + sLineBreak +
'2,0,2,,0,0,0,Harry Potter,0' + sLineBreak +
'3,0,3,,0,0,0,Scooby Doo,0' + sLineBreak +
'4,0,4,,0,0,0,Baby Lonney Toons,0';
var
i: Integer;
StringList: TStringList;
begin
StringList := TStringList.Create;
try
StringList.Text := Str;
Writeln(StringList.Count);
for i := 0 to StringList.Count-1 do begin
Writeln(StringList[i]);
end;
finally
StringList.Free;
end;
Readln;
end.
Output:
5
UID,CardNo,Pin,Password,Group,StartTime,EndTime,Name,SuperAuthorize
1,0,1,,0,0,0,Johnny Test,0
2,0,2,,0,0,0,Harry Potter,0
3,0,3,,0,0,0,Scooby Doo,0
4,0,4,,0,0,0,Baby Lonney Toons,0
Related
Iam trying to insert data from a StringGrid to the Oracle DB table for that i tried like below.
function TfrmMapping.LoadtoTable: Boolean;
var
I, J: Integer;
lQuery, s: string;
lData: TArray<string>;
begin
for I := 0 to vTableColumns.count - 1 do
begin
if I <> vTableColumns.count - 1 then
begin
s := s + vTableColumns[I] + ',';
end
else
begin
s := s + vTableColumns[I];
end;
end;
for I := 1 to StrGrdLoadCSVData.RowCount - 1 do
begin
vSortedGrid.Add(StrGrdLoadCSVData.Rows[I].CommaText);
end;
for I := 0 to vSortedGrid.count - 1 do
begin
lQuery := 'Insert into ' + cmbBXDBTables.Text + '(' + s + ') values(' +
vSortedGrid[I] + ')';
DataModSample.FDQuery1.SQL.Clear;
DataModSample.FDQuery1.SQL.Add(lQuery);
DataModSample.FDQuery1.ExecSQL;
end;
Result := True;
end;
In the code , Iam adding all the data of StringGrid(StrGrdLoadCSVData) to a StringList(vSortedGrid), and now iam trying to loop through the StringList to add each row to the DB, But Iam not able to insert because my is taking the values like this
Insert into abc(sno,Name)values(1,welcome);
It is because there are no quotes to welcome it is giving an error.
it is error like this : [FireDAC][Phys][Ora]ORA-00984:column not allowed here
How i can modify my code to insert the data successfully to Db.
EDIT
My table Structure is :
Name Type
--------- ------------
SNO NUMBER(38)
NAME VARCHAR2(15)
my desired result in the table should be like this :
SNO NAME
---------- ----------
1 Hello
2 Welcome
The values in the table comming from the string List
It is because there are no quotes to welcome it is giving an error.
So from what you say :
for I := 0 to vSortedGrid.count - 1 do
begin
lQuery := 'Insert into ' + cmbBXDBTables.Text + '(' + s + ') values('+IntToStr(i+1)+',' +
QuotedStr(vSortedGrid[I]) + ')';
DataModSample.FDQuery1.SQL.Clear;
DataModSample.FDQuery1.SQL.Add(lQuery);
DataModSample.FDQuery1.ExecSQL;
end;
Result := True;
end;
Note: Better to use parameters.
Update:
Another option to insert from TStringGrid using TFDTable:
procedure TForm1.Button2Click(Sender: TObject);
Var I : Integer;
begin
for i := 1 to StringGrid1.RowCount-1 do
begin
try
FDTable1.Append;
FDTable1SNO.Value := StrToInt( StringGrid1.Cells[0,i] );
FDTable1SName.Value := StringGrid1.Cells[1,i];
FDTable1.Post;
except on E: Exception do
begin
MessageDlg(E.Message,mtError,[mbOK],0);
MessageBeep(MB_ICONERROR);
end;
end;
end;
Another option to insert from the TStringGrid using TFDQuery (avoid SQL Injection):
procedure TForm1.Button1Click(Sender: TObject);
Var I : Integer; TableName : String;
begin
TableName := 'Table1';
for i := 1 to StringGrid1.RowCount-1 do
begin
try
FDQuery1.SQL.Text := 'Insert Into '+TableName+' Values(:Val1 , :Val2)' ;
FDQuery1.Params.ParamByName('Val1').Value := StrToInt( StringGrid1.Cells[0,i] );
FDQuery1.Params.ParamByName('Val2').Value := StringGrid1.Cells[1,i];
FDQuery1.ExecSQL;
except on E: Exception do
begin
MessageDlg(E.Message,mtError,[mbOK],0);
MessageBeep(MB_ICONERROR);
end;
end;
You can also Create parameters as you need at Runtime for exemple:
FDQuery1.Params.CreateParam(ftString,'ParamName',ptInput) ;
Also you can use GetTableNames() to get all tables in the Database.
I Modified the Code like below
function TfrmMapping.LoadtoTable: Boolean;
var
I, J: Integer;
lQuery, s, lcolvalues: string;
begin
for I := 0 to vTableColumns.count - 1 do
begin
if I <> vTableColumns.count - 1 then
begin
s := s + vTableColumns[I] + ',';
end
else
begin
s := s + vTableColumns[I];
end;
end;
for I := 1 to StrGrdLoadCSVData.RowCount - 1 do
begin
for J := 0 to vTableColumns.count - 1 do
begin
if J <> vTableColumns.count - 1 then
begin
lcolvalues := lcolvalues +
QuotedStr(StrGrdLoadCSVData.Cells[J, I]) + ',';
end
else
begin
lcolvalues := lcolvalues + QuotedStr(StrGrdLoadCSVData.Cells[J, I]);
end;
end;
lQuery := 'Insert into ' + cmbBXDBTables.Text + '(' + s + ') values (' +
lcolvalues + ')';
DataModSample.FDQuery1.SQL.Clear;
DataModSample.FDQuery1.SQL.Add(lQuery);
DataModSample.FDQuery1.ExecSQL;
lcolvalues := '';
end;
Result := True;
end;
This is inserting values to Table from the string grid, I didn't use Parameter passing as of now. I have to try that also for ensuring more security.
Thank You #Sami , Buy using your concept of FDQuery I have got this idea...
I have code like this to copy some text to clipboard.
uses Clipbrd;
var
text: string;
begin
text := 'Some non-latin text, for example Russian: Привет!'
Clipboard.AsText := text;
end;
Win7-8 OS, Russian locale (and format) set in OS Region settings, Delphi 7.
The problem is that it works only when I switch (shift+alt) to Russian keyboard layout when copying. Otherwise it will be pasted as "Ïðèâåò!" instead of "Привет!".
How can I fix that?
I think that I need somehow convert text to Unicode and call Unicode clipboard copy function from WinAPI? But how to do that?
Convert the text to Unicode by whatever means you see fit. In Delphi 7 that typically involves using WideString.
Once you have the text encoded as UTF-16, for instance in a WideString, you need to call SetClipboardData using the CF_UNICODETEXT clipboard format. This is wrapped by Delphi as the SetAsHandle method of the global Clipboard object.
I've not tested it, but this function should set you on the way:
uses
Windows, Clipbrd;
procedure SetClipboardText(const Text: WideString);
var
Count: Integer;
Handle: HGLOBAL;
Ptr: Pointer;
begin
Count := (Length(Text)+1)*SizeOf(WideChar);
Handle := GlobalAlloc(GMEM_MOVEABLE, Count);
Try
Win32Check(Handle<>0);
Ptr := GlobalLock(Handle);
Win32Check(Assigned(Ptr));
Move(PWideChar(Text)^, Ptr^, Count);
GlobalUnlock(Handle);
Clipboard.SetAsHandle(CF_UNICODETEXT, Handle);
Except
GlobalFree(Handle);
raise;
End;
end;
Thank you very much, by thees means you can also convert Encoding in the clipboard if you catch WM_CLIPBOARDUPDATE message - very usefull with old versions of delphi, e.g. for Russian Language.
Here is a full working code:
(N.B. This buffer listening tecnique will only work for Windows Vista and newer OS, that's why I use dynamic WinAPI linking, see this article -
http://delphidabbler.com/articles?article=9)
type
TAddOrRemoveClipboardFormatListener = function (hWndNewViewer : HWND) : BOOL; stdcall;
var _isClipboardChangeRequired : boolean;
var _addClipboardFormatListener, _removeClipboardFormatListener : TAddOrRemoveClipboardFormatListener;
procedure TDM.DataModuleCreate(Sender: TObject);
begin
<...>
_addClipboardFormatListener := GetProcAddress(GetModuleHandle('user32.dll'), 'AddClipboardFormatListener');
_removeClipboardFormatListener := GetProcAddress(GetModuleHandle('user32.dll'), 'RemoveClipboardFormatListener');
if (Assigned(_addClipboardFormatListener) AND NOT _addClipboardFormatListener(Application.Handle)) then
begin
_isClipboardChangeRequired := false;
ShowMessage('Не удалось установить перехватчик изменения буфера обмена! Кодировка при вставке в контролы может быть неверной!' +
sLineBreak + 'Код ошибки (' + IntToStr(GetLastError()) + '): ' + SysErrorMessage(GetLastError()));
// WriteLog([ssWarn], ClassName, 'Не удалось установить перехватчик изменения буфера обмена! Кодировка при вставке в контролы может быть неверной!' +
// sLineBreak + 'Код ошибки (' + IntToStr(GetLastError()) + '): ' + SysErrorMessage(GetLastError()));
end
else
_isClipboardChangeRequired := true;
<...>
end;
procedure TDM.DataModuleDestroy(Sender: TObject);
begin
if Assigned(_removeClipboardFormatListener) then
_removeClipboardFormatListener(Application.Handle);
end;
procedure TDM.ApplicationEvents_ClipboardChangeMessage(var Msg: tagMSG; var Handled: Boolean);
const
// WM_CLIPBOARDUPDATE is not defined in the Messages unit of all supported
// versions of Delphi, so we defined it here for safety.
// (взято отсюда: http://delphidabbler.com/articles?article=9)
WM_CLIPBOARDUPDATE = $031D;
MAX_CLIPBOARD_OPEN_ATTEMPTS = 3;
var
textBuf : array[0..512] of WideChar;
clipHandle : THandle;
dataPtr: Pointer;
dataSize : integer;
str : string;
attemptCount : integer;
isClipboardOpened : boolean;
begin
// если система не поддерживает слежение за буфером обмена, прекращаем дальнейшие попытки это сделать
if (NOT Assigned(_addClipboardFormatListener)) then
begin
ApplicationEvents_ClipboardChange.OnMessage := nil;
Exit;
end;
if (Msg.message = WM_CLIPBOARDUPDATE) AND (Clipboard.HasFormat(CF_UNICODETEXT)) then
if NOT _isClipboardChangeRequired then
begin
_isClipboardChangeRequired := true;
Exit;
end
else
begin
attemptCount := 1;
isClipboardOpened := false;
repeat
try
Clipboard.Open();
isClipboardOpened := true;
except
if (attemptCount >= MAX_CLIPBOARD_OPEN_ATTEMPTS) then
begin
OutputDebugString(PChar('Внимание: Нет доступа к буферу обмена - невозможно изменить его кодировку!'));
Exit;
end
else
inc(attemptCount);
end;
until isClipboardOpened;
clipHandle := Clipboard.GetAsHandle(CF_UNICODETEXT);
dataPtr := GlobalLock(clipHandle);
if (dataPtr <> nil) then
try
dataSize := GlobalSize(clipHandle);
ZeroMemory(#textBuf, sizeof(textBuf));
CopyMemory(#textBuf, dataPtr, dataSize);
SetString(str, textBuf, dataSize);
str := Trim(str);
Clipboard.AsText := str;
_isClipboardChangeRequired := false;
OutputDebugString(PChar('Clipboard encoding converted!'));
finally
GlobalUnlock(clipHandle);
end;
Clipboard.Close();
end;
end;
I have a DB grid which is sorted (the user clicked a few radio buttons and checkboxes to influence the display).
I would like to export all of the data (not just what is visible in the grid), sorted identically, to CSV - how do I do so? The data - not the user settings, just to clarify.
Thanks in advance for any help
[Update] I build sqlQuery bit by bit, depending on the user's settings of checkboxes & radio groups, then, when one of them changes, I
ActivityADQuery.SQL.Clear();
ActivityADQuery.SQL.Add(sqlQuery);
ActivityADQuery.Open(sqlQuery);
That is to say that there isn't a hard coded query, it varies and I want to export the current settings.
I don't know enough if I want to export from the grid or the dataset (I am just not a db guy, this is my first DBgrid), but I suspect that I want the grid, because it has a subset of fields of he dataset.
I guess that TJvDBGridCSVExport is a Jedi component(?) I have tried to avoid them so far, great as they sound, because I prefer discreet, stand-alone, components to installing a huge collection. That may not be the cleverest thing to do, but it's how I feel - ymmv (and prolly does)
Another solution, works also with (multi)selected rows:
procedure TReportsForm.ExportToCSV(const aGrid : TDBGrid; const FileName : String);
Var
I, J : Integer;
SavePlace : TBookmark;
Table : TStrings;
HeadTable : String;
LineTable : String;
First : Boolean;
Begin
HeadTable := '';
LineTable := '';
Table := TStringList.Create;
First := True;
Try
For I := 0 To Pred(aGrid.Columns.Count) Do
If aGrid.Columns[I].Visible Then
If First Then
Begin
// Use the text from the grid, in case it has been set programatically
// E.g., we prefer to show "Date/time" than "from_unixtime(activity.time_stamp, "%D %b %Y %l:%i:%S")"
// HeadTable := HeadTable + aGrid.Columns[I].FieldName;
HeadTable := HeadTable + ActivityReportStringGrid.Columns[i].Title.Caption + ','; // Previous separated wth semi-colon, not comma! (global)
First := False;
End
Else
begin
// HeadTable := HeadTable + ';' + aGrid.Columns[I].FieldName;
HeadTable := HeadTable + ActivityReportStringGrid.Columns[i].Title.Caption + ',';
end;
Delete(HeadTable, Length(HeadTable), 1); // Remove the superfluous trailing comma
Table.Add(HeadTable);
First := True;
// with selection of rows
If aGrid.SelectedRows.Count > 0 Then
Begin
For i := 0 To aGrid.SelectedRows.Count - 1 Do
Begin
aGrid.DataSource.Dataset.GotoBookmark(pointer(aGrid.SelectedRows.Items[i]));
For j := 0 To aGrid.Columns.Count - 1 Do
If aGrid.Columns[J].Visible Then
If First Then
Begin
lineTable := lineTable + aGrid.Fields[J].AsString;
First := False;
End
Else
lineTable := lineTable + ',' + aGrid.Fields[J].AsString;
Delete(LineTable, Length(LineTable), 1); // Remove the superfluous trailing comma
Table.Add(LineTable);
LineTable := '';
First := True;
End;
End
Else
//no selection
Begin
SavePlace := aGrid.DataSource.Dataset.GetBookmark;
aGrid.DataSource.Dataset.First;
Try
While Not aGrid.DataSource.Dataset.Eof Do
Begin
For I := 0 To aGrid.Columns.Count - 1 Do
If aGrid.Columns[I].Visible Then
If First Then
Begin
lineTable := lineTable + aGrid.Fields[I].AsString;
First := False;
End
Else
lineTable := lineTable + ',' + aGrid.Fields[I].AsString;
Delete(LineTable, Length(LineTable), 1); // Remove the superfluous trailing comma
Table.Add(LineTable);
LineTable := '';
aGrid.DataSource.Dataset.Next;
First := True;
End;
aGrid.DataSource.Dataset.GotoBookmark(SavePlace);
Finally
aGrid.DataSource.Dataset.FreeBookmark(SavePlace);
End;
End;
Table.SaveToFile(FileName);
Finally
Table.Free;
End;
End; // ExportToCSV()
You could use a own tiny procedure wich could be adapted to your needs
Procedure Dataset2SeparatedFile(ads: TDataset; const fn: String; const Separator: String = ';');
var
sl: TStringList;
s: String;
i: Integer;
bm: TBookmark;
Procedure ClipIt;
begin
s := Copy(s, 1, Length(s) - Length(Separator));
sl.Add(s);
s := '';
end;
Function FixIt(const s: String): String;
begin
// maybe changed
Result := StringReplace(StringReplace(StringReplace(s, Separator, '', [rfReplaceAll]), #13, '', [rfReplaceAll]), #10, '', [rfReplaceAll]);
// additional changes could be Quoting Strings
end;
begin
sl := TStringList.Create;
try
s := '';
For i := 0 to ads.FieldCount - 1 do
begin
if ads.Fields[i].Visible then
s := s + FixIt(ads.Fields[i].DisplayLabel) + Separator;
end;
ClipIt;
bm := ads.GetBookmark;
ads.DisableControls;
try
ads.First;
while not ads.Eof do
begin
For i := 0 to ads.FieldCount - 1 do
begin
if ads.Fields[i].Visible then
s := s + FixIt(ads.Fields[i].DisplayText) + Separator;
end;
ClipIt;
ads.Next;
end;
ads.GotoBookmark(bm);
finally
ads.EnableControls;
ads.FreeBookmark(bm);
end;
sl.SaveToFile(fn);
finally
sl.Free;
end;
end;
this is my simple code to generate
all possible combinations of a set for
example
1,2,3:
Display:
123
132
213
231
312
321
i want to create variable number of for loops to let the user determine the length of given string...
does anyone have an idea...
thank's in advance.
type
TNumber = '0'..'9';
procedure TForm1.Button1Click(Sender: TObject);
var
Numbers: array[0..3] of TNumber;
a, b, c, d: Integer;
s: string;
begin
Numbers[0] := '1';
Numbers[1] := '8';
Numbers[2] := '7';
Numbers[3] := '2';
for a := low(Numbers) to High(Numbers) do
for b := low(Numbers) to High(Numbers) do
for c := low(Numbers) to High(Numbers) do
for d := low(Numbers) to High(Numbers) do
begin
s := Numbers[a] + Numbers[b] + Numbers[c] + Numbers[d];
if
(Occurrences('1', s) > 1 ) or
(Occurrences('8', s) > 1 ) or
(Occurrences('7', s) > 1 ) or
(Occurrences('2', s) > 1 )
then
Continue
else
Memo1.Lines.Add(s);
end;
end;
function TForm1.Occurrences(const Substring, Text: string): Integer;
var
Offset: Integer;
begin
Result := 0;
Offset := PosEx(Substring, Text, 1);
while Offset <> 0 do
begin
Inc(Result);
Offset := PosEx(Substring, Text, offset + length(Substring));
end;
end;
end.
Here is some code that produces the output you desire. You'd need to work it around a bit for your needs, but the concept expressed in this recursive solution is the important thing:
program Permuatations;
{$APPTYPE CONSOLE}
type
TElements = '1'..'3';
procedure EnumerateCombinations(const Stem: string; Len: Integer);
var
i: Integer;
el: TElements;
Used: set of TElements;
begin
if Len=0 then
exit;
Used := [];
for i := 1 to Length(Stem) do
Include(Used, Stem[i]);
for el := low(el) to high(el) do
begin
if el in Used then
continue;
if Len=1 then
Writeln(Stem+el)
else
EnumerateCombinations(Stem+el, Len-1)
end;
end;
procedure Main;
begin
EnumerateCombinations('', 1+ord(high(TElements))-ord(low(TElements)));
end;
begin
Main;
Readln;
end.
Output:
123
132
213
231
312
321
If you change the definition of TElements, for example to '1'..'4' then you will see the 24 possible permutations.
i fill a tdictionary , read from a file, to iterate over the key-value-pairs. iterating was solved in delphi dictionary iterating.
the problem is that the values in the dict are not kept, probably a scope-problem with variables. i am more used to java... the values do exist directly after assigning them to the dictionary in the procedure parsetextfile, then get lost:
program parsefile;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes, StrUtils, Dialogs, Generics.collections;
var key : string;
dict: TDictionary<String, TStringlist>;
KeysList, Valuename: TStringList;
KeyName: string;
i: integer;
function DeleteSpaces(str: string): string;
var
i: Integer;
begin
i:=0;
while i<=Length(str) do
if str[i]=' ' then Delete(str, i, 1)
else Inc(i);
Result:=str;
end;
procedure HandleOneKey(KeyIndex:Integer; PrevKeys:string);
var L:TStringList;
i:Integer;
Part: string;
KeyName: string;
begin
KeyName := KeysList[KeyIndex];
L := dict[KeyName];
for i:=0 to L.Count-1 do
begin
writeln(L[i]);
Part := KeyName + '=' + L[i];
if KeyIndex = (KeysList.Count-1) then
WriteLn(PrevKeys + ' ' + Part)
else
HandleOneKey(KeyIndex+1, PrevKeys + ' ' + Part);
end;
end;
procedure Split(const Delimiter: Char;Input: string;const Strings: TStrings);
begin
Strings.Clear;
Strings.Delimiter := Delimiter;
Strings.DelimitedText := Input;
end;
procedure parsetestfile;
var testfile: Textfile;
text: string;
splitarray: TStringList;
subsplit1, subsplit2: TStringList;
begin
splitarray := TStringList.Create;
subsplit1:= TStringList.Create;
subsplit2:= TStringList.Create;
AssignFile(testfile, 'g:\testfile.txt') ;
Reset(testfile);
while not Eof(testfile) do
begin
ReadLn(testfile, text);
if AnsiContainsStr(text, '=') then
begin
Split('=', text, splitarray);
splitarray[0] := trim(splitarray[0]);
splitarray[1] := DeleteSpaces(splitarray[1]);
if AnsiStartsStr('data', splitarray[0]) then
begin
split(' ', splitarray[0], subsplit1);
splitarray[0]:=subsplit1[1];
split(',', splitarray[1], subsplit2);
dict.Add(splitarray[0], subsplit2);
for ValueName in dict.Values do
begin
for i := 0 to Valuename.Count - 1 do
write('Values are : '+ Valuename[i]);
writeln;
end;//for
end;//end-data-check
end;//end-=-check
end;//while
CloseFile(testfile);
splitarray.Free;
subsplit1.Free;
subsplit2.Free;
end;
begin
dict := TDictionary<String, TStringlist>.Create;
parsetestfile;
KeysList := TStringList.Create;
for KeyName in dict.Keys do
KeysList.Add(KeyName);
for i := 0 to Keyslist.Count - 1 do
begin
writeln('Keylist Items: ' + Keyslist[i]);
end;
if KeysList.Count > 0 then
begin
HandleOneKey(0, '');
end;
dict.Destroy;
Keyslist.Free;
WriteLn('Press ENTER to make the window go away');
ReadLn;
end.
Top Edit
I now saw you're more used to Java, that kind of explains your problem. Java uses an Garbage Collector: if you've got a reference to something, that one thing is valid. Delphi doesn't use a GC, you're responsible for freeing all the memory you allocate. This leads to the second problem: you can free memory you're holding a reference to, there's nothing stopping you from doing that. In your parsetestfile procedure you're adding subsplit2 to the dictionary, so you're keeping a copy of that reference. Later in the same procedure you're freeing subsplit2, so your dictionary now holds a reference to what Delphi considers to be "free memory"!
With Delphi you need to be very careful and deliberate with life cycle management. In this case you obviously can't free the subsplit2 in the parsetestfile procedure itself, but you do need to free it later. You'll need to free it when you free the Dict, look at my initial code for how to do that.
*Recom
Here's your code with lots of things fixed. Please read the comments, I inserted comments wherever I changed something.
It compiles and values survive the parse procedure, but I'm not sure what you want to achieve and you forgot to provide a sample text file: I had to "make one up".
program Project23;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes, StrUtils, Dialogs, Generics.collections;
var deviceid, key, topmodule : string;
dict: TDictionary<String, TStringlist>;
KeysList: TStringList;
KeyName: string;
i: integer;
function DeleteSpaces(str: string): string;
var
i: Integer;
begin
i:=0;
while i<=Length(str) do
if str[i]=' ' then Delete(str, i, 1)
else Inc(i);
Result:=str;
end;
procedure HandleOneKey(KeyIndex:Integer; PrevKeys:string);
var L:TStringList;
i:Integer;
Part: string;
KeyName: string;
begin
KeyName := KeysList[KeyIndex];
L := dict[KeyName];
for i:=0 to L.Count-1 do
begin
writeln(L[i]);
Part := KeyName + '=' + L[i];
if KeyIndex = (KeysList.Count-1) then
WriteLn(PrevKeys + ' ' + Part)
else
HandleOneKey(KeyIndex+1, PrevKeys + ' ' + Part);
end;
end;
procedure Split(const Delimiter: Char;Input: string;const Strings: TStrings);
begin
Strings.Clear;
Strings.Delimiter := Delimiter;
Strings.DelimitedText := Input;
end;
procedure parsetestfile;
var testfile: Textfile;
text: string;
splitarray: TStringList;
subsplit1, subsplit2: TStringList;
ValueName:TStringList; // Never Ever ignore compiler warnings!
i: Integer; // Never Ever ignore compiler warnings!
begin
splitarray := TStringList.Create;
subsplit1:= TStringList.Create;
AssignFile(testfile, 'c:\temp\testfile.txt') ;
Reset(testfile);
while not Eof(testfile) do
begin
ReadLn(testfile, text);
if AnsiContainsStr(text, '=') then
begin
Split('=', text, splitarray);
splitarray[0] := trim(splitarray[0]);
splitarray[1] := DeleteSpaces(splitarray[1]);
if AnsiStartsStr('data', splitarray[0]) then
begin
subsplit2:= TStringList.Create; // Moved the creation of subsplit2 over here, because you need one fresh list for every line of text you read.
split(' ', splitarray[0], subsplit1); // can't split on SPACE because the previous split allready broke the text at "=" and at SPACE. That's how DelimitedText works!
// splitarray[0]:=subsplit1[1]; // splitarray[0] already contains the stuff before "="; And you should check the nubmer of lines in subsplit1!
split(',', splitarray[1], subsplit2);
dict.Add(splitarray[0], subsplit2);
for ValueName in dict.Values do
begin
for i := 0 to Valuename.Count - 1 do
writeLN('Values are : '+ Valuename[i]); // Only use Write when you intend to write the line terminator later
writeln;
end;//for
end;//end-data-check
end;//end-=-check
end;//while
CloseFile(testfile);
splitarray.Free;
subsplit1.Free;
// subsplit2.Free; // Ooops! You're freeing Subsplit2, after you added it as a value in the dict.
end;
begin
dict := TDictionary<String, TStringlist>.Create;
parsetestfile;
KeysList := TStringList.Create;
for KeyName in dict.Keys do
KeysList.Add(KeyName);
for i := 0 to Keyslist.Count - 1 do
begin
writeln('Keylist Items: ' + Keyslist[i]);
end;
if KeysList.Count > 0 then
begin
HandleOneKey(0, '');
end;
dict.Free; // dict.Destroy; // never call "Destroy" directly, call .Free.
Keyslist.Free;
WriteLn('Press ENTER to make the window go away');
ReadLn;
end.