Главная » Delphi » Автоматическое определение кодировки текстовых файлов

0

Так получилось, что проекты в этой главе — пока чисто демонстрационные (кроме, конечно, создания компонента RichEditint). Просто трудно придумать достаточно короткий проект реальной программы с тем, чтобы не отвлекаться от сути излагаемой проблемы. Мы еще совершим в этой главе полезное дело— доделаем, насколько получится, убогий перекодировщик из главы 5. А пока вот еще один пример, который мы оформим в виде законченной программы, но до поры до времени он ничего особенно практически полезного делать не будет.

Вы не раз встречали программы, которые автоматически определяют кодировку текстового документа (здесь речь идет только об однобайтных кодировках, потому что в чистых текстах Unicode фактически не употребляется). На практике для русскоязычных текстов достаточно распознавать три вида кодировок: СР866, Win 1251 и KOl-8 (будем их так сокращенно называть, в приложении 3 даны более официальные названия), а также "до кучи" отличать английский текст от русского. Как же это делается?

Принцип заключается в том, что производится статистическая проверка текста на встречаемость различных сочетаний букв, и он отлично изложен в статье [23], на которую мы и будем опираться8. Автор статьи, однако, на мой взгляд, чересчур усложнил алгоритм, разбирая, например, редко встречающиеся случаи наличия текста с псевдографикой (вполне можно допустить приемлемый процент ошибок при определении кодировки), а также в стремлении заставить алгоритм работать быстрее. Ускорение процесса достигается за счет того, что просматривается не весь файл, а лишь достаточно большой фрагмент его. Что такое "достаточно большой"? На этот вопрос точного ответа нет, а т. к. мы будем иметь дело лишь с текстовыми документами, которые обычно имеют небольшой объем, то на современных машинах это не очень актуально, и ведет лишь к увеличению риска определить что-то неправильно. Мегабайтный текстовый файл даже при "ламерском" побайтном чтении с диска современные машины "перелопачивают" за доли секунды. А когда в дальнейшем пойдет речь о создании реально работающей программы, мы найдем "правильный" способ ускорения процесса (см. об этом далее).

Поэтому мы сделаем так: будем просто читать каждый файл, как бинарный, т. е. содержащий только коды символов, затем преобразовывать эти коды в символы в соответствии с таблицами кодов, и вести статистику для каждой разновидности кодировок — соответствуют ли двухбуквенные сочетания допустимым в русском языке. При этом, естественно, какое-то количество недопустимых сочетаний все равно встретится — не только из-за орфографических ошибок, но и в каких-нибудь, например, аббревиатурах ("ЛДОЮЛ"), кальках с иностранных языков ("кэб", "лао чзы", "Давидофф") и т. п. Далее мы будем сравнивать вычисленные проценты появления допустимых сочетаний друг с другом и без всяких ухищрений решать, что кодировка та, в которой процент этот больше. Ну, а английский текст отличить проще простого — у него подавляющая часть кодов будет меньше 128. Все это не до конца корректно— например, может встретиться какая-нибудь неизвестная кодировка, или вообще бессмысленный набор знаков (скажем, письмо при двойной перекодировке Win-KOI-Win), и тогда корректно было бы признаться, что кодировку мы не знаем. Но, во-первых, если мы текст будем отображать, то и так будет видно по результату, что программа не справилась. А во- вторых, "перелопатив" по этому алгоритму все текстовые файлы, которые встретились на диске С: автора (более 10 тысяч), программа ошиблась только в нескольких случаях, когда русского текста было немного среди большого количества английского текста и числовых значений.

