Страница Ивана Рощина > Статьи >

© Иван Рощин, Москва

ZXNet: 500:95/462.53
E-mail: bestview@mtu-net.ru
WWW: http://www.ivr.da.ru

Программа для форматирования текстов

Радиомир. Ваш компьютер» 6/2005)

Введение
Текст программы
Комментарии
Руководство пользователя
Литература

Введение

Иногда приходится иметь дело с текстовыми файлами, в которых каждый абзац представлен в виде одной длинной строки (например, такие файлы получаются при использовании моего конвертора DOC –> TXT — см. [1]), а надо преобразовать их в «обычный» вид, когда абзацы разбиты на строки, ширина которых не больше указанной, а первая строка каждого абзаца выделена отступом заданной величины. Для этой цели я написал небольшую программу на Turbo C, которую предлагаю вашему вниманию.

Текст программы

/* =============================
     File: "txt_form.c"
     Compiler: Turbo C 2.01
 ============================= */

#include <stdio.h>
#include <stdlib.h>

/* ==================== Описание функций ==================== */

int fgetc_2 (FILE* f);
FILE* fopen_1 (char* name, char* mode);
void fcloseall_1 ();
int fgetc_1 (FILE* f);
void ungetc_1 (int c, FILE* f);
void fputc_1 (int c, FILE* f);
long ftell_1 (FILE* f);
void fseek_1 (FILE* f, long offset, int whence);

/* ================== Пошла сама программа ================== */

int main (int argc, char *argv[])
{
 FILE *f_src, *f_dst;
 unsigned int text_width; /* Ширина формируемого текста (т.е.
                             максимальная длина строки). */
 unsigned int text_indent; /* Добавляемый абзацный отступ. */
 long begin_str; /* Смещение начала текущей строки в исходном
                    текстовом файле. */
 long len_str;   /* Длина текущей строки. */
 long cut_str;   /* Смещение последней (на данный момент)
                    позиции в исходном текстовом файле,
                    соответствующей возможному месту переноса
                    текущей строки (т.е. в этой позиции пробел).
                    Если cut_str=-1 - значит, такой позиции пока
                    не обнаружено. */
 long i;         /* Счётчик. */
 int c;          /* Текущий символ. */
 int first_str;  /* Признак "выводим первую строку абзаца" (1 -
                    да, 0 - нет. */
 long n=0;       /* Номер обрабатываемого абзаца. */

 printf ("\n\
+----------------------------------------------------------+\n\
|                      Text Formatter                      |\n\
|----------------------------------------------------------|\n\
|(c) Ivan Roshin, Moscow, 26 Sep 2004                      |\n\
|E-mail: bestview@mtu-net.ru      WWW: http://www.ivr.da.ru|\n\
+----------------------------------------------------------+\n\
");

 if (argc!=5)
 {
  printf ("\nSyntax: txt_form text_width text_indent \
source_file destiny_file\n\n");
  exit(1);
 }
 if (sscanf(argv[1],"%u",&text_width)!=1)
 {
  printf ("\nIncorrect text_width!\n\n"); exit (1);
 }
 if (sscanf(argv[2],"%u",&text_indent)!=1)
 {
  printf ("\nIncorrect text_indent!\n\n"); exit (1);
 }
 f_src=fopen_1(argv[3],"rb");
 f_dst=fopen_1(argv[4],"wb");

/* ====================== Главный цикл ====================== */

 while (c=fgetc_2(f_src),c!=EOF)  /* Пока не конец текста. */
 {
  printf ("\rParagraph: %li",++n);
  if (c==13) /* Пустой абзац. */
  {
   fputc_1 (13,f_dst);
   fputc_1 (10,f_dst);
   continue; /* К началу главного цикла. */
  }

  /* Непустой абзац. */

  ungetc_1(c,f_src);
  for (i=0;i<text_indent;i++) fputc_1(' ',f_dst); /* Отступ. */
  len_str=text_indent;
  first_str=1;
  begin_str=ftell_1(f_src);
  cut_str=-1;

  /* Обработка абзаца. */

  while (c=fgetc_2(f_src),(c!=13)&&(c!=EOF))
  /* Пока не конец абзаца или текста. */
  {
   len_str++;

   /* Если текущий символ - пробел, то в этом месте строка
      может быть разорвана; запоминаем позицию пробела. */

   if (c==' ') cut_str=ftell_1(f_src)-1;

   /* Если длина строки превысила максимальную, и при этом есть
      место, в котором строка может быть разорвана, то выдаём
      в формируемый файл часть строки от начала до места
      разрыва и затем код конца строки; после этого положение
      начала строки в исходном файле - это позиция после того
      пробела, где был разрыв. */

   if ((len_str>text_width)&&(cut_str!=-1))
   {
    fseek_1 (f_src,begin_str,SEEK_SET);
    for (i=begin_str;i<cut_str;i++)
    {
     fputc_1(fgetc_2(f_src),f_dst);
    }
    fputc_1 (13,f_dst);
    fputc_1 (10,f_dst);
    begin_str=cut_str+1;
    fseek_1 (f_src,begin_str,SEEK_SET);
    len_str=0;
    first_str=0;
    cut_str=-1;
   }
  }

  /* Дошли до конца абзаца. Если есть остаток строки
     (len_str!=0), то выводим его и код конца строки. */

  if (len_str!=0)
  {
   long temp=ftell_1(f_src);         /* Запомнили позицию. */
   fseek_1 (f_src,begin_str,SEEK_SET);
   for (i=0;i<len_str-first_str*text_indent;i++)
   {
    fputc_1(fgetc_2(f_src),f_dst);
   }
   fseek_1 (f_src,temp,SEEK_SET);    /* Восстановили позицию. */
   fputc_1 (13,f_dst);
   fputc_1 (10,f_dst);
  }

 }  /* Конец главного цикла. */

/* ================== Весь текст обработан ================== */

 fcloseall_1 ();
 printf ("\nOK!\n\n");
 exit (0);
}

