Stringlist with CommaText is returning comma and spaces instead of just comma in Delphi - delphi

I have a string list, each string looks something like this.
2023/01/30,08:47:27, 0. 7.71,CM212-A2,03,Bad head/nozzle detect,380000,Stage No1, Head No2, Nozzle Postion5, Nozzle No0, (Single Lane), PCB ID: , Bad head/nozzle detectReset
I am then adding that string into another list with commatext, and should be giveing a result like so
2023/01/30
08:47:27
0. 7.71
CM212-A2
03
Bad head/nozzle detect
Instead it returns
2023/01/30
08:47:27
0.
7.71
CM212-A2
03
Bad
head/nozzle
detect
It seems it will create a new string for each Comma like I want, but also is creating a new string for each space which I don't want.
Adding Code just incase its needed.
procedure TForm4.Button1Click(Sender: TObject);
var
datalist : tstringlist;
eventlist : tstringlist;
i,x: Integer;
filename : string;
Event : array[1..200] of TEvent;
begin
datalist := tstringlist.Create;
eventlist := tstringlist.Create;
X := 1;
//get latest file
filename := getlastmodifiedfilename('C:\Users\tngmorse\Desktop\LockHeads\Win32\Debug\');
//end
datalist.LoadFromFile(filename);
//search for head lockout
for i := 0 to datalist.count - 1 do
begin
if containstext(datalist[i],'Bad head') then
begin
memo1.Lines.Add(datalist[i]);
// eventlist.Clear;
eventlist.CommaText := datalist[i];
// Event[x].Dateofevent := 'today';
Event[x].Dateofevent := eventlist[0]; //spaces are also breaking it.
Event[x].Timeofevent := eventlist[1];
Event[x].Version := eventlist[2];
Event[x].Machine := Eventlist[3];
Event[x].EventNumber := Eventlist[4];
Event[x].EventName := Eventlist[5];
Event[x].TableNumber := EventList[6];
Event[x].Table := Eventlist[7];
Event[x].Head := Eventlist[8];
Event[x].Nozzle := EventList[9];
Event[x].NozzleType := Eventlist[10];
Event[x].Lane := Eventlist[11];
Event[x].PCBID := Eventlist[12];
Event[x].Effect := Eventlist[13];
memo1.Lines.Add('Date: '+Event[x].Dateofevent);
memo1.Lines.Add('Time: '+Event[x].Timeofevent);
memo1.Lines.Add('Machine: '+Event[x].Machine);
memo1.Lines.Add('Event: '+Event[x].EventName);
memo1.Lines.Add('Table: '+Event[x].Table);
memo1.Lines.Add('Head: '+Event[x].Head);
memo1.Lines.Add('Nozzle: '+Event[x].Nozzle);
memo1.Lines.Add('Status: '+Event[x].Effect);
x:=x+1;
//break down list by comma
//add to record
end;
end;
//end
showmessage('lines: '+inttostr(datalist.Count));
datalist.Destroy;
end;

This behavior can be controlled by the StrictDelimiter property:
If StrictDelimiter is True, individual strings in DelimitedText are only separated by Delimiter or quoted between QuoteChar. If StrictDelimiter is False, spaces and non-printable character are also used as delimiters.
So, set it to True to get the desired behavior.

Related

How to print data of _RecordSet to the text file in delphi 10

how to print all data/records contain in Recordset to the text file in Delphi 10 ?
I could not able to find any Method or property for it.Please guide, i am newbie in delphi.
I have done following:
Var:
CurrField : Field;
RecSet:_RecordSet ;
Begin:
RecSet:= command.Execute(records,Params,-1);
CurrField := RecSet.Fields[0];
end;
but i want to print complete records/data contain in RecSet(_RecordSet type) in text file.
You can write it yourself. If the recordset is relatively small, the easiest way is to use a TStringList.
var
i: Integer;
s: string;
SL: TStringList;
begin
SL := TStringList.Create;
try
while not RecSet.Eof do
begin
// Clear string for the next row
s := '';
// Loop through the fields in this row, creating a comma-separated list
for i := 0 to RecSet.FieldCount - 1 do
s := s + RecSet.Fields[i].Value + ',';
// Remove unnecessary final comma at end
SetLength(s, Length(s) - 1);
// Add to the stringlist
SL.Add(s);
end;
// Save the stringlist content to disk
SL.SaveToFile('YourFileName.txt');
finally
SL.Free;
end;
end;

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.)

Delete , Add, Edit stringList