Создадим новый проект под названием Kodirovka.dpr (модуль формы назовем code.pas — на диске в папке Glava8\3). Программа, которую мы сделаем, будет просматривать все файлы в заданном каталоге, фильтровать их на предмет принадлежности к текстовому формату и определять кодировку. Прежде, чем приступать к делу, надо составить таблицы кодировок — хотя, конечно, для Win 1251 и ср866 можно использовать и функции API OEMtoANSi и ANSitoOEM, но уже для KOI-8 такой возможности нет. поэтому для единообразия я делаю все вручную (а вдруг вы захотите еще и другими кодировками дополнить?). Для этого пронумеруем русские буквы подряд от 1 до 32 (отбросив "Ё") и поставим их в соответствие номерам символов от 128 до 255 в различной кодировке. Заглавные и строчные варианты будем считать за одт и ту же букву. Если номеру не соответствует никакая буква, то ставим в этом месте 0 (см. далее исходный текст модуля code.pas).

Но самое главное— составить таблицу недопустимых сочетаний букв. К счастью, мы живем в эпоху компьютеров и заниматься лингвистическими изысканиями долго нам не придется — необходимо просто обработать на этот предмет достаточно большое по объему количество текстов, желательно литературных. Автор статьи [23] утверждает, что обработал некий 800-меш- байтный файл, ну а автор этих строк проверил его результаты на нескольких текстах из Библиотеки Мошкова и убедился, что все практически совпадает. Результирующая таблица представляет собой матрицу 32×32 (буква "Ё" опущена), в ячейках которой стоит 1 — если буквы на пересечении сочетаются, и 0 — если нет (причем ведущие буквы сочетаний расположены в строках матрицы, а ведомые — в столбцах).

Задача отнесения к текстовому формату формально не решается, но на практике можно применить следующий алгоритм: следует отсеять все файлы, в которых хоть один раз встретится символ с нулевым значением. Картинки, программы, базы данных, DOC-файлы в формате Word 97/200/ХР, XLS- файлы и т. п. чаще всего содержат поля с нулевым значением байт, а в текстовом формате это исключено. В достаточно больших архивах нулевые байты также обязательно встретятся. Но для надежности и ускорения работы мы дополнительно введем, во-первых, фильтр по часто встречающимся расширениям заведомо не текстовых форматов (.doc .rtf .exe .dll .drv .sys .ax .bin .асш .vxd .bmp .jpg .jpeg .gif .tiff .psd .psp .pdf .zip .rar .pcb .pdb .sch .tbb .tbi .msi .wav .mp3 .avi .ovl — сколько фантазии хватит), во-вторых, будем рассматривать только файлы размером не более 500 Кбайт. Практика показывает, что при таком подходе ошибки практически исключены и алгоритм работает достаточно быстро.

Итак, установим на форму компонент Panel, установим его свойство BorderStyle в bsSingie, a Color— в ciwhite, очистим заголовок caption. Растянем ее на всю форму. Затем на нее установим RichEdit (хотя было бы здб- рово испытать наш вновь созданный RichEditint, но здесь это безразлично, т. к. мы вручную ничего вводить не будем, и в целях воспроизводимости примера у всех читателей я поставил обычный RichEdit). У него нужно сделать следующие установки: свойство BorderStyle В bsNone, ParentColor В True (тогда редактор сольется С фоном), ScrollBars В ssVertical, Readonly В True. Растянем RichEditi так, чтобы вверху панели оставалось свободное пространство. Далее установим на этом пространстве однострочный редактор Edit, растянув его так, чтобы справа и слева оставалось место, и не забудем установить его свойство Autoselect в False— мелкий белый текст на синем фоне совершенно неразборчив, и убирать выделение по умолчанию лучше во всех случаях, кроме самых необходимых (см. об этом также в главе 9). Правда, мы к тому же будем убирать выделение программно, устанавливая курсор на конец строки, но и специальная установка также не помешает. В свойство Text введем начальную строку сл.

Справа от Editl поставим кнопку Buttonl4 и изменим ее заголовок на "…", установив шрифт (свойство Font) пожирнее (Arial 18 кегля полужирный). Ниже поставим еще одну кнопку Button2, растянем ее подлиннее, также установим шрифт побольше (Arial 12 кегля полужирный), и изменим заголовок на Искать!. Левее установим еще одну кнопку Button3 с тем же шрифтом (можно просто клонировать Button2 через Copy-Paste), сделаем ее покороче, установим заголовок Отмена и свойство Enabled в False. В свойствах формы установим BorderStyle в bsSingie, а также удалим кнопку для распахивания окна на весь экран (свойство Borderlcon I biMaxsimize В False), чтобы не возиться с "якорями", и при этом у пользователя не возникло бы желания изменять размеры формы. Позицию формы при запуске (position), как всегда, поменяем на poDesktopCenter, заголовок заменим на Определение кодировки, и заменим иконку на свою (folder04.ico на диске).

