               ПРОГРАММИРОВАНИЕ 1979, No. 6. - Р. 92-94
*******************************************************************

                                                     УДК 681.145.2

              ГЕНЕРАЦИЯ ПРОГРАММЫ  ПЕЧАТИ ШАПКИ ДОКУМЕНТА
                     ПО ОПИСАНИЮ СТРУКТУРЫ ШАПКИ

                            Н. Н. Безруков

     При программировании задач обработки данных на языках высокого
уровня (в частности ПЛ/1 или Коболе)  приходится  выполнять  весьма
трудоемкую работу по программированию печати шапок документов. Тру-
доемкость этой работы в значительной степени  связана  с  тем,  что
шапку приходится  описывать  детально в виде составляющих ее строк.
Это приводит  к необходимости предварительного  составления  макета
шапки. В  случае изменения шапки (замена текста графы,  изъятие или
добавление граф и т.д.) приходится составлять новый макет и перера-
батывать в  соответствии с ним программу печати шапки.  Поскольку в
процессе написания и эксплуатации программы шапка документов  меня-
ется довольно часто, приходится выполнять значительный объем работы
по перепрограммированию.  Кроме того, часто на печать выдаются нес-
колько документов, шапки которых отличаются лишь незначительно. При
программировании печати шапки путем задания составляющих  ее строк
объем работы  по программированию печати нескольких аналогичных ша-
пок фактически совпадает с объемом работы по программированию тако-
го же числа совершенно различных шапок.
     Составление шапки можно значительно упростить,  если описывать
ее не в виде составляющих ее строк,  а составляющих ее граф.  Такой
подход позволяет использовать  для  программирования  печати  шапок
универсальную программу, автоматически генерирующую шапку (вместе с
программой для ее печати) по описанию составляющих  ее  граф  и  их
взаимного расположения.
     Для описания шапки в виде составляющих  ее  граф  предлагается
использовать формальный  язык,  называемый в дальнейшем структурным
описанием шапки  (СТРОШ),  который  аналогичен  средствам   задания
структур в ПЛ/1 и Коболе. Отметим, что аналогичный подход использо-
ван в системе РЯОД [1]. Ниже приводится описание синтаксиса СТРОШ с
помощью БНФ.  В  используемом варианте БНФ фигурные скобки означают
итерацию, а квадратные - факультативные конструкции

     <СТРОШ - программа>::=1[<заголовок>] <описание графы> {<описа-
ние графы>}
     <заголовок>::=<литерал>{<литерал>}
     <описание графы>::=<номер уровня><текст графы><тип графы>
     <номер уровня>::=<целое без знака>
     <текст графы>::=<литерал>{<литерал>}
     <тип графы>::='Г' ! 'Ш' <ширина графы>
     <литерал>::='<символ>{<символ>}'
     <ширина графы>::=<целое без знака>

     Семантику структурного описания шапки  рассмотрим  на  примере
шапки документа, содержащего некоторые сведения о книгах (рис 1).

   -----------------------------------------------------------------
   |                      |    СВЕДЕНИЯ        |          |        |
   |      СВЕДЕНИЯ        |      ОБ            |          |        |
   |         О            |    АВТОРЕ          |          |        |
   |    ПРОИЗВЕДЕНИИ      |--------------------|          |        |
   |----------------------|      ФАМИЛИЯ       |          |        |
   |              |  ГОД  |         ИМЯ        |    ГОД   |        |
   | НАИМЕНОВАНИЕ |ИЗДАНИЯ|      ОТЧЕСТВО      | РОЖДЕНИЯ |  ЦЕНА  |
   -----------------------------------------------------------------
                                Рис 1.


     Структурное описание шапки имеет вид

     1
       2   'СВЕДЕНИЯ' 'О' 'ПРОИЗВЕДЕНИИ' 'Г'
         3 'НАИМЕНОВАНИЕ' 'Ш' 14
         3 'ГОД' 'ИЗДАНИЯ' 'Ш' 7
       2   'СВЕДЕНИЯ' 'ОБ' 'АВТОРЕ' 'Г'
         3 'ФАМИЛИЯ' 'ИМЯ' 'ОТЧЕСТВО' 'Ш' 20
         3 'ГОД' 'РОЖДЕНИЯ' 'Ш' 10
       2   'ЦЕНА' 'Ш' 8

     В СТРОШ  каждая графа имеет свой номер уровня,  отражающий ие-
