Есть ли способ форматировать текстовый вывод в Ada

есть ли способ форматировать выводимую строку? Я пытаюсь получить довольно представление о следующем выходе

1: Ashley | 01033438392 | Wellington, New Zealand | 1987- 4-14  
2: Aloha | 01087651234 | Hawaii, United States of America | 1988- 9-23
3: Jack | 01082840184 | Beijing, China | 1989- 6-19

если бы я программировал на C, я бы сделал что-то вроде

printf("%10s | %11s | %20s | %4d-%2d-%2dn",name,phone,address,year,month,day);

можно ли сделать такое форматирование в Ada 05?

PS пожалуйста, просто игнорируйте имена, номера телефонов, адрес и дату рождения. Я придумал их за 30 секунд...

6 ответов


Это можно сделать, но механизмы немного громоздким и довольно многословно.
Обычно я пишу отдельные процедуры для обработки более сложных выходных данных, например даты, и использую их с остальной обработкой строк для ясности.

package Integer_IO is new Ada.Text_IO.Integer_IO (Integer);

procedure Output_Date ( Day : in Integer; Month: in Integer; Year: in Integer) is 
begin  
  Integer_IO.Put(Item => Day, Width => 2); 
  Text_IO.Put("-");
  Integer_IO.Put(Item => Month, Width => 2); 
  Text_IO.Put("-");
  Integer_IO.Put(Item => Year, Width => 4);
end Output_Date;

procedure Output_String ( Item : in String; 
                          Width : in Integer; 
                          Separator : in String := "|";
                          Truncate : Boolean := False) is 
  Field_Index : Integer := Text_IO.Col;
begin 
  if Item'length > Width and Truncate then 
    Text_IO.Put(Item(1..Width) & Separator);
  else 
    Text_IO.Put(Item) & Separator;
  end if;

  Text_IO.Set_Col ( Field_Index + Width + 1 );
end Output_String;

это приведет к введению полей фиксированной длины, которые при необходимости позволят усечь длинные строки или переместить последующие записи в следующую строку. Задает тип будет установить положение линии напишите, потенциально поместив его на следующую строку, если текущая позиция записи уже превысила запрошенную.

Я бросил усечение строки там как шанс использовать срезание массива и манипуляцию выводом Text_IO, но я обычно не поклонник усечения по умолчанию, так как, позволяя строке переполнять запрошенную ширину или отступ на следующей строке, как правило, делают ошибки форматирования более очевидными.

Итак, распечатайте что-то вроде вашей первой строки, учитывая код выше, может выглядеть примерно так:

Name  : String  := "Ashley"
Phone : String  := "01033438392"
Address: String := "Wellington, New Zealand"

Day    : Integer := 14;
Month : Integer  := 4;
Year   : Integer := 1987;

Output_String(Item=> Name,    Width => 10);
Output_String(Item=> Phone,   Width => 11);
Output_String(Item=> Address, Width => 20);
Output_Date(Day,Month,Year);

текст IO в Ada обычно громоздкий, но обычно имеет преимущество сделать то, что вы делаете относительно ясно.


обратите внимание, что в C++ в эти дни printf() находится на грани амортизации, в пользу использования потоков с форматерами потоков. Это удобно, но массово небезопасно (по крайней мере в паре смыслов этого слова). В наши дни разработчикам рекомендуется использовать потоки C++ (с их разнообразными манипуляторами) вместо этого.

в Ada вы можете манипулировать строками в очень похожем стиле на потоки C++ с помощью оператора catenation& где люди C++ используют вставку потока оператор (<<). В некотором смысле метод Ada лучше, потому что вы можете вложить catenated выражения, которые вы не можете сделать с выражениями, вставленными в поток.

проблема здесь заключается в том, что нет никаких удобный эквиваленты в C++ форматирования типа setfill(), hex и setw(). Там действительно должно быть, и (hex исключено) их не сложно написать самому, но пока они не существуют.

например, a setw()/setfill() эквивалент будет чем-то например:

Fill_Char : Character := ' ';

function Set_Fill (New_Fill : Character) return String is
begin
    Fill_Char := New_Fill;
    return "";
end Set_Fill;

--// Dumb tail-recursive implementation. 
function Set_Width(Source : in String; Width : in Positive) return String is
begin
    if Width <= Source'length then --'
        return Source;
    else 
        return Fill_Char & Set_Width(Source, Width - 1);
    end if;
end Set_Width;

Unfilled_String : constant String := "123456";
Filled_String : constant String := Set_Width(Unfilled_String & Set_Fill('0'), 8);
--// The above string should end up being "00123456"

если вы действительно хотите printf() интерфейс printf() вполне вызывается из Ada, конечно. Вы должны беспокоиться о переходе между строками размера Ada и строками с нулевым завершением, но это то, что Ada.Interfaces.C.Strings там.


Да есть. Хотя это не так просто, как в c.

посмотреть §A. 4.4 Обработка Строк Ограниченной Длины для создания строк предопределенного размера и использования integer'Image для преобразования чисел. Оператор & полезен для объединения строк и вывода с помощью ada.text_io.put_line().


вам может понравиться этот простой моделирование карточной игры использует Ada.Strings.Fixed формат оси меток ASCII-граф. См.function Label, который использует Tail и Trim в формате Integer'Image of a Lower и Upper значение.

код:

function Label (J : Integer) return String is
   use Ada.Strings; use Ada.Strings.Fixed;
   Lower : String := Integer'Image(J * Bin_Size);
   Upper : String := Integer'Image((J + 1) * Bin_Size);
begin
   return Tail(Trim(Lower, Left), 4, '0') & "-" &
      Tail(Trim(Upper, Left), 4, '0') & " |*";
end Label;

есть вспомогательные инструменты для этой конкретной установки формата.

пакета Ады.Text_IO.Integer_IO

procedure Put(Item : in Num; Width : in Field := Default_Width; Base : in Number_Base := Default_Base);

ставит поле с Item выровнено по правому и белому символу в качестве наполнителя. Где Width ширина поля и Base 10 как defualt.

пакета Ады.Веревка.Исправлено

function Head (Source : in String; Count : in Natural; Pad : in Character := Space) return String;
function Tail (Source : in String; Count : in Natural; Pad : in Character := Space) return String;

возвращает форматированную строку. Где Count ширина поля и Pad заполнитель для поле. Head выравнивает строку слева. Tail выравнивает строку направо.

пусть ширина столбца будет длиной 8 символов и используйте тире в качестве наполнителя.

Put_Line (Head ("Ashley", 8, '-'));
Put_Line (Head ("Aloha", 8, '-'));
Put_Line (Head ("Jack", 8, '-'));
Put_Line (Tail ("Ashley", 8, '-'));
Put_Line (Tail ("Aloha", 8, '-'));
Put_Line (Tail ("Jack", 8, '-'));

выход

Ashley--
Aloha---
Jack----
--Ashley
---Aloha
----Jack

атрибут discrete_type'

возвращает длину которого дискретного типа должна быть представлена в виде текста.

пример

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
with Ada.Calendar; use Ada.Calendar;

