In Delphi would this be sufficient to tabify a string?
StringReplace(Text, StringOfChar(' 'TabWidth), #9, [rfReplaceAll])
No — that one-liner is not safe.
It blindly replaces every run of exactly
The result is that alignment is destroyed as soon as the line already contains something before the blanks.
It blindly replaces every run of exactly
TabWidth blanks by a single TAB, even if those blanks are in the middle of a line or span a column boundary that is not a multiple of TabWidth.The result is that alignment is destroyed as soon as the line already contains something before the blanks.
Example (TabWidth = 8)
plain
Copy
' Hello' // 4 spaces + “Hello”becomes
plain
Copy
#9'Hello' // 1 tab + “Hello”and the text now starts in column 8 instead of column 4.
What you really want is:
- Split the string into lines.
- For every line, walk from left to right and only convert sequences of blanks that reach (or cross) the next 8-column boundary into the minimum number of tabs+spaces that keeps the following text in the same column.
- Re-assemble the lines.
The RTL does not contain a ready-made “tabify” routine, so you have to write it yourself (or use an existing library such as SynEdit’s
ConvertSpacesToTabs / ConvertTabsToSpaces functions).A minimal correct tabifier could look like this:
delphi
Copy
function TabifyLine(const ALine: string; ATabWidth: Integer): string;
var
I, Col, RunStart: Integer;
SpaceCnt: Integer;
begin
Result := '';
Col := 1; // 1-based column
I := 1;
while I <= Length(ALine) do
begin
if ALine[I] = ' ' then
begin
RunStart := I;
SpaceCnt := 0;
while (I <= Length(ALine)) and (ALine[I] = ' ') do
begin
Inc(SpaceCnt);
if (Col + SpaceCnt - 1) mod ATabWidth = 0 then
begin
// we have reached a tab stop -> emit a tab
Result := Result + #9;
Col := Col + SpaceCnt;
SpaceCnt := 0;
RunStart := I + 1;
end;
Inc(I);
end;
// blanks that did not reach a tab stop stay as spaces
if SpaceCnt > 0 then
begin
Result := Result + Copy(ALine, RunStart, SpaceCnt);
Col := Col + SpaceCnt;
end;
end
else
begin
Result := Result + ALine[I];
if ALine[I] = #9 then
Col := ((Col - 1) div ATabWidth + 1) * ATabWidth + 1
else
Inc(Col);
Inc(I);
end;
end;
end;
function TabifyText(const AText: string; ATabWidth: Integer): string;
var
Lines: TStringList;
I: Integer;
begin
Lines := TStringList.Create;
try
Lines.Text := AText;
for I := 0 to Lines.Count - 1 do
Lines[I] := TabifyLine(Lines[I], ATabWidth);
Result := Lines.Text;
finally
Lines.Free;
end;
end;So the short answer:
StringReplace(Text, StringOfChar(' ',TabWidth), #9, [rfReplaceAll]) is not sufficient; use a real tabifier instead.