Jump to content
robertjohns

Find, Replace and Save

Recommended Posts

How can I find all instances of a string in a file , replace all instance of the string and save the copy of the file in Delphi

Share this post


Link to post
1 hour ago, robertjohns said:

How can I find all instances of a string in a file , replace all instance of the string and save the copy of the file in Delphi

TFile.WriteAllText('OutFileName.txt', TFile.ReadAllText('FileName.txt').Replace('Replace from', 'Replace To'));

Edited by aehimself

Share this post


Link to post
6 hours ago, aehimself said:

TFile.WriteAllText('OutFileName.txt', TFile.ReadAllText('FileName.txt').Replace('Replace from', 'Replace To'));

Thanks , no it is not a text file it is binary file where I need to search all instances of t.o.p.s and need to replace only t as h in all the instances by inc method and save the file

Share this post


Link to post
9 hours ago, robertjohns said:

How can I find all instances of a string in a file , replace all instance of the string and save the copy of the file in Delphi

Will this work?

 

memo2.Text := StringReplace(memo1.Text, 'TextToRepace', 'ReplaceWith', [rfReplaceAll, rfIgnoreCase]);

Share this post


Link to post
40 minutes ago, jjw said:

Will this work?

 

memo2.Text := StringReplace(memo1.Text, 'TextToRepace', 'ReplaceWith', [rfReplaceAll, rfIgnoreCase]);

StringReplace method will not work for this .. because we need to loop and find the all similar occurrences of t.o.p.s in whole the files and all the found occurrences need to be replaced by t to h and then after change need to save it.

Share this post


Link to post
10 hours ago, robertjohns said:

Thanks , no it is not a text file it is binary file where I need to search all instances of t.o.p.s and need to replace only t as h in all the instances by inc method and save the file

Since the replacement does not change the total size of the data you can load the file into a buffer suitable for binary data, do the replacement in this buffer and finally write it back to the file. A suitable buffer would be a TMemoryStream. The question is what these '.' bytes in your target string are. If these are actually zero bytes displayed as '.' in a hex viewer the target would be simply the UTF16 string 'tops', if these are real dot characters it would probably rather be the ANSI string 't.o.p.s'.  You can probably answer that by looking at the byte following the 's', it should be 0 if this is UTF16 text. Let's assume it is, then the algorithm would be like this (untested!):

 

procedure ReplaceBytesInFile(const aFilename: string; const
    aSearchBytes, aReplaceBytes: TBytes);
var
  LBuffer: TMemoryStream;
  LNumBytes: Integer;
  LPos, LEnd: PByte;
begin
  LNumBytes := Length(aSearchBytes);
  Assert(LNumBytes = Length(aReplaceBytes), 'Arrays have to be of the same length!');
  if LNumBytes = 0 then
    Exit;

  LBuffer := TMemoryStream.Create();
  try
    LBuffer.LoadFromFile(aFilename);
    LPos := LBuffer.Memory;
    LEnd := LPos;
    Inc(LEnd, LBuffer.Size - LNumBytes );
    while LPos < LEnd do begin
      if LPos^ = aSearchBytes[0] then begin
        if CompareMem(LPos, @aSearchBytes[0], LNumBytes) then begin
          CopyMemory(LPos, @aReplaceBytes[0], LNumBytes);
          // or
          // Move( aReplaceBytes[0], LPos^, LNumBytes );
          Inc(LPos, LNumBytes);
        end
        else
          Inc(LPos);
      end
      else
        Inc(LPos);
    end;
    LBuffer.SaveToFile(aFilename);
  finally
    LBuffer.Free;
  end;
end;

  ReplaceBytesInFile(theFilename,
    TEncoding.Unicode.GetBytes('tops'),
    TEncoding.Unicode.GetBytes('hops')
    );

 

Share this post


Link to post
9 hours ago, robertjohns said:

t.o.p.s

 

1 hour ago, PeterBelow said:

Since the replacement does not change the total size of the data you can load the file into a buffer suitable for binary data, do the replacement in this buffer and finally write it back to the file. A suitable buffer would be a TMemoryStream. The question is what these '.' bytes in your target string are. If these are actually zero bytes displayed as '.' in a hex viewer the target would be simply the UTF16 string 'tops', if these are real dot characters it would probably rather be the ANSI string 't.o.p.s'.  You can probably answer that by looking at the byte following the 's', it should be 0 if this is UTF16 text. Let's assume it is, then the algorithm would be like this (untested!):

 