Вам может показаться, что я слишком много вожусь с установками для демонстрационного примера, однако позднее мы используем этот проект для более практической цели, и потому лучше все сделать сразу. Мало того, для украшения программы (и не лень ведь!) поставим в левый верхний угол панели компонент image и вставим в него картинку, пародирующую стиль заголовка поисковика Google (google.bmp на диске). Для полноты ощущений ниже установим компонент Label, покрасим его в бледно-голубой цвет, и установим его свойство AutoSize в False, очистим заголовок, на будущее установим шрифт пожирнее (также Arial 18 кегля полужирный) и растянем компонент во всю длину экрана. То, что получилось в результате, вы можете видеть на рис. 8.3.

Рис. 8.3. Форма для поиска текстовых файлов и определения их кодировки

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

"Приколы", подобные нашему заголовку а-ля Google, всегда лучше делать с помощью картинок, а не с использованием различных компонентов для вывода фигурного текста, просто по той причине, что компонент использует системные шрифты, и вы ограничены выбором из трех-пяти обязательных их разновидностей, остальных в системе пользователя просто может не оказаться. Да и выбор эффектов несрааненно больше и их проще реализовать.

Далее напишем обработчик события показа формы для установки "правильного" начального фокуса ввода, начальных свойств RichEdit, а также добавим процедуры скрытия текстового курсора для RichEdit:

procedure TForml.FomShowtSender: TObject); (при запуске} begin

RichEditl.Lines.Clear; (очистка RichEdit)

with RichEditl.DefAttributes do

begin

Name:=’Times New Roman'; {начальные атрибуты) Style: = [fsBold]; Size:=12; end;

Editl.SetFocus; {фокус на Editl) Edi tl.SelStart:=length(Editl.Text); Editl.SelText:=”; {курсор в конец текста Editl) end;

procedure TForml.RichEditIChange!Sender: TObject); begin

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

procedure TForml.RichEditlMouseDown(Sender: TObject; Button:

TMouseButton; Shift: TShiftState; X, У: Integer); begin

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

Общий план действий такой: пользователь жмет на кнопку "…", устанавливает папку, в которой будет производиться поиск (или вводит ее вручную), потом жмет на Искать!, и дальше программа работает по нашему алгоритму, выводя результаты построчно в RichEdit. Для этого нам надо сначала решить проблему ввода имени папки через вызов диалога — тот самый вопрос, который мы в проекте SiideShow из главы 2 временно решали только через открытие конкретного файла. Есть много разных способов это сделать: так, Delphi предлагает компоненты типа DirectoryOutline, SheilTreeView и аналогичные (закладка Samples — кстати, во встроенной справке о них ни слова, так что если хотите ознакомиться, читайте [3,4]). Кто хочет, может использовать и их, однако автора раздражают некоторые "фичи" — так, упомянутый SheilTreeView при запуске программы, и даже при запуске Delphi с соответствующим проектом, упорно дребезжит флоппи-дисководом, пытаясь прочесть несуществующую дискету (справедливости ради надо отметить, что в

Windows ХР не так назойливо, как в Windows 98). Есть и другие неоправданные сложности. Конечно, их можно, подобно RichEdit, модернизировать (и сделать это в данном случае проще простой правкой исходного текста и последующим перекомпилированием, т. к. в папке C:\Program Files\BORLAND \Delphi7\Demos\ShellControls лежит полный исходный код модуля ShellCtrls). и автор [4] даже уже отчасти провел за нас эту полезную работу. Но мы в самом начале договаривались, что будем использовать стандартные средства Delphi до последней возможности, так что от модернизированных компонентов откажемся. Я покажу оптимальный, на мой взгляд, вариант использования "родных" компонентов для задания папки в следующей главе. До кучи следует упомянуть и способ самостоятельного формирования списка папок с помощью ListBox, которым, очевидно, и пользуются все продвинутые программисты, но мне представляется, что выполнение этой задачи для такой цели, как просто задать текущую папку, — чересчур уж громоздкая процедура.

