SRL Archive - Jr. Member General Help and Discussion

Reading last line from a .txt file


Foundry

2013-11-29 09:00:12

I am making a script for a game that writes to an event log around every 15 seconds. How would I get a string of the last line of the .txt file? I looked at the documentation for Simba regarding file i/o but I am a little confused. Could someone guide me in the right direction?

Janilabo

2013-11-29 09:33:09

From: Foundry
I am making a script for a game that writes to an event log around every 15 seconds. How would I get a string of the last line of the .txt file? I looked at the documentation for Simba regarding file i/o but I am a little confused. Could someone guide me in the right direction?
Hello Foundry,

Here is some string handling commands, that I wrote for pumbaa plugin, which you could possibly use for file reading tasks:

{==============================================================================]
Explodes str with multiple separators/delimiters (d).
The importance order for d items is from left to right (=>).
So place the important ones first and then less important after those.
[==============================================================================}
function StrExplodeMulti(d: TStringArray; str: string): TStringArray;
var
a, b, i, l, p, r, s, z, g: Integer;
m: Boolean;
begin;
SetLength(Result, 1);
l := Length(str);
s := Length(d);
if ((l > 0) and (s > 0)) then
begin
r := 0;
z := 0;
for a := 0 to (s - 1) do
begin
p := Length(d[a]);
if (p > 0) then
if (p <= l) then
begin
if (z = 0) then
z := Length(d[a])
else
if (Length(d[a]) < z) then
z := Length(d[a]);
d[r] := d[a];
Inc(r);
end;
end;
if (r > 0) then
begin
s := r;
a := 1;
p := 1;
r := 0;
while ((a + (z - 1)) <= l) do
begin
m := False;
for i := 0 to (s - 1) do
begin
g := Length(d[i]);
if ((a + (g - 1)) <= l) then
begin
for b := 1 to g do
begin
m := (str[((a + b) - 1)] = d[i][b]);
if not m then
Break;
end;
if m then
Break;
end;
end;
if m then
begin
Result[r] := Copy(str, p, (a - p));
if ((a + (g + 1)) = l) then
Exit;
p := (a + g);
a := ((a + g) - 1);
Inc(r);
SetLength(Result, (r + 1));
end;
Inc(a);
end;
Result[r] := Copy(str, p, ((l - p) + 1));
end else
Result[0] := '';
end else
Result[0] := '';
end;

{==============================================================================]
Explodes str with multiple separators/delimiters (d).
The importance order for d items is from left to right (=>).
So place the important ones first and then less important after those.
NOTE: Supports limit.
[==============================================================================}
function StrExplodeMultiEx(d: TStringArray; str: string; limit: Integer): TStringArray;
var
a, b, i, l, p, r, s, z, g: Integer;
m: Boolean;
begin;
SetLength(Result, 1);
l := Length(str);
s := Length(d);
if ((l > 0) and (s > 0)) then
begin
r := 0;
z := 0;
for a := 0 to (s - 1) do
begin
p := Length(d[a]);
if (p > 0) then
if (p <= l) then
begin
if (z = 0) then
z := Length(d[a])
else
if (Length(d[a]) < z) then
z := Length(d[a]);
d[r] := d[a];
Inc(r);
end;
end;
if (r > 0) then
begin
s := r;
a := 1;
p := 1;
r := 0;
while (((r + 1) < limit) and ((a + (z - 1)) <= l)) do
begin
m := False;
for i := 0 to (s - 1) do
begin
g := Length(d[i]);
if ((a + (g - 1)) <= l) then
begin
for b := 1 to g do
begin
m := (str[((a + b) - 1)] = d[i][b]);
if not m then
Break;
end;
if m then
Break;
end;
end;
if m then
begin
Result[r] := Copy(str, p, (a - p));
if ((a + (g + 1)) = l) then
Exit;
p := (a + g);
a := ((a + g) - 1);
Inc(r);
SetLength(Result, (r + 1));
end;
Inc(a);
end;
Result[r] := Copy(str, p, ((l - p) + 1));
end else
Result[0] := '';
end else
Result[0] := '';
end;

{==============================================================================]
Returns the lines of str as TStringArray.
[==============================================================================}
function StrLines(str: string): TStringArray;
var
d: TStringArray;
begin
if (str <> '') then
begin
SetLength(d, 3);
d[0] := #13#10;
d[1] := #13;
d[2] := #10;
Result := StrExplodeMulti(d, str);
end else
begin
SetLength(Result, 1);
Result[0] := str;
end;
end;