procedure ReplaceBytesInFile(const aFilename: string; const
    aSearchBytes, aReplaceBytes: TBytes);
var
  LBuffer: TMemoryStream;
  LNumBytes: Integer;
  LPos, LEnd: PByte;
begin
  LNumBytes := Length(aSearchBytes);
  Assert(LNumBytes = Length(aReplaceBytes), 'Arrays have to be of the same length!');
  if LNumBytes = 0 then
    Exit;

  LBuffer := TMemoryStream.Create();
  try
    LBuffer.LoadFromFile(aFilename);
    LPos := LBuffer.Memory;
    LEnd := LPos;
    Inc(LEnd, LBuffer.Size - LNumBytes );
    while LPos < LEnd do begin
      if LPos^ = aSearchBytes[0] then begin
        if CompareMem(LPos, @aSearchBytes[0], LNumBytes) then begin
          CopyMemory(LPos, @aReplaceBytes[0], LNumBytes);
          // or
          // Move( aReplaceBytes[0], LPos^, LNumBytes );
          Inc(LPos, LNumBytes);
        end
        else
          Inc(LPos);
      end
      else
        Inc(LPos);
    end;
    LBuffer.SaveToFile(aFilename);
  finally
    LBuffer.Free;
  end;
end;

  ReplaceBytesInFile(theFilename,
    TEncoding.Unicode.GetBytes('tops'),
    TEncoding.Unicode.GetBytes('hops')
    );

 

Thanks the code works but I am sorry I did not explained my question properly . yes '.' represents 00 in hex viewer .

HEX value for t.o.p.s is

74 00 6f 00 70 00 73

 

and HEX value for h.o.p.s is

68 00 6f 00 70 00 73

 

so I have to change hex value from 74 to 68

 

value should be changed by hex method not tops to hops

 

Thanks in advance

 

Share this post


Link to post

Break the task into steps.

Implement all the steps you can.

Ask for hints on the steps you can't.

Share this post


Link to post
On 11/22/2022 at 5:00 PM, robertjohns said:

 

Thanks the code works but I am sorry I did not explained my question properly . yes '.' represents 00 in hex viewer .

HEX value for t.o.p.s is

74 00 6f 00 70 00 73

 

and HEX value for h.o.p.s is

68 00 6f 00 70 00 73

 

so I have to change hex value from 74 to 68

 

value should be changed by hex method not tops to hops

 

Thanks in advance

 

So what is the problem? The procedure takes an array of bytes, just feed it the correct one, e. g.

var
  theFile: string;
  SearchFor, ReplaceWith: TBytes;
begin
  SearchFor := [$74, $00, $6f, $00, $70, $00, $73];
  ReplaceWith := [$68, $00, $6f, $00, $70, $00, $73];
  ReplaceBytesInFile(theFile,  SearchFor, ReplaceWith);

 

Edited by PeterBelow

Share this post


Link to post
On 11/23/2022 at 10:58 PM, PeterBelow said:

So what is the problem? The procedure takes an array of bytes, just feed it the correct one, e. g.


var
  theFile: string;
  SearchFor, ReplaceWith: TBytes;
begin
  SearchFor := [$74, $00, $6f, $00, $70, $00, $73];
  ReplaceWith := [$68, $00, $6f, $00, $70, $00, $73];
  ReplaceBytesInFile(theFile,  SearchFor, ReplaceWith);

 

Will ReplaceBytesInFile function work for multiple hex changes like If I try to replace all these hex lines by other hex

 

$76, $00, $61, $00, $6F, $00, $70, $00, $5F, $00, $00
$5F, $00, $74, $00, $7A, $00, $5F, $00, $00
$77, $00, $68, $00, $79, $00, $70, $00, $5F, $00, $00
$44, $10, $6D, $00, $6F, $00, $64, $00, $65, $00, $6D, $00, $5F, $00, $00
$69, $10, $00, $00, $6C, $00, $75, $00, $65, $00, $74, $00, $6F, $00, $6F, $00, $74, $00, $68, $00, $5F, $00, $00
$6F, $10, $6D, $00, $64, $00, $74, $00, $70, $00, $73, $00, $65, $00, $63, $00, $61, $00, $70, $00, $70, $00, $5F, $00, $00
$65, $10, $6D, $00, $64, $00, $74, $00, $70, $00, $5F, $00, $00
$00, $10, $61, $00, $00, $00, $6C, $00, $5F, $00, $00
$34, $10, $64, $00, $73, $00, $70, $00, $5F, $00, $00
$8C, $10, $6B, $00, $65, $00, $79, $00, $6D, $00, $61, $00, $73, $00, $74, $00, $65, $00, $72, $00, $5F, $00, $00
$3B, $00, $00, $00, $6F, $00, $6F, $00, $74, $00, $5F, $00, $00

 

 

