Печать в Word без COM объектов или печать в Word под Linux

Решается задача печати по шаблону в Word под Linux (можно и под Win). Главное — это без использования COM Objects. Word’a под Linux нет, но зато есть Open Office. Основная идея это использование старого формата RTF, который читается любой версией Word, Open Office и даже WordPad.
Статья состоит из:
1) Теория и общее описание алгоритма.
2) Описание использования
3) Код методов
4) Рабочая обработка — пример использования под 8.2 (обычные формы)

Моя первая публикация (чего-то полезного) не только на инфостарте, но вообще в интернете, так что особо сильно не пинайте Cool

Тем, кому не интересна теория, предлагаю сразу перейти ко 2-му пункту, быстро прочесть как пользоваться и скопировать код методов из 3-го пункта. Некоторое время после написания публикации я буду находиться на поддержке, отвечать на вопросы (возможно исправлять баги).

 Исправил редкий баг, всем кто пользовался до «10.06.2014 13:03:56» рекомендую обновить метод  РаспарситьСтроку(Стр, ХешКодировки)

1. Теория и общее описание алгоритма.

Сразу хочу заметить, что не претендую на самое красивое, лучшее и оптимальное решение этой задачи, все ниже описанное можно и нужно оптимизировать и я даже знаю как, но только руки не доходят. Формат RTF очень старый и имеет много надстроек и дополнений, всю его спецификацию я разумеется не изучал. Если простыми словами, то основа формата блоки заключенные в фигурные скобки, вот такие «{ }». Внутри блока расположены команды-маркеры, которые начинаются с символа «» и описывают шрифт, цвет фона текста и т.д. т.п. и текст (если блок текстовый). Таблицы в RTF легко склеиваются между собой достаточно просто в тексте удалить все символы, разделяющие таблицы. Честно говоря, иметь счастье разбиаться со спецификацией таблиц у меня не было ни какого желанию, по этому решено было использовть свойство склейки, просто повторяющуюся многократно строку необходимо заключить в секцию и многократно вывести. О секциях расскажу ниже. На этом рассмотрение теории по RTF можно закончить. Вы и сами можете по исследовать этот вопрос, создав файл *.rtf и открыв его хоть блокнотом, хоть Hex редактором. Алгоримтм на самом деле банален и прост. Читаем файл, превращаем в текст. Далее работаем с текстом. В первом приближении это всего два метода. Первый — это построить дерево из блоков, команд и текста. Второй — это превратить дерево в текст классическим рекурсивным обходом. Вот текст методов:

Функция РаспарситьСтрокуПерваяВерсия(Стр)

Перем Дерево, Стек, Мас, Анализ, Секции, Параметры;
Стек = Новый Массив;
Дерево = Новый Массив;
Мас = Дерево;
НачатоСлово = Ложь;
БылСлеш = ложь;
Темп = "";
Блок = Новый Структура("Дерево, Родитель");
ХешКодировки = ПолучитьХешКодировки();
Для х = 1 по СтрДлина(Стр) Цикл
Символ = Сред(Стр, х, 1);
Если НачатоСлово Тогда
Если Символ = " " Тогда
НачатоСлово = Ложь;
Темп = Темп + Символ;
Анализ = АнализСлова(Блок, Темп, ХешКодировки);
Темп = "";
Мас.Добавить(Анализ);
Продолжить;
КонецЕсли;
Если Символ = "{" Тогда
Если БылСлеш Тогда
Темп = Темп + Символ;
Иначе
Анализ = АнализСлова(Блок, Темп, ХешКодировки);
Мас.Добавить(Анализ);
Темп = "";
НачатоСлово = Ложь;
БылСлеш = Ложь;
Блок = Новый Структура("Тип, Дерево, Родитель", "Блок", Новый Массив, ?(Стек.Количество() = 0, Неопределено, Стек[Стек.ВГраница()]));
Стек.Добавить(Блок);
Мас.Добавить(Блок);
Мас = Блок.Дерево;
Продолжить;
КонецЕсли;
КонецЕсли;
Если Символ = "}" Тогда
Если БылСлеш Тогда
Темп = Темп + Символ;
Иначе
Анализ = АнализСлова(Блок, Темп, ХешКодировки);
Мас.Добавить(Анализ);
Темп = "";
НачатоСлово = Ложь;
БылСлеш = Ложь;
Стек.Удалить(Стек.ВГраница());
Если Стек.Количество() = 0 Тогда
Мас = Дерево;
Иначе
Мас = Стек[Стек.ВГраница()].Дерево;
КонецЕсли;
Продолжить;
КонецЕсли;
КонецЕсли;
Если Символ = "" Тогда
Если БылСлеш Тогда
БылСлеш = Ложь;
Иначе
БылСлеш = Истина;
КонецЕсли;
Иначе
БылСлеш = Ложь;
КонецЕсли;
Темп = Темп + Символ;
Иначе
Если Символ = "{" Тогда
Блок = Новый Структура("Тип, Дерево, Родитель", "Блок", Новый Массив, ?(Стек.Количество() = 0, Неопределено, Стек[Стек.ВГраница()]));
Стек.Добавить(Блок);
Мас.Добавить(Блок);
Мас = Блок.Дерево;
Продолжить;
КонецЕсли;
Если Символ = "}" Тогда
Стек.Удалить(Стек.ВГраница());
Если Стек.Количество() = 0 Тогда
Мас = Дерево;
Иначе
Мас = Стек[Стек.ВГраница()].Дерево;
КонецЕсли;
Продолжить;
КонецЕсли;
Если Символ <> " " Тогда
Темп = Символ;
НачатоСлово = Истина;
Если Символ = "" Тогда
БылСлеш = Истина;
Иначе
БылСлеш = Ложь;
КонецЕсли;
Продолжить;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Возврат Дерево;

