img
00:00
imgDRKB online - "Дедушка RTF" еще послужит
imgimgimg
  Общие вопросы
  Delphi IDE, компиллятор, отладчик, редактор
  Язык программирования Дельфи
  VCL
  Системные функции и WinAPI
  Базы данных
  Работа с файловой системой
  Репортинг, работа с принтером
  Работа с сетью, интернетом, протоколами
  Работа с графикой и мультимедиа
  Математика, алгоритмы
  Форматы файлов, данных. Конвертация форматов
  ANSI ---> ASCII
  BMP ---> AVI (для TAnimate)
  BMP ---> EMF
  BMP ---> DIB
  BMP ---> ICO
  BMP ---> JPG
  BMP --> EMF (Enhanced Metafile)
  BMP ---> RTF
  BMP ---> WMF
  CUR ---> BMP
  ICO ---> BMP
  JPG ---> BMP
  TIF ---> PDF
  TXT ---> GIF
  DOC ---> HTML
  WMF ---> BMP
  RTF-->HTML
  HTML --> RTF
  DFM -->TXT, TXT --> DFM
  Win1251 <-> Koi8r
  Как инсталлировать INF файл?
  Как конвертировать WideString в String?
  Как определить графический формат файла (не используя расширение)?
  Как прочитать MP3 ID3-Tag?
  Как прочитать заголовок wav файла?
  Информация о AVI файле (разбор заголовка AVI)
  Как работать с DWG файлами (AutoCAD)?
  Как работать с GIF файлами?
  Как работать с PDF файлами?
  Как разрезать wav файл?
  Как узнать размер картинки для JPG, GIF и PNG файлов?
  Компонент для работы с PCX файлами
  Пример работы чтения и сохранении wav-файлов
  Работа с TGA файлами
  Формат wave файла
  Декомпилляция звукового файла формата Wave и получение звуковых данных
  Flash SWF --> EXE
  Преобразование иконок в Gliph-ы
  Документация на основе RTF-шаблона
  "Дедушка RTF" еще послужит
  ActiveX, COM, DCOM, MIDAS, CORBA, интерфейсы, OLE, DDE
  Разработка приложений
  Kylix
  Delphi.Net
  Развлечения
  
  [drkb=4011] Комментариев: 0 