Share this post


Link to post

Please use your brain, its not just there to keep your head from imploding, you know :classic_cool:.

The code works for every byte sequence as long as both arrays are of the same length, so the total size of the data does not change.

Share this post


Link to post
45 minutes ago, robertjohns said:

Will ReplaceBytesInFile function work for multiple hex changes like If I try to replace all these hex lines by other hex

 

$76, $00, $61, $00, $6F, $00, $70, $00, $5F, $00, $00
$5F, $00, $74, $00, $7A, $00, $5F, $00, $00
$77, $00, $68, $00, $79, $00, $70, $00, $5F, $00, $00
$44, $10, $6D, $00, $6F, $00, $64, $00, $65, $00, $6D, $00, $5F, $00, $00
$69, $10, $00, $00, $6C, $00, $75, $00, $65, $00, $74, $00, $6F, $00, $6F, $00, $74, $00, $68, $00, $5F, $00, $00
$6F, $10, $6D, $00, $64, $00, $74, $00, $70, $00, $73, $00, $65, $00, $63, $00, $61, $00, $70, $00, $70, $00, $5F, $00, $00
$65, $10, $6D, $00, $64, $00, $74, $00, $70, $00, $5F, $00, $00
$00, $10, $61, $00, $00, $00, $6C, $00, $5F, $00, $00
$34, $10, $64, $00, $73, $00, $70, $00, $5F, $00, $00
$8C, $10, $6B, $00, $65, $00, $79, $00, $6D, $00, $61, $00, $73, $00, $74, $00, $65, $00, $72, $00, $5F, $00, $00
$3B, $00, $00, $00, $6F, $00, $6F, $00, $74, $00, $5F, $00, $00

 

 

Thanks .. No Actually I am confused with

var
  theFile: string;
  SearchFor, ReplaceWith: TBytes;
begin
  SearchFor := [$76, $00, $61, $00, $6F, $00, $70, $00, $5F, $00, $00];
  ReplaceWith := [$00, $00, $61, $00, $6F, $00, $70, $00, $5F, $00, $5F];
  ReplaceBytesInFile(theFile,  SearchFor, ReplaceWith);

 

$76, $00, $61, $00, $6F, $00, $70, $00, $5F, $00, $00
$5F, $00, $74, $00, $7A, $00, $5F, $00, $00
$77, $00, $68, $00, $79, $00, $70, $00, $5F, $00, $00
$44, $10, $6D, $00, $6F, $00, $64, $00, $65, $00, $6D, $00, $5F, $00, $00
$69, $10, $00, $00, $6C, $00, $75, $00, $65, $00, $74, $00, $6F, $00, $6F, $00, $74, $00, $68, $00, $5F, $00, $00
$6F, $10, $6D, $00, $64, $00, $74, $00, $70, $00, $73, $00, $65, $00, $63, $00, $61, $00, $70, $00, $70, $00, $5F, $00, $00
$65, $10, $6D, $00, $64, $00, $74, $00, $70, $00, $5F, $00, $00
$00, $10, $61, $00, $00, $00, $6C, $00, $5F, $00, $00
$34, $10, $64, $00, $73, $00, $70, $00, $5F, $00, $00
$8C, $10, $6B, $00, $65, $00, $79, $00, $6D, $00, $61, $00, $73, $00, $74, $00, $65, $00, $72, $00, $5F, $00, $00
$3B, $00, $00, $00, $6F, $00, $6F, $00, $74, $00, $5F, $00, $00

 

How can declare other hex lines for variable SearchFor, ReplaceWith: TBytes;

 

 

Thanks in advance

Share this post


Link to post

maybe, needs test about memory usage, you can

  • open file as "bytes"
  • and in a looping, search your byte sequences!

for example using "MemoryStream" search for "xx" bytes .... try find "Bitmap" signature on stream

 

type
  TMyBuffer = array [0 .. 1] of byte;
 

function MyFindBitmapSignature(AMemoryStream: TStringStream): int64;
  function MyIsTheSame(const ABuffer: TMyBuffer; const AFind: TMyBuffer): boolean;  // better for many bytes comparing
  begin
    result := false;
    //
    if (Length(ABuffer) <> Length(AFind)) then
      exit;
    //
    for var i: integer := 0 to (Length(ABuffer)-1) do
      if (ABuffer[..i..] <> AFind[..i..]) then
        exit(false);
    //
    result := true;
  end;