рархию данной графы в структуре шапки.  Заголовок имеет самую высо-
кую иерархию и наименьший номер уровня,  равный единице  (в  данном
примере текст  заголовка  отсутствует).  Ширина граф задается после
служебного слова 'Ш' и указывается только для элементных граф, т.е.
граф не  имеющих подчиненных.  Текст,  содержащийся в каждой графе,
представляется в виде литералов,  причем каждый  литерал  описывает
одну строку графы.  Графа, имеющая подчиненные, дополнительно отме-
чается служебным слдовом 'Г' (этот элемент языка отражает специфику
реализованного транслятора).
     Такой способ описания шапки удобен тем,  что, во-первых, отпа-
дает необходимость составления макета шапки перед ее программирова-
нием,  во-вторых,  добавление,  изъятие  или   корректировка   граф
выполняется предельно просто и не затрагивает описание граф, остаю-
щихся без изменения, в-третьих, для описания аналогичных шапок мож-
но прямо использовать описания всех совпадающих граф.  Поэтому раз-
работка транслятора с языка СТРОШ на ПЛ/1 (или  Кобол) представляет
определенный интерес.
     Автором реализован транслятор с  языка  СТРОШ  на  ПЛ/1.  Этот
транслятор, названный СТРОШ1, написан полностью на ПЛ/1 и его объем
составляет порядка 100 операторов ПЛ/1.  Такой малый объем трансля-
тора связан  с  тем,  что  был  найден алгоритм построения шапки по
структурному описанию за один проход.
     Основная идея алгоритма состоит в том, чтобы формировать шапку
параллельно с синтаксическим разбором  структурного  описания,  ис-
пользуя рекурсивный  спуск  по номерам уровня.  При этом необходимо
обеспечить совпадение структуры дерева синтаксического разбора, не-
явно представленного последовательностью рекурсивных вызовов [2] со
структурой шапки.  С этой целью графы,  имеющие  одинаковые  номера
уровней, обрабатываются  итеративно.  Рекурсивный вызов выполняется
только в случае появления номера уровня, большего, чем номер уровня
обрабатываемой графы  (с  целью  определить суммарный размер группы
граф, подчиненных данной).  Таким образом,  построение группы  граф
имеющих одинаковый номер уровня, происходит на одном уровне вложен-
ности рекурсивных вызовов,  что и  обеспечивает  совпадение  неявно
представленного дерева синтаксического разбора со структурой шапки.
     На рис.  2 приведен протокол  работы  транслятора  СТРОШ1  при
построении шапки по приведенному выше структурному описанию. Транс-
лятор СТРОШ1 эксплуатируется с сентября 1978 г. и опыт, накопленный
за этот период показал, что язык СТРОШ весьма удобен для программи-
рования печати шапок документов.  Вместе с тем возможности, предос-
тавляемые транслятором  СТРОШ1 в ряде случаев оказались недостаточ-
ными. Поэтому в настоящее время разрабатывается  более  совершенный
транслятор с языка СТРОШ (СТРОШ2),  который обладает более широкими
возможностями по управлению форматом шапки (в СТРОШ1  из  сервисных
функций имеется только центровка текста относительно границ графы).
 Пользователь сможет задавать символы,  используемые  как  горизон-
тальный и вертикальный разделители, смещение шапки относительно на-
чала строки, нумерацию элементарных граф и др.

                ОПИСАНИЕ СТРУКТУРЫ ШАПКИ
     1
       2   'СВЕДЕНИЯ' 'О' 'ПРОИЗВЕДЕНИИ' 'Г'
         3 'НАИМЕНОВАНИЕ' 'Ш' 14
         3 'ГОД' 'ИЗДАНИЯ' 'Ш' 7
       2   'СВЕДЕНИЯ' 'ОБ' 'АВТОРЕ' 'Г'
         3 'ФАМИЛИЯ' 'ИМЯ' 'ОТЧЕСТВО' 'Ш' 20
         3 'ГОД' 'РОЖДЕНИЯ' 'Ш' 10
       2   'ЦЕНА' 'Ш' 8


     Данному описанию соответствует шапка:

   -----------------------------------------------------------------
   |                      |    СВЕДЕНИЯ        |          |        |
   |      СВЕДЕНИЯ        |      ОБ            |          |        |
   |         О            |    АВТОРЕ          |          |        |
   |    ПРОИЗВЕДЕНИИ      |--------------------|          |        |
   |----------------------|      ФАМИЛИЯ       |          |        |
   |              |  ГОД  |         ИМЯ        |    ГОД   |        |
   | НАИМЕНОВАНИЕ |ИЗДАНИЯ|      ОТЧЕСТВО      | РОЖДЕНИЯ |  ЦЕНА  |
   -----------------------------------------------------------------

     Данная шапка имеет ширину 65,  высоту 9, и может быть получена