А здесь мы применим более простой способ: в модуле FileCtrl (его исходный текст находится в папке C:\Program Files\BORLAND\Delphi7\Source\Vel) определены целых две функции с одинаковым названием selectDirectory, различающиеся синтаксисом. Функция, вызываемая по варианту

SelectDirectory (const Caption: string; const Root: WideString; out Directory: string): Boolean;

показывает специальное окно для выбора именно папки и во всем удобна, кроме того, что автору так и не удалось заставить окно это возникать в нужном месте экрана. А вторая функция (синтаксис см. далее) вызывает общий стандартный диалог Windows (хорошо знакомый еще по Windows Зле) с несколько более расширенной функциональностью, чем нам требуется — кроме собственно окна для выбора папки, она показывает еще и окно с файлами, которые в этой папке содержатся. Если есть желание отполировать программу до предела, можно и самим вызывать диалог типа SHBrowseForFoider прямо через API, но это довольно громоздкое занятие, которое, на мой взгляд, ничем не оправдывается — посмотрите описание этой функции с примером использования в первоисточнике [14]10. Я просто применял эту вторую функцию, которая к тому же сразу комплектует диалог полезным окошком выбора диска (рис. 8.4).

Крупнейшим недостатком данного способа является то, что вызываемый диалог англоязычный, и исправить это положение, к сожалению, трудно — тогда проще использовать все же указанные ранее компоненты Delphi.

Рис. 8.4. Диалог Windows для аыбора папки

Принципиальные вопросы мы решили, так что приступим: добавим в предложение uses модуль FileCtrl и объявим переменную stpath: string. После этого напишем обработчик щелчка на кнопке Buttoni:

procedure TForml.ButtonlClick(Sender: TObject); begin

ChDir(Editl.Text); (устанавливаем текущую директорию и

вызываем диалог установки директории:) if SelectDirectory(stpath, П,0) then Editl.Text:=stpath; end;

Осталось самое главное: собственно реализовать алгоритм поиска. Для этого мы объявим следующие константы и переменные (сверху закомментированы номера русских букв подряд— так легче составлять таблицы перекодировки):

(А-1 Б"2 В-3 Г-4 Д-5 Е=6 Ж~7 3=8 И=9 Й=10 K=ll JI=12 М=13 Н=14 0-15 П=16 Р-17 С=18 Т=19 У"2О Ф=21 Х=22 Ц-=23 Ч-*24 Ш=25 111=26 Ъ-27 Ы=28 Ь*>29 Э=30 Ю=31 Я=32}

const alt: array [128..255] of byte = (1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16, 17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, о, о, о, о, о, о, о, о, о, о, о, о, о, о, о, 0,

17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);

const win: array [128..255] of byte = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16, 17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16, 17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32);

const koi: array [128..255] of byte – (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 1, 2,23, 5, 6,21, 4,22, 9,10,11,12,13,14,15, 16,32,17,18,19,20, 7, 3,29,28, 8,25,30,26,24,27, 31, 1, 2,23, 5, 6,21, 4,22, 9,10,11,12,13,14,15, 16,32,17,18,19,20, 7, 3,29,28, 8,25,30,26,24,27);