var
  LBuffer: TMyBuffer;
  LFind  : TMyBuffer;
begin
  result                 := -1;
  AMemoryStream.Position := 0;
  LFind[0]               := 66;
  LFind[1]               := 77;
  //
  for var i: int64 := 0 to ((AMemoryStream.Size div 2) - 1) do
    begin
      AMemoryStream.Read(LBuffer, 2);
      //
      if MyIsTheSame(LBuffer, LFind) then
        exit(i * 2);
      //
      (*
        if (LBuffer[0] = 66) and (LBuffer[1] = 77) then // 'nul, nul,... "BM" ...'
        begin
        exit(i * 2);
        end;
      *)
    end;
end;
 

Edited by programmerdelphi2k

Share this post


Link to post

no problem, use the same idea about! read your binary-file as "bytes"...

-- see on Help: TStream subclasses, like: TMemoryStream, TStringStream, TFileStream, etc...

-- if found the "correspondent values", just store in a other stream (or the same) and when end, just "save to file"

--- if a[12] = 123 then a[12]=100;

Edited by programmerdelphi2k

Share this post


Link to post
23 hours ago, robertjohns said:

Thanks .. No Actually I am confused with

var
  theFile: string;
  SearchFor, ReplaceWith: TBytes;
begin
  SearchFor := [$76, $00, $61, $00, $6F, $00, $70, $00, $5F, $00, $00];
  ReplaceWith := [$00, $00, $61, $00, $6F, $00, $70, $00, $5F, $00, $5F];
  ReplaceBytesInFile(theFile,  SearchFor, ReplaceWith);

 

$76, $00, $61, $00, $6F, $00, $70, $00, $5F, $00, $00
$5F, $00, $74, $00, $7A, $00, $5F, $00, $00
$77, $00, $68, $00, $79, $00, $70, $00, $5F, $00, $00
$44, $10, $6D, $00, $6F, $00, $64, $00, $65, $00, $6D, $00, $5F, $00, $00
$69, $10, $00, $00, $6C, $00, $75, $00, $65, $00, $74, $00, $6F, $00, $6F, $00, $74, $00, $68, $00, $5F, $00, $00
$6F, $10, $6D, $00, $64, $00, $74, $00, $70, $00, $73, $00, $65, $00, $63, $00, $61, $00, $70, $00, $70, $00, $5F, $00, $00
$65, $10, $6D, $00, $64, $00, $74, $00, $70, $00, $5F, $00, $00
$00, $10, $61, $00, $00, $00, $6C, $00, $5F, $00, $00
$34, $10, $64, $00, $73, $00, $70, $00, $5F, $00, $00
$8C, $10, $6B, $00, $65, $00, $79, $00, $6D, $00, $61, $00, $73, $00, $74, $00, $65, $00, $72, $00, $5F, $00, $00
$3B, $00, $00, $00, $6F, $00, $6F, $00, $74, $00, $5F, $00, $00

 

How can declare other hex lines for variable SearchFor, ReplaceWith: TBytes;

 

 

Thanks in advance

D11 actually accepts initialized TBytes arrays in typed constants, so yo can do this:

 

procedure ReplaceBytesInFile(aStream: TMemoryStream; const
    aSearchBytes, aReplaceBytes: TBytes);
var
  LNumBytes: Integer;
  LPos, LEnd: PByte;
begin
  LNumBytes := Length(aSearchBytes);
  Assert(LNumBytes = Length(aReplaceBytes), 'Arrays have to be of the same length!');
  if LNumBytes = 0 then
    Exit;

  LPos := aStream.Memory;
  LEnd := LPos;
  Inc(LEnd, aStream.Size - LNumBytes );
  while LPos < LEnd do begin
    if LPos^ = aSearchBytes[0] then begin
      if CompareMem(LPos, @aSearchBytes[0], LNumBytes) then begin
        CopyMemory(LPos, @aReplaceBytes[0], LNumBytes);
        // or
        // Move( aReplaceBytes[0], LPos^, LNumBytes );
        Inc(LPos, LNumBytes);
      end
      else
        Inc(LPos);
    end
    else
      Inc(LPos);
  end; {while}
end;

type
  TDataRec = record SearchFor, ReplaceBy: TBytes end;