{==============================================================================]
Returns the total count of lines that str contains.
[==============================================================================}
function StrLineCount(str: string): Integer;
var
s: TStringArray;
begin
if (str <> '') then
begin
s := StrLines(str);
Result := Length(s);
end else
Result := 1;
end;

{==============================================================================]
Returns the line text from str by number.
NOTE: Line numbers start from 1!
[==============================================================================}
function StrLine(str: string; number: Integer): string;
var
d, r: TStringArray;
begin
if ((str <> '') and (number > 0)) then
begin
SetLength(d, 3);
d[0] := #13#10;
d[1] := #13;
d[2] := #10;
r := StrExplodeMultiEx(d, str, (number + 1));
if (Length(r) >= number) then
Result := r[(number - 1)]
else
Result := '';
end else
Result := '';
end;

var
f, h, i: Integer;
p, s: string;
d: TStringArray;
r: Boolean;

begin
p := (AppPath + 'settings.xml');
f := OpenFile(p, False);
r := ReadFileString(f, s, FileSize(f));
CloseFile(f);
if r then
begin
ClearDebug;
d := StrLines(s);
h := High(d);
for i := 0 to h do
WriteLn('Line ' + IntToStr(i + 1) + ' = "' + d[i] + '"');
SetLength(d, 0);
i := StrLineCount(s);
WriteLn('Count of lines in file: ' + IntToStr(i));
if (i > 0) then
WriteLn('Last line (' + IntToStr(i) + '): "' + StrLine(s, i) + '"');
end;
end.

Provided a small example how you can use em. :) You need to change the p variable to your own path, of course.
Hope any of it helps you out!

P.S. StrExplodeMulti and StrExplodeMultiEx are core functions, that those string line reading functions (StrLines, StrLine, StrLineCount) are based on. :)

Edit: I accidentally said File commands, even though they are String commands that could be used with file strings - it's correct now!

Foundry

2013-11-29 10:01:48

From: Janilabo
Hello Foundry,

Here is some file commands, that I wrote for pumbaa plugin, which you could possibly use:

{==============================================================================]
Explodes str with multiple separators/delimiters (d).
The importance order for d items is from left to right (=>).
So place the important ones first and then less important after those.
[==============================================================================}
function StrExplodeMulti(d: TStringArray; str: string): TStringArray;
var
a, b, i, l, p, r, s, z, g: Integer;
m: Boolean;
begin;
SetLength(Result, 1);
l := Length(str);
s := Length(d);
if ((l > 0) and (s > 0)) then
begin
r := 0;
z := 0;
for a := 0 to (s - 1) do
begin
p := Length(d[a]);
if (p > 0) then
if (p <= l) then
begin
if (z = 0) then
z := Length(d[a])
else
if (Length(d[a]) < z) then
z := Length(d[a]);
d[r] := d[a];
Inc(r);
end;
end;
if (r > 0) then
begin
s := r;
a := 1;
p := 1;
r := 0;
while ((a + (z - 1)) <= l) do
begin
m := False;
for i := 0 to (s - 1) do
begin
g := Length(d[i]);
if ((a + (g - 1)) <= l) then
begin
for b := 1 to g do
begin
m := (str[((a + b) - 1)] = d[i][b]);
if not m then
Break;
end;
if m then
Break;
end;
end;
if m then
begin
Result[r] := Copy(str, p, (a - p));
if ((a + (g + 1)) = l) then
Exit;
p := (a + g);
a := ((a + g) - 1);
Inc(r);
SetLength(Result, (r + 1));
end;
Inc(a);
end;
Result[r] := Copy(str, p, ((l - p) + 1));
end else
Result[0] := '';
end else
Result[0] := '';
end;