&quot;Дедушка RTF&quot; еще послужит
"Дедушка RTF" еще послужит
Мы живем в роскошное время - большинство ресурсов тратится человечеством впустую, буквально на ветер. Это тем более верно для ресурсов компьютерных: типичная загрузка процессора среднего (например, моего) компьютера - что-то около 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.
Что же мы увидим? Хех, нечто вроде показанного ниже:
delphi
{\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 и получим что-то вроде:
delphi
{\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.
Мы применим второй вариант, несколько модифицированный и с такими "фичами":
delphi
RTFgen rtf=new RTFgen ("main"); \\
rtf.addvar ("text","HELL-O-WORLD");
rtf.generate (savefile);



Реализация
После таких детальных установок реализовать полученное довольно просто. Результирующая программа правильно обрабатывает, в основном, корректный ввод - и колбасится от некорректного. Обработка и анализ ошибок - удел коммерческих продуктов, да и то, как правило, со многими глюками и оговорками. Конечно, злобный хакер легко может предположить, что со времени создания нашего объекта, который при создании аккуратно ничего не открывает, некая посторонняя программа подменит наш корневой шаблон, уж не говоря обо всем многообразии возможных ошибок в самом шаблоне,- но отлов этих полутора миллионов вариантов остается вам в качестве домашнего задания.
Итак, вот как просто выглядит наш класс для генерации RTF-документов на основании иерархических шаблонов:
delphi
using System;
using System.IO;
using System.Collections;
using System.Text;
namespace Catch {
public class RTFgen
{
private string BaseTemp;
private StreamWriter RTFout;
private string path;
private Hashtable variables;
public RTFgen (string root) {
path=Directory.GetCurrentDirectory ()+"\\Templates\\";
if ((new FileInfo (path+root+".txt")).Exists) {
BaseTemp=root;
variables=new Hashtable ();
}

else
throw (new FileNotFoundException ());
}
public void addvar (string varname, string varvalue) {
variables.Add (varname, varvalue);
}

public void generate (string filename) {
RTFout = File.CreateText (filename);
ProcessFile (BaseTemp);
RTFout.Close ();
}

private void ProcessFile (string Temp) {
string input;
string filename=path+Temp+".txt";
if ((new FileInfo (filename)).Exists) {
StreamReader TempIn=File.OpenText (filename);
while ((input=TempIn.ReadLine ())!=null)
ProcessLine (input);
TempIn.Close ();
}

}
private void ProcessLine (string line2do) {
int pos1=0, pos2=0;
if ((pos1=line2do.IndexOfAny (new char [2]{'<','%'}
))>-1) {
Out (line2do.Substring (0, pos1));
if (line2do [pos1]=='<')
if ((pos2=line2do.IndexOf ('>', pos1+1))>0)
ProcessFile (line2do.Substring (pos1+1, pos2-pos1-1));
if (line2do [pos1]=='%')
if ((pos2=line2do.IndexOf ('%', pos1+1))>0)
ProcessVariable (line2do.Substring (pos1+1, pos2-pos1-1));
ProcessLine (line2do.Substring (pos2+1, line2do.Length-pos2-1));
}
else
Out (line2do);
}
private void ProcessVariable (string var) {
string varname=var, varvalue="";
int pos1;
if ((pos1=var.IndexOf ('='))>-1) {
varname=var.Substring (0, pos1);
varvalue=var.Substring (pos1+1, var.Length-pos1-1);
}

Out (variables.ContainsKey (varname)?(string) variables[varname]: varvalue);
}
private void Out (string s){
byte [] bt=Encoding.GetEncoding (1251).GetBytes (s);
foreach (byte b in bt)
RTFout.Write (b<128?((char) b).ToString ():"\\'"+b.ToString ("x"));
}

}
}



Как видите, все вращается вокруг рекурсии. Говоря попросту, обработать файл шаблона - значит обработать его строки одна за одной (ProcessFile). Обработать строку - значит найти в ней первую макроподстановку файла или переменной, затем распечатать начало до макро, сам макрос обработать в зависимости от типа, а остаток строки рекурсивно обработать как отдельную строку. Заметьте, что обработка файла порождает еще один цикл косвенной рекурсии - что, в общем, делает программу весьма интересной.
Переменные реализованы через хеш-таблицу, так что сами переменные и значения могут быть довольно "странными", по крайней мере с точки зрения традиционных языков: в значении может встречаться знак равенства, поскольку после первого вхождения дальнейшее воспринимается как единая строка. Имена и значения могут содержать пробелы и любые спецсимволы, кроме знаков "=" и "%", в том числе знаки "<" и ">", хотя такое использование позже может вас же ввести в заблуждение, так что злоупотреблять этим не стоит.
Налицо хорошая компактность и инкапсуляция кода в классе, а также сильное разделение кода и данных - все по максимуму осталось в шаблонах; программа имеет только доступ к полям, да и то опциональный - хорошо спланированный шаблон может уже содержать большинство полей хорошо заданными по умолчанию.
Последнее, что осталось тут не освещенным, это собственно шаблон. Мелочь, на которую нужно обратить внимание: не форматируйте шаблоны с помощью красивых отступов табуляцией - некоторые редакторы, вроде MS Word, поймут это как пробелы в тексте, и результат будет не самым лучшим. Впрочем, переносы строки игнорируются всеми "читателями", так что все-таки какой-то приличный вид всегда можно поддерживать, тем более что в шаблоны вы поместите фрагменты одного-двух уровней вложения. Для приведенного выше примера использовался следующий текст main.txt:
delphi
{\rtf1\ansi\ansicpg1251\uc1\deff1\stshfdbch0\stshfloch0\stshfhich0\stshfbi0\deflang1049\deflangfe1049
<fonttbl>
<colortbl>
<stylesheet>
{\*\rsidtbl \rsid5533391}

{\*\generator %Application=RTFgen 1.0%;}
<info>
<pnsec>
<pageset>
{\f48\fs18%text=Hello World%}
}



Этот шаблон рессембирует структуру RTF-файла, порождаемого MS Word. Вообще, MS Word, как правило, вставляет секции независимо от того, нужны ли они будут в документе. Например, секция colortabl будет вставлена, даже если в тексте ни единого цветового выделения. Точно так же pnsec описывает "пульки" и отступы для списков до девяти уровней вложения включительно - хотя ваш текст может вообще не включать списков. Насколько это ваш стиль - судите сами, другие редакторы ведут себя более компактно, создавая служебные таблицы динамически на основе реально используемых элементов документа. В конце концов, у вас целых две штатные возможности исключить ненужные вам части: удалить включение файла из шаблона и просто удалить вложенный файл (грубо, но работает без ошибок по определению).
Сам формат секций объяснять не стану - долго и вообще не особо касается программирования. Самое простое:
delphi
private void Out (string s){
byte [] bt=Encoding.GetEncoding (1251).GetBytes (s);
foreach (byte b in bt)
RTFout.BaseStream.WriteByte (b);
}



delphi
static void Main (string [] args) {
String s;
Class1 c=new Class1();
while ((s = Console.ReadLine ())!=null)
c.ProcessLine (s,-2);
}

bool firstline=true;
void ProcessLine (string s, int level) {
int pos1, pos2;
if ((pos1=s.IndexOfAny (new Char [2]{'{','}'}
))>-1) {
if (firstline)
firstline=false;
else
Console.WriteLine (s.Substring (0, pos1));
if (s [pos1]=='{') {
if ((pos2=s.IndexOfAny (new Char [2]{'{','}'}
, pos1+1))>-1) {
Shift (level);
if (s [pos2]=='}') {
Console.Write ("{"+s.Substring (pos1+1, pos2-pos1));
ProcessLine (s.Substring (pos2+1, s.Length-pos2-1), level);
}
else {
Console.Write ("{");
ProcessLine (s.Substring (pos1+1, s.Length-pos1-1), level+1);
}

}
}
if (s [pos1]=='}') {
Console.Write ("}
");
ProcessLine (s.Substring (pos1+1, s.Length-pos1-1), level-1);
}
} else
Console.WriteLine (s);
}
void Shift (int n) {for (int i=0; i<=n; i++) Console.Write ('\t');}




Я отнюдь не уверяю, что это самый корректный или наиболее правильный способ форматирования - просто это то, как нравится лично мне, в том числе "листочки" на одной строке, несколько первых уровней без отступов и так далее. Если бы не эти "исключения", программа могла бы быть и проще, и короче. Вызывается эта консольная программа командой вроде:
RTFnice < file.rtf >out.txt
По ходу обратите внимание на то, как вызывается нестатический метод из статического: поскольку на момент вызова статического main может не существовать ни одного экземпляра класса, то попытка обращения к его обычным нестатическим методам некорректна - нужно явно создать экземпляр и потом вызывать другие функции нашей программы. Для новичков в NET зачастую это шокирующая новость.
Итого
RTF - хоть и не самый новый, но, тем не менее, мощный и достаточно гибкий формат для документов, переносимых на различные платформы. Если вы заинтересованы в консистентной генерации документов и обмене ими вне зависимости от платформы и без привязки к MS Office - RTF очень даже способен вам в этом помочь. Предлагаемая технология позволяет сравнительно просто и компактно генерировать RTF с помощью несложного кода. Показанный класс RTFgen тривиально портируется на языки, штатно поддерживающие хеш-таблицы, такие как Java или Perl, и чуть сложнее - на те, где таблицы символов придется реализовать самостоятельно. Относительно сложный синтаксис шаблонов и большое количество параметров на поверку оказались мнимыми проблемами - по крайней мере, все оказалось не сложнее HTML, а повторное использование шаблонов может вообще свести вашу работу к минимуму.
Кстати, сама "машинка" после небольших переделок сможет генерировать и другие структурированные форматы, такие как HTML, XML, PDF, PS и т.д., так что описанные принципы могут иметь самое различное применение.
На КП-диске выложены образцы шаблонов (реально используемые мной в системе управления публикациями Catch) и сам класс RTFgen.cs. Обратите внимание: каталог Temlates должен находиться уровнем ниже каталога приложения - так что, если вы будете строить приложение в Visual Studio для отладки скопируйте Templates в Debug и\или Release.

2004.04.27 Автор: Арсений Чеботарёв
http://www.cpp.com.ua

@Drkb::04311
Количество статей: 4366
 
Вход
Имя:
Пароль:
Запомнить
Регистрация Забыли пароль?
Мини-чат :)
Необходима регистрация
Архив мини-чата
12-12-2019 19:32
Prokok
Всем привет!

12-12-2019 18:12
Programmer
admin создай беседу в вк про десксофт
09-12-2019 15:21
spex
ку
07-12-2019 23:51
admin
Это хоумпейдж, для души, с чего бы ему умирать? К тому же на хостинге другие сервисы подняты, для себя.
07-12-2019 22:49
Oleg4260
Мда... тут 2 страницы пролистаешь и уже на год раньше дата сообщения
походу сайт мёртвый. странно, что он до сих пор хостится, и даже админ вчера онлайн утром был.
07-12-2019 22:48
Oleg4260
сейчас
07-12-2019 22:48
Oleg4260
2019
07-12-2019 22:45
Oleg4260
Ого, это сайт 2007 года!
07-12-2019 22:43
Oleg4260
Только тут письмо на почту долго приходит
07-12-2019 22:43
Oleg4260
А ну регистрируйтесь, быстро! Мне одному скучно.
Статистика
 СегодняВсего
Посетителей4622139879
Запросов5222325466922
Online
Пользователей0
Гостей47
imgimgimgimg
 
img
     00:00