Как объединить два документа Word в один через COM-соединение в 1С с сохранением уникального форматирования и колонтитулов?

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

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

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

В чем физический смысл проблемы колонтитулов?

В структуре Microsoft Word колонтитулы жестко привязаны к понятию Разделов (Sections). По умолчанию создаваемый новый документ содержит только один раздел. Если мы просто добавим разрыв страницы (константа wdPageBreak со значением 7), новый раздел создан не будет. Страница перенесется, но останется в рамках того же раздела, унаследовав все его параметры.

Для того чтобы разделить оформление (колонтитулы, поля, ориентацию), нам необходимо принудительно вставить разрыв раздела (константа wdSectionBreakNextPage со значением 2). Однако даже после вставки нового раздела по умолчанию включена опция "Как в предыдущем" (в объектной модели Word это свойство называется LinkToPrevious). Это свойство связывает колонтитулы нового раздела с предыдущим. Чтобы разорвать эту связь, его нужно принудительно отключить программным путем.

Рассмотрим несколько способов решения этой задачи: от профессионального (с управлением разделами) до альтернативных вариантов.

Решение №1: Программное отключение связи колонтитулов (Оптимальный способ)

Этот способ является наиболее быстрым, стабильным и правильным. Мы не используем буфер обмена операционной системы (который может быть занят пользователем или другими процессами), а работаем напрямую с объектной моделью Word через COM-соединение.

Проанализируем алгоритм по шагам:

  1. Инициализируем COM-объект Word.Application и открываем первый (основной) документ.
  2. Перемещаем курсор в самый конец первого документа с помощью метода Selection.EndKey(6).
  3. Вставляем разрыв раздела с новой страницы (параметр 2).
  4. Находим в коллекции разделов только что созданный нами раздел. Это всегда будет последний элемент в коллекции Sections.
  5. Для нового раздела отключаем связь колонтитулов с предыдущим разделом. Обратите внимание, что в Word существует три типа колонтитулов для каждого раздела (основной, первой страницы и четных страниц). Нам необходимо отключить свойство LinkToPrevious для всех типов верхних (Headers) и нижних (Footers) колонтитулов.
  6. Вставляем второй файл целиком на месте курсора с помощью метода InsertFile. При этом все его стили и разметка сохранятся в рамках нового изолированного раздела.
  7. Сохраняем получившийся файл и корректно закрываем COM-соединение, чтобы процесс WINWORD.EXE не завис в оперативной памяти сервера.

Рассмотрим пример реализации этого алгоритма на встроенном языке 1С:


// Объявляем пути к файлам
ПутьКПервомуФайлу = "C:\Docs\Dogovor.docx";
ПутьКоВторомуФайлу = "C:\Docs\Smeta.docx";
ПутьКРезультату = "C:\Docs\ObshiyDocument.docx";

Попытка
    // Создаем COM-объект для работы с Word
    WordApp = Новый COMОбъект("Word.Application");
    WordApp.Visible = Ложь; // Скрываем окно Word для ускорения работы
Исключение
    ВызватьИсключение "Не удалось запустить Microsoft Word. Проверьте, установлена ли программа на компьютере.";
КонецПопытки;

Попытка
    // Открываем первый документ
    Документ = WordApp.Documents.Open(ПутьКПервомуФайлу);
    
    // Перемещаем курсор в конец первого документа (6 = wdStory)
    WordApp.Selection.EndKey(6);
    
    // Вставляем разрыв раздела со следующей страницы (2 = wdSectionBreakNextPage)
    WordApp.Selection.InsertBreak(2);
    
    // Получаем новый (последний) раздел результирующего документа
    КоличествоРазделов = Документ.Sections.Count;
    НовыйРаздел = Документ.Sections.Item(КоличествоРазделов);
    
    // Отключаем наследование колонтитулов из предыдущего раздела
    // 1 = wdHeaderFooterPrimary (основной колонтитул)
    НовыйРаздел.Headers.Item(1).LinkToPrevious = Ложь;
    НовыйРаздел.Footers.Item(1).LinkToPrevious = Ложь;
    
    // 2 = wdHeaderFooterFirstPage (колонтитул первой страницы, если используется)
    НовыйРаздел.Headers.Item(2).LinkToPrevious = Ложь;
    НовыйРаздел.Footers.Item(2).LinkToPrevious = Ложь;
    
    // 3 = wdHeaderFooterEvenPages (колонтитул четных страниц, если используется)
    НовыйРаздел.Headers.Item(3).LinkToPrevious = Ложь;
    НовыйРаздел.Footers.Item(3).LinkToPrevious = Ложь;
    
    // Вставляем второй файл в место нахождения курсора
    // Метод InsertFile(ИмяФайла, Диапазон, ПодтвердитьПреобразование, СвязатьСИсточником, ВставитьКакКартинку)
    WordApp.Selection.InsertFile(ПутьКоВторомуФайлу, , Ложь, Ложь, Ложь);
    
    // Сохраняем объединенный документ по новому пути
    Документ.SaveAs(ПутьКРезультату);
    Документ.Close();
    
    Сообщить("Документы успешно объединены!");
    
