© Иван Рощин, Москва
| ZXNet | : 500:95/462.53 |
| : 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);}
}
При компиляции этой программы непосредственно из среды Turbo C вы
получите
c:\work\tc2\tcc.exe -O -Z -mt -lt -Ic:\work\tc2\include -Lc:\work\tc2\lib txt_form.c
Опции
Чтобы ещё уменьшить размер, можно воспользоваться свободно распространяемым
компрессором исполняемых файлов UPX (http://upx.sourceforge.net), который
может сжимать
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» уже не нужен, его можно стереть и
дать его имя оставшемуся сжатому файлу. Исполняемый файл программы, который
выложен на моей
Между прочим, при сжатии
И ещё одно примечание: указываемая при вызове UPX опция
Всё очень просто: при запуске программы в командной строке сначала надо указать параметры форматирования (первое число ширина получаемого текста в символах, т.е. максимальная длина строки; второе число величина абзацного отступа, т.е. сколько пробелов надо добавлять в начале первой строки каждого абзаца), а затем имя исходного файла (он должен находиться в текущем каталоге) и имя формируемого файла (он создаётся также в текущем каталоге). Если запустить программу без аргументов или с неправильным их количеством, то она выдаст краткую справку о том, какие аргументы и в каком порядке надо указывать.
Пример командной строки:
txt_form 64 4 text_1.txt text_2.txt
В процессе работы программа отображает порядковый номер форматируемого в данный момент абзаца и таким образом даёт понять, что она не зависла. :)
Программа возвращает операционной системе код завершения: 0 успешное завершение, 1 произошла ошибка при работе с файлами (при возникновении такой ошибки программа выводит соответствующее сообщение и завершает свою работу).
Замечу, что если в обрабатываемом тексте попадётся слишком длинное слово (длиннее, чем указанная при запуске программы ширина текста), то это слово не будет разорвано, и длина строки с этим словом окажется больше максимальной.
В обрабатываемых файлах код конца строки может быть как парой байтов
13, 10, так и одним байтом 13 это позволяет получать
форматированный
В формируемом текстовом файле строки заканчиваются парой байтов 13, 10.