/* =============================================================
   Функция fgetc_2 - если в файле f с текущей позиции находится
   пара байтов 13,10, то функция читает оба байта и возвращает
   13, иначе читает один байт и возвращает его значение.
 ============================================================ */

int fgetc_2 (FILE* f)
{
 int c;
 c=fgetc_1(f);
 if (c==13)
 {
  c=fgetc_1(f);
  if (c!=10) ungetc_1(c,f);
  return 13;
 }
 return c;
}

/* =============================================================
   Аналоги некоторых функций для работы с файлами (fopen,
   fcloseall, fgetc, ungetc, fputc, ftell, fseek). При ошибке
   прерывают выполнение программы, печатая соответствующее
   сообщение; таким образом, после вызова этих функций не надо
   проверять, была ли ошибка.
 ============================================================ */

FILE* fopen_1 (char* name, char* mode)
{
 FILE* f;
 char* s;
 char* s1="read";
 char* s2="write";

 f=fopen(name,mode);
 if (f==NULL)
 {
  if (mode[0]=='r') s=s1; else s=s2;
  printf ("\nCan't open file \"%s\" for %s!\n\n",name,s);
  exit (1);
 }
 return (f);
}

void fcloseall_1 ()
{
 if (fcloseall()==EOF)
 {
  printf ("\nClose error!\n\n"); exit (1);
 }
}

int fgetc_1 (FILE* f)
{
 int c;
 c=fgetc(f);
 if ferror(f) {printf ("\nRead error!\n\n"); exit (1);}
 return c;
}

void ungetc_1 (int c, FILE* f)
{
 if (ungetc(c,f)==EOF)
 {
  printf ("\nUNGETC error!\n\n"); exit (1);
 }
}

void fputc_1 (int c, FILE* f)
{
 if (fputc(c,f)==EOF) {printf ("\nWrite error!\n\n"); exit (1);}
}

long ftell_1 (FILE* f)
{
 long a;
 a=ftell(f);
 if (a==-1L) {printf ("\nFTELL error!\n\n"); exit (1);}
}

void fseek_1 (FILE* f, long offset, int whence)
{
 if (fseek(f,offset,whence)!=0)
 {printf ("\nPosition error!\n\n"); exit (1);}
}
Скачать исполняемый файл программы (для DOS) (8 КБ ZIP)
Скачать листинг программы в текстовом виде (2 КБ ZIP)

Комментарии