ОчевидноИсключение
    // В случае ошибки обязательно закрываем приложение Word, чтобы не оставлять висящие процессы
    Если ЗначениеЗаполнено(WordApp) Тогда
        WordApp.Quit();
    КонецЕсли;
    ВызватьИсключение КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;

// Корректно завершаем сессию Word
WordApp.Quit();
WordApp = Неопределено;

Решение №2: Копирование через буфер обмена (Альтернативный интерактивный метод)

Иногда разработчики прибегают к копированию содержимого страниц через буфер обмена с использованием метода PasteAndFormat. Этот способ моделирует поведение пользователя: открывается второй документ, выделяется его содержимое, копируется в буфер обмена, а затем вставляется в первый документ с сохранением исходного форматирования источника (параметр 16 — wdFormatOriginalFormatting).

Рассмотрим пример кода, реализующий данный подход:


// Создаем COM-соединение
WordApp = Новый COMОбъект("Word.Application");
WordApp.Visible = Ложь;

// Открываем оба документа
Док1 = WordApp.Documents.Open("C:\Docs\Dogovor.docx");
Док2 = WordApp.Documents.Open("C:\Docs\Smeta.docx");

// Переходим в конец первого документа и делаем разрыв раздела
WordApp.Selection.EndKey(6);
WordApp.Selection.InsertBreak(2);

// Выделяем весь текст во втором документе и копируем его
Док2.Activate();
WordApp.Selection.WholeStory();
WordApp.Selection.Copy();

// Возвращаемся к первому документу и вставляем скопированное с сохранением формата
Док1.Activate();
WordApp.Selection.EndKey(6);
WordApp.Selection.PasteAndFormat(16); // 16 = wdFormatOriginalFormatting

// Закрываем второй документ без сохранения изменений
Док2.Close(Ложь);

// Сохраняем первый документ
Док1.SaveAs("C:\Docs\ObshiyDocument.docx");
Док1.Close();

WordApp.Quit();

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

Решение №3: Избавление от COM-объектов через стандартные средства 1С (Самый стабильный серверный вариант)

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

Если шаблоны документов формируются внутри самой 1С как макеты табличных документов (.mxl), мы можем объединить их штатными средствами платформы 1С и сохранить в формате PDF. Такой файл гарантированно откроется у любого клиента, а его верстка, колонтитулы и ориентация страниц останутся неизменными.

Выясним, как реализовать этот алгоритм:


// Создаем результирующий табличный документ
ТабДокОбщий = Новый ТабличныйДокумент;

// Получаем первый табличный документ (например, макет договора)
ТабДокДоговор = ПолучитьТабличныйДокументДоговора(); // Ваша функция формирования договора
ТабДокОбщий.Вывести(ТабДокДоговор);

// Вставляем разрыв страницы средствами платформы 1С
ТабДокОбщий.ВывестиГоризонтальныйРазделительСтраниц();

// Получаем второй табличный документ (например, макет сметы с альбомной ориентацией)
ТабДокСмета = ПолучитьТабличныйДокументСметы(); // Ваша функция формирования сметы
ТабДокОбщий.Вывести(ТабДокСмета);

// Настраиваем параметры общего документа
ТабДокОбщий.АвтоМасштаб = Истина;

// Сохраняем в один PDF файл, который легко отправить по почте
ПутьКФайлуPDF = "C:\Docs\ReadyDocument.pdf";
ТабДокОбщий.Записать(ПутьКФайлуPDF, ТипФайлаТабличногоДокумента.PDF);

Сообщить("Документ успешно сформирован в формате PDF без использования MS Word!");

Этот метод работает мгновенно, полностью платформонезависим (будет без проблем работать на серверах под Linux и в облачных инсталляциях 1С:Fresh) и не требует лицензирования пакета Microsoft Office на сервере приложений.

Резюме и рекомендации

Подводя итоги, дадим несколько практических советов по выбору архитектурного решения:

  1. Если у вас тонкие клиенты на Windows и MS Office установлен локально на компьютерах пользователей, используйте Решение №1. Оно надежно отрабатывает на стороне клиента и позволяет гибко управлять структурой документа Word через разрывы разделов и отключение LinkToPrevious.
  2. Избегайте использования буфера обмена (Решение №2), за исключением случаев интерактивной работы в режиме толстого клиента под контролем пользователя.
  3. Если код должен выполняться в фоновом задании на сервере, лучшим решением будет уход от COM-технологий в сторону генерации PDF или MXL слиянием табличных документов (Решение №3). Это сбережет ресурсы вашего сервера и обеспечит стабильность работы системы 1С:Предприятие.
← На главную