Работа с форматом RTF в Delphi

Мы живем в роскошное время - большинство ресурсов тратится человечеством впустую, буквально на ветер. Это тем более верно для ресурсов компьютерных: типичная загрузка процессора среднего (например, моего) компьютера - что-то около 10%, огромный винчестер завален никому не нужными файлами, из которых вряд ли используется более 20-30%, а до многих очередь так никогда и не доедет, из полутора же гигабайт оперативной памяти я нагружаю, максимум, 600-700 мег. Аналогичная "роскошная" ситуация и на уровне прикладного программирования: типичная программа содержит массу не используемого кода и ресурсов. Вполне естественно, что такие программы порождают столь же толстые и бестолковые документы. Ситуация отчасти объясняется новыми технологиями программирования, нацеленными на получение быстрых результатов в ущерб оптимизации и надежности кода. Возможно, не обходится и без "тихого сговора" с производителями комплектующих, непрестанно ищущих повод для нашего апгрейда за наш же счет. В качестве иллюстрации можете открыть любой "документ MS Word" с расширением doc и посмотреть, каково соотношение между полезной информацией (это еще предполагая, что напечатанный текст априори является такой информацией) и различной "пургой". На самом деле применение интегрированного COM-формата представления оправдано в одном случае из десяти тысяч. В большинстве же ситуаций вполне достаточно вообще неформатированного текстового представления, с которым справится любой первобытный vim. В других случаях требуется минимум форматирования - вроде автоматического центрирования, отступов и выделения курсивом. Для этих целей вовсе не обязательно таскать за собой COM-storage. Два лучших кандидата на представление "слегка украшенного" текста - это HTML (ака XML-совместимый) и RTF. О HTML вы, вероятно, и так уже все знаете, а вот о RTF речь пойдет ниже - и, естественно, не с точки зрения "эникейщика", а с позиции программирования. Не станем следовать примеру американских "конвейерных" программистов, уверовавших в огромную пользу всевозможных библиотек и классов, а посему полжизни проводящих: а) в поисках библиотеки, которая делает именно то, что нужно; б) в чатах по поводу "а почему не работает найденная библиотека так-то и так-то". Как следствие - мы опускаем (в натуре) такую припару, как RichTextEdit. Помимо прочего, вы все равно не сможете программно ничего сделать "rich" в этом элементе управления без знания RTF. Это не говоря уже о том, что знание сила - и, один раз постигнув, как что-то устроено, вы сможете сколько угодно использовать эти знания. Не станем мы использовать и соответствующий класс Java - там тоже один туман, а пользы от этой субстанции не больше, чем от квалификатора final abstract. В качестве инструмента мы выберем C# - просто он сейчас под рукой, к тому же дока Visual Studio, MSDN, имеет неплохой референс по RTF. Вообще, DOT NET - это, как по мне, самая удобная иерархия классов за всю историю ООП. Плохо только, что она стоит на COM - а потому не переносима никуда дальше Windows. Однако не нужно думать, что мы используем что-то, зависящее от NET,- напротив, наш "натуралистический" подход позволит переносить идеи на любой язык, существующий в природе, будь то Perl или COBOL, лишь бы он мог выводить текстовые файлы. Как и что устроено в RTF Итак, что же представляет собой RTF, который мы собрались порвать на куски и скормить нашим программам? По определению это "богатый текст" - в смысле, текст с украшениями в виде форматирования. А на самом-то деле… на самом деле это тоже текст, но не в том смысле, как мы привыкли его воспринимать (и видеть на экране), а в том, что все элементы форматирования - суть текстовые, то есть все символы "printable quotable". Это удобно по многим причинам и роднит RTF с такими языками разметки, как HTML и PostScropt. Одна из "радостей" - это возможность визуальной отладки для программы, то есть возможность собственными глазами увидеть, что делает наша программа. Сделать это не просто, а очень просто - достаточно открыть RTF в любом текстовом редакторе, который не станет интерпретировать символы разметки особым образом,- взять тот же Notepad. Что же мы увидим? Хех, нечто вроде показанного ниже:

{\rtf1\ansi\deflang1049{\fonttbl {\f4\fnil\fcharset204\fprq0 Arial;}{\f35\fnil\fcharset204\fprq0 Times New Roman;}}{\stylesheet {\s0{\*\keycode \shift\ctrl N}\snext0\f4\fs20\sl240\slmult1\ql\nowidctlpar\widctlpar Normal;}}{\info {\*\company Comizdat}{\creatim\yr2004\mo3\dy19\hr13\min39\sec0}{\author ac2k1}}\viewscale150\margl1701\margr850\margt1134\margb1134\widowctrl\plain\f35\fs24\pard\f4\fs18\lang1033 Hello World\par}

Как вы поняли (гы-ы), это традиционное приветствие "Hello World" в виде RTF. "Капец! - скажут некоторые из вас.- Какой же это, блин, читабельный текст?!". Ну, это как посмотреть - для папуаса наши книжки тоже 100% все-непонятное, также, как и текст на C++ для непосвященного. Кстати, это еще "милое" представление от текстового редактора Atlantis, а если посмотреть, что делает MS Word (ну, примерно то же, что он делает с HTML) - то там вообще грустная картина.
С другой стороны в кошмарном виде этого текста нет совершенно ничего страшного, по крайней мере не больше, чем в выражениях RegExp-а. Просто этот текст плохо отформатирован, поскольку никакой редактор не ожидает, что у вас хватит наглости читать RTF в ноутпаде. Попробуем "растопырить" наш RTF и получим что-то вроде:

{\rtf1\ansi\deflang1049{\fonttbl{\f4\fnil\fcharset204\fprq0 Arial;}{\f35\fnil\fcharset204\fprq0 Times New Roman;}{\stylesheet{\s0{\*\keycode \shift\ctrl N}\snext0\f4\fs20\sl240\slmult1\ql\nowidctlpar\widctlpar Normal;}}{\info{\*\company Comizdat}{\creatim\yr2004\mo3\dy19\hr13\min39\sec0}{\author ac2k1}}\viewscale150\margl1701\margr850\margt1134\margb1134\widowctrl\plain\f35\fs24\pard\f4\fs18\lang1033 Hello World\par}

Это уже совсем другое дело. Во-первых, мы видим, что RTF, так же как и XML, имеет иерархическую структуру, то есть документ состоит из элементов, каждый из которых, в свою очередь, также состоит из элементов,- и так далее. Как видно, элементы (группы) заключены в фигурные скобки. В начале документа идет какая-то служебная информация, то есть как бы HEADER.
Следующее - мы видим подобие атрибутов, которые, правда, хоть и начинаются с "\", но заканчиваются… ничем они не заканчиваются, кроме следующего атрибута или символа конца элемента. Пробельные символы здесь не то чтобы игнорируются - но лучше не разрывать ничего на куски, а аккуратно складывать поэлементно в ровные строчки. Имена атрибутов вполне мнемоничны - например, даже мне, неучу, ясно, что \margt1134 обозначает "верхний отступ 1134 сам-знаешь-чего" (твипов на самом деле, тысячных долей дюйма - то есть отступ равен 1,134 дюйма).
Все программы по обработке RTF условно делятся на читателей и писателей. Такое различие оправдано, поскольку многие программы просто генерируют свой "аутпут" в этом формате, и их совсем не парит разбирать чужие документы. Писатель, как обычно, "полуграмотный", то есть использует только 3% всего лексикона. Разбор RTF - операция, на порядок более сложная, с учетом всего множества атрибутов. В качестве допустимого минимума читатель должен просто игнорировать непонятные ему команды форматирования, так чтобы, пропустив неясный блок (конечно, при условии, что этот блок корректно упакован в ограничители), продолжить интерпретацию с понятного места. Таким образом, RTF, в общем-то, легко расширяется: можно добавить любые теги, не волнуясь, что это приведет к "непоняткам" с текстом или форматированием. Именно таким образом в RTF можно вставить любые бинарные данные (например, в MIME-кодировке) или специально обрабатываемые элементы (как гиперссылки или поля MS Word).
Итак, все, в общем-то, понятно, попробуем теперь сгенерировать какой-нибудь RTF.
Архитектура "писателя"
Задача генерации текстовых, в том числе вложенных, структур имеет два главных решения, известные по генерации HTML: первое - просто формировать выходной поток как текст (возможно, с небольшой "автоматизацией") - так, как это делает, например, perl или С++; второе - формировать основной шаблон и подставлять в него вычисляемые текстовые макроподстановки, как это делают ASP, PHP, ColdFusion и Delphi.
Мы применим второй вариант, несколько модифицированный и с такими "фичами":·В макроподстановке будут использоваться не выражения, а только переменные; все вычисления, если они нужны (например, подстановка даты, времени или полей из баз данных) должны производиться в программе - в том числе раскрутка циклических включений. ·Для упрощения манипуляций с документом разрешим шаблону включать другой шаблон - причем, если тот не найдется, ошибкой это считаться не будет (видимо, нас просто заломало его писать). ·Макропеременные по возможности должны быть "красивыми" - то есть будут иметь читабельный вид с точечной нотацией или пробелами, где префикс будет выступать в качестве квалификатора, то есть типа %header.charset=\ansi%. На самом деле это только красота - в таблице атомов (символов, переменных - как угодно) мы не станем строить дерево, а будем сравнивать текстуально с точками вместе. Впрочем, поиск в хеш-таблице и без нашего ведома будет производиться по короткой схеме. ·Макропеременные будут иметь значения по умолчанию, так что можно как снабжать чем-то шаблон, так и вообще ничем не снабжать его,- ошибки не будет. Если переменная не снабжена значением по умолчанию и при этом ей не присвоено значение - это тоже не ошибка: значит, эта переменная просто не нужна. ·При передаче значений в макросы не будет никакого "квотирования" - то есть мы не будем фильтровать спецсимволы RTF и не будем никак препятствовать передаче "голого" RTF - это открывает двери для всякого "хака" макета, то есть в любом месте можно "вломиться" с новым форматированием или вставить служебные элементы. Предполагается, что мы генерируем документы в подконтрольной среде (локально) и, если что-то делается, то мы знаем, что и зачем. ·Вывод нашего генератора будет направляться в указанный файл - все проблемы (относительно проверки корректности путей, а также того, перезаписывать ли существующий, и т.п.) при этом полагаются на вызывающую программу. ·Интерфейс будущего класса-генератора будет самым что ни на есть примитивным: создать документ на основе шаблона такого-то, подставить поля и получить вывод. Такой механизм позволяет генерировать серии документов по одному шаблону в таком слегка оптимизированном режиме:
создать документустановить глобальные переменные, типа шрифтов,стандартных реквизитов и т.п.цикл {установить переменные поля, обычно из БДполучить результат (сохранить, отпечатать, т.п.)}·Еще парочка соглашений: шаблоны не будут иметь никакого особого расширения, а будут представлять собой обычные TXT, чтобы разработчик мог просто щелкнуть - и редактировать без проблем. Это расширение не указывается при задании имени шаблона. Шаблоны по умолчанию будут находиться в специальном подкаталоге Templates. Это упростит управление массой файлов, но при этом позволит оптимально использовать вложенных секций, поскольку они часто будут повторяться. Рекурсию мы не станем проверять - эта возможность будет включена в коммерческую версию.
В качестве ограничителей макроподстановок будут использоваться угловые скобки - я не нашел их в тексте RTF, и это дает надежду, что они не используются самим RTF. Для файлов будем использовать "<" и ">", для переменных - %имя=значение%. В реальной жизни мы, естественно, будем квотировать эти символы, но пока будем считать, что наш текст не включает таких редких и экзотических символов.
Наконец - в духе экстремального программирования - создадим примерный фрагмент кода, который будет использовать наш несуществующий класс. Таким образом, еще до того, как написать реализацию, мы начинаем его использовать и являемся заказчиками и тестерами собственного же кода. Вот типичный фрагмент работающей программы, использующий наш класс:
RTFgen rtf=new RTFgen ("main"); \\

rtf.addvar ("text","HELL-O-WORLD");

rtf.generate (savefile);

Отправить комментарий

Проверка
Антиспам проверка
Image CAPTCHA
...