const chardouble: array [1..32,1..32] of byte = ((1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1), (1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,1,1,1,0,1,1), (1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,0,0,1), (1,1,1,1,1,1,0,0,1,0,1,1,1,1,1,0,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0), (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,0,1,1,1,0,1,1), (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1), (1,1,0,1,1,1,1,0,1,0,1,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,0,0,1,0,0,0), (1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,0,1,1,1,1,1,1), (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1), (0,0,0,1,1,1,0,1,0,0,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1), (1,0,1,0,0,1,1,1,1,0,1,1,0,1,1,0,1,1,1,1,0,0,1,0,1,0,0,0,0,0,1,0), (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,0,1,1,1,0,1,1,0,1,1), (1,1,1,1,1,1,0,0,1,0,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,0,0,1,1,1,0,1), (1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1), (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1), (1,0,0,0,0,1,0,0,1,0,1,1,0,1,1,1,1,1,1,1,0,0,1,1,1,0,0,1,1,1,1,1), (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1), (1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1), (1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1), (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,0,0,0,1,1,1), (1,0,0,0,0,1,0,0,1,0,0,1,1,1,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0), (1,0,1,1,1,1,0,0,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,0,0,0,0,1,0,0), (1,0,1,0,0,1,0,0,1,0,1,1,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0),

(1,0,1,0,0,1,0,0,1,0,1,1,1,1,1,0,1,0,1,1,1,0,0,0,1,0,0,0,1,0,0,0), (1,0,1,0,1,1,0,0,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,0,0,0,1,0,1,0), (1,0,0,0,0,1,0,0,1,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0), (0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1), (0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,0,0,0,0,1), (0,1,1,1,1,1,0,1,1,0,1,0,1,1,1,0,0,1,1,0,1,1,1,1,1,1,0,0,1,0,1,1), (0,0,0,1,0,1,0,1,0,0,0,1,1,1,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0), (0,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,1,1,0,0,0,0,1,0), (0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,0,0,0,1,1));

conat stExt: string =

(‘.doc .rtf .exe .dll .drv .sys .ax .bin .acm .vxd .bmp . jpg -jpeg .gif .tiff .psd .psp .pdf .zip .rar .pcb .pdb .sch .tbb .tbi .msi .wav .mp3 .avi .ovl’);

var

Formi: TForml;

stpath,stsearch,fname,st,stl:string;

fd:file;

sf:TsearchRec;

nfile,nsl,ns2,nx,altn,winn, )coi8:integer;

xw:word;

xs, xofsrbyte;

FlagCancel:byte=0;

Константа chardoubie представляет собой ту самую таблицу допустимых сочетаний букв, которую мы обсуждали ранее. Обработчик щелчка на кнопке Button2 (Искать) приведен далее. В нем мы учли, что имя папки пользователь может ввести вручную или через буфер обмена, следовательно, с возможными ошибками, которые мы постараемся исправить по максимуму.

procedure TForml.Button2Clic)c(Sender: TObject); {ищем файлы и обрабатываем их) begin

FlagCancel:=0; {флаг отмены} Button3.Enabled:=True; {активируем Отмену) Button2.Enabled:=False; {дезактивируем Поиск) stpath:=Editl.Text; {название папки)

if stpath=” then exit; {если оно пустое – сразу на выход) while stpath[1]=1 ‘ do delete(stpath,1,1);

{удаляем возможные пробелы в начале} if stpath="’ then exit; {если пусто – сразу на выход)

if stpath[length(stpath!]=’\’ then delete(stpath,length(stpath), 1) ; Iудаляем концевой знак   если он есть)

try

ChDir(stpath); Iпроверяем, есть ли такая папка! except

Application.MessageBox(‘Несуиествующая

директория’, ‘Error’, МВ_ОК); exit; Iесли нет – на выход) end;

RichEditl.Lines.Clear; i‘очищаем поле результатов) Formi.Labell.Caption:=”; {и заголовок Label) Application.ProcessMessages; Iчтобы сразу сработало) stsearch:=’\*';

stsearch:=stpath+stsearch; {строка для поиска файлов} nfile:=0;

if FindFirst(stsearch,$23,sf!then

I $23 = не просматриваем системные файлы, тома и каталоги) repeat

if FlagCanceloO then break; {если флаг отмены не 0, то прерываем)

fname:=stpath+’\’+sf.Name; {полное имя файла) {все к одному регистру:)