procedure Test is

   subtype Index is Positive range 95 .. 1223;

   procedure Put_Line ( I : in out Index; Name : String; Phone : Natural; Address : String; T : in out Time ) is
   begin
      Put (I, Index'Width);
      Put (": ");
      Put (Head (Name, 10, ' '));
      Put (" | ");
      Put (Tail (Phone'Img (Phone'Img'First + 1 .. Phone'Img'Last), 13, '0'));
      Put (" | ");
      Put (Head (Address, 20, ' '));
      Put (Year (T), Year_Number'Width);
      Put ("-");
      Put (Month (T), Month_Number'Width);
      Put ("-");
      Put (Day (T), Day_Number'Width);
      I := Positive'Succ (I);
      T := T + Duration (60 * 60 * 24 * 3);
      New_Line;
   end;

   I : Index := Index'First;
   Now : Time := Clock;
begin

   Put_Line (I, "Ashley", 1033438392, "Wellington, New Zealand", Now);
   Put_Line (I, "Aloha", 01087651234, "Hawaii, United States of America", Now);
   Put_Line (I, "Jack", 01082840184, "Beijing, China", Now);
   I := Index'Last - 3;
   Put_Line (I,"Ashley", 1033438392, "Wellington, New Zealand", Now);
   Put_Line (I,"Aloha", 01087651234, "Hawaii, United States of America", Now);
   Put_Line (I,"Jack", 01082840184, "Beijing, China", Now);

end;

выход

   95: Ashley     | 0001033438392 | Wellington, New Zeal 2015-  5- 24
   96: Aloha      | 0001087651234 | Hawaii, United State 2015-  5- 27
   97: Jack       | 0001082840184 | Beijing, China       2015-  5- 30
 1220: Ashley     | 0001033438392 | Wellington, New Zeal 2015-  6-  2
 1221: Aloha      | 0001087651234 | Hawaii, United State 2015-  6-  5
 1222: Jack       | 0001082840184 | Beijing, China       2015-  6-  8

I я бы рекомендовал создать тип для номера телефона, я не знаю, должен ли это быть строка или номер с нулями заголовка, номер телефона может иметь разную длину, я думаю.


вы также можете использовать GNAT.Formatted_String пакета. Он работает по крайней мере с Ada 2012 (не может проверить, существует ли он в Ada 2005). Он очень похож на использование printf, но с небольшой синтаксической разницей.

вот рабочий простой пример http://tpcg.io/iJwfWa:

with Ada.Text_IO; use Ada.Text_IO;
with GNAT.Formatted_String; use GNAT.Formatted_String;

procedure Hello is
  formatStr : constant String := "Hello, %-5s !";
  nameArray : constant array (1..3) of String  (1..4) :=
  (1 => "_Foo",
   2 => "_Bar",
   3 => "_Fuu");
  gnatFormat : Formatted_String := +(formatStr); -- initialisation needed
begin
  for index in nameArray'range loop
    gnatFormat := +(formatStr); --reaffectation needed
    Put_Line(-(gnatFormat & nameArray(index)));
  end loop;
end Hello;

выход :

(GNATMAKE v7.1.1 on https://www.tutorialspoint.com/compile_ada_online.php)
$gnatmake -o hello *.adb
gcc -c hello.adb
gnatbind -x hello.ali
gnatlink hello.ali -o hello

$hello
Hello,  _Foo !
Hello,  _Bar !
Hello,  _Fuu !

другой пример с вашими входами http://tpcg.io/iJwfWa :

with Ada.Text_IO; use Ada.Text_IO;
with GNAT.Formatted_String; use GNAT.Formatted_String;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

procedure Hello is
  formatStr : constant String := "%-10s | %11d | %35s | %4d-%2d-%2d";
  type T_element_descriptor is record
    name : Unbounded_String;
    number : Integer;
    city : Unbounded_String;
    birth_y : Integer; -- should use a date object ...
    birth_m : Integer;
    birth_d : Integer;
  end record;
  elementArray : constant array (1..3) of T_element_descriptor :=
  (1 => (To_Unbounded_String("Ashley"), 01033438392, To_Unbounded_String("Wellington, New Zealand"), 1987, 4, 14),
   2 => (To_Unbounded_String("Aloha"), 01087651234, To_Unbounded_String("Hawaii, United States of America"), 1988, 9, 23),
   3 => (To_Unbounded_String("Jack"),  01082840184, To_Unbounded_String("Beijing, China"), 1989, 6, 19));
  gnatFormat : Formatted_String := +formatStr;
begin
  for index in elementArray'range loop
    gnatFormat := +(formatStr);
    Put_Line(-(gnatFormat
    & To_String(elementArray(index).name)
    & elementArray(index).number
    & To_String(elementArray(index).city)
    & elementArray(index).birth_y
    & elementArray(index).birth_m
    & elementArray(index).birth_d
    ));
  end loop;
end Hello;

выходы:

(GNATMAKE v7.1.1 on https://www.tutorialspoint.com/compile_ada_online.php)
$gnatmake -o hello *.adb
gcc -c hello.adb
gnatbind -x hello.ali
gnatlink hello.ali -o hello

$hello
Ashley     |  1033438392 |             Wellington, New Zealand | 1987- 4-14
Aloha      |  1087651234 |    Hawaii, United States of America | 1988- 9-23
Jack       |  1082840184 |                      Beijing, China | 1989- 6-19

лучший пример приведен в элементе g-forstr.ads файл с gnat:

--  This package add support for formatted string as supported by C printf()

--  A simple usage is:
--
--     Put_Line (-(+"%s" & "a string"));
--
--  or with a constant for the format:
--
--     declare
--       Format : constant Formatted_String := +"%s";
--     begin
--       Put_Line (-(Format & "a string"));
--     end;
--
--  Finally a more complex example:
--
--     declare
--        F : Formatted_String := +"['%c' ; %10d]";
--        C : Character := 'v';
--        I : Integer := 98;
--     begin
--        F := F & C & I;
--        Put_Line (-F);
--     end;

--  Which will display:

--     ['v' ;         98]

--  Each format specifier is: %[flags][width][.precision][length]specifier

--  Specifiers:
--    d or i    Signed decimal integer
--    u         Unsigned decimal integer
--    o         Unsigned octal
--    x         Unsigned hexadecimal integer
--    X         Unsigned hexadecimal integer (uppercase)
--    f         Decimal floating point, lowercase
--    F         Decimal floating point, uppercase
--    e         Scientific notation (mantissa/exponent), lowercase
--    E         Scientific notation (mantissa/exponent), uppercase
--    g         Use the shortest representation: %e or %f
--    G         Use the shortest representation: %E or %F
--    c         Character
--    s         String of characters
--    p         Pointer address
--    %         A % followed by another % character will write a single %

--  Flags:

--    -         Left-justify within the given field width;
--              Right justification is the default.

--    +         Forces to preceed the result with a plus or minus sign (+ or -)
--              even for positive numbers. By default, only negative numbers
--              are preceded with a - sign.

--    (space)   If no sign is going to be written, a blank space is inserted
--              before the value.

--    #         Used with o, x or X specifiers the value is preceeded with
--              0, 0x or 0X respectively for values different than zero.
--              Used with a, A, e, E, f, F, g or G it forces the written
--              output to contain a decimal point even if no more digits
--              follow. By default, if no digits follow, no decimal point is
--              written.

--    ~         As above, but using Ada style based <base>#<number>#

--    0         Left-pads the number with zeroes (0) instead of spaces when
--              padding is specified.

--  Width:
--    number    Minimum number of characters to be printed. If the value to
--              be printed is shorter than this number, the result is padded
--              with blank spaces. The value is not truncated even if the
--              result is larger.

--    *         The width is not specified in the format string, but as an
--              additional integer value argument preceding the argument that
--              has to be formatted.
--  Precision:
--    number    For integer specifiers (d, i, o, u, x, X): precision specifies
--              the minimum number of digits to be written. If the value to be
--              written is shorter than this number, the result is padded with
--              leading zeros. The value is not truncated even if the result
--              is longer. A precision of 0 means that no character is written
--              for the value 0.

--              For e, E, f and F specifiers: this is the number of digits to
--              be printed after the decimal point (by default, this is 6).
--              For g and G specifiers: This is the maximum number of
--              significant digits to be printed.

--              For s: this is the maximum number of characters to be printed.
--              By default all characters are printed until the ending null
--              character is encountered.

--              If the period is specified without an explicit value for
--              precision, 0 is assumed.

--    .*        The precision is not specified in the format string, but as an
--              additional integer value argument preceding the argument that
--              has to be formatted.