[Webinar] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 444
  • Last Modified:

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.
0
Ridey
Asked:
Ridey
  • 4
  • 2
1 Solution
 
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.
0
 
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?
0
 
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.
0
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 
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
0
 
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.
0
 
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...
0
 
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?
0

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

  • 4
  • 2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now