I don't see why so many people make this so hard. It is quite simple:
function ShouldDeleteLine(const UserID, Line: string): Boolean;
begin
// Remember: Pos(Needle, Haystack)
Result := Pos(UserID + '#', Line) = 1; // always 1-based!
end;
procedure DeleteLinesWithUserID(const FileName, UserID: string);
var
SL: TStringList;
I: Integer;
begin
if not FileExists(FileName) then
Exit;
SL := TStringList.Create;
try
SL.LoadFromFile(FileName); // Add exception handling for the
// case the file does not load properly.
// Always work backward when deleting items, otherwise your index
// may be off if you really delete.
for I := SL.Count - 1 downto 0 do
if ShouldDeleteLine(SL[I], UserID) then
begin
SL.Delete(I);
// if UserID is unique, you can uncomment the following line.
// Break;
end;
SL.SaveToFile(FileName);
finally
SL.Free;
end;
end;
As Arioch'The says, if you save to the same file name, you risk losing your data when the save fails, so you can do something like
SL.SaveToFile(FileName + '.dup');
if FileExists(FileName + '.old') then
DeleteFile(FileName + '.old');
RenameFile(FileName, FileName + '.old');
RenameFile(FileName + '.dup', FileName);
That keeps a backup of the original file as FileName + '.old'
.
Explanations
Working backward
Why work backward? Because if you have the following items
A B C D E F G
^
And you delete the item at ^
, then the following items will shift downward:
A B C E F G
^
If you iterate forward, you will now point to
A B C E F G
^
and E
is never examined. If you go backward, then you will point to:
A B C E F G
^
Note that E
, F
and G
were examined already, so now you will indeed examine the next item, C
, and you won't miss any. Also, if you go upward using 0 to Count - 1
, and delete, Count
will become one less and at the end, you will try to access past the boundary of the list. This can't happen if you work backwards using Count - 1 downto 0
.
Using + '#'
If you append '#'
and test for Pos() = 1
, you will be sure to catch the entire UserID
up to the delimiter, and not a line with a user ID that only contains the UserID
you are looking for. IOW, if UserID
is 'velthuis'
, you don't want to delete lines like 'rudyvelthuis#rvelthuis01#password'
or 'velthuisresidence#vr#password2'
, but you do want to delete 'velthuis#bla#pw3'
.
E.g. when looking for a user name, you look for '#' + UserName + '#'
for the same reason.