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.
What’s the difference between your function and the Delphi function (in StrUtils unit): StringReplace ?
StringReplace replaces occurrences of a single pattern, but ReplaceAll accepts multiple search patterns.
StringReplace(before,’ a ‘,’ THE ‘,[rfReplaceAll, rfIgnoreCase]);
just use the rfReplaceAll
Don’t know exactly since witch version of Delphi is available
can I use ReplaceAll() to remove all XML tags i.e. {something here} from an XML file for a simple XML to Text conversion?
Yes! If you want to remove several different tags all together, ReplaceAll() is a good choice.