Delete multiple string left in [Delphi] - delphi

Hello I need a simple function to delete left text strings, see the example below:
procedure TForm1.Button1Click(Sender: TObject);
var
S: string;
begin
S := 'Hello test test test [delimitator] goodby.. test teest test';
Delete(S, Pos('[delimitator]', S), MaxInt);
RichEdit1.Text := S;
end;
This function clears all the characters on the right, even if I don't know how many characters there are.
The question is, how to do the reverse, to delete all the characters on the left?

You already know what to use - Pos() and Delete(). Just tweak how you use them:
procedure TForm1.Button1Click(Sender: TObject);
var
S: string;
begin
S := 'Hello test test test [delimitator] goodby.. test teest test';
Delete(S, 1, Pos('[delimitator]', S));
RichEdit1.Text := S;
end;

Thanks to everyone's help. This is not very professional, but I came to that conclusion, which can be used separately or together.
procedure TForm1.Button2Click(Sender: TObject);
var
left_delimitator,
right_delimitator: integer;
get_left, get_right,
left_dbg, right_dbg: string;
begin
RichEdit1.Clear;
get_left := 'test 123 [delimitator] all string left deleted.';
get_right := 'all string rigth deleted. [delimitator] test 123';
left_delimitator := Pos('[delimitator]', get_left);
if left_delimitator > 0 then
begin
Delete(get_left, 1, Pos('[delimitator]', get_left));
left_dbg := '[' + get_left;
RichEdit1.Lines.Add( left_dbg );
end;
right_delimitator := Pos('[delimitator]', get_right);
if right_delimitator > 0 then
begin
Delete(get_right, Pos('[delimitator]', get_right), MaxInt);
right_dbg := '[delimitator]' + ' ' + get_right;
RichEdit1.Lines.Add( right_dbg );
end;
end;

You might use the following function to get this done.
Function GetRightPart(InputString, Delimiter : String) : String;
VAR
HelpArr : TArray<String>;
begin
HelpArr := InputString.Split([Delimiter]) ;
Result := HulpArr[1];
end;

Related

Getting cyrillic string in richedit with Delphi