This is a bit confusing but will try best to explain it. please ask if you need more details.
First i have a class called TPlayers Like so..
TPlayers = class
Private
p : array[1..20] of TStringList;
function GetPlayer(i:integer): TStringList;
Public
Property player[i : integer] : TStringList read GetPlayer;
constructor Create; virtual;
implementation
uses
main;
{constructor}
constructor TPlayers.Create;
begin
p[1] := TStringList.Create;
p[2] := TStringList.Create;
p[3] := TStringList.Create;
p[4] := TStringList.Create;
p[5] := TStringList.Create;
p[6] := TStringList.Create;
end;
function TPlayers.GetPlayer(i: integer): TStringList;
begin
Result := p[i];
end;
I now have FTherePlayers := TPlayers.Create to create the class.
First time i add to the stringlist like so
FTherePlayers.Player[strtoint(name2)].Add('posx='+inttostr(posL.x));
or with variables taken out
FTherePlayers.Player[1].Add('posx=15');
This seems to be fine, but next i try to update it like so
FTherePlayers.Player[strtoint(ID)].Values['posx='] := xpos;
or with variables taken out
FTherePlayers.Player[1].Values['posx='] := 12;
but then i check that value after changing it and it still says 15, thus when i do
showmessage(fthereplayers.player[1].Values['posx']);
it returns 15 but it should be 12. Any idea why its not changeing?
thanks
Glen
You have an extra equals sign at the end of the Name index value of the Values property. You need to use only the name portion of a name value pair without the equals sign. So, in your code just replace the following lines:
// here is an extra equals sign in 'posx=' index value
FTherePlayers.Player[1].Values['posx='] := 12;
FTherePlayers.Player[strtoint(ID)].Values['posx='] := xpos;
with this:
FTherePlayers.Player[1].Values['posx'] := 12;
FTherePlayers.Player[strtoint(ID)].Values['posx'] := xpos;

Pascal : Delphi Length String Command Error

I'm writing a section of code to read in CSV files and parse information out of them (currently I just have the beginning part of the code which will read in the headers at the beginning of the file. When I try to compile this code I'm receiving an error on the line which takes the length of the line from file.
The error I'm recieving is: [Error] MCLRandomizer.pas(*): Missing operator or semicolon
while not EOF(csvFile) do begin
i :=0;
ReadLn(csvFile, line);
if lineOne = true then begin
length := Length(line); //error here
while length > 0 do begin
dx := Pos(',', line);
buffer := Copy(line, 0, dx-1);
headers[i] := buffer;
line := Copy(line, dx+1, length);
length := Length(line); //error here
end;
lineOne := false;
end;
end;
Pascal makes no difference between length and Length ... they both are LENGTH
Rename the variable, it messes up the function.
FTR: If you really, really want you can write
length := System.Length(line);
(assuming length is an Integer). I agree with the other posters that that would be a bad idea.
A solution I developed to read a csv file into a record structure (actually an array of records structures) is
program read_file_into_array_of_records;
{$APPTYPE CONSOLE}
uses
SysUtils, StrUtils;
type
Tscore = record
name : string [25];
marks : integer;
end;
var
input_file: TextFile;
file_record : string[100];
score : array [0..3] of Tscore;
index : integer;
// function that returns all text up to a comma or the end of the line
function get_value() : string;
var
comma_pos: integer;
value: string[100];
begin
comma_pos := Pos(',', file_record);
// if comma found cut out all text up to it
if comma_pos <> 0 then
begin
value := leftstr(file_record, comma_pos - 1);
delete(file_record, 1, comma_pos);
end
else
begin
// no comma found so just take everything that remains
value := file_record;
end;
get_value := value;
end;
// procedure to fill one record by breaking up the comma separated values
procedure fill_record (index: integer);
begin
// call the function get_value as many times as needed to get
// each comma separated value
score[index].name := get_value();
score[index].marks := strtoint(get_value());
end;
// procedure to fill array with contents of csv file
procedure fill_array ();
begin
index := 0;
while not EoF(input_file) do
begin
readln(input_file, file_record);
fill_record (index);
index := index + 1;
end;
end;
// procedure to display contents of array
procedure display_array ();
begin
for index := 0 to 3 do
begin
writeln(score[index].name, ' got ', score[index].marks, ' marks' );
end;
readln;
end;
// main prog
begin
assignfile(input_file, 'scores.csv');
reset(input_file);
fill_array ();
closefile(input_file);
display_array();
end.
the contents of scores.csv:
james,31
jane,23
toby,34
ruth,40
Moreover, Pascal strings at 1, not 0. (copy() statement)