с помощью следующей ПЛ-программы:

  PUT EDIT((65)'-',
 '|                      |   СВЕДЕНИЯ         |          |        |',
 '|      СВЕДЕНИЯ        |      ОБ            |          |        |',
 '|         О            |    АВТОРЕ          |          |        |',
 '|    ПРОИЗВЕДЕНИИ      |--------------------|          |        |',
 '|----------------------|      ФАМИЛИЯ       |          |        |',
 '|              |  ГОД  |         ИМЯ        |    ГОД   |        |',
 '| НАИМЕНОВАНИЕ |ИЗДАНИЯ|      ОТЧЕСТВО      | РОЖДЕНИЯ |  ЦЕНА  |',
  (65)'-')(SKIP,A);

                                Рис 2.

     В литералах,  описывающих строки графы, первый символ рассмат-
ривается как управляющий. Помимо центровки текста предусмотрены уп-
равляющие символы, обеспечивающие разрядку, вертикальное построение
текста и некоторые другие полезные возможности компоновки  текста в
графе. Кроме того,  в шапке допускается наличие полей, значения ко-
торых определяются динамически.
     СТРОШ-компилятор записывает  сгенерированную  ПЛ-программу для
печати шапки в задаваемый пользователем раздел  библиотеки исходных
текстов. На  этапе  компиляции  этот  текст  вставляется в исходную
программу с помощью оператора %INCLUDE препроцессора. Таким образом
внесение изменений в шапку фактически не требует модификации исход-
ной программы: корректируется лишь вставляемый в исходную программу
раздел библиотеки.
     В заключение отметим, что поскольку в реальных задачах процент
повторяющихся граф в различных шапках достаточно высок,  можно соз-
дать "банк граф (групп граф)".  Такой подход, на наш взгляд, позво-
ляет в  значительной степени унифицировать процесс создания и моди-
фикации программ печати шапок документов. При этом компоновка шапок
из отдельных граф (групп граф) может выполняться с помощью конкате-
нации разделов соответствующей библиотеки или  с  помощью  простого
макрогенератора.

                              ЛИТЕРАТУРА

     1. А.А.  Стогний,  Е.Л. Ющенко, В.И. Войтко, Е.И. Машбиц, Л.В.
Верник, Н.Н. Безруков. Система решения задач обработки данных, ори-
ентированная на непрофессиональных пользователей ЭВМ.- В сб.: Алго-
ритмы и организация решения экономических задач,  вып.14.  М.,"Ста-
тистика",1979.
     2. Д.   Грис.   Конструирование   компиляторов   для  цифровых