При компиляции этой программы непосредственно из среды Turbo C вы получите exe-файл. Но можно получить более короткий com-файл, если воспользоваться имеющимся в Turbo C компилятором «tcc.exe». Ниже приведена командная строка для вызова этого компилятора (вместо «c:\work\tc2» вы, конечно, должны подставить свой путь к каталогу Turbo C):

c:\work\tc2\tcc.exe -O -Z -mt -lt -Ic:\work\tc2\include -Lc:\work\tc2\lib txt_form.c

Опции «-O» и «-Z» в этой строке — соответственно включение оптимизации переходов и включение оптимизации использования регистров, что также способствует уменьшению размера исполняемого файла программы.

Чтобы ещё уменьшить размер, можно воспользоваться свободно распространяемым компрессором исполняемых файлов UPX (http://upx.sourceforge.net), который может сжимать DOS-файлы в форматах COM и EXE (этим, конечно, его возможности не ограничиваются). Я использую последнюю на момент написания этой статьи версию UPX — 1.24. Изучив указания в документации UPX о том, как достичь максимальной степени сжатия, я создал командный файл «compress.bat», содержащий две команды (каждая команда сжимает файл своим методом):

upx --best --crp-ms=999999 --nrv2b --8086 -o result_2b.com txt_form.com
upx --best --crp-ms=999999 --nrv2d --8086 -o result_2d.com txt_form.com

После запуска этого командного файла UPX создаст два сжатых файла — «result_2b.com» и «result_2d.com». Из них надо выбрать наименьший, а другой — стереть. После этого исходный файл «txt_form.com» уже не нужен, его можно стереть и дать его имя оставшемуся сжатому файлу. Исполняемый файл программы, который выложен на моей web-странице и на сайте журнала, получен именно таким образом.

Между прочим, при сжатии com-файлов длины получающихся файлов «result_2b.com» и «result_2d.com» у меня почему-то всегда совпадали, а вот при сжатии exe-файлов — действительно различались.

И ещё одно примечание: указываемая при вызове UPX опция «--8086» нужна, чтобы добавляемый к сжатой программе код распаковщика использовал только инструкции процессора 8086 — тогда сжатая программа сможет распаковаться даже на компьютере с таким процессором (например, на IBM PC XT). Если же эта возможность для вас не имеет значения, то опцию «--8086» можно убрать и, таким образом, ещё чуть-чуть сократить размер сжатой программы (так как распаковщик будет более коротким). В этом случае для запуска сжатой программы потребуется компьютер с как минимум 286-м процессором.

Руководство пользователя

Всё очень просто: при запуске программы в командной строке сначала надо указать параметры форматирования (первое число — ширина получаемого текста в символах, т.е. максимальная длина строки; второе число — величина абзацного отступа, т.е. сколько пробелов надо добавлять в начале первой строки каждого абзаца), а затем — имя исходного файла (он должен находиться в текущем каталоге) и имя формируемого файла (он создаётся также в текущем каталоге). Если запустить программу без аргументов или с неправильным их количеством, то она выдаст краткую справку о том, какие аргументы и в каком порядке надо указывать.

Пример командной строки:

txt_form 64 4 text_1.txt text_2.txt

В процессе работы программа отображает порядковый номер форматируемого в данный момент абзаца и таким образом даёт понять, что она не зависла. :)

Программа возвращает операционной системе код завершения: 0 — успешное завершение, 1 — произошла ошибка при работе с файлами (при возникновении такой ошибки программа выводит соответствующее сообщение и завершает свою работу).

Замечу, что если в обрабатываемом тексте попадётся слишком длинное слово (длиннее, чем указанная при запуске программы ширина текста), то это слово не будет разорвано, и длина строки с этим словом окажется больше максимальной.

В обрабатываемых файлах код конца строки может быть как парой байтов 13, 10, так и одним байтом 13 — это позволяет получать форматированный txt-файл из doc-файла в кодировке Windows-1251 (в таких doc-файлах, по моим наблюдениям, абзацы представлены как раз в виде длинных строк, заканчивающихся байтом 13).

В формируемом текстовом файле строки заканчиваются парой байтов 13, 10.

Литература

  1. И.Рощин. «Простейший конвертор DOC –> TXT». «Радиомир. Ваш компьютер» 10/2003.
Страница Ивана Рощина > Статьи >