КонецФункции

и метод преобразования дерева в строку, совсем уж маленький Laughing

Функция СформироватьСтроку(Дерево) Экспорт

Перем рет, Элем;
рет = ""; Стек = Новый Массив;
Для каждого Элем из Дерево Цикл
Если Элем.Тип = "Блок" Тогда
рет = рет + "{" + СформироватьСтроку(Элем.Дерево) + "}";
Иначе
рет = рет + Элем.Текст;
КонецЕсли;
КонецЦикла;
Возврат рет;

КонецФункции

Далее все усложняется тем, что необходимо помимо обычного текста отслеживать секции и параметры, запоминать их засосвывать в какую-нибудь структуру. В какую только ? Размумеется в такую, что бы было максимально просто и удобно работать как проффесионалу, так и новичку (ну что бы мозг не напрягался). В начале я думал построить такое же дерево, но это не удобано. в результате пришел к тому, что проще всего сделать Хеш (но 1С Соотвестсвие), в котром можно обратиться к нужному элементу (секции или параметру) по имени. Причем элемент Хеша это массив, на случай если в макете будут встречаться параметры или секции несколько раз. В результате, 1 раз заполнив параметр, он автоматический заполниться по всему документу, аналогично и относительно секции. Далее еще 2 метода, первый — это копирование секции, зная где ее начало и где конец, и второй — это заполнение параметра (просто подмена текста).  Секции при копировании пропускают все управляющие блоки, т.е. теги секций и параметров. Все эти операции происходят прямо в дереве значений. Дерево по сути — это структура с большим количеством коротких строк, работать с которыми 1С-ке гораздо проще (я правда не знаю какой алгоритм для работы со строками использует фирма 1С, но в любом случае оперировать строками порядка 1-го мегобайта это не по феншую). В результате на пентиуме 4, шаблон размером под 1 мегобайт (почти 40 страниц) заполняется всего за 1 минуту. Для тех кто разберется со спецификацией RTF бонус в том, что уже все разложено по полочкам и можно менять шрифт, цвет и прочие параметры текста прямо на лету в самом шаблоне, хотя это можно делать и на уровне секций, например создать чередующуюся цветами таблицу.

2. Описание использования.

Описание использования состоит из 2-х частей:  правила составления макетов (для пользователей)  и правила вывода макетов (для программистов)

