Finding the width of a tab char - tab width

Hi everyone

If a file exists that have multiple lines of (can be of any value) however each string contains tab chars of variable length.
I need to replace the tab values with equivelant spaces so that the file looks the same.

Let me try and visually display this for you.

abcde+#9+ abcde+#9+#9+a (on this line the tab values are all a width of 4)
abcde+#9+ abcde+#9+#9+a (on this line the first tab has a width of 3 the second a width of 5 and the the 3rd a width of 4)
abcde+#9+ abcde+#9+#9+a (on this line the first tab has a width of 8 the second a width of 2 and the the 3rd a width of 4)
abcde+#9+ abcde+#9+#9+a (on this line the first tab has a width of 5 the second a width of 4 and the the 3rd a width of 4)

now for me to make the resulting file look the same i would need to rplace each tab with enough spaces to make up th width of the tab char.

In the real life enviroment I would not know where the tabs exist in the file no would i know in advance what the width value of the tab chars would be. I can not import the file into a visual control and variable fonts would not be an issue. Thus what i need is an algorithym of some type the produce the result that i need.

Your help as always will be appreciated.
LVL 1
RideyAsked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

ZhaawZSoftware DeveloperCommented:
As far as I know, width of tab-char depends on editor that you're using to view contents of a file.
If you're using Notepad, it also depends on a font - different fonts show different result.
quilleyCommented:
> nor would i know in advance what the width value of the tab chars would be

In order to solve this problem there must be a way of discovering the tab widths. Are they contained in the data? If so, how are they encoded? If not, how does the application displaying the existing data know how wide the tabs are?
ZhaawZSoftware DeveloperCommented:
hmm... if you want to replace those tabs with spaces, i suppose that you want to get the resulting text that looks like table, right? with rows and fine columns.
in this case calculating width of each column (in chars) would be better than calculating width of tabs.
JavaScript Best Practices

Save hours in development time and avoid common mistakes by learning the best practices to use for JavaScript.

RideyAuthor Commented:
quilley

The file when read as a text file indicates the tab chars only as #9, however when opening the file in notepad the tab widths differ, fundamentally the problem is that I do not know how to identify the widthof the tab chars as there is no indication of the actual width when using readln.

Zhaawz

I know that the font influences the tab width, and that is why i want to do the conversion without using a visual component.

The table is only an indication of where the tab chars would be in the line and what the width of the tab would be. In other words what I want the resulting text file to be is as follows :
abcde     abcde        a
abcde    abcde         a
abcde         abcde      a
abcde      abcde        a

this however is only an example since the tab char can be anyware in the actual line of text(string).


Thanks for the intrest shown so far.
Regards
quilleyCommented:
Er... now I think I understand what you're after. If you calculate a maximum value for the width of a field based on the data in the file, you can format it by padding the fields with spaces up to that width.

Here's my solution; to try it paste two Memo controls on a form, add the functions and event procedures below, run the program and paste your data into Memo1.

function MaxFieldWidth(const S: string): Integer;
var
  N, Count: integer;
