Sometimes we need to locate and replace more than one substring in a string, and Delphi does not have a function for this purpose. In this topic, I am going to implement and discuss such a function with the following prototype:

function ReplaceAll(
  const Subject: String;               // the source string
  const OldPatterns: array of String;  // the substrings to locate and replace
  const NewPatterns: array of String;  // the replacement substrings
  IgnoreCase: Boolean                  // is search case insensitive?
): String;                             // returns the string, whose substrings are changed

ReplaceAll replaces all occurrences of the substrings specified by OldPatterns array with the substrings specified by NewPatterns array. The number of substrings in NewPatterns array can be less than the number of substrings in OldPatterns array. In this case, the substrings without corresponding substitute will be removed from the source string. The IgnoreCase parameter determines whether the search is case insensitive or not.

Just a quick solution

At the first glance, the solution seems to be straightforward: iterate through the substrings array and use StringReplace function to replace each substring with its corresponding substitute.

function ReplaceAll(const Subject: String;
  const OldPatterns, NewPatterns: array of String;
  IgnoreCase: Boolean): String;
var
  ReplaceFlags: TReplaceFlags;
  NewPattern: String;
  I: Integer;
begin
  ReplaceFlags := [rfReplaceAll];
  if IgnoreCase then
    Include(ReplaceFlags, rfIgnoreCase);
  Result := Subject;
  for I := Low(OldPatterns) to High(OldPatterns) do
  begin
    if I <= High(NewPatterns) then
      NewPattern := NewPatterns[I] 
    else
      NewPattern := '';
    Result := StringReplace(Result, OldPatterns[I], NewPattern, ReplaceFlags);
  end;
end;

When a substring is located and substituted, it might not be replaced by the next substring. However, this implementation does not consider this issue and may return an unpredicted result. In addition, StringReplace function is slow in some cases.

Another solution

Here is another implementation of ReplaceAll function. It looks a bit complicated, but I have tried to explain the code by inline comments.

function ReplaceAll(const Subject: String;
  const OldPatterns, NewPatterns: array of String;
  IgnoreCase: Boolean): String;
var
  Buffer: String;       // the result buffer
  BufferPos: Integer;   // the number of characters in the buffer
 
  // appends characters to the result buffer
  procedure Append(P: PChar; Count: Integer);
  var
    NewBufferPos: Integer;  // tne number of characters in the buffer, after append
  begin
    // calculate the number of characters in the buffer after append
    NewBufferPos := BufferPos + Count;
    // if there is not enough space in the buffer, expand it
    if NewBufferPos > Length(Buffer) then
      SetLength(Buffer, NewBufferPos + 512);
    // copy character(s) to the buffer
    if Count = 1 then
      Buffer[NewBufferPos] := P^
    else
      Move(P^, Buffer[BufferPos + 1], Count * SizeOf(Char));
    // update buffer position
    BufferPos := NewBufferPos;
  end;
 
  {$IFDEF UNICODE}
  // workaround: to have pointer to the Unicode version of StrLComp function
  function StrLComp(const S1, S2: PChar; MaxLen: Integer): Integer;
  begin
    Result := SysUtils.StrLComp(S1, S2, MaxLen);
  end;
 
  // workaround: to have pointer to the Unicode version of StrLIComp function
  function StrLIComp(const S1, S2: PChar; MaxLen: Integer): Integer;
  begin
    Result := SysUtils.StrLIComp(S1, S2, MaxLen);
  end;
  {$ENDIF}
 
var
  Compare: function(const S1, S2: PChar; MaxLen: Integer): Integer;
  Len: Integer;    // length of the current substring to locate
  I: Integer;      // index of the current substring to locate
  S: PChar;        // pointer to the current search location
  E: PChar;        // pointer to the last search location
  P: PChar;        // pointer to the beginning of unchanged characters
begin
  // which compare function should be used? 
  // keep pointer to the appropriate function
  if IgnoreCase then
    @Compare := @StrLIComp   // case insensitive
  else
    @Compare := @StrLComp;   // case sensitive
  // pre-allocate the result buffer
  SetString(Buffer, nil, Length(Subject));
  // no characters in the buffer yet
  BufferPos := 0;
  // suppose the string is not going to be altered
  P := PChar(Subject);
  // find out end of the string as the last search position
  E := PChar(Subject) + Length(Subject);
  // start the search from the beginning of the string
  S := PChar(Subject);
  // while end of string is not reached
  while S < E do
  begin
    // iterate through each substring
    for I := Low(OldPatterns) to High(OldPatterns) do
    begin
      // keep length of the current substring
      Len := Length(OldPatterns[I]);
      // if substring is located at this position
      if (S + Len <= E) and (Compare(S, PChar(OldPatterns[I]), Len) = 0) then
      begin
        // any unchanged characters left behind?
        // append them to the buffer
        if P <> S then
          Append(P, S - P);
        // is there substitute for the substring?
        // append it to the buffer
        if (I <= High(NewPatterns)) and (Length(NewPatterns[I]) <> 0) then
          Append(PChar(NewPatterns[I]), Length(NewPatterns[I]));
        // skip the found substring
        Inc(S, Len);
        // suppose the string is not going to be altered any more
        P := S;
        // go one character back, because we advance one character later
        Dec(S);
        // we are done at this position, no need to search for the other substrings
        Break;
      end;
    end;
    // advance to the next search position
    Inc(S);
  end;
  // any unchanged characters left behind?
  // append them to the buffer
  if P <> S then
    Append(P, S - P);
  // adjust length of the result buffer
  SetLength(Buffer, BufferPos);
  Result := Buffer;
end;

If something in code is not clear or you have suggestions to improve it, please do not hesitate to drop me a line.

Reader's Comments »

  1. 1. By Nademox on June 4, 2011 at 13:51

    What’s the difference between your function and the Delphi function (in StrUtils unit): StringReplace ?

  2. 2. By Kambiz on June 4, 2011 at 23:15

    StringReplace replaces occurrences of a single pattern, but ReplaceAll accepts multiple search patterns.

  3. 3. By fernando on October 25, 2011 at 17:24

    StringReplace(before,’ a ‘,’ THE ‘,[rfReplaceAll, rfIgnoreCase]);

    just use the rfReplaceAll

    Don’t know exactly since witch version of Delphi is available

  4. 4. By Steffen on November 22, 2011 at 15:26

    can I use ReplaceAll() to remove all XML tags i.e. {something here} from an XML file for a simple XML to Text conversion?

  5. 5. By Kambiz on December 18, 2011 at 19:20

    Yes! If you want to remove several different tags all together, ReplaceAll() is a good choice.