{==============================================================================]
Explodes str with multiple separators/delimiters (d).
The importance order for d items is from left to right (=>).
So place the important ones first and then less important after those.
NOTE: Supports limit.
[==============================================================================}
function StrExplodeMultiEx(d: TStringArray; str: string; limit: Integer): TStringArray;
var
a, b, i, l, p, r, s, z, g: Integer;
m: Boolean;
begin;
SetLength(Result, 1);
l := Length(str);
s := Length(d);
if ((l > 0) and (s > 0)) then
begin
r := 0;
z := 0;
for a := 0 to (s - 1) do
begin
p := Length(d[a]);
if (p > 0) then
if (p <= l) then
begin
if (z = 0) then
z := Length(d[a])
else
if (Length(d[a]) < z) then
z := Length(d[a]);
d[r] := d[a];
Inc(r);
end;
end;
if (r > 0) then
begin
s := r;
a := 1;
p := 1;
r := 0;
while (((r + 1) < limit) and ((a + (z - 1)) <= l)) do
begin
m := False;
for i := 0 to (s - 1) do
begin
g := Length(d[i]);
if ((a + (g - 1)) <= l) then
begin
for b := 1 to g do
begin
m := (str[((a + b) - 1)] = d[i][b]);
if not m then
Break;
end;
if m then
Break;
end;
end;
if m then
begin
Result[r] := Copy(str, p, (a - p));
if ((a + (g + 1)) = l) then
Exit;
p := (a + g);
a := ((a + g) - 1);
Inc(r);
SetLength(Result, (r + 1));
end;
Inc(a);
end;
Result[r] := Copy(str, p, ((l - p) + 1));
end else
Result[0] := '';
end else
Result[0] := '';
end;

{==============================================================================]
Returns the lines of str as TStringArray.
[==============================================================================}
function StrLines(str: string): TStringArray;
var
d: TStringArray;
begin
if (str <> '') then
begin
SetLength(d, 3);
d[0] := #13#10;
d[1] := #13;
d[2] := #10;
Result := StrExplodeMulti(d, str);
end else
begin
SetLength(Result, 1);
Result[0] := str;
end;
end;

{==============================================================================]
Returns the total count of lines that str contains.
[==============================================================================}
function StrLineCount(str: string): Integer;
var
s: TStringArray;
begin
if (str <> '') then
begin
s := StrLines(str);
Result := Length(s);
end else
Result := 1;
end;

{==============================================================================]
Returns the line text from str by number.
NOTE: Line numbers start from 1!
[==============================================================================}
function StrLine(str: string; number: Integer): string;
var
d, r: TStringArray;
begin
if ((str <> '') and (number > 0)) then
begin
SetLength(d, 3);
d[0] := #13#10;
d[1] := #13;
d[2] := #10;
r := StrExplodeMultiEx(d, str, (number + 1));
if (Length(r) >= number) then
Result := r[(number - 1)]
else
Result := '';
end else
Result := '';
end;

var
f, h, i: Integer;
p, s: string;
d: TStringArray;
r: Boolean;

begin
p := (AppPath + 'settings.xml');
f := OpenFile(p, False);
r := ReadFileString(f, s, FileSize(f));
CloseFile(f);
if r then
begin
ClearDebug;
d := StrLines(s);
h := High(d);
for i := 0 to h do
WriteLn('Line ' + IntToStr(i + 1) + ' = "' + d[i] + '"');
SetLength(d, 0);
i := StrLineCount(s);
WriteLn('Count of lines in file: ' + IntToStr(i));
if (i > 0) then
WriteLn('Last line (' + IntToStr(i) + '): "' + StrLine(s, i) + '"');
end;
end.

Provided a small example how you can use em. :) You need to change the p variable to your own path, of course.
Hope any of it helps you out!

P.S. StrExplodeMulti and StrExplodeMultiEx are core functions, that those string line reading functions (StrLines, StrLine, StrLineCount) are based on. :)


Thank you! Exactly what I was needing :)

Janilabo

2013-11-29 10:10:47

From: Foundry
Thank you! Exactly what I was needing :)
Really glad to hear that I was able to help you out man!

Although, because the commands are interpreted they might not be as fast as you need.. :\ This depends of your file size(s).
Compiled versions would be way faster - enough fast to work quickly with even large files really.

Hey @Foundry, below is less generic solution for this purpose - as in, if you simply need to get just that last line and thats it.

Here you go:

{==============================================================================]
Returns the last s position in str.
[==============================================================================}
function StrLastPos(s, str: string): Integer;
var
l, p: Integer;
begin
Result := 0;
l := Length(str);
if (Length(s) <= l) then
begin
p := 0;
repeat
p := PosEx(s, str, (p + 1));
if (p > 0) then
Result := p;
until (p <= 0);
end;
end;

