Как в 1С сформировать многостраничный документ Word на основе одностраничного макета?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке печатных форм часто возникает задача: в конфигурацию 1С загружен макет печатной формы в формате Microsoft Word (двоичные данные) объемом в одну страницу. Требуется программно сформировать итоговый файл, содержащий множество страниц, при этом на каждой странице должны быть выведены свои уникальные данные (например, комплект однотипных актов, договоров или уведомлений на разных контрагентов).

В отличие от табличного документа 1С, где вывод секций организован привычными методами Вывести() и Присоединить(), в текстовых процессорах логика работы с областями устроена иначе. Если заполнять переменные документа через коллекцию Variables, они применяются ко всему файлу целиком, из-за чего простое дублирование параметров на разных страницах вызывает сложности. Разберем по шагам несколько рабочих способов решения этой задачи.

Способ 1. Объединение временных файлов через метод InsertFile (COM-соединение)

Проанализируем базовый подход, который не требует сложной работы с разметкой документа. Идея заключается в следующем: для каждой строки или набора данных мы заполняем отдельный одностраничный документ во временный файл, а затем последовательно объединяем их в единый результирующий файл с помощью метода InsertFile объекта Range.

Разберем пошаговый алгоритм реализации:

  1. Извлекаем двоичные данные макета из метаданных или справочника и сохраняем исходный шаблон во временный файл на клиенте или сервере под управлением Windows.
  2. В цикле по данным открываем шаблон через COM-объект Word.Application, заполняем параметры (через Variables с последующим вызовом Fields.Update() либо через поиск и замену текста), после чего сохраняем каждую страницу как отдельный временный файл.
  3. Создаем новый пустой документ Word и последовательно вставляем в него файлы страниц с добавлением разрывов страниц.
  4. Сохраняем или открываем итоговый объединенный файл пользователю, после чего удаляем все временные файлы.

Посмотрим на пример программного кода объединения предварительно сохраненных временных файлов:


// Создаем 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
    КонецЕсли;
    ВызватьИсключение ОписаниеОшибки();
КонецПопытки;

Способ 2. Дублирование форматированного диапазона и замена меток (FormattedText)

Рассмотрим более производительный вариант работы через COM, который не создает десятки промежуточных файлов на диске. Вместо сохранения отдельных документов мы открываем один шаблон и в цикле копируем его форматированное содержимое с помощью свойства FormattedText.

В этом случае вместо переменных Variables рекомендуется использовать строковые маркеры в тексте шаблона (например, [НомерДоговора], [Дата], [Контрагент]). Это позволяет выполнять замену только в рамках добавленного диапазона Range, не затрагивая уже заполненные предыдущие страницы.

Разберем по шагам данный метод:

  1. Открываем документ-шаблон и считываем весь его исходный диапазон в переменную: ИсходныйДиапазон = Документ.Content.FormattedText.
  2. Заполняем маркеры первой страницы через метод Range.Find.Execute с параметром замены wdReplaceAll (значение 2).
  3. В цикле для каждой следующей страницы переходим в конец документа, вставляем разрыв страницы 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);
    КонецЕсли;
    ВызватьИсключение ОписаниеОшибки();
КонецПопытки;

Способ 3. Прямое формирование DOCX без Microsoft Word (OpenXML и ZIP)

Проанализируем ограничения COM-объектов. Использование COMОбъект("Word.Application") жестко привязано к операционной системе Windows и установленному пакету Microsoft Office. Такой код не сможет работать на сервере 1С под управлением Linux или в веб-клиенте. Кроме того, при сбоях в памяти могут оставаться зависшие процессы WINWORD.EXE.

Для создания быстрого, надежного и кроссплатформенного решения применяется прямая работа со структурой файла .docx, который представляет собой стандартный ZIP-архив:

  1. С помощью объекта ЧтениеZipФайла извлекаем из шаблона основной файл разметки: word/document.xml.
  2. Внутри XML-структуры находим теги абзацев <w:p> и таблиц <w:tbl>, составляющих страницу.
  3. В цикле дублируем тело страницы, подставляя нужные значения вместо текстовых меток, и разделяем страницы тегом разрыва: <w:p><w:r><w:br w:type="page"/></w:r></w:p>.
  4. Записываем измененный document.xml обратно в архив с помощью объекта ЗаписьZipФайла.

Такой подход выполняется за доли секунды, абсолютно стабилен в серверных фоновых заданиях и не зависит от наличия офисных пакетов на сервере.

Способ 4. Использование типовых подсистем БСП и Табличных документов

Если конфигурация разработана на базе Библиотеки стандартных подсистем (БСП), выясним возможности штатной подсистемы печати:

  1. Печать по офисным шаблонам БСП: Библиотека стандартных подсистем содержит встроенные механизмы работы с макетами DOCX и ODT. В макете размечаются области и списки специальными тегами, после чего типовые методы БСП автоматически размножают строки и страницы без прямого программирования COM-вызовов.
  2. Табличный документ как альтернатива: Если формат DOCX не является строгим требованием интеграции, гораздо проще перенести макет в стандартный макет типа «Табличный документ». В нем секции выводятся стандартным методом ТабличныйДокумент.Вывести(), между страницами программно вставляется горизонтальный разрыв страницы (свойство АвтоОтметкаНезаполненного или метод ВывестиГоризонтальныйРазделительСтраниц()), а результат может быть сохранен в формате PDF штатной функцией платформы ТабличныйДокумент.Записать().

Важные рекомендации при работе

  1. Очистка процессов: Всегда используйте конструкцию Попытка ... Исключение при работе с COM-объектами и закрывайте приложение Word в блоке обработки ошибок, чтобы предотвратить утечки памяти на рабочих станциях.
  2. Отказ от Variables в пользу замен: При формировании многостраничных документов маркеры прямой замены в тексте надежнее встроенных переменных Word, так как переменные документа являются глобальными для всего файла.
  3. Удаление временных файлов: При использовании метода InsertFile не забывайте удалять созданные промежуточные файлы с диска с помощью метода УдалитьФайлы().
← На главную