begin
  Result := 0;
  Count := 0;

  for N := 1 to Length(S) do
    if S[N] in [#9, #10, #13] then
    begin
      if Count > Result then
        Result := Count;
      Count := 0
    end
    else
      Inc(Count);

  if Count > Result then
    Result := Count;
end;

function ReplaceTabs(const S: string; Width: Integer): string;
var
  N, Start: integer;
begin
  Result := '';
  Start := 1;

  for N := 1 to Length(S) do
    if S[N] in [#9, #10, #13] then
    begin
      Result := Result + Copy(S, Start, N - Start);
      if S[N] = #9 then
        Result := Result + StringOfChar(' ', Width + Start - N)
      else
        Result := Result + S[N];
      Start := N + 1;
    end;

  Result := Result + Copy(S, Start, MaxInt);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Font.Name := 'Courier New';
  Memo2.Font.Name := 'Courier New';
  Memo1.WantTabs := True;
end;

procedure TForm1.Memo1Change(Sender: TObject);
begin
  with Memo1 do
    Memo2.Text := ReplaceTabs(Text, MaxFieldWidth(Text) + 4);
end;

Note 1: The "+ 4" in the last line is the number of *extra* spaces to add between columns.

Note 2: This code uses the same width for all columns. It's possible to calculate a different width for each column, but I thought that might complicate things unnecessarily.
quilleyCommented:
P.S. Just came across a TTabList class I wrote some time ago for working with Tab-delimited text files. Its a derivation of TStringList which allows you to reference columns as well as rows. You can even assign a value to a column that doesn't exist; that row will be extended to accommodate the value. It's not the most efficient way of coding it, but it's simple and works fine for small data sets.

First here's an example which displays a single column of data in a list box:

procedure TForm1.Button1Click(Sender: TObject);
var
  TabList: TTabList;
  N: Integer;
begin
  TabList := TTabList.Create;
  TabList.LoadFromFile('Test.txt');
  for N := 0 to TabList.Count - 1 do
    ListBox1.Items.Add(TabList.Fields[N, 0]);    // 0 = First column
end;

Now here's the class. Enjoy :-)

unit TabList;

interface

uses SysUtils, Classes;

type
  TTabList = class(TStringList)
  private
    function GetField(Index, FieldIndex: Integer): string;
    procedure SetField(Index, FieldIndex: Integer; const Value: string);
  public
    function IndexOfValue(FieldIndex: Integer; const S: string): Integer;
    property Fields[Index, FieldIndex: Integer]: string read GetField
      write SetField;
  end;

implementation

const
  SBadFieldIndex = 'Field index out of bounds';

{ TTabList }

function CountTabs(const S: string): Integer;
var
  N: Integer;
begin
  Result := 0;
  for N := 1 to Length(S) do
    if S[N] = #9 then
      Inc(Result);
end;

function GetTabPos(const S: string; Index: Integer): Integer;
var
  I: Integer;
begin
  Result := 0;
  I := 0;
  while (Result < Length(S)) and (I < Index) do
  begin
    Inc(Result);
    if S[Result] = #9 then
      Inc(I);
  end;
  if I <> Index then
    Result := 0;
end;

function GetFieldValue(const S: string; Index: Integer): string;
var
  I, J: Integer;
begin
  if Index < 0 then
    raise Exception.Create(SBadFieldIndex);

  I := GetTabPos(S, Index);
  if (I > 0) or (Index = 0) then
  begin
    J := GetTabPos(S, Index + 1);
    if J = 0 then
      J := Length(S) + 1;
    Result := Copy(S, I + 1, J - I - 1);
  end
  else
    Result := '';
end;

procedure SetFieldValue(var S: string; Index: Integer; const Value: string);
var
  I, J: Integer;
begin
  if Index < 0 then
    raise Exception.Create(SBadFieldIndex);

  I := GetTabPos(S, Index);
  if (I = 0) and (Index > 0) then
  begin
    S := S + StringOfChar(#9, Index - CountTabs(S));
    I := Length(S);
  end;
  J := GetTabPos(S, Index + 1);
  if J = 0 then
    J := Length(S) + 1;
  S := Copy(S, 1, I) + Value + Copy(S, J, MaxInt);
end;

function TTabList.GetField(Index, FieldIndex: Integer): string;
begin
  Result := GetFieldValue(Strings[Index], FieldIndex);
end;

function TTabList.IndexOfValue(FieldIndex: Integer; const S: string): Integer;
var
  N: Integer;
begin
  Result := -1;
  for N := 0 to Count - 1 do
    if CompareText(S, Fields[N, FieldIndex]) = 0 then
    begin
      Result := N;
      break;
    end;
end;

procedure TTabList.SetField(Index, FieldIndex: Integer; const Value: string);
var
  S: string;
begin
  S := Strings[Index];
  SetFieldValue(S, FieldIndex, Value);
  Strings[Index] := S;
end;

end.

P.S. Perhaps I should have called FieldIndex "Column"? Oh well, never mind...

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
quilleyCommented:
Sorry, forgot to free object in example:

  TabList := TTabList.Create;
  with TabList do
  try
    LoadFromFile('Test.txt');
    for N := 0 to Count - 1 do
      ListBox1.Items.Add(Fields[N, 0]);    // 0 = First column
  finally
    Free;
  end;

Better?
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.