function StrLastLine(str: string): string;
var
a, b, c, l: Integer;
begin
if (str <> '') then
begin
a := StrLastPos(#13, str);
b := StrLastPos(#10, str);
if ((a > 0) or (b > 0)) then
begin
l := Length(str);
c := Max(a, b);
Result := Copy(str, c, ((l - c) + 1));
end else
Result := str;
end else
Result := '';
end;

var
f: Integer;
p, s: string;
r: Boolean;

begin
p := (AppPath + 'settings.xml');
f := OpenFile(p, False);
r := ReadFileString(f, s, FileSize(f));
CloseFile(f);
ClearDebug;
if r then
WriteLn('Last line: "' + StrLastLine(s) + '"');
s := 'This' + #13#10 +
'is' + #13 +
'just' + #10 +
'a' + #13 +
'test!';
WriteLn('TEST: "' + StrLastLine(s) + '"');
end.

..so it's a lot simpler solution. Not as useful as the one I posted first, but simpler and maybe quicker too?
Should be easier to understand at least, that's for sure, hehe. :p

Foundry

2013-12-25 14:39:32

From: Janilabo
Really glad to hear that I was able to help you out man!

Although, because the commands are interpreted they might not be as fast as you need.. :\ This depends of your file size(s).
Compiled versions would be way faster - enough fast to work quickly with even large files really.

Hey @Foundry, below is less generic solution for this purpose - as in, if you simply need to get just that last line and thats it.

Here you go:

{==============================================================================]
Returns the last s position in str.
[==============================================================================}
function StrLastPos(s, str: string): Integer;
var
l, p: Integer;
begin
Result := 0;
l := Length(str);
if (Length(s) <= l) then
begin
p := 0;
repeat
p := PosEx(s, str, (p + 1));
if (p > 0) then
Result := p;
until (p <= 0);
end;
end;

function StrLastLine(str: string): string;
var
a, b, c, l: Integer;
begin
if (str <> '') then
begin
a := StrLastPos(#13, str);
b := StrLastPos(#10, str);
if ((a > 0) or (b > 0)) then
begin
l := Length(str);
c := Max(a, b);
Result := Copy(str, c, ((l - c) + 1));
end else
Result := str;
end else
Result := '';
end;

var
f: Integer;
p, s: string;
r: Boolean;

begin
p := (AppPath + 'settings.xml');
f := OpenFile(p, False);
r := ReadFileString(f, s, FileSize(f));
CloseFile(f);
ClearDebug;
if r then
WriteLn('Last line: "' + StrLastLine(s) + '"');
s := 'This' + #13#10 +
'is' + #13 +
'just' + #10 +
'a' + #13 +
'test!';
WriteLn('TEST: "' + StrLastLine(s) + '"');
end.

..so it's a lot simpler solution. Not as useful as the one I posted first, but simpler and maybe quicker too?
Should be easier to understand at least, that's for sure, hehe. :p


Thanks for the first function that reads only the last line (that will help me debug some things a lot faster). I have been using the functions you posted initially from your plugin and they have helped immensely, but I just have one question. How do I put the strings back together after they are exploded?

For example I made a procedure that breaks a ~500 line text file into a string array so I can interpret the data. I am having a problem putting it back together the way it was, though. Here is the function.

procedure StoredLog.update();
var
StringArray, NewArray: TStringArray;
Log, i, j, k: Integer = 0;
EndPathName: String;
IndexesToDelete: TIntegerArray;
begin
Log := OpenFile(Directory + '.txt', True);
ReadFileString(Log, self.MainString, FileSize(Log));
CloseFile(Log);
self.LineCount := StrLineCount(self.MainString);
if self.LogType = 0 then
begin
setLength(StringArray, self.LineCount);
StringArray := StrLines(self.MainString);
for i := self.LineCount - MaxLinesToLog to self.LineCount - 1 do
begin
for j := 0 to high(IgnoreNames) do
begin
if (pos(IgnoreNames[j], StringArray[i]) > 0) then
begin
setLength(IndexesToDelete, k + 1);
IndexesToDelete[k] := i;
inc(k);
break;
end;
end;
end;
StringArray.deleteIndexes(IndexesToDelete);
for i := 0 to MaxLinesToLog - 1 do
begin
setLength(NewArray, i + 1);
NewArray[i] := StringArray[high(StringArray) - MaxLinesToLog + i];
end;
self.MainString := NewArray.implodeArr(#10); // I would want to put a new line symbol after each line, right?
end;
end;

The string is put back together fine but every time I use strLine on the new string it returns an empty.

Janilabo

2013-12-26 00:31:58

From: Foundry
Thanks for the first function that reads only the last line (that will help me debug some things a lot faster). I have been using the functions you posted initially from your plugin and they have helped immensely, but I just have one question. How do I put the strings back together after they are exploded?

For example I made a procedure that breaks a ~500 line text file into a string array so I can interpret the data. I am having a problem putting it back together the way it was, though. Here is the function.

procedure StoredLog.update();
var
StringArray, NewArray: TStringArray;
Log, i, j, k: Integer = 0;
EndPathName: String;
IndexesToDelete: TIntegerArray;
begin
Log := OpenFile(Directory + '.txt', True);
ReadFileString(Log, self.MainString, FileSize(Log));
CloseFile(Log);
self.LineCount := StrLineCount(self.MainString);
if self.LogType = 0 then
begin
setLength(StringArray, self.LineCount);
StringArray := StrLines(self.MainString);
for i := self.LineCount - MaxLinesToLog to self.LineCount - 1 do
begin
for j := 0 to high(IgnoreNames) do
begin
if (pos(IgnoreNames[j], StringArray[i]) > 0) then
begin
setLength(IndexesToDelete, k + 1);
IndexesToDelete[k] := i;
inc(k);
break;
end;
end;
end;
StringArray.deleteIndexes(IndexesToDelete);
for i := 0 to MaxLinesToLog - 1 do
begin
setLength(NewArray, i + 1);
NewArray[i] := StringArray[high(StringArray) - MaxLinesToLog + i];
end;
self.MainString := NewArray.implodeArr([URL=http://villavu.com/forum/usertag.php?do=list&action=hash&hash=10]#10[/URL] ); // I would want to put a new line symbol after each line, right?
end;
end;

The string is put back together fine but every time I use strLine on the new string it returns an empty.
Ello Foundry,

I think you are dealing with an ancient issue over here: UNIX-style (#10) vs. DOS-style (#13#10) newline.. :p
It seems to me your problem over here is easy to fix thogh, but that really depends what you meant by using my plugin to do this - so, which plugin do you use?
Have you simply tried using Implode with #13#10 ? (Replace #10 with #13#10)

I wrote the commands for this topic to support both system strings, UNIX and DOS. ..but that really might be an issue if/when you use both ways randomly in your strings… Then it would need some backtracing to get it parsed back to the way it was.. That means, I recommend sticking with 1 way only - which probably would be #13#10 (Windows).

Foundry

2013-12-26 14:12:45

From: Janilabo
Ello Foundry,

I think you are dealing with an ancient issue over here: UNIX-style (#10) vs. DOS-style (#13#10) newline.. :p
It seems to me your problem over here is easy to fix thogh, but that really depends what you meant by using my plugin to do this - so, which plugin do you use?
Have you simply tried using Implode with #13#10 ? (Replace #10 with #13#10)

I wrote the commands for this topic to support both system strings, UNIX and DOS. ..but that really might be an issue if/when you use both ways randomly in your strings… Then it would need some backtracing to get it parsed back to the way it was.. That means, I recommend sticking with 1 way only - which probably would be #13#10 (Windows).


My bad, I didn't mean to say plugin. I meant I used the functions you posted at the beginning of the thread. I also tried using #13 #10 as the 'glue' but that didn't work either. I am attempting to use the SRL ImplodeArr function right here.

Janilabo

2013-12-26 15:22:27

From: Foundry
My bad, I didn't mean to say plugin. I meant I used the functions you posted at the beginning of the thread. I also tried using #13 #10 as the 'glue' but that didn't work either. I am attempting to use the SRL ImplodeArr function right here.
Have you tried using:

self.MainString := Implode(#13#10, NewArray);

or

self.MainString := Implode(#10, NewArray);

I am not exactly sure how the SRL version works, but the Simba version should do the trick. :)

Foundry

2013-12-26 16:09:58

From: Janilabo
Have you tried using:

self.MainString := Implode(#13#10, NewArray);

or

self.MainString := Implode(#10, NewArray);

I am not exactly sure how the SRL version works, but the Simba version should do the trick. :)


This did the trick, thanks!