const
  Data: array [0..1] of TDataRec = (
    (
     SearchFor: [$74, $00, $6f, $00, $7, $00, $73];
     ReplaceBy: [$68, $00, $6f, $00, $7, $00, $73]),
    (
     SearchFor: [$76, $00, $61, $00, $6F, $00, $70, $00, $5F, $00, $00];
     ReplaceBy: [$00, $00, $61, $00, $6F, $00, $70, $00, $5F, $00, $00])
     // add more data as needed, adjust array upper bound accordingly
    );
                   
procedure TForm2.Test;
var
  I: Integer;
  theFile: string;
  LBuffer: TMemoryStream;
begin
  theFile := 'pathname here';
  LBuffer := TMemoryStream.Create();
  try
    LBuffer.LoadFromFile(theFile);
    for I := Low(Data) to High(Data) do
      ReplaceBytesInFile(LBuffer,  Data[I].SearchFor, Data[I].ReplaceBy);

    LBuffer.SaveToFile(theFile);
  finally
    LBuffer.Free;
  end;
end;

Untested!

Edited by PeterBelow

Share this post


Link to post

Thanks a lot for your help but I get error Out of memory while expanding memory stream.

Share this post


Link to post
48 minutes ago, robertjohns said:

Thanks a lot for your help but I get error Out of memory while expanding memory stream.

How large is the file you are trying to load? Did you build for a 32 or 64 bit platform? Try the latter.

Share this post


Link to post
27 minutes ago, PeterBelow said:

How large is the file you are trying to load? Did you build for a 32 or 64 bit platform? Try the latter.

It is 32 bit Platform and the file size is 3GB

Edited by robertjohns

Share this post


Link to post
9 minutes ago, robertjohns said:

It is 32 bit and the file size is 3GB

a 32 bit process can allocate a total of 2 GBs of RAM (or 3 if it's large address aware). Loading 3 GBs of data in the memory would use that up by itself, leaving no room for anything else (like the TMemoryStream class or any other variables). So it is expected.

As @PeterBelow advised, build a 64 bit executable, or use FileStreams instead of loading everything in the memory.

Share this post


Link to post

I think that my "propose" and TFileStream can solve it!

Each step on "stream-buffer", and replace with your "bytes". then it's not necessary "up" all data on memory!

Share this post


Link to post

try this:

 

function MyMinValue(const A, B: integer): integer;
begin
  result := A;
  //
  if (A > B) then
    result := B;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  MyFileStreamSource: TFileStream;
  MyFileStreamTarget: TFileStream;
  MyFileSourceSize  : int64;
  MyBuffer          : TBytes; // array of byte;
  MyBufferSize      : integer;
begin
  // See on Help about more infos: fmOpenRead, fmCreate, fmShareExclusive, etc...
  //
  DeleteFile('d:\newfile.mp4');
  //
  MyFileStreamSource := TFileStream.Create('d:\bohemian-rhapsody-queen-how-to-play-guitar.mp4', fmOpenRead or fmShareExclusive);
  try
    MyFileSourceSize := MyFileStreamSource.Size;
    MyBufferSize     := MyMinValue(512, MyFileSourceSize);
    //
    SetLength(MyBuffer, MyBufferSize);
    //
    MyFileStreamTarget := TFileStream.Create('d:\newfile.mp4', fmCreate or fmShareExclusive);
    try
      MyFileStreamSource.Position := 0;
      //
      while (MyFileSourceSize > 0) do
        begin
          MyFileStreamSource.Read(MyBuffer[0], MyBufferSize); // read and put on the next position...
          //
          // what to do with MyBuffer values...
          //
          MyFileStreamTarget.Write(MyBuffer[0], MyBufferSize);  // write and put on the next position...
          //

          Dec(MyFileSourceSize, MyBufferSize);

          //
          MyBufferSize := MyMinValue(512, MyFileSourceSize); // NOTE: verify this value, if ok ..
        end;
    finally
      MyFileStreamTarget.Free;
    end;
  finally
    MyFileStreamSource.Free;
  end;
  //
  ShowMessage('end');
end;

Edited by programmerdelphi2k

Share this post


Link to post
MyFileSourceSize := MyFileStreamSource.Size;

Is unnecessary, MyFileSourceSize.Size - MyFileSourceSize.Position will always return the unprocessed bytes.

 

  MyFileStreamSource.Read(MyBuffer[0], MyBufferSize); // read and put on the next position...

Make sure you also check if .Read returns MyBufferSize. Otherwise you will have "rouge" data what you will write back to the destination, effectively corrupting the output.

 

Also, the pseudocode above won't handle data, which is interrupted by the end of a buffer.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×