Stringlist with delimiter as a string?

I have an attribute called HistoryText in a object that is stored as a string.
I want to show all rows in a grid. I should be able to delete and edit rows in the grid.
The format is:
16.5.2003-$-12:09-$-anna-$-Organization created
2.6.2005-$-13:03-$-jimmy-$-Organization edited
19.12.2005-$-13:33-$-madeleine-$-Organization edited
So each row have 4 fields, date, time, user, and message with a delimiter string as '-$-'.
As the delimiter a string and not a char it cannot be assigned to the stringlists delimiter property.
I have a routine to extract the string to a Stringlist:
procedure ParseDelimited(const aStringList: TStringList; const aOrgList, aDelimiter: string);
var
vDelimiterPos : integer;
vPartialStr : string;
vRemaingTxt : string;
vDelimiterLength : integer;
begin
vDelimiterLength := Length(aDelimiter);
if (AnsiRightStr(aOrgList, Length(aDelimiter)) = aDelimiter) then
vRemaingTxt := aOrgList
else
vRemaingTxt := aOrgList + aDelimiter;
aStringList.BeginUpdate;
aStringList.Clear;
try
while Length(vRemaingTxt) > 0 do
begin
vDelimiterPos := Pos(aDelimiter, vRemaingTxt);
vPartialStr := Copy(vRemaingTxt,0,vDelimiterPos-1);
aStringList.Add(vPartialStr);
vRemaingTxt := Copy(vRemaingTxt,vDelimiterPos+vDelimiterLength,MaxInt);
end;
finally
aStringList.EndUpdate;
end;
end;
and it seems to work fine. My problem is syncing the changes in the StringList back to the original String property ? There are so much historical data with this delimiter so I don't think change it to a TChar is a realistic option.
Update:
A clarification. I think I can manage to convert the String to a StringList with the method above. Then display it in the grid should not be so hard. The problem come when I want to convert the TStringList back to the original String property wih '-$-' as delimiter. I cannot do HistoryText := myStringList.Delimitedtext for example.
Second update:
I have solved it. You all got a +1 for fast answers and really trying to help. In summary how I did it.
Read from Historytext:
MyStringList.Text := Historytext;
Now each row have 3 delimiters of '-$-' and each line is separated by a linefeed as usual.
In a loop parse the Stringlist and show it in the grid. I don't bother about MyStringList anymore.
Let the user delete and edit rows in the grid.
When finished loop by row and columns in the grid and build a new string with the same format as original.
Assign that string to HistoryText.
So shift focus from StringList to the grid made it easier :)
Instead of Delimiter (a char) and DelimitedText, you can also use LineBreak (a string) and Text:
lst := TStringList.Create;
try
lst.LineBreak := '-$-';
lst.Text := '16.5.2003-$-12:09-$-anna-$-Organization created';
Memo1.Lines := lst; // or whatever you do with it
finally
lst.Free;
end;
Ans it works even the other way round.
Wild stab in the dark (it isn't very clear what you are asking):
Work through the grid rows:
For each row:
assign an empty string to a temporary string var
For each column add row/column value plus your delimiter to temporary string var
remove last delimiter from temporary string var (if it is non-empty)
add temporary string var to stringlist
Write stringlist's text property back to your HistoryText
const
Delimiter = '-$-';
var
row: Integer;
col: Integer;
SL: TStringList;
rowString: string;
begin
SL := TStringList.Create;
try
for row := 0 to StringGrid1.RowCount - 1 do begin
rowString := '';
for col := 0 to StringGrid1.ColCount - 1 do begin
rowString := StringGrid1.Cells[col, row] + Delimiter;
end;
if rowString <> '' then begin
rowString := Copy(rowString, 1, Length(rowString) - Length(Delimiter));
end;
SL.Add(rowString);
end;
HistoryText := SL.Text;
finally
SL.Free;
end;
end;
Using Uwe's solution of TStrings' LineBreak property:
var
row: Integer;
col: Integer;
SLRows: TStringList;
SLCols: TStringlist;
begin
SLRows := TStringList.Create;
try
SLCols := TStringList.Create;
try
SLCols.LineBreak := '-$-';
for row := 0 to StringGrid1.RowCount - 1 do begin
SLCols.Clear;
for col := 0 to StringGrid1.ColCount - 1 do begin
SLCols.Add(StringGrid1.Cells[col, row]);
end;
SLRows.Add(SLCols.Text);
end;
HistoryText := SLRows.Text;
finally
SLCols.Free;
end;
finally
SLRows.Free;
end;
end;

Resources