При разработке печатных форм часто возникает задача: в конфигурацию 1С загружен макет печатной формы в формате Microsoft Word (двоичные данные) объемом в одну страницу. Требуется программно сформировать итоговый файл, содержащий множество страниц, при этом на каждой странице должны быть выведены свои уникальные данные (например, комплект однотипных актов, договоров или уведомлений на разных контрагентов).
В отличие от табличного документа 1С, где вывод секций организован привычными методами Вывести() и Присоединить(), в текстовых процессорах логика работы с областями устроена иначе. Если заполнять переменные документа через коллекцию Variables, они применяются ко всему файлу целиком, из-за чего простое дублирование параметров на разных страницах вызывает сложности. Разберем по шагам несколько рабочих способов решения этой задачи.
Проанализируем базовый подход, который не требует сложной работы с разметкой документа. Идея заключается в следующем: для каждой строки или набора данных мы заполняем отдельный одностраничный документ во временный файл, а затем последовательно объединяем их в единый результирующий файл с помощью метода InsertFile объекта Range.
Разберем пошаговый алгоритм реализации:
Word.Application, заполняем параметры (через Variables с последующим вызовом Fields.Update() либо через поиск и замену текста), после чего сохраняем каждую страницу как отдельный временный файл.Посмотрим на пример программного кода объединения предварительно сохраненных временных файлов:
// Создаем COM-объект Microsoft Word
Попытка
WordПриложение = Новый COMОбъект("Word.Application");
WordПриложение.Visible = Ложь;
Исключение
ВызватьИсключение "Не удалось инициализировать Microsoft Word. Проверьте установку MS Office.";
КонецПопытки;
Попытка
// Создаем результирующий пустой документ
ИтоговыйДокумент = WordПриложение.Documents.Add();
ИтоговыйSelection = WordПриложение.Selection;
КоличествоФайлов = МассивВременныхФайлов.Количество();
Для Индекс = 0 По КоличествоФайлов - 1 Цикл
ПутьКФайлу = МассивВременныхФайлов[Индекс];
// Вставляем содержимое временного файла
ИтоговыйSelection.Range.InsertFile(ПутьКФайлу);
// Если это не последняя страница, вставляем разрыв страницы (код wdPageBreak = 7)
Если Индекс < КоличествоФайлов - 1 Тогда
ИтоговыйSelection.InsertBreak(7);
ИтоговыйSelection.Collapse(0); // wdCollapseEnd = 0
КонецЕсли;
КонецЦикла;
// Делаем документ видимым пользователю или сохраняем на диск
WordПриложение.Visible = Истина;
WordПриложение.Activate();
Исключение
// Гарантированно закрываем процесс при ошибке
Если WordПриложение <> Неопределено Тогда
WordПриложение.Quit(0); // wdDoNotSaveChanges = 0
КонецЕсли;
ВызватьИсключение ОписаниеОшибки();
КонецПопытки;
Рассмотрим более производительный вариант работы через COM, который не создает десятки промежуточных файлов на диске. Вместо сохранения отдельных документов мы открываем один шаблон и в цикле копируем его форматированное содержимое с помощью свойства FormattedText.
В этом случае вместо переменных Variables рекомендуется использовать строковые маркеры в тексте шаблона (например, [НомерДоговора], [Дата], [Контрагент]). Это позволяет выполнять замену только в рамках добавленного диапазона Range, не затрагивая уже заполненные предыдущие страницы.
Разберем по шагам данный метод:
ИсходныйДиапазон = Документ.Content.FormattedText.Range.Find.Execute с параметром замены wdReplaceAll (значение 2).wdPageBreak, вставляем сохраненный ИсходныйДиапазон и производим поиск с заменой меток только в пределах добавленной секции.Посмотрим на пример реализации дублирования диапазонов:
Попытка
WordПриложение = Новый COMОбъект("Word.Application");
WordПриложение.Visible = Ложь;
Документ = WordПриложение.Documents.Open(ПутьКШаблону);
// Запоминаем исходный блок с форматированием до замены меток
ИсходныйБлок = Документ.Content.FormattedText;
Для Индекс = 0 По МассивДанных.Количество() - 1 Цикл
ТекущиеДанные = МассивДанных[Индекс];
Если Индекс > 0 Тогда
// Переходим в конец документа и добавляем разрыв страницы
ДиапазонКонца = Документ.Content;
ДиапазонКонца.Collapse(0); // wdCollapseEnd
ДиапазонКонца.InsertBreak(7); // wdPageBreak
// Вставляем копию исходного шаблона
ДиапазонКонца.Collapse(0);
ДиапазонКонца.FormattedText = ИсходныйБлок;
РабочийДиапазон = ДиапазонКонца;
Иначе
РабочийДиапазон = Документ.Content;
КонецЕсли;
// Выполняем замену параметров в пределах текущего фрагмента
Поиск = РабочийДиапазон.Find;
Поиск.ClearFormatting();
Поиск.Replacement.ClearFormatting();
Поиск.Execute("[Номер]", Ложь, Истина, Ложь, Ложь, Ложь, Истина, 1, Ложь, Строка(ТекущиеДанные.Номер), 2);
Поиск.Execute("[Контрагент]", Ложь, Истина, Ложь, Ложь, Ложь, Истина, 1, Ложь, Строка(ТекущиеДанные.Контрагент), 2);
КонецЦикла;
WordПриложение.Visible = Истина;
Исключение
Если WordПриложение <> Неопределено Тогда
WordПриложение.Quit(0);
КонецЕсли;
ВызватьИсключение ОписаниеОшибки();
КонецПопытки;
Проанализируем ограничения COM-объектов. Использование COMОбъект("Word.Application") жестко привязано к операционной системе Windows и установленному пакету Microsoft Office. Такой код не сможет работать на сервере 1С под управлением Linux или в веб-клиенте. Кроме того, при сбоях в памяти могут оставаться зависшие процессы WINWORD.EXE.
Для создания быстрого, надежного и кроссплатформенного решения применяется прямая работа со структурой файла .docx, который представляет собой стандартный ZIP-архив:
ЧтениеZipФайла извлекаем из шаблона основной файл разметки: word/document.xml.<w:p> и таблиц <w:tbl>, составляющих страницу.<w:p><w:r><w:br w:type="page"/></w:r></w:p>.document.xml обратно в архив с помощью объекта ЗаписьZipФайла.Такой подход выполняется за доли секунды, абсолютно стабилен в серверных фоновых заданиях и не зависит от наличия офисных пакетов на сервере.
Если конфигурация разработана на базе Библиотеки стандартных подсистем (БСП), выясним возможности штатной подсистемы печати:
ТабличныйДокумент.Вывести(), между страницами программно вставляется горизонтальный разрыв страницы (свойство АвтоОтметкаНезаполненного или метод ВывестиГоризонтальныйРазделительСтраниц()), а результат может быть сохранен в формате PDF штатной функцией платформы ТабличныйДокумент.Записать().Попытка ... Исключение при работе с COM-объектами и закрывайте приложение Word в блоке обработки ошибок, чтобы предотвратить утечки памяти на рабочих станциях.InsertFile не забывайте удалять созданные промежуточные файлы с диска с помощью метода УдалитьФайлы().