При разработке внешних обработок и отчетов в 1С часто возникает задача вывода печатных форм в формате Microsoft Word (документы .doc или .docx). Если макет имеет фиксированную структуру, задача решается простой заменой параметров-маркеров. Однако ситуация усложняется, когда в документ необходимо вывести динамический список (например, список сотрудников в приказе), количество элементов в котором заранее неизвестно, причем для разных частей текста требуется сохранить разное оформление (выделение дат, ФИО или должностей жирным шрифтом).
В этой статье мы подробно разберем, как решить эту задачу. Мы проанализируем различные подходы — от классического использования COM-объектов (на примере реального кейса с форума) до современных кроссплатформенных стандартов с использованием Библиотеки стандартных подсистем (БСП).
Представим сценарий: нам необходимо сформировать приказ о назначении временно исполняющих обязанности. В документе должен быть последовательный список пунктов вида:
1. С [ДатаНачала] по [ДатаОкончания] поручить [ЗамещающийСотрудник] исполнение обязанностей [ЗамещаемыйСотрудник]...
Если в обработке выбран один сотрудник, в документе должен появиться один пункт. Если сотрудников несколько — пункты должны идти друг за другом по порядку, с автонумерацией. Главная сложность заключается в том, что внутри одного абзаца используется разное оформление текста: даты и ФИО сотрудников должны быть выделены жирным шрифтом, а связующий текст — обычным. Простая замена текста по всему документу здесь не сработает, так как при вставке нового текста оформление может "слететь", а структура документа превратиться в хаотичный набор символов.
Рассмотрим решение, основанное на работе с COM-объектом Word.Application. Этот метод применим, если база 1С работает под управлением операционной системы Windows и на клиентском компьютере (или сервере) установлен пакет Microsoft Office.
Идея решения заключается в использовании одной опорной закладки (Bookmark) в шаблоне Word, которая указывает на место начала вывода списка. В цикле по сотрудникам мы позиционируемся на эту закладку, пишем форматированный текст частями, динамически создаем новые временные закладки для позиционирования курсора и сдвигаем рабочую область вниз.
Разберем типичную ошибку позиционирования, с которой часто сталкиваются разработчики на начальном этапе. Посмотрим на пример кода, который приводит к неверному результату ("смешиванию" текста):
// НЕПРАВИЛЬНЫЙ ПОДХОД — приводит к "каше" в итоговом документе
Для Каждого Параметр Из СтруктураПараметров.Значение Цикл
ТекстСтрокиПриказа = Строка(Параметр.Номер) + ". С [ДатаНачала] по [ДатаОкончания] поручить [ЗамещающийСотрудникДолжностьдп]...";
СсылкаМакет.Bookmarks("ТекстСтрокиПриказа").Select();
СсылкаМакет.Application.Selection.TypeText(ТекстСтрокиПриказа);
// Попытка создать закладки поверх уже вставленного текста
СсылкаМакет.Bookmarks.Add("ДатаНачала", Word.Application.Selection.Range());
СсылкаМакет.Bookmarks("ДатаНачала").Select();
СсылкаМакет.Application.Selection.BoldRun();
СсылкаМакет.Application.Selection.TypeText(Параметр.ДатаНачала);
// ... дальнейший ввод текста ломает структуру и смещает координаты курсора
КонецЦикла;
Выясним причину этой ошибки. Метод TypeText() вставляет текст в текущую позицию курсора, смещая его в конец вставки. Если мы пытаемся вставить весь текст абзаца сразу, а затем программно искать внутри него места для замены параметров, координаты закладок сбиваются, и последующие вставки накладываются друг на друга, образуя нечитаемую смесь шрифтов и слов.
Чтобы избежать этой проблемы, необходимо выводить текст строго последовательно, небольшими фрагментами, сразу применяя форматирование к каждому фрагменту и пересоздавая управляющую закладку в конце только что вставленного текста. Разберем рабочее пошаговое решение:
// РАБОЧИЙ ВАРИАНТ: Последовательный вывод с динамическим пересозданием закладки
Для Каждого Параметр Из СтруктураПараметров.Значение Цикл
// 1. Позиционируемся на базовую закладку начала строки
СсылкаМакет.Bookmarks("ТекстСтрокиПриказа").Select();
// 2. Выводим номер пункта и статичное начало строки (обычный шрифт)
ТекстСтрокиПриказа = Строка(Параметр.Номер) + ". С ";
СсылкаМакет.Application.Selection.TypeText(ТекстСтрокиПриказа);
// 3. Динамически создаем закладку для даты начала, выделяем ее жирным и заполняем
СсылкаМакет.Bookmarks.Add("ДатаНачала", Word.Application.Selection.Range());
СсылкаМакет.Bookmarks("ДатаНачала").Select();
СсылкаМакет.Application.Selection.BoldRun(); // Включаем жирный шрифт
СсылкаМакет.Application.Selection.TypeText(Параметр.ДатаНачала);
// 4. Возвращаем обычный шрифт (или создаем новую закладку для связующего текста)
СсылкаМакет.Bookmarks.Add("по", Word.Application.Selection.Range());
СсылкаМакет.Bookmarks("по").Select();
СсылкаМакет.Application.Selection.BoldRun(); // Вызов BoldRun переключает состояние (вкл/выкл)
СсылкаМакет.Application.Selection.TypeText(" по ");
// 5. Выводим дату окончания (жирный шрифт)
СсылкаМакет.Bookmarks.Add("ДатаОкончания", Word.Application.Selection.Range());
СсылкаМакет.Bookmarks("ДатаОкончания").Select();
СсылкаМакет.Application.Selection.BoldRun();
СсылкаМакет.Application.Selection.TypeText(Параметр.ДатаОкончания);
// 6. Выводим текст " поручить " (обычный шрифт)
СсылкаМакет.Bookmarks.Add("поручить", Word.Application.Selection.Range());
СсылкаМакет.Bookmarks("поручить").Select();
СсылкаМакет.Application.Selection.BoldRun();
СсылкаМакет.Application.Selection.TypeText(" поручить ");
// 7. Должность замещающего сотрудника (обычный шрифт)
СсылкаМакет.Bookmarks.Add("ЗамещающийСотрудникДолжностьдп", Word.Application.Selection.Range());
СсылкаМакет.Bookmarks("ЗамещающийСотрудникДолжностьдп").Select();
СсылкаМакет.Application.Selection.TypeText(Параметр.ЗамещающийСотрудникДолжностьдп + " ");
// 8. ФИО замещающего сотрудника (жирный шрифт)
СсылкаМакет.Bookmarks.Add("ЗамещающийСотрудникФИОдп", Word.Application.Selection.Range());
СсылкаМакет.Bookmarks("ЗамещающийСотрудникФИОдп").Select();
СсылкаМакет.Application.Selection.BoldRun();
СсылкаМакет.Application.Selection.TypeText(Параметр.ЗамещающийСотрудникФИОдп);
// 9. Окончание фразы с переносом строки (обычный шрифт)
СсылкаМакет.Bookmarks.Add("исполнениеобязанностей", Word.Application.Selection.Range());
СсылкаМакет.Bookmarks("исполнениеобязанностей").Select();
СсылкаМакет.Application.Selection.BoldRun();
// Добавляем символ переноса строки в конце абзаца (Символы.ПС)
СсылкаМакет.Bookmarks.Add("ЗамещаемыйСотрудникДолжностьрп", Word.Application.Selection.Range());
СсылкаМакет.Bookmarks("ЗамещаемыйСотрудникДолжностьрп").Select();
ТекстОкончания = Параметр.ЗамещаемыйСотрудникДолжностьрп
+ " в течение установленной продолжительности рабочего дня наряду с работой, "
+ "определённой трудовым договором." + Символы.ПС;
СсылкаМакет.Application.Selection.TypeText(ТекстОкончания);
// КРИТИЧЕСКИ ВАЖНО: Пересоздаем базовую закладку в текущей точке курсора для следующей итерации цикла
СсылкаМакет.Bookmarks.Add("ТекстСтрокиПриказа", Word.Application.Selection.Range());
КонецЦикла;
Этот метод гарантирует, что каждый новый пункт приказа будет начинаться строго с новой строки, а все управляющие теги и форматирование шрифтов будут применены корректно без взаимного наложения.
Описанный выше программный вывод фрагментов текста в коде 1С имеет один существенный недостаток: если бизнес-требования изменятся и текст приказа потребуется скорректировать (например, заменить фразу или добавить новые знаки препинания), разработчику придется заново переписывать и отлаживать всю цепочку добавления закладок в конфигураторе.
Чтобы упростить поддержку, мы можем применить метод копирования готового шаблона абзаца:
ШаблонСтроки.МестоВывода.ШаблонСтроки, копируем ее в буфер обмена с помощью вызова метода Selection.Copy().МестоВывода, вставляем шаблон из буфера нужное количество раз с помощью метода Selection.Paste().[ДатаНачала] на реальные значения) в полученных абзацах.Этот подход значительно чище, так как весь длинный текст и его стили остаются в самом макете Word, а код 1С отвечает только за тиражирование фрагмента и заполнение параметров.
Использование COM-технологии накладывает жесткие ограничения на архитектуру решения: код не будет работать на серверах под управлением Linux, а также на клиентских компьютерах без установленного пакета MS Office (например, при использовании macOS или тонкого клиента в веб-браузере).
В современных конфигурациях 1С эту проблему решает подсистема "Печать" из состава БСП. Она позволяет выполнять печать документов формата .docx на стороне сервера без привлечения внешних COM-компонентов.
Разберем по шагам, как настроить такой вывод:
{v8 Область.СтрокаПриказа}
{v8 НомерСтроки}. С {v8 ДатаНачала} по {v8 ДатаОкончания} поручить {v8 ЗамещающийСотрудникФИО}...
{/v8 Область.СтрокаПриказа}
{v8 ДатаНачала} прямо в редакторе Word.
// Пример заполнения макета DOCX через БСП
ДанныеОбъекта = Новый Структура; // Здесь собираются данные для шапки документа
// Инициализируем макет, сохраненный как двоичные данные
МакетИнфо = УправлениеПечатью.ИнициализироватьМакетОфисногоДокумента(ПолучитьМакет("МакетПриказа"), "DOCX");
ДокументИнфо = УправлениеПечатью.ИнициализироватьПечатнуюФорму("DOCX", МакетИнfo.Параметры);
// Заполняем шапку документа
УправлениеПечатью.ЗаполнитьПараметрыАдресации(ДокументИнфо.Параметры, ДанныеОбъекта);
// Выводим динамические строки в цикле
Для Каждого СтрокаСотрудник Из ТаблицаСотрудников Цикл
// Получаем область строки приказа из макета
ОбластьСтроки = УправлениеПечатью.ОбластьМакета(МакетИнфо, "СтрокаПриказа");
// Заполняем параметры конкретной строки
ОбластьСтроки.Параметры.Вставить("НомерСтроки", СтрокаСотрудник.Номер);
ОбластьСтроки.Параметры.Вставить("ДатаНачала", Формат(СтрокаСотрудник.ДатаНачала, "ДФ=dd.MM.yyyy"));
ОбластьСтроки.Параметры.Вставить("ДатаОкончания", Формат(СтрокаСотрудник.ДатаОкончания, "ДФ=dd.MM.yyyy"));
ОбластьСтроки.Параметры.Вставить("ЗамещающийСотрудникФИО", СтрокаСотрудник.ФИО);
// Присоединяем заполненную область к итоговому документу
УправлениеПечатью.ПрисоединитьОбласть(ДокументИнфо, ОбластьСтроки);
КонецЦикла;
// Получаем готовый двоичный файл для сохранения или отправки на печать
ДвоичныеДанныеФайла = УправлениеПечатью.СформироватьДокумент(ДокументИнфо);
УправлениеПечатью.ОчиститьВременныеФайлы(МакетИнфо);
Преимущества использования БСП:
WINWORD.EXE в оперативной памяти сервера.Проанализируем все рассмотренные варианты, чтобы выбрать оптимальный для вашей задачи:
| Критерий сравнения | COM-соединение (Вариант 1) | Копирование шаблона (Вариант 2) | Использование БСП (Вариант 3) |
|---|---|---|---|
| Зависимость от MS Office | Да (обязателен на клиенте/сервере) | Да (обязателен на клиенте/сервере) | Нет (работает автономно) |
| Кроссплатформенность | Нет (только Windows) | Нет (только Windows) | Да (Windows, Linux, macOS) |
| Скорость работы | Низкая (медленный вызов методов COM) | Средняя | Высокая (прямая работа с архивом DOCX) |
| Удобство изменения макета | Крайне низкое (код жестко завязан на фразы) | Высокое (изменения вносятся в Word) | Очень высокое (удобная разметка тегами) |
Подводя итог, можно сделать вывод: если вы поддерживаете старую конфигурацию 1С и вынуждены работать через COM-соединение, используйте последовательный вывод по закладкам с обязательным пересозданием базовой закладки на каждой итерации (как показано в Варианте 1), либо метод копирования областей. Для всех современных конфигураций на платформе 1С 8.3 настоятельно рекомендуется использовать стандартные механизмы печати Библиотеки стандартных подсистем (БСП).