Главная » Delphi » Программа преобразования Unicode в чистый текст

0

Попробуем создать одну небольшую утилитку, которая иногда может очень пригодиться на практике. Существует множество программ, позволяющих осуществлять перекодировку русскоязычного текста из произвольной восьмибитной кодировки в любую другую. Однако прочтение текста в кодировке Unicode, если это не штатный формат какой-либо Unicode-программы, может стать проблемой. Такая задача может возникнуть, например, если вы хотите прочесть скрытый текст документа Word — дело в том, что популярнейший редактор имеет интересную привычку сохранять изменения, которые внесены в документ. Уничтожить следы вашего творческого процесса, чтобы ими не воспользовались супостаты, легко: нужно выделить весь текст готового документа и перенести его через буфер обмена в новый документ и сохранить под старым именем. Заодно это позволит сильно сократить размер файла, особенно если вы по ходу вставляли и убирали из него картинки. Но иногда просто необходимо восстановить удаленные по ошибке фрагменты текста, а это не так-то просто сделать, если вы уже вышли из программы. Для старых He-Unicode версий (Word 6.0) достаточно было изменить расширение файла с doc на txt и открыть его как "просто текст", но в Unicode-форматах (Word 97, 2000, ХР) вы ничего не разберете — приходится применять программу перекодировки. Кроме того, это единственный известный автору способ спасения DOC-файлов, испорченных в результате, например, некорректных дисковых операций (или просто нечаянным ручным редактированием).

Преобразование "вручную"

Вот короткий алгоритм перевода Unicode в "чистый текст" (который, впрочем, в случае DOC-файлов окажется все равно довольно "грязным", хотя и читабельным), который мы воплотим на практике— разумеется, ограничимся только русской и английской кодовыми страницами. Как мы уже говорили, в Unicodc для английского языка первый (старший) байт двухбайтного символа равен 0, а для кириллицы — 4. Русские буквы начинаются со значения младшего байта, равного 16, т.е. прописная "А" соответствует коду 04 16, прописная "Б" — 04 17 и т. д. Всего 64 знака, описывающих прописные и строчные буквы русского алфавита, кроме "Ё" и "е", которые мы опустим. Для того чтобы раскодировать такую запись, нужно проделать следующие операции:

1.     Прочесть значение первого байта.

2.     Если оно равно 0, то следующий байт записать в новый файл без изменений, если только он тоже не равен нулю (в формате DOC довольно много резервных полей с нулевым значением байт, но они нас не интересуют), перейти к чтению следующей пары.

3.     В противном случае, если значение первого байта равно 4, то прибавить к значению второго байта 176 (в кодировке Win 1251 русские буквы начинаются с символа номер 192) и записать полученное значение в новый файл, перейти к чтению следующей пары.

4.      В противном случае (не 0 и не 4) можно записать оба байта, как есть, а можно сразу перейти к чтению следующей пары (чтобы миновать, скажем, коды картинок и служебные поля). Во втором случае мы потеряем многие служебные символы, но они все равно будут отображаться неправильно — чтобы в конечном тексте отображалось все корректно, над алгоритмом нужно очень потрудиться.

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

Итак, создадим новый проект, назовем его Unicode, модуль с формой назовем просто Code, и сохраним все это дело в отдельной папке (Glava8\l). Заголовок формы переделаем в Unicode reader, для порядка можно заменить и иконку. На форму поместим главное меню, диалог открытия файла и компонент Memo, который нам в принципе не нужен, но будет служить для контроля.

Для него мы установим свойство Readonly в True (нам редактировать ничего не надо), и введем вертикальную линейку прокрутки (scroiiBars установить в ssVertical). Текстовый курсор в Memo нам тоже не требуется (только жутко раздражает), но, разумеется, просто взять и отменить его нельзя (от введения столь немудреной операции, несомненно, пострадала бы репутация разработчиков Windows), поэтому создадим обработчики:

procedure TForml.FormPaint(Sender: TObject); begin