I have a formatted text on a wordpad file(rtf). I'm trying to open it on a richedit on a delphi form. The problem is that the string is in cyrillic(Bulgarian) and it's saved with weird hieroglyphs or whatever those are "Âëåçå ïîòðåáèòåë". Is there a way to transfer/translate the hieroglyphs to the richedit, so they can appear as proper text?
This function I use to check if the file is empty so I can then enter the first rtf tag, or remove the closing tag, so I can add more text in there without breaking the file
function FileIsEmpty(const FileName: String): Boolean;
var
fad: TWin32FileAttributeData;
begin
Result := GetFileAttributesEx(PChar(FileName), GetFileExInfoStandard, #fad) and
(fad.nFileSizeLow = 0) and (fad.nFileSizeHigh = 0);
end;
This is the code I use to format the text and also give it to the file:
procedure FormatLogAndAddToFile(richEditLog : TRichEdit; richEditTextColor : TRichEdit);
var
i : integer;
s, c, finalText : string;
sString : TStringList;
begin
with frmMain do
begin
sString := TStringList.Create;
sString.LoadFromFile('C:\Users\lyuben\Desktop\Lyuben Airport Delphi\Log File\TestFormating.rtf');
if Pos('{\rtf}', sString.Strings[0]) <> 0 then
begin
sString.Delete(0);
end
else
begin
sString.Delete(sString.Count - 1);
end;
sString.SaveToFile('C:\Users\lyuben\Desktop\Lyuben Airport Delphi\Log File\TestFormating.rtf');
sString.free;
AssignFile(logFile, 'C:\Users\lyuben\Desktop\Lyuben Airport Delphi\Log File\TestFormating.rtf');
Append(logFile);
if FileIsEmpty('C:\Users\lyuben\Desktop\Lyuben Airport Delphi\Log File\TestFormating.rtf') = True then
begin
WriteLn(logFile, '{\rtf\ansi\ansicpg1252\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Calibri;}}');
end;
for i := 0 to richEditLog.Lines.Count do
begin
s := richEditLog.Lines[i];
c := richEditTextColor.Lines[i];
if c = 'blue' then
begin
finalText := '{\colortbl ;\red0\green128\blue255;\red255\green0\blue0;}' +
'\viewkind4\uc1 \pard\sa200\sl276\slmult1\cf1\f0\fs32\lang9 ' + s + '\cf2\par';
end
else if c = 'red' then
begin
finalText := '{\colortbl ;\red255\green0\blue0;}' +
'\viewkind4\uc1 \pard\sa200\sl276\slmult1\cf1\f0\fs32\lang9 ' + s + '\par';
end
else if c = 'green' then
begin
finalText := '{\colortbl ;\red0\green128\blue128;\red255\green0\blue0;}' +
'\viewkind4\uc1 \pard\sa200\sl276\slmult1\cf1\f0\fs32\lang9 ' + s + '\cf2\par';
end;
WriteLn(logFile, finalText);
end;
WriteLn(logFile, '}');
CloseFile(logFile);
end;
end;
This is the code I use to add the log lines to the file. I also have little bit of code that checks if the file has lines with a date that is entered on a TDateEdit, so I can only get log from the date I've entered.
procedure OpenLogInRichEdit(dateFilter : Boolean; searchDate : tDate);
var
sTime : string;
dateExists : Boolean;
I : integer;
begin
with frmMain do
begin
dateExists := false;
frmLogSearch.tLogRichEdit.Clear;
frmLogSearch.tLogRichEdit.Lines.LoadFromFile('C:\Users\lyuben\Desktop\Lyuben Airport Delphi\Log File\TestFormating.rtf');
sTime := DateTimeToStr(searchDate);
if dateFilter then
begin
for I := 0 to frmLogSearch.tLogRichEdit.Lines.Count do
begin
if Pos(sTime, frmLogSearch.tLogRichEdit.Lines[i]) <> 0 then
begin
frmLogSearch.tLogRichEdit.Lines.Delete(i);
dateExists := True;
end;
end;
if dateExists = false then
begin
ShowMessage('No log from this day!');
end;
end;
end;
end;
This is how I add the text to the richedits I use later for the procedure FormatLogAndAddToFile.
dateTimeNow := Now;
logText.Lines.Add('<' + DateTimeToStr(dateTimeNow) + '> Изтрита е поръчка');
logTextColor.Lines.Add('red');
And this is how I eventually call the procedures. First the procedure to get the formatted log to the richedits
OpenLogInRichEdit(tcxCheckBoxDate.Checked, tcxDate.Date);
And this is the procedure to format the text and give it to the file
LogFileUse.FormatLogAndAddToFile(logText, logTextColor);
Thanks to the comments I've managed to make it work. I've changed the code above. Instead of having 'fcharset0' as a tag, I now have 'fcharset1' and I also changed 'lang9' to 'lang1026' and now I save it properly to the file and it opens perfectly!
If all this scary code is here only to add colored lines to the file, than you should use TRichEdit.SelAttributes with friends: Colorful text in the same line in TRichEdit This way TRichEdit will be able to correctly handle encoding. And if you need some fancy file header or footer, that you do not want to create from code, than you can create empty rtf-file with required header/footer, and use it as a template.

Replacing string assignment by PChar operation

I have a puzzling result that I'm struggling to understand.
I've been attempting to improve the speed of this routine
function TStringRecord.GetWord: String;
begin
// return the next word in Input
Result := '';
while (PC^ <> #$00) and not PC^.IsLetter do begin
inc(FPC);
end;
while (PC^ <> #$00) and PC^.IsLetter do begin
Result := Result + PC^;
inc(FPC);
end;
end;
by replacing the Result := Result + PC^ by a pointer-based operation. This
is my attempt:
function TStringRecord.GetWord2: String;
var
Len : Integer;
StartPC,
DestPC : PChar;
begin
// return the next word in Input
Result := '';
while (PC^ <> #$00) and not PC^.IsLetter do begin
inc(FPC);
end;
Len := Length(Input);
SetLength(Result, Len);
StartPC := PChar(Result);
DestPC := PChar(Result);
while (PC^ <> #$00) and PC^.IsLetter do begin
WStrPLCopy(DestPC, PC, 1);
inc(FPC);
inc(DestPC);
end;
SetLength(Result, DestPC - StartPC);
end;
According to my line profiler, WStrPLCopy(DestPC, PC, 1) takes 50 times longer
than Result := Result + PC^. As far as I can tell, this is because on entry
to WStrPLCopy there is a call to _WStrFromPWChar which seems to copy many more
characters than the one necessary. How can I avoid this, or can someone suggest
an alternative PChar-based method?
The remainder of my code is below:
TStringRecord = record
private
FPC: PChar;
FInput: String;
procedure SetInput(const Value: String);
public
function NextWord : String;
function NextWord2 : String;
property Input : String read FInput write SetInput;
property PC : PChar read FPC;
end;
procedure TStringRecord.SetInput(const Value: String);
begin
FInput := Value;
FPC := PChar(Input);
end;
This is how I would write it:
function TStringRecord.GetWord: String;
var beg: PChar;
begin
// return the next word in Input
while (FPC^ <> #0) and not FPC^.IsLetter do
inc(FPC);
beg := FPC;
while (FPC^ <> #0) and FPC^.IsLetter do
inc(FPC);
SetString(result, beg, FPC-beg);
end;
With this, code is very readable, and you have a single memory allocation, and I guess you could not write anything faster (but by inlining PC^.IsLetter, which is the only call to an external piece of code).

Faster way to split text in Delphi TStringList

I have an app that needs to do heavy text manipulation in a TStringList. Basically i need to split text by a delimiter ; for instance, if i have a singe line with 1000 chars and this delimiter occurs 3 times in this line, then i need to split it in 3 lines. The delimiter can contain more than one char, it can be a tag like '[test]' for example.
I've wrote two functions to do this task with 2 different approaches, but both are slow in big amounts of text (more then 2mbytes usually).
How can i achieve this goal in a faster way ?
Here are both functions, both receive 2 paramaters : 'lines' which is the original tstringlist and 'q' which is the delimiter.
function splitlines(lines : tstringlist; q: string) : integer;
var
s, aux, ant : string;
i,j : integer;
flag : boolean;
m2 : tstringlist;
begin
try
m2 := tstringlist.create;
m2.BeginUpdate;
result := 0;
for i := 0 to lines.count-1 do
begin
s := lines[i];
for j := 1 to length(s) do
begin
flag := lowercase(copy(s,j,length(q))) = lowercase(q);
if flag then
begin
inc(result);
m2.add(aux);
aux := s[j];
end
else
aux := aux + s[j];
end;
m2.add(aux);
aux := '';
end;
m2.EndUpdate;
lines.text := m2.text;
finally
m2.free;
end;
end;
function splitLines2(lines : tstringlist; q: string) : integer;
var
aux, p : string;
i : integer;
flag : boolean;
begin
//maux1 and maux2 are already instanced in the parent class
try
maux2.text := lines.text;
p := '';
i := 0;
flag := false;
maux1.BeginUpdate;
maux2.BeginUpdate;
while (pos(lowercase(q),lowercase(maux2.text)) > 0) and (i < 5000) do
begin
flag := true;
aux := p+copy(maux2.text,1,pos(lowercase(q),lowercase(maux2.text))-1);
maux1.add(aux);
maux2.text := copy(maux2.text,pos(lowercase(q),lowercase(maux2.text)),length(maux2.text));
p := copy(maux2.text,1,1);
maux2.text := copy(maux2.text,2,length(maux2.text));
inc(i);
end;
finally
result := i;
maux1.EndUpdate;
maux2.EndUpdate;
if flag then
begin
maux1.add(p+maux2.text);
lines.text := maux1.text;
end;
end;
end;
I've not tested the speed, but for academic purposes, here's an easy way to split the strings:
myStringList.Text :=
StringReplace(myStringList.Text, myDelimiter, #13#10, [rfReplaceAll]);
// Use [rfReplaceAll, rfIgnoreCase] if you want to ignore case
When you set the Text property of TStringList, it parses on new lines and splits there, so converting to a string, replacing the delimiter with new lines, then assigning it back to the Text property works.
The problems with your code (at least second approach) are
You are constantly using lowecase which is slow if called so many times
If I saw correctly you are copying the whole remaining text back to the original source. This is sure to be extra slow for large strings (eg files)
I have a tokenizer in my library. Its not the fastest or best but it should do (you can get it from Cromis Library, just use the units Cromis.StringUtils and Cromis.Unicode):
type
TTokens = array of ustring;
TTextTokenizer = class
private
FTokens: TTokens;
FDelimiters: array of ustring;
public
constructor Create;
procedure Tokenize(const Text: ustring);
procedure AddDelimiters(const Delimiters: array of ustring);
property Tokens: TTokens read FTokens;
end;
{ TTextTokenizer }
procedure TTextTokenizer.AddDelimiters(const Delimiters: array of ustring);
var
I: Integer;
begin
if Length(Delimiters) > 0 then
begin
SetLength(FDelimiters, Length(Delimiters));
for I := 0 to Length(Delimiters) - 1 do
FDelimiters[I] := Delimiters[I];
end;
end;
constructor TTextTokenizer.Create;
begin
SetLength(FTokens, 0);
SetLength(FDelimiters, 0);
end;
procedure TTextTokenizer.Tokenize(const Text: ustring);
var
I, K: Integer;
Counter: Integer;
NewToken: ustring;
Position: Integer;
CurrToken: ustring;
begin
SetLength(FTokens, 100);
CurrToken := '';
Counter := 0;
for I := 1 to Length(Text) do
begin
CurrToken := CurrToken + Text[I];
for K := 0 to Length(FDelimiters) - 1 do
begin
Position := Pos(FDelimiters[K], CurrToken);
if Position > 0 then
begin
NewToken := Copy(CurrToken, 1, Position - 1);
if NewToken <> '' then
begin
if Counter > Length(FTokens) then
SetLength(FTokens, Length(FTokens) * 2);
FTokens[Counter] := Trim(NewToken);
Inc(Counter)
end;
CurrToken := '';
end;
end;
end;
if CurrToken <> '' then
begin
if Counter > Length(FTokens) then
SetLength(FTokens, Length(FTokens) * 2);
FTokens[Counter] := Trim(CurrToken);
Inc(Counter)
end;
SetLength(FTokens, Counter);
end;
How about just using StrTokens from the JCL library
procedure StrTokens(const S: string; const List: TStrings);
It's open source
http://sourceforge.net/projects/jcl/
As an additional option, you can use regular expressions. Recent versions of Delphi (XE4 and XE5) come with built in regular expression support; older versions can find a free regex library download (zip file) at Regular-Expressions.info.
For the built-in regex support (uses the generic TArray<string>):
var
RegexObj: TRegEx;
SplitArray: TArray<string>;
begin
SplitArray := nil;
try
RegexObj := TRegEx.Create('\[test\]'); // Your sample expression. Replace with q
SplitArray := RegexObj.Split(Lines, 0);
except
on E: ERegularExpressionError do begin
// Syntax error in the regular expression
end;
end;
// Use SplitArray
end;
For using TPerlRegEx in earlier Delphi versions:
var
Regex: TPerlRegEx;
m2: TStringList;
begin
m2 := TStringList.Create;
try
Regex := TPerlRegEx.Create;
try
Regex.RegEx := '\[test\]'; // Using your sample expression - replace with q
Regex.Options := [];
Regex.State := [preNotEmpty];
Regex.Subject := Lines.Text;
Regex.SplitCapture(m2, 0);
finally
Regex.Free;
end;
// Work with m2
finally
m2.Free;
end;
end;
(For those unaware, the \ in the sample expression used are because the [] characters are meaningful in regular expressions and need to be escaped to be used in the regular expression text. Typically, they're not required in the text.)

Exporting DBgrid to CSV?

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;

delphi scope of variables question

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.

Resources