вычислительных машин. М. "Мир", 1975.


                                                         Приложение

         /***************************************************/
         /*     З А Г О Т О В К А    N E A T A B            */
         /* БЛОК КОМПИЛЯЦИИ СТРОШ В ПЛ/1                    */
         /* БЛОК ОФОРМЛЕН В ВИДЕ НЕЗАВИСИМОЙ ПРОГРАММЫ      */
         /* ТОЧКА ВХОДА NEATAB                              */
         /* ИСХОДНЫЕ ДАННЫЕ - ФАЙЛ SYSIN                    */
         /* ПРОТОКОЛ КОМПИЛЯЦИИ И РЕЗУЛЬТАТ - ФАЙЛ SYSPRINT */
         /***************************************************/

         NEATAB: PROC OPTIONS(MAIN);

         DCL STR(50)/* ПОЛЕ ДЛЯ ШАПКИ */ CHAR(120) INIT((50)(120)' '),
            DELSTR/* СТРОКА РАЗДЕЛИТЕЛЕЙ */ CHAR(120) INIT((120)'-'),
            FIELD/*ТЕКСТ ОДНОЙ СТРОКИ ГРАФЫ */ CHAR(120) VAR,
            VD/* ВЕРТИКАЛЬНЫЙ РАЗДЕЛИТЕЛЬ */ CHAR(1) INIT('|'),
            (POS#/* ПОСЛЕДНИЙ ЗАПОЛНЕННЫЙ СИМВОЛ В STR */ INIT(2),
            WIDTH/* ОБЩАЯ ШИРИНА ШАПКИ */ INIT(0),
            BLANK#,/* ЧИСЛО ПРОБЕЛОВ ДЛЯ ЦЕНТРОВКИ */
            LEVEL,/* ТЕКУЩИЙ НОМЕР УРОВНЯ */
            DEPTH/* ОБЩАЯ ВЫСОТА ШАПКИ */ INIT(0)) BIN FIXED;

            PUT SKIP LIST('ОПИСАНИЕ СТРУКТУРЫ ШАПКИ');
            GET LIST(LEVEL);
            PUT SKIP EDIT(LEVEL)(X(3*LEVEL),F(2));
            CALL TITLE(DEPTH,WIDTH);
            WIDTH=WIDTH+1;

         /* ПОСТРОЕНИЕ ВЕРТИКАЛЬНЫХ РАЗДЕЛИТЕЛЕЙ */
            DO I=1 TO WIDTH;
               DO K=1 BY 1 TO DEPTH WHILE(SUBSTR(STR(K),I,1)=' ');
                  SUBSTR(STR(K),I,1)=VD;
                  END;
               END;
         /* ВЫВОД РЕЗУЛЬТАТОВ НА ПЕЧАТЬ */
            PUT SKIP(3) LIST('ДАННОМУ ОПИСАНИЮ СООТВЕТСТВУЕТ ШАПКА:');
            SUBSTR(STR(DEPTH),1,WIDTH)=DELSTR;
            DO I=DEPTH BY -1 TO 1; PUT SKIP EDIT(STR(I))(A); END;
            PUT EDIT('ДАННАЯ ШАПКА ИМЕЕТ ШИРИНУ ',
            WIDTH,', ВЫСОТУ ',DEPTH,
            'И МОЖЕТ БЫТЬ НАПЕЧАТАНА ОПЕРАТОРОМ:')
            (SKIP(3),2(A,F(3)),SKIP,A);

            CLOSE FILE(SYSPRINT);
            OPEN FILE (SYSPRINT) LINESIZE(80);

            PUT EDIT('PUT EDIT((',WIDTH,')''-'',')(A,F(3));
            DO K=DEPTH-1 BY -1 TO 2;
               PUT EDIT('''',STR(K),''',')(SKIP,A,A(WIDTH),A);
               END;
            PUT EDIT('(',WIDTH,')''-''))(SKIP,A);')(SKIP,A,F(3),A);

         TITLE: PROC(TOP,WIDTH) RECURSIVE;
         DCL(TOP,/* ВЕРХНЯЯ СТРОКА ГРАФ, ПОДЧИНЕННЫХ ДАННОЙ */
            WIDTH,/* ШИРИНА ГРАФЫ */
            LEVELTOP/* ВЕРХНЯЯ СТРОКА ГРАФ ДАННОЙ ГРУППЫ */ INIT(0),
            CUR,/* ИНДЕКС В STR ТЕКУЩЕЙ СТРОКИ ГРАФЫ */
            LM,/* ЛЕВАЯ ГРАНИЦА ГРАФЫ */
            TOTAL/* ОБЩАЯ ШИРИНА ГРУППЫ ГРАФ ДАННОГО УРОВНЯ */ INIT(0),
            OLDLEVEL,/* НОМЕР УРОВНЯ ТЕКУЩЕЙ ГРУППЫ ГРАФ */
            HEIGHT/* ВЫСОТА ГРАФЫ (ГРУППЫ ГРАФ) */ ) BIN FIXED,
            TEXT(20)/* ТЕКСТ ГРАФЫ */ CHAR(120) VAR;

            ON ENDFILE(SYSIN) LEVEL=1;

            GET LIST(LEVEL); PUT SKIP EDIT(LEVEL)(X(3*LEVEL),F(2));
            OLDLEVEL=LEVEL;
            DO WHILE(LEVEL>=OLDLEVEL);
               DO HEIGHT=1 BY 1;
                  GET LIST(FIELD); PUT EDIT(' ''',FIELD,''' ')(A);
                  IF FIELD='Ш' THEN DO; /* ЭЛЕМЕНЬАРНАЯ ГРАФА */
                     GET LIST(WIDTH); PUT EDIT(WIDTH)(F(3));
                     GET LIST(LEVEL);
                     PUT SKIP EDIT(LEVEL)(X(3*LEVEL),F(2));
                     HEIGHT=HEIGHT-1; WIDTH=WIDTH+1;
                     LM=POS#; POS#=POS#+WIDTH;
                     CUR=1;
                     GOTO #PRINT;
                     END;
                  ELSE IF FIELD='Г' THEN DO; /* ГРУППОВАЯ ГРАФА */
                     HEIGHT=HEIGHT-1; LM=POS#;
                     CALL TITLE(TOP,WIDTH);
                     CUR=TOP;
                     GOTO #PRINT;
                     END;
                  ELSE TEXT(HEIGHT)=FIELD;
                  END;
         #PRINT: /* ПОСТРОЕНИЕ ГРАФЫ */
               SUBSTR(STR(CUR),LM,WIDTH-1)=DELSTR;
               DO M=HEIGHT BY -1 TO 1;
                  CUR=CUR+1;
                  BLANK#=FLOOR((WIDTH-LENGTH(TEXT(M)))/2);
                  SUBSTR(STR(CUR),LM+BLANK#)=TEXT(M);
                  END;
               TOTAL=TOTAL+WIDTH; LEVELTOP=MAX(LEVELTOP,CUR);
               END;
            WIDTH=TOTAL; TOP=LEVELTOP+1;
         END TITLE;
         END NEATAB;