правила составления макетов (для пользователей) 

  1. Секции оформляются 2-мя тегами: откытием секции и закрытием секции (Пример:…).
  2. Параметры оформляются одним тегом через квадратные скобки (Пример: [Параметр1]).
  3. Любой текст за пределами секции принадлежит документу и будет выводиться в любом случае (Пример: Этот текст выведется в любом случаеЭтот текст выведется только если будет выведена секция «ШапкаДокумента» значение параметра «тестовый» = [тестовый])
  4. Секции могут быть вложенными. (Пример:Шапка:Шапка:) 
  5. Макет может состоять из произвольного количества секций и параметров. Правило составления секции аналогично правило расставлению скобок в обычно тексте. Нельзя что бы в любом месте количество закрывающих скобок было больше открывающих. На параметры нет никаких ограничений.
  6. Можно создавать параметры с одинаковым именем. При этом все параметры с одинаковым именем будут заполнены одним значением (Пример: Дата документа = [ДатаДокумента], и тут тоже быдет выведена такая же дата документа [ДатаДокумента]).
  7. Можно создавать секции с одинаковым именем, даже вложенные одна в другую. При этом они будут выведен столько раз, сколько задано в алгоритме. (Для примера предположим, что секция «Тестовая» выведена будет 2 раза и текст макета следующий: «1. Текст секции2. Текст вложенной». При таких условиях результатом будет текст: «1. Текст секции  2. Текст вложенной  2. Текст вложенной 1. Текст секции  2. Текст вложенной  2. Текст вложенной «)
  8. Правила составления имени секции или параметра. Можно использовать пробелы в имени (но не рекомендуется). Имена регистрозависимы ([ИмяПараметра] и [имяпараметра] это 2 разных параметра). Можно смело использовать цифры и спец символы, кроме «<«, «>», «[«, «]» или последовательности «
  9. Таблицы. Таблица формируется внутри секции. Телом секции является одна строка таблицы с параметрами. После объявления начала секции не должно быть ни текста ни пробелов, только перевод на новую строку. Аналогично для строки таблицы с параметрами и следующим за ней объявлением конца секции.
  10. Возможные ошибки. Не обязательно, но крайне рекомендуется обрамлять пробелами все теги секций и параметров (Пример: «Пробел»»Пробел» текст секции «пробел»[Параметр]»пробел» конец текста секции «пробел»»Пробел»

правила вывода макетов (для программистов)

  1. Работа начинается с получения Хеша кодировки (Соответствия) функцией ПолучитьХешКодировки(). Все остальные функции используют этот Хеш.
  2. Далее используется функция РаспарситьСтроку(Стр, ХешКодировки), Стр — это обычная строка (полученная считыванием файла-шаблона RTF). В результате возвтращается структура с 2-мя параметрами «Дерево» и  «Секции». Дерево — это макет RTF в виде древовидной структуры. Секции — структура секций и параметров макета.
  3. Далее вызывая методы ЗаполнитьПараметр(СтруктураСекций, ИмяПараметра, ЗначениеЗаполнения, ХешКодировки) и ВывестиСекцию(Запись, ИмяСекции, ЭтоТаблица = ложь, х_раз = 1) формируем результирующий документ (Это происходит очень быстро в памяти в структуре «Дерево»). В методе ВывестиСекцию параметр ЭтоТаблица уточняет обычная это секция или секция формирующая таблицу.
  4. Правила вывода секций. Перед выводом секции необходимо заполнить параметры внутри ее тела (если это после, то вместо параметров будут пробелы). Сначала необходимо выводить вложенную секцию, после уровнем выше и т.д. до корневой.
  5. После формирования документа нужно вызвать метод УдалитьСекции(Запись), Запись — это структура (с 2-мя параметрами «Дерево» и  «Секции») возвтращаемая методом РаспарситьСтроку(Стр, ХешКодировки).
  6. Последний шаг — функция СформироватьСтроку(Дерево). Возвращает результирующий документ в виде строки. Запиав строку в фаил RTF получим документ RTF. 

Еще один момент. В зависимости от того в каком редакторе вы создавали RTF-фаил, внутренний текст может сильно различаться. Я писал код, тестировал и отлаживался на MS Word 2010-м. Правда я не думаю это на что-то может повлиять, но все же решил вас предупредить.

3. Код методов

Почему-то весь текст не влазит, разделил на 3 части.

Код методов из общего модуля:

Первая часть:

Процедура Печать(Элем, Данные)  Экспорт

Перем Обработка, Спр, Двоичка, Темп;
Если ТипЗнч(Элем) = Тип("Строка") Тогда
Спр = Справочники.R_ПечатьМакетовRTF.НайтиПоНаименованию(Элем);
Если НЕ ЗначениеЗаполнено(Спр) Тогда
ВызватьИсключение "Элемент справочника " + Элем + " не найден";
КонецЕсли;
Иначе
Спр = Элем;
КонецЕсли;
Двоичка = Спр.Обработка.Получить();
Макет = Спр.Макет.Получить();
Если НЕ ЗначениеЗаполнено(Двоичка) Тогда
ВызватьИсключение "Обработка не выбрана";
КонецЕсли;
Темп = ПолучитьИмяВременногоФайла();
Двоичка.Записать(Темп);
Обработка = ВнешниеОбработки.Создать(Темп);
Обработка.Печать(Макет, Данные);
УдалитьФайлы(Темп);

КонецПроцедуры

Функция СформироватьСтроку(Дерево) Экспорт

Перем рет, Элем;
рет = ""; Стек = Новый Массив;
Для каждого Элем из Дерево Цикл
Если Элем.Тип = "Блок" Тогда
рет = рет + "{" + СформироватьСтроку(Элем.Дерево) + "}";
Иначе
рет = рет + Элем.Текст;
КонецЕсли;
КонецЦикла;
Возврат рет;

КонецФункции

Функция РаспарситьСтроку(Стр, ХешКодировки) Экспорт

Перем Дерево, Стек, Мас, Анализ, Секции, НачатоСлово, БылСлеш, Параметр, Секция, Темп, Блок, СтруктураСекций;
Секции = Новый Массив;
Стек = Новый Массив;
Дерево = Новый Массив;
Мас = Дерево;
НачатоСлово = Ложь;
БылСлеш = Ложь;
Параметр = Неопределено;
Секция = Неопределено;
Темп = "";
Блок = Новый Структура("Дерево, Родитель");
Для х = 1 по СтрДлина(Стр) Цикл
Символ = Сред(Стр, х, 1);
Если НачатоСлово Тогда
Если Символ = " " Тогда
НачатоСлово = Ложь;
Темп = Темп + Символ;
Анализ = АнализСлова(Блок, Темп, ХешКодировки);
Темп = "";
Если КомпоновкаКлючевыхСлов(Секции, Анализ, Секция, Параметр) Тогда
Мас.Добавить(Анализ);
КонецЕсли;
ПоискКлючевыхСлов(Секции, Анализ, Секция, Параметр);
Продолжить;
КонецЕсли;
Если Символ = "{" Тогда
Если БылСлеш Тогда
Темп = Темп + Символ;
Иначе
Анализ = АнализСлова(Блок, Темп, ХешКодировки);
Если КомпоновкаКлючевыхСлов(Секции, Анализ, Секция, Параметр) Тогда
Мас.Добавить(Анализ);
КонецЕсли;
ПоискКлючевыхСлов(Секции, Анализ, Секция, Параметр);
Темп = "";
НачатоСлово = Ложь;
БылСлеш = Ложь;
Блок = Новый Структура("Тип, Дерево, Родитель", "Блок", Новый Массив, ?(Стек.Количество() = 0, Неопределено, Стек[Стек.ВГраница()]));
Если КомпоновкаКлючевыхСлов(Секции, Блок, Секция, Параметр) Тогда
Стек.Добавить(Блок);
Мас.Добавить(Блок);
Мас = Блок.Дерево;
КонецЕсли;
Продолжить;
КонецЕсли;
КонецЕсли;
Если Символ = "}" Тогда
Если БылСлеш Тогда
Темп = Темп + Символ;
Иначе
Анализ = АнализСлова(Блок, Темп, ХешКодировки);
Если КомпоновкаКлючевыхСлов(Секции, Анализ, Секция, Параметр) Тогда
Мас.Добавить(Анализ);
КонецЕсли;
ПоискКлючевыхСлов(Секции, Анализ, Секция, Параметр);
Темп = "";
НачатоСлово = Ложь;
БылСлеш = Ложь;
Если КомпоновкаКлючевыхСлов(Секции, Блок, Секция, Параметр) Тогда
Стек.Удалить(Стек.ВГраница());
Если Стек.Количество() = 0 Тогда
Мас = Дерево;
Иначе
Мас = Стек[Стек.ВГраница()].Дерево;
КонецЕсли;
КонецЕсли;
Продолжить;
КонецЕсли;
КонецЕсли;
Если Символ = "" Тогда
Если БылСлеш Тогда
БылСлеш = Ложь;
Иначе
БылСлеш = Истина;
КонецЕсли;
Иначе
Если БылСлеш и Символ <> "u" и Символ <> "'" Тогда
Если Лев(Темп, 2) = "'" или Лев(Темп, 2) = "u" или Лев(Темп, 1) <> "" Тогда
Анализ = АнализСлова(Блок, Лев(Темп, СтрДлина(Темп) - 1), ХешКодировки);
Темп = "";
Если КомпоновкаКлючевыхСлов(Секции, Анализ, Секция, Параметр) Тогда
Мас.Добавить(Анализ);
КонецЕсли;
ПоискКлючевыхСлов(Секции, Анализ, Секция, Параметр);
КонецЕсли;
КонецЕсли;
БылСлеш = Ложь;
КонецЕсли;
Темп = Темп + Символ;
Иначе
Если Символ = "{" Тогда
Блок = Новый Структура("Тип, Дерево, Родитель", "Блок", Новый Массив, ?(Стек.Количество() = 0, Неопределено, Стек[Стек.ВГраница()]));
Если КомпоновкаКлючевыхСлов(Секции, Блок, Секция, Параметр) Тогда
Стек.Добавить(Блок);
Мас.Добавить(Блок);
Мас = Блок.Дерево;
КонецЕсли;
Продолжить;
КонецЕсли;
Если Символ = "}" Тогда
Если КомпоновкаКлючевыхСлов(Секции, Блок, Секция, Параметр) Тогда
Стек.Удалить(Стек.ВГраница());
Если Стек.Количество() = 0 Тогда
Мас = Дерево;
Иначе
Мас = Стек[Стек.ВГраница()].Дерево;
КонецЕсли;
КонецЕсли;
Продолжить;
КонецЕсли;
Темп = Символ;
НачатоСлово = Истина;
Если Символ = "" Тогда
БылСлеш = Истина;
Иначе
БылСлеш = Ложь;
КонецЕсли;
КонецЕсли;
КонецЦикла;
СтруктураСекций = СформироватьСтруктуруСекций(Дерево, Секции);
Возврат Новый Структура("Дерево, Секции", Дерево, СтруктураСекций);

КонецФункции

Функция ПолучитьХешКодировки() Экспорт

Перем Хеш;
Хеш = Новый Соответствие;
Хеш["'c0"] = "А";
Хеш["'c1"] = "Б";
Хеш["'c2"] = "В";
Хеш["'c3"] = "Г";
Хеш["'c4"] = "Д";
Хеш["'c5"] = "Е";
Хеш["'c6"] = "Ж";
Хеш["'c7"] = "З";
Хеш["'c8"] = "И";
Хеш["'c9"] = "Й";
Хеш["'ca"] = "К";
Хеш["'cb"] = "Л";
Хеш["'cc"] = "М";
Хеш["'cd"] = "Н";
Хеш["'ce"] = "О";
Хеш["'cf"] = "П";
Хеш["'d0"] = "Р";
Хеш["'d1"] = "С";
Хеш["'d2"] = "Т";
Хеш["'d3"] = "У";
Хеш["'d4"] = "Ф";
Хеш["'d5"] = "Х";
Хеш["'d6"] = "Ц";
Хеш["'d7"] = "Ч";
Хеш["'d8"] = "Ш";
Хеш["'d9"] = "Щ";
Хеш["'da"] = "Ъ";
Хеш["'db"] = "Ы";
Хеш["'dc"] = "Ь";
Хеш["'dd"] = "Э";
Хеш["'de"] = "Ю";
Хеш["'df"] = "Я";
Хеш["'e0"] = "а";
Хеш["'e1"] = "б";
Хеш["'e2"] = "в";
Хеш["'e3"] = "г";
Хеш["'e4"] = "д";
Хеш["'e5"] = "е";
Хеш["'e6"] = "ж";
Хеш["'e7"] = "з";
Хеш["'e8"] = "и";
Хеш["'e9"] = "й";
Хеш["'ea"] = "к";
Хеш["'eb"] = "л";
Хеш["'ec"] = "м";
Хеш["'ed"] = "н";
Хеш["'ee"] = "о";
Хеш["'ef"] = "п";
Хеш["'f0"] = "р";
Хеш["'f1"] = "с";
Хеш["'f2"] = "т";
Хеш["'f3"] = "у";
Хеш["'f4"] = "ф";
Хеш["'f5"] = "х";
Хеш["'f6"] = "ц";
Хеш["'f7"] = "ч";
Хеш["'f8"] = "ш";
Хеш["'f9"] = "щ";
Хеш["'fa"] = "ъ";
Хеш["'fb"] = "ы";
Хеш["'fc"] = "ь";
Хеш["'fd"] = "э";
Хеш["'fe"] = "ю";
Хеш["'ff"] = "я";
Хеш["u1040"] = "А";
Хеш["u1041"] = "Б";
Хеш["u1042"] = "В";
Хеш["u1043"] = "Г";
Хеш["u1044"] = "Д";
Хеш["u1045"] = "Е";
Хеш["u1046"] = "Ж";
Хеш["u1047"] = "З";
Хеш["u1048"] = "И";
Хеш["u1049"] = "Й";
Хеш["u1050"] = "К";
Хеш["u1051"] = "Л";
Хеш["u1052"] = "М";
Хеш["u1053"] = "Н";
Хеш["u1054"] = "О";
Хеш["u1055"] = "П";
Хеш["u1056"] = "Р";
Хеш["u1057"] = "С";
Хеш["u1058"] = "Т";
Хеш["u1059"] = "У";
Хеш["u1060"] = "Ф";
Хеш["u1061"] = "Х";
Хеш["u1062"] = "Ц";
Хеш["u1063"] = "Ч";
Хеш["u1064"] = "Ш";
Хеш["u1065"] = "Щ";
Хеш["u1066"] = "Ъ";
Хеш["u1067"] = "Ы";
Хеш["u1068"] = "Ь";
Хеш["u1069"] = "Э";
Хеш["u1070"] = "Ю";
Хеш["u1071"] = "Я";
Хеш["u1072"] = "а";
Хеш["u1073"] = "б";
Хеш["u1074"] = "в";
Хеш["u1075"] = "г";
Хеш["u1076"] = "д";
Хеш["u1077"] = "е";
Хеш["u1078"] = "ж";
Хеш["u1079"] = "з";
Хеш["u1080"] = "и";
Хеш["u1081"] = "й";
Хеш["u1082"] = "к";
Хеш["u1083"] = "л";
Хеш["u1084"] = "м";
Хеш["u1085"] = "н";
Хеш["u1086"] = "о";
Хеш["u1087"] = "п";
Хеш["u1088"] = "р";
Хеш["u1089"] = "с";
Хеш["u1090"] = "т";
Хеш["u1091"] = "у";
Хеш["u1092"] = "ф";
Хеш["u1093"] = "х";
Хеш["u1094"] = "ц";
Хеш["u1095"] = "ч";
Хеш["u1096"] = "ш";
Хеш["u1097"] = "щ";
Хеш["u1098"] = "ъ";
Хеш["u1099"] = "ы";
Хеш["u1100"] = "ь";
Хеш["u1101"] = "э";
Хеш["u1102"] = "ю";
Хеш["u1103"] = "я";
Хеш["А"] = "'c0";
Хеш["Б"] = "'c1";
Хеш["В"] = "'c2";
Хеш["Г"] = "'c3";
Хеш["Д"] = "'c4";
Хеш["Е"] = "'c5";
Хеш["Ж"] = "'c6";
Хеш["З"] = "'c7";
Хеш["И"] = "'c8";
Хеш["Й"] = "'c9";
Хеш["К"] = "'ca";
Хеш["Л"] = "'cb";
Хеш["М"] = "'cc";
Хеш["Н"] = "'cd";
Хеш["О"] = "'ce";
Хеш["П"] = "'cf";
Хеш["Р"] = "'d0";
Хеш["С"] = "'d1";
Хеш["Т"] = "'d2";
Хеш["У"] = "'d3";
Хеш["Ф"] = "'d4";
Хеш["Х"] = "'d5";
Хеш["Ц"] = "'d6";
Хеш["Ч"] = "'d7";
Хеш["Ш"] = "'d8";
Хеш["Щ"] = "'d9";
Хеш["Ъ"] = "'da";
Хеш["Ы"] = "'db";
Хеш["Ь"] = "'dc";
Хеш["Э"] = "'dd";
Хеш["Ю"] = "'de";
Хеш["Я"] = "'df";
Хеш["а"] = "'e0";
Хеш["б"] = "'e1";
Хеш["в"] = "'e2";
Хеш["г"] = "'e3";
Хеш["д"] = "'e4";
Хеш["е"] = "'e5";
Хеш["ж"] = "'e6";
Хеш["з"] = "'e7";
Хеш["и"] = "'e8";
Хеш["й"] = "'e9";
Хеш["к"] = "'ea";
Хеш["л"] = "'eb";
Хеш["м"] = "'ec";
Хеш["н"] = "'ed";
Хеш["о"] = "'ee";
Хеш["п"] = "'ef";
Хеш["р"] = "'f0";
Хеш["с"] = "'f1";
Хеш["т"] = "'f2";
Хеш["у"] = "'f3";
Хеш["ф"] = "'f4";
Хеш["х"] = "'f5";
Хеш["ц"] = "'f6";
Хеш["ч"] = "'f7";
Хеш["ш"] = "'f8";
Хеш["щ"] = "'f9";
Хеш["ъ"] = "'fa";
Хеш["ы"] = "'fb";
Хеш["ь"] = "'fc";
Хеш["э"] = "'fd";
Хеш["ю"] = "'fe";
Хеш["я"] = "'ff";
Возврат Хеш;

КонецФункции

Процедура ЗаполнитьПараметр(СтруктураСекций, ИмяПараметра, ЗначениеЗаполнения, ХешКодировки) Экспорт

Перем х, Темп, Символ, Элем, Параметр, Значение;
Параметр = СтруктураСекций.Параметры[ИмяПараметра];
Если НЕ ЗначениеЗаполнено(Параметр) Тогда
ВызватьИсключение "Не существует параметра с именем " + ИмяПараметра;
КонецЕсли;
Значение = Строка(ЗначениеЗаполнения);
Темп = "";
Для х = 1 по СтрДлина(Значение) Цикл
Символ = Сред(Значение, х, 1);
Если Символ = "" Тогда
Темп = Темп + "\";
Продолжить;
КонецЕсли;
Если Символ = "{" Тогда
Темп = Темп + "{";
Продолжить;
КонецЕсли;
Если Символ = "}" Тогда
Темп = Темп + "}";
Продолжить;
КонецЕсли;
ХешСимвол = ХешКодировки[Символ];
Темп = ?(ЗначениеЗаполнено(ХешСимвол), Темп + ХешСимвол, Темп + Символ);
КонецЦикла;
Для каждого Элем из Параметр Цикл
Элем.Текст = Темп;
КонецЦикла;

КонецПроцедуры

Вторая часть:

Процедура ВывестиСекцию(Запись, ИмяСекции, ЭтоТаблица = ложь, х_раз = 1) Экспорт
//Можно конечно в цикле вызывать метод ВывестиСекцию(Дерево, МасСекций)
//Но лучше использовать параметр "х_раз", это ускорит работу
Перем у, х, Мас, ИндексНачала, ИндексКонца, Элем, Секция, ТемпМас, раз, ХешСекций, МасСекций;
Перем Дерево, СтруктураСекций, НачалоСекции;
Дерево = Запись.Дерево;
СтруктураСекций = Запись.Секции;
ХешСекций = СтруктураСекций.ХешСекций;
МасСекций = СтруктураСекций.Секции[ИмяСекции];
Если НЕ ЗначениеЗаполнено(МасСекций) Тогда
ВызватьИсключение "Не найдена секция с именем " + ИмяСекции;
КонецЕсли;
у = МасСекций.Вграница();
Пока у >= 0 Цикл
Секция = МасСекций[у];
ИндексНачала = -1;
ИндексКонца = -1;
Мас = ?(ЗначениеЗаполнено(Секция.БлокНачала.Родитель), Секция.БлокНачала.Родитель.Дерево, Дерево);
Для х = 0 по Мас.Вграница() Цикл
Если Мас[х] = Секция.БлокНачала Тогда
ИндексНачала = х;
Для х = ИндексНачала по Мас.Вграница() Цикл
Если Мас[х] = Секция.БлокКонца Тогда
ИндексКонца = х;
Прервать;
КонецЕсли;
КонецЦикла;
Прервать;
КонецЕсли;
КонецЦикла;
ТемпМас = Новый Массив;
НачалоСекции = Ложь;
Для х = ИндексНачала + 1 + ?(ЭтоТаблица, 1, 0) по ИндексКонца - 1 Цикл
Если НачалоСекции Тогда
Если ХешСекций[Мас[х]] = "Конец" Тогда
НачалоСекции = Ложь;
Продолжить;
КонецЕсли;
Иначе
Если ХешСекций[Мас[х]] = "Начало" Тогда
НачалоСекции = Истина;
Продолжить;
Иначе
ТемпМас.Добавить(Мас[х]);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Для раз = 1 по х_раз Цикл
х = ТемпМас.ВГраница();
Пока х >= 0 Цикл
Элем = СкопироватьЭлементДерева(ТемпМас[х], ТемпМас[х].Родитель);
Мас.Вставить(ИндексНачала, Элем);
х = х - 1;
КонецЦикла;
КонецЦикла;
у = у - 1;
КонецЦикла;

КонецПроцедуры

Процедура УдалитьСекции(Запись) Экспорт

Перем Мас, Секция, НачатоУдаление, х, Элем, МасСекций, Дерево, СтруктураСекций;
Дерево = Запись.Дерево;
СтруктураСекций = Запись.Секции;
Для Каждого Элем из СтруктураСекций.Секции Цикл
МасСекций = Элем.Значение;
у = МасСекций.Вграница();
Пока у >= 0 Цикл
Секция = МасСекций[у];
Мас = ?(ЗначениеЗаполнено(Секция.БлокНачала.Родитель), Секция.БлокНачала.Родитель.Дерево, Дерево);
х = 0;
НачатоУдаление = Ложь;
Пока х  0 Тогда
Если Найти(Элем.Текст, ">") > 0 Тогда
ДобавлениеСекции(Секции, Элем, "Секция");
Иначе
Секция = Элем;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЕсли;
Если НЕ ЗначениеЗаполнено(Секция) Тогда
Если (Элем.Тип = "Текст") Тогда
Если Найти(Элем.Текст, "[") > 0 Тогда
Если Найти(Элем.Текст, "]") > 0 Тогда
ДобавлениеСекции(Секции, Элем, "Параметр");
Иначе
Параметр = Элем;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЕсли;

КонецПроцедуры

Функция КомпоновкаКлючевыхСлов(Секции, Элем, Секция, Параметр)

Перем рет;
рет = Истина;
Если ЗначениеЗаполнено(Секция) Тогда
рет = Ложь;
Если Элем.Тип = "Текст" Тогда
Секция.Текст = Секция.Текст + Элем.Текст;
Секция.Значение = Секция.Значение + Элем.Значение;
Если Найти(Элем.Текст, ">") > 0 Тогда
ДобавлениеСекции(Секции, Секция, "Секция");
Секция = Неопределено;
КонецЕсли;
КонецЕсли;
Иначе
Если ЗначениеЗаполнено(Параметр) Тогда
рет = Ложь;
Если Элем.Тип = "Текст" Тогда
Параметр.Текст = Параметр.Текст + Элем.Текст;
Параметр.Значение = Параметр.Значение + Элем.Значение;
Если Найти(Элем.Текст, "]") > 0 Тогда
ДобавлениеСекции(Секции, Параметр, "Параметр");
Параметр = Неопределено;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЕсли;
Возврат рет;

КонецФункции

Процедура ДобавлениеСекции(Секции, Секция, Тип = "Параметр")

Секции.Добавить(Новый Структура("Секция, Тип", Секция, Тип));

КонецПроцедуры

Функция АнализСлова(Блок, Стр, ХешКодировки)

Перем рет, х, Символ, БылСлеш, у, Значение, Темп, ЗначениеКодировки;
ЭтоТекст = Ложь;
Символ = Сред(Стр, 1, 1);
Если КодСимвола(Символ, 1) = 13 Тогда
Символ = Сред(Стр, 2, 1);
Если Символ = "" Тогда
Символ = Сред(Стр, 3, 1);
Если Символ = "'" или Символ = "u" Тогда
ЭтоТекст = Истина;
КонецЕсли;
Иначе
Если КодСимвола(Символ, 1) = 10 Тогда
Символ = Сред(Стр, 3, 1);
Если Символ = "" Тогда
Символ = Сред(Стр, 4, 1);
Если Символ = "'" или Символ = "u" Тогда
ЭтоТекст = Истина;
КонецЕсли;
Иначе
ЭтоТекст = Истина;
КонецЕсли;
Иначе
Если Символ = "" Тогда
Символ = Сред(Стр, 2, 1);
Если Символ = "'" или Символ = "u" Тогда
ЭтоТекст = Истина;
КонецЕсли;
Иначе
ЭтоТекст = Истина;
КонецЕсли;
КонецЕсли;
КонецЕсли;
Иначе
Если Символ = "" Тогда
Символ = Сред(Стр, 2, 1);
Если Символ = "'" или Символ = "u" Тогда
ЭтоТекст = Истина;
КонецЕсли;
Иначе
ЭтоТекст = Истина;
КонецЕсли;
КонецЕсли;
Если ЭтоТекст Тогда
у = 0; Темп = ""; БылСлеш = Ложь; Значение = "";
Для х = 1 по СтрДлина(Стр) Цикл
Символ = Сред(Стр, х, 1);
Если БылСлеш Тогда
Если Символ = "" или Символ = "{" или Символ = "}" Тогда
Значение = Значение + Символ;
БылСлеш = Ложь;
Продолжить;
КонецЕсли;
Если Символ = "'" Тогда
Темп = "'";
у = 2;
БылСлеш = Ложь;
Продолжить;
КонецЕсли;
Если Символ = "u" Тогда
Темп = "u";
у = 4;
БылСлеш = Ложь;
Продолжить;
КонецЕсли;
Иначе
Если у > 0 Тогда
у = у - 1;
Темп = Темп + Символ;
Если у = 0 Тогда
ЗначениеКодировки = ХешКодировки[Темп];
Если ЗначениеЗаполнено(ЗначениеКодировки) Тогда
Значение = Значение + ЗначениеКодировки;
КонецЕсли;
КонецЕсли;
Продолжить;
КонецЕсли;
Если Символ = "" Тогда
БылСлеш = Истина;
Продолжить;
КонецЕсли;
Значение = Значение + Символ;
КонецЕсли;
КонецЦикла;
рет = Новый Структура("Тип, Текст, Значение, Родитель", "Текст", Стр, Значение, Блок);
Иначе
рет = Новый Структура("Тип, Текст, Родитель", "Команда", Стр, Блок);
КонецЕсли;
Возврат рет;

КонецФункции

Третья часть:

Функция СформироватьСтруктуруСекций(Дерево, Секции)

Перем рет, Темп, Запись, Мас, ТемпМас;
рет = новый Структура("Секции, Параметры, ХешСекций");
рет.Секции = Новый Соответствие;
рет.Параметры = Новый Соответствие;
рет.ХешСекций = Новый Соответствие;
Для каждого Элем из Секции Цикл
Если Элем.Тип = "Параметр" Тогда
Темп = Элем.Секция.Значение;
Темп = ОчиститьНазвание(Темп);
Если рет.Параметры[Темп] = Неопределено Тогда
ТемпМас = Новый Массив;
ТемпМас.Добавить(Элем.Секция);
рет.Параметры[Темп] = ТемпМас;
Иначе
рет.Параметры[Темп].Добавить(Элем.Секция);
КонецЕсли;
Элем.Секция.Текст = "";
Продолжить;
КонецЕсли;
Если Элем.Тип = "Секция" Тогда
Запись = ОчиститьСекцию(Дерево, Элем);
Если рет.Секции[Запись.Название] = Неопределено Тогда
Если Запись.ТипСекции = "Конец" Тогда
ВызватьИсключение "Конец секции не может располагаться до ее начала";
КонецЕсли;
Темп = Новый Структура("Дерево, БлокНачала, БлокКонца", Запись.Дерево, Запись.БлокСекции, Неопределено);
ТемпМас = Новый Массив;
ТемпМас.Добавить(Темп);
рет.Секции[Запись.Название] = ТемпМас;
Иначе
Если Запись.ТипСекции = "Начало" Тогда
Темп = Новый Структура("Дерево, БлокНачала, БлокКонца", Запись.Дерево, Запись.БлокСекции, Неопределено);
рет.Секции[Запись.Название].Добавить(Темп);
Иначе
Темп = рет.Секции[Запись.Название];
х = Темп.Вграница();
Пока х >= 0 Цикл
Если Темп[х].БлокКонца = Неопределено Тогда
ПоискКонцаСекции(Дерево, Запись, Темп[х]);
Прервать;
КонецЕсли;
х = х - 1;
КонецЦикла;
КонецЕсли;
КонецЕсли;
рет.ХешСекций[Запись.БлокСекции] = Запись.ТипСекции;
КонецЕсли;
КонецЦикла;
Возврат рет;

КонецФункции

Процедура ПоискКонцаСекции(Дерево, ЗаготовкаСекции, ЭлементСекции)

Перем Блок, Элем, ПоследнийБлок;
Блок = ЗаготовкаСекции.БлокСекции;
Пока ЗначениеЗаполнено(Блок) Цикл
Если ЗаготовкаСекции.Дерево = ЭлементСекции.Дерево Тогда
ЭлементСекции.БлокКонца = ЗаготовкаСекции.БлокСекции;
Прервать;
КонецЕсли;
Если НЕ ЗначениеЗаполнено(Блок.Родитель) Тогда
БлокСекции = Блок;
КонецЕсли;
Блок = Блок.Родитель;
КонецЦикла;
Если НЕ ЗначениеЗаполнено(ЭлементСекции.БлокКонца) Тогда
Если ЗаготовкаСекции.Дерево = Дерево Тогда
Для каждого Элем из Дерево Цикл
Если Элем = БлокСекции Тогда
ЭлементСекции.БлокКонца = Элем;
Прервать;
КонецЕсли;
КонецЦикла;
КонецЕсли;
Если НЕ ЗначениеЗаполнено(ЭлементСекции.БлокКонца) Тогда
ЭлементСекции.БлокКонца = ЭлементСекции.БлокНачала;
КонецЕсли;
КонецЕсли;

КонецПроцедуры

Функция ОчиститьНазвание(ОчищаемоеНазвание)

Перем рет, х, Символ, Название;
рет = ""; Название = СокрЛП(ОчищаемоеНазвание);
Для х = 1 по СтрДлина(Название) Цикл
Символ = Сред(Название, х, 1);
Если Символ = "]" Тогда
Прервать;
Иначе
Если НЕ (КодСимвола(Символ, 1) = 13 или КодСимвола(Символ, 1) = 10 или Символ = "[") Тогда
рет = рет + Символ;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Возврат рет;

КонецФункции

Функция СкопироватьЭлементДерева(Элем, Родитель)

Перем х, рет, НовЭлем;
Если Элем.Тип = "Блок" Тогда
рет = Новый Структура("Тип, Дерево, Родитель", "Блок", Новый Массив, Родитель);
Для х = 0 по Элем.Дерево.ВГраница() Цикл
НовЭлем = СкопироватьЭлементДерева(Элем.Дерево[х], рет);
рет.Дерево.Добавить(НовЭлем);
КонецЦикла;
ИначеЕсли Элем.Тип = "Текст" Тогда
рет = Новый Структура("Тип, Текст, Значение, Родитель", "Текст", Элем.Текст, Элем.Значение, Элем.Родитель);
ИначеЕсли Элем.Тип = "Команда" Тогда
рет = Новый Структура("Тип, Текст, Родитель", "Команда", Элем.Текст, Элем.Родитель);
Иначе
ВызватьИсключение "Не ожиданный элемент тип = " + Элем.Тип;
КонецЕсли;
Возврат рет;

КонецФункции

Функция ОчиститьСекцию(Дерево, Секция)

Перем рет, Темп, х;
рет = Новый Структура(«Дерево, БлокСекции, Название, ТипСекции»);
Темп = ОчиститьНазвание(СокрЛП(Секция.Секция.Значение));
Если Лев(Темп, 2) = «</» Тогда
рет.ТипСекции = «Конец»;
рет.Название = Сред(Темп, 3, СтрДлина(Темп) — 3);
ИначеЕсли Лев(Темп, 1) = «<» Тогда
рет.ТипСекции = «Начало»;
рет.Название = Сред(Темп, 2, СтрДлина(Темп) — 2);
Иначе
ВызватьИсключение «Невозможно определить тип секции»;
КонецЕсли;
рет.Название = ОчиститьНазвание(рет.Название);
Секция.Секция.Текст = «»;
Темп = Секция.Секция.Родитель.Родитель;
рет.БлокСекции = Секция.Секция.Родитель;
рет.Дерево = ?(ЗначениеЗаполнено(Темп), Темп.Дерево, Дерево);
Возврат рет;

КонецФункции

8 Comments

  1. Yashazz

    Автор, пожалей читателей, исправь ошибки, читать же невозможно!

    Кстати, а никто не баловался xslt-преобразованием в rtf-формат? Так, чтобы ррраз — и по прописанному готовенькое?

    Reply
  2. kroitoru.g.g

    (1) Yashazz, Я безжалостен 🙂

    Reply
  3. kroitoru.g.g

    К несчастью метод

    Функция ОчиститьСекцию(Дерево, Секция)

    нормально не оформляется из-за текста = «< / »

    Поэтому добавил его в виде обычного текста.

    Reply
  4. K_A_O

    Понимаю, что смотреть что-то чужое, когда свое работает — не хочется. Но я вот вот с разбором rtf тоже проходил, и вроде достаточно сложные отчеты строились.

    http://infostart.ru/public/71345/

    Reply
  5. kroitoru.g.g

    (4) K_A_O, А вы COM объекты используете ?

    Я специально не использовал, что бы под Linux работало.

    Reply
  6. K_A_O

    (5) Разбирается rtf файл как строка, правда с помощью регулярных выражений, но если использовать нативную компоненту, то будет работать везде. Разбор ведется так же через соответствие представления «u1040 = А» и т.д.

    Reply
  7. It-developer

    А что аналога ЗапуститьПриложение нет для Линукс?

    Reply
  8. vkuznecov

    Добрый день ! Есть ли у вас вариант для управляемого приложения ?

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *