Binary version of StringReplace
I'm trying to run AnsiStrings.StringReplace on a RawByteString holding a blob of data, some of which needs to be replaced. It would work, except that inside StringReplace it converts my string to a PAnsiChar, and so the search ends up bailing out as soon as it hi开发者_C百科ts the first #0 byte inside the blob.
I'm looking for a routine that works just like StringReplace, but is safe to use on blobs that may contain null bytes. Does anyone know of one?
I'd guess the "Offending" function in StringReplace is AnsiPos->AnsiStrPos
So... I guess short of an already working solution, I'd copy/paste the StringReplace code and change AnsiPos for something else. (i.e. AnsiStrings.PosEx)
function RawByteStringReplace(const S, OldPattern, NewPattern: AnsiString;
Flags: TReplaceFlags): AnsiString;
var
SearchStr, Patt, NewStr: AnsiString;
Offset: Integer;
begin
//Removed the uppercase part...
SearchStr := S;
Patt := OldPattern;
NewStr := S;
Result := '';
while SearchStr <> '' do
begin
Offset := AnsiStrings.PosEx(Patt, SearchStr);
if Offset = 0 then
begin
Result := Result + NewStr;
Break;
end;
Result := Result + Copy(NewStr, 1, Offset - 1) + NewPattern;
NewStr := Copy(NewStr, Offset + Length(OldPattern), MaxInt);
if not (rfReplaceAll in Flags) then
begin
Result := Result + NewStr;
Break;
end;
SearchStr := Copy(SearchStr, Offset + Length(Patt), MaxInt);
end;
end;
I have not performed extensive testing, but I think that this code works.
type
TDynByteArray = packed array of byte;
procedure BufReplace(var BufStart: PByte; var BufLen: cardinal; const Find: TDynByteArray; const Replace: TDynByteArray);
var
pos: PByte;
BufEnd: PByte;
i: Integer;
Match: boolean;
begin
{$POINTERMATH ON}
if Find = nil then Exit;
pos := BufStart;
BufEnd := BufStart + BufLen;
while pos < BufEnd do
begin
Match := false;
if pos^ = Find[0] then
if pos + length(Find) < BufEnd then
begin
Match := true;
for i := 1 to high(Find) do
if PByte(pos + i)^ <> Find[i] then
begin
Match := false;
break;
end;
end;
if Match then
begin
if length(Find) = length(Replace) then
Move(Replace[0], pos^, length(Replace))
else
begin
if length(Replace) < length(Find) then
begin
Move(Replace[0], pos^, length(Replace));
MoveMemory(pos + length(Replace), pos + length(Find), BufEnd - pos - length(Find));
dec(BufLen, length(Find) - length(Replace));
ReallocMem(BufStart, BufLen);
end
else
begin
inc(BufLen, length(Replace) - length(Find));
ReallocMem(BufStart, BufLen);
MoveMemory(pos + length(Replace), pos + length(Find), BufEnd - pos - length(Find));
Move(Replace[0], pos^, length(Replace))
end;
end;
inc(pos, length(Replace));
end
else
inc(pos);
end;
end;
To test it:
procedure TestIt;
var
len: cardinal;
a, b: TDynByteArray;
begin
len := 16;
GetMem(buf, len);
FillChar(buf^, 16, $11);
PByte(buf + 3)^ := $55;
SetLength(a, 2);
a[0] := $55;
a[1] := $11;
SetLength(b, 1);
b[0] := $77;
BufReplace(buf, len, a, b);
end;
Hmm. Seems like it couldn't be too hard to write your own. Just iterate through the buffer until you find a match on the first byte. Then see if the subsequent bytes match. If so, you found it, now replace. Keep going or quit, depending on what you need. Obviously simpler if the sizes are the same. If not, then you can set up a second buffer and copy bytes from the base buffer into the new buffer.
精彩评论