HideCaret(Memol.Handle); (скрываем курсор! end;

procedure TForml.MemolChange(Sender: TObject); begin

HideCaret(Memol.Handle); (скрываем курсор} end;

procedure TForml.MemolClick(Sender: TObject); begin

HideCaret(Memol.Handle); (скрываем курсор} end;

Первый будет скрывать курсор при запуске, второй — при изменениях в Memol и третий — при щелчке на него мышью (проще не получается!).

В диалоге открытия файла я установил фильтр по расширению * .doc, хотя, разумеется, это необязательно. Теперь введем переменные:

var

Formi: TForml; ff, fо: file; xs,xofs:byte; xw:word; xz, i:integer; st:string;

Мы выбрали нетипизированные файлы, потому что так удобно определять точный размер файла, и также читать численные значения в формате word (т. е. по два байта), а писать — в формате byte (по одному). В главном меню создадим единственный пункт Открыть, и обработчик соответствующего события для него будет выглядеть так:

procedure TForml.OpenlClick(Sender: TObject); begin

If OpenDialogl.Execute then begin

assignfile(ff,OpenDialogl.FileName); (открываем исходный файл}

reset(ff,2); (для чтения двухбайтными словамиI xz:=filesize(ff); (размер файла в двухбайтных словах} st:=OpenDialogl.FileName; /преобразуем название} delete(st,length(st)-2,3); st:=st+’txt';

assignfile(fo,st); (создаем txt} rewrite (fo,l);

st:=”; (строка понадобится для вывода в Memo}

for i:=»l to xz do

begin

blockread(ff,xw,l); (читаем слово} xofs:=hi(xw); (делим его на байты} xs:=lo(xw);

if (xofs=0) than (наш алгоритм} begin if (xs<>0) then begin blockwrite (fo,xs, 1) ; if xs=13 then begin

Memol.Lines.Add(st); (выводим в Memo каждую строку} st: = "; end

else st:=st+chr(xs); {дополняем строку} end; end else if (xofs=4) then begin

if xs<>0 then begin

xs:=xs+176; blockwrite(fo,xs,1); st:=st+chr(xs); end;

end else begin

blockwrite(fo,xs,1); {если не 0 и не 4 – пишем все} blockwrite(fo,xofs,1); {в файл, но не в строку} end; end;

closefile(ff); closefile(fo);

Memol.SelStart:=Memol.Perform (EM_LINEINDEX,1,0)+1;

Memol.Perform(EM_SCROLLCAR?T,0,0); Memol.SetFocus; end;

end; {Cpenl

Последние три оператора устанавливают наш невидимый курсор в начало текста, чтобы не потребовалось его сразу прокручивать— после загрузки строки указатель в Мелю станет в самый конец файла, но не знаю, как вы, а я привык рассматривать файлы с начала. Остальное в принципе здесь должно быть все понятно— алгоритм мы описали раньше. Разумеется, писать на диск или нет по условию "старший байт не 0 и не 4" — дело ваше. Если не будете, то потеряете все символы типа фигурных скобок, но не это главное — в файле может оказаться и обычный, не Unicode, скрытый текст (вот так вот устроен Word!). Если вам это неважно, то при исключении этих операторов мусора в файле будет существенно меньше.

Рис. 8.2. Результат работы Unicode reader над текстом этой главы

Кстати, в Word, в отличие от текстовых форматов Microsoft, перевод строки (который здесь представляет собой конец абзаца) обозначается одним символом с номером 13 (CR), а не двумя (CR + LF), поэтому, чтобы выводить текст в Memo постепенно, мы поставили условие выводе именно по этому знаку. Как будет выглядеть начало этой главы после загрузки ее в нашу программу, представлено на рис. 8.2. Одновременно создастся соответствующий текстовый файл. В конце текста можно найти много любопытного: отброшенные варианты, сведения об авторе и программе и пр.

Преобразование через WideString

А почему бы не воспользоваться средствами Windows и Object Pascal, которые позволяют читать двухбайтные символы? Вопрос только в том, как их преобразовать в однобайтные, и это придется делать уже на уровне строк — обычная (ANSI) строка может прямо приравниваться к "широкой" (widestring). Простой механизм преобразования на уровне одиночных символов мне не известен (приравнять тип char к widechar, как в случае строк, компилятор не позволит) — если читатели слышали о таком, пусть поделятся. Но и со строками в принципе получается даже проще, чем "вручную" — другое дело, что для отсеивания мусора все равно придется принимать отдельные меры.

Добавим к меню пункт Файл | Unicode, и напишем вот такой обработчик щелчка по этому пункту:

prooedure TForml.UnicodelClick(Sender: TObject); var wch:WideChar;

wst:WideString; begin

If OpenDialogl.Execute then begin

Memol.Lines.Clear; wst:=”;

assignfile(ff,OpenDialogl.FileName); (открываем исходный файл) reset(ff,sizeof(wch)); (для чтения двухбайтным символами) xz:=filesize (ff) ; {размер файла в ci-гмволах) for i:=l to xz do begin try

blockread(ff,wch,1); except

continue; end;

if wch>#31 then wst:=wst+wch else if wch=#13 then begin st:=wst;

Memol.Lines.Add(st); {выводим в Memo каждую строку} and; and;

closefile(ff); end; end;

Если мы выполним эту процедуру, мы увидим, что в результирующей строке теперь будет много "мусорных" символов в виде "?", которые придется — при желании — отсеивать отдельно. Главное же в том, что протекает все это много медленнее, чем раньше. Отмечу, что мы вообще тут читаем файл неправильно — побайтно (или даже по два байта) читать дисковый файл в Windows, где памяти всегда более чем достаточно, есть совершеннейшее "ламер- ство", и допустимо только, если мы читаем небольшой объем данных. Позже мы вернемся к этому вопросу подробнее, но боюсь, что в данном случае замедление происходит совсем по иной причине — ведь в обоих случаях методика чтения одинаковая.

Заметки на полях

Я еще со времен Turbo Pascal заметил, что "штатная" реализация многих простых функций в продуктах Borland тормозит программу по совершенно необъяснимым причинам. Базовая для многих графических операций функция PutPixel, которая окрашивает заданную точку DOS-экрана в нужный цвет, выполняется много медленнее, чем прямой ее аналог, написанный даже не на ассемблере, а обычными паскалевскими способами прямой записи в регистры портов и в память:

procedure outpix(x,y:word;col:byte);

var m:byte;

begin

port[S3CE]:=5; port[$3CF]:=2; port[$3CE]:=8;

port($3CF]:=128 ahr (x mod 8); m: =mem[$A000:y* 80+x div 81; memlSAOOO:y*80+x div 8]:=col; end;

Я не буду пояснять здесь текст данной процедуры, это доаольно долго, просто приаожу ее как пример того, что самостоятельно написанный код не так уж и редко может работать лучше "штатного". Это, видимо, касается и использованных ранее манипуляций с "широкими" строками. Несложно попробовать написать собственную процедуру преобразования типа WideChar в ANSiChar, исключив преобразование "широкой" строки в обычную, а потом посмотреть, что именно тормозит в приведенном ранее алгоритме. Я не стал этого делать по одной простой причине: сама процедура чтения тогда получится вырожденной — фактически окажется, что мы используем тот же "ручной" алгоритм, что и раньше. Зачем тогда вообще связываться с "широкими" символами?

Источник: Ревнч Ю. В.  Нестандартные приемы программирования на Delphi. — СПб.: БХВ-Петербург, 2005. — 560 е.: ил.

По теме:

  • Комментарии