if pos(ExtractFileExt(ANSIUpperCase(fname)),

ANSIUpperCase(stExt))<>0 then continue; {если расширение совпадает с запрещенным, то на выход) if not ReadFileFormat then continue;Iчитаем файл) nfile:=nfile+J; {что-то получили) Formi.Labell.Caption:=’Найлеко:'; st:=’ кодировка:’+st;

stl:=IntToStr(nfile)+’. ‘+fname; {номер найденного файла) with RichEdit! do {выводим красиво в RichEdit) begin Lines.Add(stl);

SelAttributes.Style:=[fsltalic];

SelStart:=perform(EM_LINEINDEX,Lines.Count,0);

SelText:=st;

per form(EM_SCROLLCARET,0,0); SelAttributes.Style:=[fsBold]; end;

Application.ProcessMessages; {чтобы все прокрутилось I until FindNext(sf)<>0; {пока фаРты не закончатся) with RichEditl do {заключающую строку)

begin

Lines.AddC ‘);

st:=’Просмотрено ‘н-XntToStr (nfile);

Lines.Add(st);

SelStart:=length(Text);

SeiText:=”;

SetFocus; (иначе скроллинга не будет) end;

Forml.Editl.SetFocus; {возвращаем фокус в Editl)

Editl.SelStart:=length(Editl.Text);

Editl.SelText:=”; Iкурсор в конец текста Editl) FindClose(sf); Iконец поиска I Button2.Enabled:=True; (активируем Поиск) Button3.Enabled:-False; (дезактивируем Отмену) end; (Button2)

Самодеятельную процедуру удаления возможных пробелов в начале строки я использовал тут в иллюстративных целях "как это делается вообще" — в последних версиях Delphi есть три универсальные функции: Trim, TrimLeft и TrimRight, которые удаляют пробелы и служебные символы (читай: знаки табуляции и концы строк) с обоих концов, в начале и в конце строки соответственно. Мы их используем в дальнейшем.

Собственно статистику мы будем вычислять в функции ReadFileFormat, текст которой приведен далее, а пока несколько комментариев. Функция ExtractFileExt возвращает расширение с ведущей точкой, поэтому у нас в строке с запрещенными расширениями они также представлены с ведущей точкой. Все процедуры со строками, в отличие от процедур с папками и файлами, чувствительны к регистру, поэтому при сравнении мы использовали на всякий случай приведение к одному регистру (верхнему) с помощью ANSiupperCase. Все, что относится к RichEdit, я подробно комментировать не буду, потому что эта "песня" тянет на отдельную книгу. В нем ничего никогда не работает так, как вы ожидаете. Тут мы захотели выделять часть строки отдельным шрифтом (имя файла жирным, а кодировку курсивом), и к тому же прокручивать окно по мере поступления строк. В принципе мы добились, чего хотели, но к самой первой строке курсивный текст добавляется почему- то отдельной строкой — если хотите, можете с этим побороться самостоятельно. Скорее всего, нужно использовать процедуру замены выделенной части текста через сообщение em replacesel, но я оставил вам это в качестве домашнего задания — вывод в RichEdit мы все равно в дальнейшем заменим на другой способ отображения.

А вот, наконец, основная функция ReadFileFormat, ради которой все и затевалось:

funotion ReadFileFormat: boolean; begin

result:=True; assignfile(fd,fname); try

reset(fd,2); {пробуем открыть – вдруг он занят) except

result:=False; exit; (тогда на выход) end;

if filesize(fd)>250*1024 /если размер больше 250К 2-байтных слов) then begin result:=False; closefile(fd); exit; end; (на выход) altn:=0; (в этих переменных будем накапливать статистику) winn:=0; koi8:=0;

while not eof(fd) do begin

blockread(fd,xw,1); xofs:=hi(xw); xs:=lo(xw);

if (xs=0) or (xofs=0) then (если хоть один символ=0) begin result:=False; closefile(fd); exit; end; (на выход) if (xofs>127) and (xs>127) than

