Разработка прикладного ПО - это, как известно, не только написание кода
программ, но и проектирование печатных документов и отчетов. Практически все
интегрированные среды имеют в своем составе генераторы отчетов, в той или иной
степени помогающие решить эту задачу. Однако, несмотря на явные достоинства,
использование генераторов отчетов имеет ряд недостатков. Они сводятся, главным
образом, к невозможности вносить правки в сформированный документ, а также
изменять шаблоны отчета привычными средствами, например обычным текстовым
редактором.
До последнего времени самым простым и широко применяемым решением
представлялось применение механизма ole. Например, для комбинации word и
visualbasic возможна такая схема:
- Создаем некий файл - шаблон документа. Там, где должна быть
"шапка" (дата, номер документа и др.), используем закладки, а
для основной части отчета создаем таблицу-заготовку
соответствующей структуры. Пример такого шаблона приведен на
рис. 1.
- Пишем программу с использованием объектной модели word:
// numstr - кол-во строк в отчете
// newdata (5,numstr) - массив с данными для заполнения таблицы,
// заранее приведенными к символьному виду
// itog - сумма, приведенная к символьному виду
// pth - путь к исходному файлу
// str_ndoc = "bs190"
// str_name = "Петров И.И."
//.................
dim objword as word.application
dim objdoc as word.document
dim objtable as word.table
// создаем объект word
set objword = new word.application
// делаем его видимым - это не обязательно, но очень интересно :)
objword.visible = true
// открываем файл шаблона
set objdoc = objword.documents.open (pth)
// делаем его активным
objdoc.activate
// заполняем "шапку документа" - номер и получатель
// - закладки 'ndoc' и 'name' соответственно
objdoc.bookmarks ("ndoc").range.text = str_ndoc
objdoc.bookmarks ("name").range.text = str_name
// связывам объект с таблицей
set objtable = objword.activedocument.tables (1)
// выделяем 2-ю строку таблицы в шаблоне
objtable.cell (2, 1).range.select
// вставляем нужное кол-во строк-1 (т.к. одна уже есть в шаблоне)
if numstr > 0 then objword.selection.insertrows (numstr - 1)
// для каждой строки в каждую ячейку вставляем нужные данные из массива
for i = 1 to numstr
for j = 1 to 5
objtable.cell (i + 1, j).range.text = newdata (j, i)
next j
next i
// проставляем сумму "Всего"
objtable.cell (numstr + 2, 5).range.text = itog
- Запускаем ее в составе всего приложения и получаем результат
(см. рис. 2).
- Пользователь, получив отчет в виде doc-файла, может легко
внести в документ любые изменения, отправить его по электронной
почте, распечатать - одним словом, распорядиться по своему
усмотрению в привычной ему среде. Так же легко он может изменить
и шаблон документа - для этого достаточно уметь работать в
текстовом редакторе.
Но эту идиллическую картину омрачает несколько неприятных моментов.
Во-первых, недостаточная гибкость приложения - если вы захотите перейти на
другой редактор, то придется писать код заново. Во-вторых, приложение работает
только в среде пакета ms office, а он стоит немалых денег. Если приложение
должно работать на 30-ти компьютерах предприятия, то установка на них ms office
обойдется примерно в 40 тыс. гривен - не каждый бюджет выдержит.
В то же время существует целый ряд достаточно полнофункциональных и
бесплатных офисных пакетов: openoffice, staroffice, easyoffice и др. Для
большинства операций, выполняемых обычно с документами, их возможностей вполне
достаточно. Но возможна ли их простая и эффективная интеграция в прикладное
программное обеспечение?
Решением этой проблемы может быть использование rtf-файлов. Этот формат,
предложенный microsoft как стандарт для обмена данными между текстовыми
редакторами, поддерживается абсолютным большинством офисных пакетов. Сама
microsoft использует его в качестве формата, в котором данные передаются через
буфер обмена между различными приложениями windows.
Кратко об rtf
В формате rtf используются только коды, представляемые символами из наборов ascii, mac и pc. Помимо текста, rtf-файл содержит команды управления в читаемой
форме. Документ состоит преимущественно из команд управления настройкой
программы чтения. Эти команды можно разделить на управляющие слова и управляющие
символы.
Управляющее слово представляет собой последовательность символов с
разделителем в конце. Например, фрагмент:
…bkmkstart ndoc…
соответствует началу закладки ndoc.
Перед управляющим словом вводится обратная косая черта (). В качестве
разделителей могут использоваться следующие символы:
- пробел, причем этот символ относится к управляющему слову;
- цифра или дефис (<->). После этих символов должен следовать
параметр с разделителем. В качестве разделителя может быть
использован пробел или другие символы (кроме цифр и букв);
- все символы, кроме цифр и букв. Эти символы не относятся к
управляющему слову.
Для задания управляющей последовательности в rtf-формате используются буквы
от А до z и от а до z, а также цифры от 0 до 9. Национальные символы к
управляющей информации не относятся.
В качестве управляющих символов используются отдельные буквы. Перед каждым
управляющим символом вводится обратная косая черта (). Например, фрагмент:
…f1fs20…
устанавливает шрифт № 1 размером в 20 единиц.
Фрагмент rtf-файла приведен ниже. Структура его, как можно видеть, напоминает
структуру html-документа:
intblphmrgposy371dxfrtext180dfrmtxtx180dfrmtxty0nowrapaspalphaaspnum
faautoadjustrightrin0lin0f1fs20lang1049langfe1049cgridlangnp1049langfenp1049
{lang1033langfe1049langnp1033 11cell 12cell 13cell} pard ql li0ri0widctlpar
intbl aspalphaaspnumfaautoadjustrightrin0lin0
В rtf-формате существует возможность объединять отдельные последовательности
в группы при помощи скобок:
{группа}
Такие группы создаются, например, при описании сносок, колонтитулов,
закладок и т.п.
Вот некоторые управляющие слова и символы, имеющие непосредственное
отношение к теме нашей статьи:
- раr - конец абзаца;
- сеll - конец столбца;
- row - конец строки (или таблицы);
- *bkmkstart <название закладки> *bkmkend - закладка. Пример:
{*bkmkstart ndoc} bs190{*bkmkend ndoc};
- pard - устанавливает стандартную настройку для абзаца;
- intbl … intbl - выделяет область таблицы;
- ' - прямой ввод в текст шестнадцатеричных чисел. При
сохранении кириллического текста он обычно сохраняется в
шестнадцатеричной форме, например:
'd1'f2'f0'ee'ea'e0 ('строка')
Поскольку нас интересуют только определенные задачи, знания приведенных выше
управляющих слов и символов вполне достаточно. Условимся для простоты называть
управляющие слова и символы тегами.
А теперь рассмотрим алгоритмы решения трех основных задач, возникающих при
создании документации.
Вставка строки на месте закладки
Пример такой закладки:
…{*bkmkstart ndoc}<значение закладки>{*bkmkend ndoc}…
Для решения данной задачи можно предложить следующий алгоритм.
- Читаем последовательно строки входного файла (в большинстве
случаев строка больше 255 символов).
- Ищем в текущей строке тег 'bkmkstart'.
- Если находим, то выделяем название закладки и сравниваем его
с искомой.
- Если совпадает, то записываем строковую строку данных после
закрывающей скобки (}).
Алгоритм реализован в виде функции in_zakl1(pth as string, zakl as string,
data as string), где pth - имя rtf-файла, zakl - имя закладки, data - строка для
добавления в файл.
Добавление строк в таблицу
Предположим, нам требуется найти m-ю строку в n-той таблице и повторить ее в
этой таблице p раз. Для поиска начала строки таблицы мы будем использовать тег
intbl, а для поиска конца - тег row. Конец самой таблицы определяется по
последовательности тегов row…pard…par.
Алгоритм решения этой задачи следующий.
- Читаем последовательно строки входного файла.
- Ищем последовательность …row…pard…par…intbl… (не обязательно
в одной строке) (n-1) раз. После этого мы находимся в начале
нужной таблицы.
- Ищем тег row (m-1) раз. После этого находимся перед нужной
строкой таблицы.
- Ищем следующий тег row и копируем содержимое файла от
(m-1)-го до m-го тега row (между row и intbl содержатся
настройки строки, они нам тоже нужны).
- Вставляем после m-го тега row скопированную нами подстроку p
раз.
Следует отметить, что недостатком предложенного алгоритма является то, что он
может копировать любую строку таблицы, кроме первой. Но в большинстве случаев
первая строка является "шапкой" документа и копировать ее нет необходимости.
Алгоритм реализован в виде функции in_tstr (pth as string, itbl as integer,
irow as integer, kol as integer), где pth - имя rtf-файла, itbl - номер таблицы,
irow - номер строки, kol - количество повторов строки.
Заполнение ячейки таблицы
Представим, что требуется найти k-ю ячейку в m-й4 строке n-й таблицы и
вставить в нее текстовую строку данных. Пример таких ячеек:
...{lang1033cgrid0<содержимое 1-й ячейки>cell<содержимое 2-й
ячейки>cell}…
Задача может быть решена по следующему алгоритму.
- Читаем последовательно строки входного файла.
- Ищем последовательность …row…pard…par…intbl… (не обязательно
в одной строке) (n-1) раз. После этого мы находимся перед нужной
нам таблицей.
- Ищем тег row (m-1) раз. После этого мы находимся в начале
нужной строки таблицы.
- Ищем k-e вхождение тега cell.
- Вставляем перед ним строку данных.
Данный алгоритм реализован в виде функции in_tcell1(pth as string, itbl as
integer, irow as integer, icell as integer, ndata as string), где pth - имя
rtf-файла, itbl - номер таблицы, irow - номер строки, icell - номер ячейки, data
- строка для занесения в ячейку.
Программа на visualbasic, демонстрирующая применение такой технологии и
функционально идентичная программе, приведенной в начале этой статьи, выглядит
так:
// numstr - кол-во строк в отчете
// newdata (5,numstr) - массив с данными для заполнения таблицы,
// заранее приведенными к символьному виду
// itog - сумма, приведенная к символьному виду
// pth - путь к файлу
// str_ndoc = "bs190"
// str_name = "Петров И.И."
dim res as boolean ' результат выполнения функций
// заполняем "шапку документа" - номер и получатель
// - закладки 'ndoc' и 'name' соответственно
res = in_zakl1(pth, "ndoc", str_ndoc)
res = in_zakl1(pth, "name", str_name)
// вставляем нужное кол-во строк-1 (т.к. одна уже есть в шаблоне)
res = in_tstr (pth, 1, 2, numstr - 1)
// для каждой строки в каждую ячейку вставляем нужные данные из массива
for i = 1 to numstr
for j = 1 to 5
res = in_tcell1(pth, 1, i + 1, j, newdata (j, i))
next j
next i
res = in_tcell1(pth, 1, numstr + 2, 5, itog) ' проставляем сумму "Всего"
Заключение
Каковы преимущества и недостатки предложенной технологии? Начнем с
достоинств. Во-первых, это более гибкая технология для формирования отчетов -
даже если часть пользователей работает с openoffice, а часть с ms office,
программа создания отчетных документов универсальна. Во-вторых, несмотря на
многоразовую перезапись файла шаблона во время работы, эта программа работает
быстрее, чем связка ole+word. Тем более что приведенные выше алгоритмы могут
совершенствоваться. Один из примеров кардинального повышения производительности
приведен в листингах варианта для pascaldelphi. В-третьих, пользуясь свободным
ПО, вы экономите деньги.
Теперь о проблемах. Основная из них - это недостаточная стандартизация
формата rtf. Производители ПО, в целом придерживаясь единого стандарта,
допускают несколько свободную трактовку частных моментов. Результат - проблемы с
использованием "чужих" rtf-файлов, подготовленных в других редакторах. Например,
ms word сохраняет графические изображения внутри rtf-файла в виде
последовательности шестнадцатеричных кодов, а oowriter - как внешний файл.
Впрочем, эти проблемы решаются - стоит только приложить некоторые усилия.
Полные листинги подпрограмм, реализующих описанные алгоритмы на visualbasic,
pascaldelphi и visualfoxpro, находятся на КП-диске.
Автор: Александр Харьков
|