Составить программу перекодировки текстового файла,
Задание 7.05. Перекодировка текстов из MS-DOS в Windows
Составить программу перекодировки текстового файла, подготовленного редактором MS-DOS, в кодовую страницу 1251 для Windows. Следует попытаться, по возможности сохранить вид таблиц, сформированных с помощью символов псевдографики.
Совет 1 (общий)
Практически все программы перекодировки символов типа байт-в-байт используют 256-байтный словарь соответствий. По коду j перекодируемого символа из j-гo байта словаря извлекается код, заменяющий исходный символ.
Совет 2 (общий)
Для максимального сохранения вида разделительных линий таблиц предлагаются следующие замены:
Совет 3 (общий)
Вообще говоря, словари перекодировки формируются в виде массива из 256 констант. Но для того, чтобы сделать структуру словаря более прозрачной, мы сформируем его программным путем с помощью процедуры to_win.
Совет 4 (Си, Паскаль)
Имя перекодируемого файла может быть указано в качестве параметра командной строки. Если этот параметр при запуске программы перекодировки не был задан, то программа должна запросить имя файла. Имя выходного файла для простоты можно зафиксировать, например tmpwin. txt.
Программа 7_05.bas
DECLARE SUB TOWIN(T01251() AS INTEGER)
DIM T0125K256) AS INTEGER
INPUT "Задайте имя файла - ",SOURCE$
TOWIN T01251()
OPEN SOURCE$ FOR INPUT AS #1
OPEN "TMPWIN.TXT" FOR OUTPUT AS #2
DO WHILE NOT EOF(l)
LINE INPUT #1, A$
FOR J=l TO LEN(A$)
MID$ (A$, J, 1) =CHR$ (T01251 (ASC (MID$ (A$, J, 1) ) ) )
NEXT J
PRINT #2, A$
LOOP
CLOSE 1,2
END
SUB TOWIN(T012510 AS INTEGER)
' Сохраняем первую половину таблицы ASCII
FOR J=0 TO 127: T01251(J)=J: NEXT J
' Увеличиваем на 64 коды букв от "А" до "n"
FOR J=128 TO 175: Т01251{J)=J+64: NEXT J
' Заменяем все символы псевдографики знаком "+"
FOR J=176 TO 223: Т01251(J)=ASC("+"): NEXT J
' Заменяем одинарную вертикальную черту
t01251(179)=ASC("|")
' Заменяем двойную вертикальную черту
t01251(186)=ASC("|")
' Заменяем одинарную горизонтальную черту
t01251(196)=ASC("-")
' Заменяем двойную горизонтальную черту
t01251(205)=ASC("=")
' Увеличиваем на 16 коды букв от "р" до "я"
FOR J=224 TO 239: Т01251(J)=J+16: NEXT J
t01251(240)=168: ' Заменяем код буквы "ё"
t0125К241) =184: ' Заменяем код буквы "Ё"
END SUB
Программа 7_05.с
#include <stdio.h>
#include <conio.h>
#include <string.h>
void to_win(void);
unsigned char to!251[256];
main(int narg, char **argv) {
FILE *fin,*fout;
unsigned char str[80],source[80];
int j,len;
if(narg==2) strcpy(source,argv[l]);
else {
printf("\n Задайте имя исходного файла - ");
scanf("%s",source); }
to_win();
fin=fopen(source,"rt");
fout=fopen("tmpwin.txt","wt");
while (Ifeof(fin)) {
fgets(str,80,fin);
len=strlen(str);
for(j=0; j<len; j++)
str[j]=to!251[str[j]];
fputs(str,fout);
}
fcloseall() ;
}
/*------------------------------------*/
void to_win(void) {
/* Формирование словаря перекодировки из MS-DOS в Windows */
int i ; /* Сохраняем первую половину таблицы ASCII */
for (i=0; i<128; i++) to1251[i]=i;
/* Увеличиваем на 64 коды букв от "А" до "n" */
for(1=128; i<176; i++)
to1251[i]=i+64;
/* Заменяем все символы псевдографики знаком "+" */
for(1=176; i<224; i++) to1251 [!] = ' + ';
и
/* Заменяем одинарную вертикальную черту */
to1251[179]='|'; /* Заменяем двойную вертикальную черту */
to1251[186]='|'; /* Заменяем одинарную горизонтальную черту */
to1251[196]='-'; /* Заменяем двойную горизонтальную черту */
to1251[205]='='; /* Увеличиваем на 16 коды букв от "р" до "я" */
for(i=224; К256; i++)
to1251[i]=i+16;
to1251[2401=168; /* Заменяем код буквы "ё" */
to1251[241]=184; /* Заменяем код буквы "Ё" */
return; }
Программа 7_05.pas
program translate; var
to1251:array [0..255] of char;
fin,fout:text;
str:string[803;
source:string[80];
j,len:integer; procedure to_win;
var
i:integer; begin
for i:=0 to 127 do
to1251[i]:=chr(i);
for i:=128 to 175 do
to1251[i]:=chr(1+64);
for i:=176 to 223 do
to1251[i]:='+';
to1251[179]:='|';
to1251[186]:='|';
to1251[196]:= '-';
to1251[205]:= '=';
for i:=224 to 239 do
to1251[i]:=chr(i+16);
to1251[240]:=chr(168);
to1251[241]:=chr(184);
end;
begin
if(ParamCount=2) then source:=ParamStr(1)
else begin
write('Задайте имя исходного файла - ');
readln(source);
end; to_win;
assign(fin,source);
reset(fin);
assign(fout, 'tmpwin.txt');
rewrite(fout);
while (not eof(fin)) do begin
readln(fin,str);
len:=length(str);
for j:=0 to len do
str[j]:=to1251[ord(str[j])];
writeln(fout,str);
end;
close (fin);
close(fout);
end.
Задание 7.06. Телефонный справочник
Составить программу, которая открывает текстовый файл для дозаписи и формирует в нем список, содержащий фамилию абонента и его телефон. Сортировкой фамилий в алфавитном порядке и исключением дублирующихся строк мы пока заниматься не будем.
Совет 1 (QBasic)
Для записи двух символьных значений в одну строку текстового файла разумно воспользоваться оператором WRITE #, т. к. в этом случае мы получим доступ к каждой компоненте строки дискового файла.
Совет 3 (Паскаль)
Паскаль не позволяет открыть несуществующий файл на дозапись. Поэтому перед обращением к процедуре append приходится отключать системный контроль за ошибками ввода/вывода. Если открытие на дозапись не состоялось, то в первый раз приходится открывать файл notebook на запись. При чтении два текстовых значения в одной файловой строке здесь, конечно, можно разделить — телефон занимает фиксированное количество позиций в конце строки, между фамилией и телефоном находится какое-то количество пробелов. Однако для вывода на экран это не требуется.
Программа 7_06.bas
CLS
OPEN "notebook" FOR APPEND AS #1
'Ввод данных с клавиатуры
DO
INPUT "ФАМИЛИЯ: ",Name$
INPUT "ТЕЛЕФОН: ",Phone$
WRITE #1, Name$,Phone$
INPUT "Добавим ";R$
LOOP WHILE LEFT$(R$,l)="f1"
CLOSE #1
CLS
OPEN "notebook" FOR INPUT AS #1
PRINT "Список абонентов в файле:"
DO WHILE NOT EOF(l)
'LINE INPUT #1, NP$ ' Чтение строки целиком
'PRINT NP$ ' Вывод данных на экран
INPUT #1, А$, В$ ' Чтение строки по компонентам
PRINT А$, В$' ' Вывод данных на экран
LOOP
CLOSE #1
END
Программа 7_06.с
#include <conio.h>
#include <stdio.h>
main() {
FILE *f;
int k;
char r,Name[20],Phone[10]; clrscr();
'f=fopen("notebook","at");
m: /* Ввод данных с клавиатуры */
printf("\n ФАМИЛИЯ: ");
scanf("%s",Name); printf("ТЕЛЕФОН: ");
scanf("%s",Phone);
fprintf(f,"%-20s %10s\n",Name,Phone);
printf("Добавим (д/н) - ");
r=getche(); if(г=='д')
goto m;
fclose(f);
clrscr();
f=fopen("notebook","rt");
printf("\пСписок абонентов в файле:\n") ;
while(!feof (f)) {
fscanf(f,"%20s %10s\n",Name,Phone);
printf("%-20s %10s\n",Name,Phone); }
fclose(f);
getch(); }
Программа 7_06.pas
program notebook;
uses Crt;
var
f:text;
R,Name,Phone:string; begin
clrscr;
assign(f, 'notebook ');
{$!-} append(f); {$!+}
if IOResult <> 0 then rewrite(f);
{ Ввод данных с клавиатуры }
repeat
write('ФАМИЛИЯ: ');
readln(Name);
write('ТЕЛЕФОН: ');
readln(Phone);
writeln(f,Name, ' ':15-length(Name), Phone:10);
write{'Добавим ');
readln(R);
until r[l] <> 'д ';
close(f);
clrscr;
reset(f);
writeln('Список абонентов в файле: ');
repeat
readln(f,r);
{ Чтение строки целиком } writeln(r);
{ Вывод данных на экран } until eof(f);
close(f);
readln;
end.
Задание 7.07. Создание резервной копии файла
Написать программу, которая извлекает из командной строки имя файла и создает в том же каталоге резервную копию файла с расширением bak.
Совет 1 (общий)
Чтобы избежать нежелательного влияния управляющих символов с кодами о о ("Возврат каретки"), ОА ("Перевод строки"), 1А ("Признак конца файла") целесообразно рассматривать резервируемые данные независимо от их происхождения как двоичный файл.
Совет 2 (QBasic)
Так как в этой системе работа с командной строкой не реализована, мы ограничимся вводом имени исходного файла по запросу программы.
Совет 3 (Си)
Функция strchr (namel,'.') определяет позицию точки в имени исходного файла. Точнее, ее значение равно указателю на позицию точки, если таковая в имени namel содержится. В противном случае функция strchr возвращает NULL. В первом случае с помощью функции strncpy в строку name2 копируется начало имени до символа '.'и скопированная часть имени принудительно завершается признаком конца строки (символом 0x0).
Совет 4 (Паскаль)
Отключение системного контроля за ошибками ввода/вывода ({$!-}) перед открытием исходного файла сделано для того, чтобы взять на себя проверку несостоявшейся операции и выдать пользователю более осмысленное сообщение. Функция fsplit расчленяет полную спецификацию файла на путь до
каталога, содержащего исходный файл (Dir), собственно имя файла (Name) и его расширение (Ext). Программа будет работать быстрее, если размер буфера для копирования очередной порции увеличить до 32 768 (кратность 512 здесь желательна, т. к. это число совпадает с длиной физического сектора).
Программа 7_07.bas
CLS : DIM k AS STRING*!
INPUT "Задайте имя файла - ", NAME1$
OPEN NAME1$ FOR BINARY AS #1
К = INSTR(NAME1$, ".")
IF К = 0 THEN NAME2$ = NAME1$
ELSE NAME2$ = LEFT$(NAME1$, К - 1)
NAME2$ = NAME2$ + ".ВАК"
PRINT NAME2$, К
OPEN NAME2$ FOR BINARY AS §2
DO
GET fl, , k
PUT #2, , k
LOOP UNTIL (EOF(l))
END
Программа 7_07.с
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 512
main(int narg, char **argv) {
FILE *f1,*f2;
int rd,wr;
char buf[N];
char namel[80],name2[80];
char *point;
if(narg < 2)
{ printf("\n He задано имя файла"); exit(0); }
strcpy(namel,argv[l]);
fl=fopen(namel,"rb");
if(fl==NULL)
{ printf("\n Файл %s не найден",namel); exit(0); }
point=strchr(namel, '. ');
if(point) {strncpy(name2,namel,point-namel};
name2[point-namel]=0x0; }
else strcpy(name2,namel); strcat(name2,".bak");
f2=fopen(name2,"wb");
do {
rd=fread(buf,l,N,fl);
wr=fwrite(buf,1,rd,f2); }
while (rd!=0 && rd == wr) ;
fclose(fl);
fclose(f2); }
Программа 7_07.pas
program reserve; uses Dos; const
N=512; var
fl,f2 : file;
rd,wr : word;
buf : array [1..N] of byte;
namel, name2 : PathStr;
dir : DirStr;
name : NameStr;
ext : ExtStr; begin
if ParamCount=0 then
begin writeln('He задано имя файла');
exit;
end;
namel:=ParamStr(1);
assign(f1,name1);
{$!-} reset(f1,l); {$I+}
if IOResult <> 0 then
begin writeln('Файл ',namel, ' не найден');
exit;
end;
fsplit(namel,dir,name,ext);
name2:=dir+name+'.bak';
assign(f2,name2);
rewrite(f2,1); repeat
blockread(fl,buf,N,rd);
blockwrite(f2,buf,rd,wr);
until (rd=0) or (rd <> wr) ;
close(fl);
close(f2);
end.
Задание 7.08. Выдача каталога на экран
Составить программу, которая извлекает из текущего каталога имена файлов с заданным расширением (например, *.pas) и выводит их на экран подобно директиве MS-DOS (dir *.pas), суммируя количество и длину обнаруженных файлов.
Совет 1 (QBasic)
Система QBasic не включает в свой состав функции или процедуры для работы с файловой системой MS-DOS, присущие алгоритмическим языкам более высокого уровня типа Си или Паскаля. Поэтому мы ограничимся тем, что из программы на Basic можно выполнить любую директиву операционной системы по оператору SHELL. Программа 7_07.bas решает поставленную задачу, однако имена файлов из текущего или указанного каталога остаются недоступными для последующей программной обработки.
Совет 2 (Си)
Для извлечения файлов из текущего или указанного каталога можно воспользоваться функциями findfirst(char *path, struct ffblk sr, int attr) и findnext (struct ffblk sr}. Аргумент path является указателем на маску отбора файлов (например — "*.с"), расширенную спецификацию маски (например — "с: \bc\source\* .с") или просто путь (например — "c:\bc"). Аргумент sr представляет собой структуру типа ffblk, на поля которой заносятся атрибуты найденного набора. Эта структура описана в файле dir. h и имеет следующие поля:
struct ffblk {
char ff_reserved[21]; //резерв на будущее
char ff_attrib; //атрибут набора данных
unsigned ff_ftime; //упакованное время создания
unsigned ff_fdate; //упакованная дата создания
long ff fsize; //длина набора в байтах
char ff_name[13]; //имя набора
};
Третий аргумент определяет атрибуты, которыми должен быть снабжен отбираемый набор данных, и может принимать разумную логическую сумму из значений, определенных в файле dos. h и задаваемых в Си следующими константами:
Цикл поиска нужных наборов данных начинается с обращения к процедуре f indf irst (поиск первого объекта) и повторяется многократными обращениями к процедуре findnext (поиск следующего объекта). Аргумент sr в обоих обращениях должен быть один и тот же. Цикл поиска продолжается до тех пор, пока функция поиска возвращает нулевое значение. Как только очередная попытка окажется неудачной, функция поиска возвращает значение -1.
Совет 3 (Си)
Самый кропотливый момент в программе связан с выводом упакованных значений времени и даты последнего обновления файла, т. к. в системной библиотеке Си подходящей функции распаковки нет. Поэтому в программе 7_07.с введены две структуры битовых полей, эквивалентные способам упаковки даты (struct dat) и времени (struct tim). Обратите внимание на последовательность описания битовых полей — от младших разрядов слова к старшим (а не наоборот). С помощью объединения union данные указанных структур накладываются на беззнаковые целочисленные (unsigned) переменные, в которые предварительно переписываются упакованные данные. Последующее извлечение содержимого битовых полей использует соответствующие составные имена, например из упакованного значения даты v.d извлекаются разряды, в которых находится номер дня v.c.day. Единственное изменение содержимого поля v.с.year связано с тем, что функции поиска в MS-DOS возвращают значение года, уменьшенное на 1980.
Вообще говоря, упакованные данные можно было бы извлечь и другим способом — логически умножить упакованную величину на константу, содержащую сплошные единицы в соответствующем поле, и сдвинуть полученный результат на нужное число разрядов вправо.
Совет 4 (Паскаль)
Для извлечения файлов из текущего или указанного каталога можно воспользоваться процедурами FindFirst(path, .attr, sr) И FindNext(sr), вклю
ченными в состав модуля DOS. Аргумент path задает маску отбора файлов (например, "*.pas"), расширенную спецификацию маски (например, "с: \tp\source\* .раs") или просто путь (например, "c:\tp"). Второй аргумент определяет атрибут, которым должен быть снабжен отбираемый набор данных, и может принимать логическую сумму из следующих значений, задаваемых в Паскале мнемоническими константами:
Аргумент sr представляет собой запись типа searchRec, на поля которой заносятся атрибуты найденного набора. Этот тип описан в модуле DOS и имеет следующую структуру:
type
SearchRec=record
Fill:array [1..21] of byte;
Attr:byte; {атрибут набора данных}
Time:longint; {упакованные дата и время создания}
Size:longint; {длина набора в байтах}
Name:string[12]; (имя набора} end;
Цикл поиска нужных наборов данных начинается с обращения к процедуре FindFirst (поиск первого объекта) и повторяется многократными обращениями к процедуре FindNext (поиск следующего объекта). Аргумент sr в обоих обращениях должен быть один и тот же. Цикл поиска продолжается до тех пор, пока системная переменная DosError принимает нулевое значение. Как только очередная попытка окажется неудачной, в DosError заносится 18.
Совет 5 (Паскаль)
Распаковка даты и времени осуществляется с помощью системной процедуры UnpackTime, первым аргументом которой является упакованный набор, возвращаемый процедурами поиска, а вторым — запись с распакованными полями типа DateTime, описанная в модуле Dos. Для придания единообразия колонкам с числовыми данными полезно малые (k < 10) числа дней, месяцев, часов, минут и секунд предварять лидирующим нулем. В случае необходимости такая добавка выполняется функцией Zero.
Программа 7_08.bas
SHELL "dir *.bas" END
Программа 7_08.с
#include <stdio.h>
#include <conio.h>
#include <dir.h>
#include <dos.h>
main() {
struct ffblk sr;
int k,nf=0;
long lf=0;
struct tim
{
unsigned sec:5;
unsigned min:6;
unsigned hour:5;
};
struct dat {
unsigned day:5;
unsigned month:4;
unsigned year:7;
};
union {struct tim a;
unsigned b;}u;
union {struct dat c;
unsigned d; }v;
clrscr();
printf("Имя файла Длина Дата Время");
k=findfirst("*.c",&sr,FA_ARCH);
while (k==0) {
u.b=sr.ff_ftime; v.d=sr.ff_fdate; nf++;
lf+= sr.ff_fsize;
printf("\n%12s %61d ",sr.ff_name,sr.ff_fsize) ;
printf(" %02d/%02d/%4d ",v.c.day,v.c.month,1980+v.c.year);
printf(" %02d:%02d",u.a.hour, u.a.min);
k=findnext(Ssr); }
printf("\n%d файлов занимают %ld байт",nf,If);
getch(); }
Программа 7_08.pas
program dir;
uses Crt,Dos; var
dt:DateTime;
sr:SearchRec;
const
nf:integer=0;
If:longint=0;
function Zero(k:byte):string;
var
s: string;
begin
Str(k,s);
if k>9 then Zero:=s else Zero:= '0 '+s; end;
begin clrscr;
writeln('Имя файла Длина Дата Врекя');
FindFirst('*.pas',AnyFile,sr);
while DosError=0 do
begin with sr,dt do
begin inc(nf);
If:=lf+Size;
UnpackTime(Time, dt);
write (Name:12, ' ',Size:6, ' ');
write(Zero(Day), '/',Zero(Month), '/',Year:4, ' ');
writeln(Zero(Hour), ': ',Zero(Min), ': ',Zero (Sec)); end;
FindNext(sr); end;
writeln(nf, ' файлов занимают ',1f, ' байт');
readln; end.
Задание 7.09. Сдвиг содержимого текстового файла
Составить программу сдвига содержимого каждой строки текстового файла на заданное число позиций вправо. Это может оказаться полезным для формирования левого поля нужной ширины перед выводом содержимого файла на принтер по командам операционной системы PRINT или COPY.
Совет 1 (QBasic)
Для считывания строк из текстового файла нужно использовать оператор LINE INPUT, иначе головные пробелы в считываемых строках будут игнорироваться и каждая строка будет обрезана по первому же -пробелу после значащих символов.
Совет 2 (Си)
В функции ind_copy начало массива str предварительно расписывается заданным количеством пробелов, а затем в его оставшуюся часть считывается очередная строка исходного файла.
Совет 3 (Паскаль)
В приведенных ниже двух вариантах программы использованы два разных подхода к чтению данных из текстового файла. В программе 7_08.pas очередная строка исходного файла считывается целиком и также целиком переписывается в выходной файл. Предварительно в ту же запись заносится п пробелов за счет указателя ширины поля после текстового операнда, содержащего единственный пробел. В программе 7_08a.pas, алгоритм которой менее рационален, строка исходного файла читается посимвольно до тех пор, пока функция eoln (f1) не обнаруживает признак конца строки. Такой прием может оказаться полезным, если строка текстового файла содержит несколько разнотипных компонент и разбираться с ними приходится, анализируя каждый символ. Однако и в этой ситуации целесообразно прочитать строку целиком и устроить аналогичную разборку в оперативной памяти.
Программа 7_09.bas
INPUT "Задайте имя исходного файла - ", NAME1$
INPUT "Задайте имя выходного файла - ", NAME2$
INPUT "Задайте величину сдвига - ", N%
OPEN NAME1$ FOR INPUT AS #1
OPEN NAME2$ FOR OUTPUT AS #2
DO WHILE NOT EOF(l)
LINE INPUT #1,A$: A$=SPACE$(N%)+A$
PRINT #2,A$
LOOP
CLOSE II: CLOSE #2
END
Программа 7_09.c
#include <stdio.h>
#include <stdlib.h>
void ind_copy(FILE *fl,FILE *f2,int n);
main(int narg, char **argv) {
FILE *fl,*f2; int n;
if(narg < 4) {
printf("\n Ошибка. Должно быть :"};
printf("\n7_09.exe файл1 файл2 n");
exit(0); }
f1l=fopen(argv[l],"rt");
f2=fopen(argv[2],"wt");
n=atoi(argv[3]};
ind_copy(fl,f2,n);
fcloseall();
}
/*-------------------------------------*/
void ind_copy(FILE *fl,FILE *f2,int n) {
char str[80]; int j;
for(j=0; j<n; j++) str[j]=' ';
while (Ifeof(fl)) {
fgets(&str[n],80,fl);
fputs(str,f2); }
return; }
Программа 7_09.pas
program indent;
var
f1,f2:text;
namel,name2:string;
n,k:integer;
procedure ind_copy(var fl,f2:text;n:integer);
var
str:string; begin
while not eof(fl) do
begin
readln(fl,str);
writeln(f2, ' ':n,str);
end;
end;
begin
if ParamCount < 3 then begin
writeln(' Параметры заданы неверно. Должно быть: ');
writeln('7_09.exe файл1 файл2 сдвиг ');
exit;
end;
namel:=ParamStr(1);
name2:=ParamStr(2);
Val(ParamStr(3),n,k);
assign(fl,namel);
reset (fl);
assign(f2,name2);
rewrite(f2);
ind_copy(f1,f2,n);
close(f1);
close(f2); end.
Программа 7_09a.pas
program indent1; var
f1,f2:text; namel,name2:string;
n,k:integer;
procedure ind_copy(var f1,f2:text;n:integer) ;
var
ch:char;
begin
while not eof(fl) do begin
write(f2, ' ':n);
while not eoln(f1) do begin
read(fl,ch); write(f2,ch);
end;
readln(f1); writeln(f2);
end;
end;
begin
if ParamCount < 3 then begin
writeln("Параметры заданы неверно. Должно быть: ');
writeln('7_09a.exe файл! файл2 сдвиг ');
exit;
end;
narnel:=ParamStr(1);
name2:=ParamStr(2);
Val(ParamStr(3),n,k);
assign(fl,namel);
reset(f1);
assign(f2,name2);
rewrite(f2);
ind_copy(f1,f2,n);
close(fl);
close(f2);
end.