(только русские двухбуквенные сочетания) begin (проверка ALT) nsl:=alt(xsl;

(номер символа по алфавиту, соотв. кодировке ALT) ns2:=alt[xofs];

(номер символа по алфавиту, соотв. кодировке ALT) if (nsloO) and (ns2<>0) then if chardoubie[nsl,ns2]<>0 then altn:=altn+l; (проверка WIN) nsl:=win[xs];

(номер символа по алфавиту, соотв. кодировке WIN) ns2:=win[xofs];

(номер символа по алфавиту, соотв. кодировке WIN) if (nsloO) and (ns2<>0) then if chardoubie[nsl,ns2]<>0 then winn:=winn+l; (проверка KOI) nsl:=koi[xs];

(номер символа по алфавиту, соотв. кодировке KOI) ns2:=koi[xofs];

(номер символа по алфавиту, соотв. кодировке K0IJ

if (nsloO) and (ns2<>0) then if chardouble[nsl,ns2]<>0 then koi8:=koi8+l; and; end;

nx:=MaxIntValue([altn,winn,koi8]);

{максимальное из полученного} if (nx<(filesize(fd) div 10)! or (nx=0) then st:=’ ASCII1 /если осмысленных сочетаний не больше 5% от объема,

то это английский) elae if nx=koi8 then st:=’ KOI-8′ {иначе или KOI8) else if nx=winn then st: = ‘ Winl251′ {или Win) else if nx=altn then st:=’ срЗбб’ {или DOS} else st:=* unknown';

{что-то другое – теоретическая ситуация, которой быть не должно)

closefile(fd); end;

Отметим, что, вообще говоря, это типичный пример того, как делать не надо. Причем не просто не надо, а очень не надо. Для простоты и наглядности мы сделали программу в стиле Turbo Pascal — в DOS с ее 640 Кбайт памяти приходилось все, по возможности, держать на диске и подгружать в память только тогда, когда данные нужны непосредственно в текущей операции. Нечего и думать было о том, например, чтобы загрузить файл в память целиком, а потом что-то с ним делать— "out of memory" вам было обеспечено. В Windows с ее 4 Гбайт виртуальной памяти это, разумеется, непростительная глупость— у меня на компьютере стоит 512 Мбайт ОЗУ, которые даже прожорливой Windows ХР обычно используются едва на треть, и туда уместится практически любой файл. И дело тут не в самом по себе быстродействии дисковой подсистемы. Можете себе представить, что происходит, когда мы задаем чтение очередных двух байт: процедура обращается к драйверу, драйвер— к контроллеру, контроллер шарит по таблице расположения файлов в начальной области диска, находит базовый кластер, откуда начинается наш файл, потом находит по цепочке связанных ссылок конкретный кластер, где хранятся нужные данные, потом переносит головки туда, считывает этот кластер (или даже несколько их целым блоком — в современных дисках с 7200 об/мин никто отдельными кластерами читать не будет, слишком неэкономично) в кэш диска, потом драйвер переносит эти данные в оперативную память, выбирает из них нужные нам два байта — и так каждый раз! Можно, конечно, было бы считать файл в строку через ту же blockread одним блоком (величина файла при поиске известна), или поручить все это системе. К данному важному вопросу мы еще не раз будем возвращаться (см. главы 14 и 21). Когда мы ранее читали DOC-файл, мы так же отмечали этот момент, но там это было не так критично, потому что файл был всего один.

Результаты работы программы над папкой с некоторыми документами автора приведены на рис. 8.5. Всего в папке было 280 файлов, большую часть которых составляли, естественно, документы Word, так что в результирующем списке оказалось всего 59 наименований. На машине с процессором Athlon 1700 МГц и диском 7200 об/мин процесс занял 30 секунд — кошмар!

Рис. 8.5. Результаты работы программы Kodirovka

В дальнейшем мы будем улучшать эту программу: введем в нее линейку- индикатор процесса поиска, добавим просмотр вложенных папок, возможность обращения к файлам прямо из программы, избавимся от опостылевшего RichEdit, ускорим чтение и, в конце концов, преобразуем ее в локальный поисковик, который, разумеется, до уровня Google Desctop Search дотянуть не удастся, но, тем не менее, он будет вполне работоспособен.

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

По теме:

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