Как вывести динамический список сотрудников в макет Word из 1С с сохранением форматирования

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

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

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

Почему возникает наслоение текста при работе с закладками Word через COM

Рассмотрим типичную ошибку, с которой сталкиваются разработчики при попытке динамического вывода строк в цикле. Посмотрим на код, который часто используют для решения этой задачи:


Для Каждого Параметр Из СтруктураПараметров.Значение Цикл
    ТекстСтрокиПриказа = Строка(Параметр.Номер) + ". С [ДатаНачала] по [ДатаОкончания] поручить...";
    СсылкаМакет.Bookmarks("ТекстСтрокиПриказа").Select();
    СсылкаМакет.Application.Selection.TypeText(ТекстСтрокиПриказа);
    
    СсылкаМакет.Bookmarks.Add("ДатаНачала", Word.Application.Selection.Range);
    СсылкаМакет.Bookmarks("ДатаНачала").Select();
    СсылкаМакет.Application.Selection.BoldRun();
    СсылкаМакет.Application.Selection.TypeText(Параметр.ДатаНачала);
    // ... и так далее для остальных параметров
КонецЦикла;

Выясним причину, почему этот код приводит к появлению «каши» и наложению текста (когда даты и фамилии выводятся слипшимся текстом в конце документа). Дело в логике работы глобального объекта Selection (Выделение) в объектной модели Microsoft Word:

  1. Метод TypeText() вставляет текст в текущую позицию курсора (выделения), после чего само выделение «сжимается» и курсор перемещается в самый конец только что вставленного фрагмента.
  2. При попытке динамически добавить новую закладку через Bookmarks.Add("ИмяЗакладки", Selection.Range), закладка создается в текущей позиции курсора (фактически, ее длина равна нулю символов).
  3. На следующей итерации цикла позиционирование по имени закладки происходит некорректно, так как новые закладки начинают наслаиваться друг на друга в одной точке документа. Происходит «каша» из слипшихся букв и цифр.

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

Решение №1: Правильная работа с COM-закладками и объектом Range

Объект Range представляет собой непрерывный фрагмент документа и, в отличие от Selection, позволяет изменять текст и форматирование без физического перемещения курсора на экране. Это значительно повышает скорость работы скрипта и предотвращает сдвиги разметки.

Разберем по шагам правильный алгоритм работы с закладками:

  1. Получаем диапазон Range существующей в шаблоне закладки.
  2. Записываем в этот диапазон нужный текст. При записи текста в свойство Range.Text сама закладка в документе Word удаляется.
  3. Поэтому сразу после записи текста мы обязаны пересоздать закладку на месте измененного диапазона, чтобы к ней можно было обратиться на следующей итерации цикла.

Посмотрим на пример кода, реализующего этот подход:


// Предположим, у нас есть ТаблицаЗначений "ТаблицаСотрудников"
Для Каждого СтрокаДанных Из ТаблицаСотрудников Цикл
    
    // 1. Получаем диапазон для вставки строки приказа
    Попытка
        ДиапазонСтроки = СсылкаМакет.Bookmarks("ТекстСтрокиПриказа").Range;
    Исключение
        Прервать; // Если закладка не найдена, прерываем цикл
    КонецПопытки;
    
    // Формируем базовый шаблон строки
    ШаблонТекста = Строка(СтрокаДанных.Номер) + ". С [ДатаНачала] по [ДатаОкончания] поручить [Сотрудник] исполнение обязанностей..." + Символы.ПС;
    
    // Записываем шаблон в диапазон (это удаляет закладку "ТекстСтрокиПриказа")
    ДиапазонСтроки.Text = ШаблонТекста;
    
    // Восстанавливаем закладку на том же месте, чтобы вставить следующую строку ниже
    СсылкаМакет.Bookmarks.Add("ТекстСтрокиПриказа", ДиапазонСтроки);
    
    // 2. Теперь производим точечную замену параметров внутри вставленного текста
    // Для этого ищем маркеры в документе и заменяем их с форматированием
    
    // Замена [ДатаНачала]
    Поиск = ДиапазонСтроки.Find;
    Поиск.ClearFormatting();
    Поиск.Text = "[ДатаНачала]";
    Если Поиск.Execute() Тогда
        ДиапазонНачала = Поиск.Parent; // Получаем Range найденного фрагмента
        ДиапазонНачала.Text = Формат(СтрокаДанных.ДатаНачала, "ДФ=dd.MM.yyyy");
        ДиапазонНачала.Font.Bold = 1; // Делаем текст жирным
    КонецЕсли;
    
    // Замена [Сотрудник]
    Поиск = ДиапазонСтроки.Find;
    Поиск.ClearFormatting();
    Поиск.Text = "[Сотрудник]";
    Если Поиск.Execute() Тогда
        ДиапазонСотрудника = Поиск.Parent;
        ДиапазонСотрудника.Text = СтрокаДанных.СотрудникФИО;
        ДиапазонСотрудника.Font.Bold = 1;
    КонецЕсли;
    
КонецЦикла;

Использование свойства Font.Bold = 1 (или True в зависимости от настроек COM-соединения) применительно к объекту Range гарантирует, что жирным шрифтом выделится только измененный параметр, а остальной текст строки приказа останется обычного начертания.

Решение №2: Дублирование шаблона строки через буфер обмена

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

Разберем этот метод подробнее:

  1. В макете Word создаем одну эталонную строку со всеми нужными преднастроенными шрифтами, отступами и маркерами (например, [Номер]. С [ДатаНачала] по [ДатаОкончания]...).
  2. Оборачиваем всю эту строку целиком в одну закладку с именем ШаблонСтроки.
  3. В коде 1С перед началом цикла выделяем эту область и копируем ее в буфер обмена Word.
  4. В цикле вставляем скопированный шаблон в конец документа (или в место другой целевой закладки), после чего производим простую замену параметров в свежевставленном фрагменте.

Посмотрим на программную реализацию этого алгоритма:


// Шаг 1. Выделяем и копируем шаблон в буфер обмена Word
Шаблон = СсылкаМакет.Bookmarks("ШаблонСтроки").Range;
Шаблон.Select();
СсылкаМакет.Application.Selection.Copy();

// Шаг 2. Переходим к целевой закладке, куда будем выводить список
ДиапазонВставки = СсылкаМакет.Bookmarks("МестоВставкиСписка").Range;
ДиапазонВставки.Select();

Для Каждого СтрокаДанных Из ТаблицаСотрудников Цикл
    // Вставляем отформатированный шаблон из буфера обмена
    СсылкаМакет.Application.Selection.Paste();
    
    // Получаем диапазон выделения после вставки
    ТекущийДиапазон = СсылкаМакет.Application.Selection.Range;
    
    // Выполняем автозамену параметров в рамках текущего диапазона
    Поиск = ТекущийДиапазон.Find;
    
    Поиск.Text = "[Номер]";
    Поиск.Replacement.Text = Строка(СтрокаДанных.Номер);
    Поиск.Execute(,,,,,,,,,, 2); // Параметр 2 означает замену всех вхождений в диапазоне
    
    Поиск.Text = "[ДатаНачала]";
    Поиск.Replacement.Text = Формат(СтрокаДанных.ДатаНачала, "ДФ=dd.MM.yyyy");
    Поиск.Execute(,,,,,,,,,, 2);
    
    Поиск.Text = "[Сотрудник]";
    Поиск.Replacement.Text = СтрокаДанных.СотрудникФИО;
    Поиск.Execute(,,,,,,,,,, 2);
    
    // Сдвигаем курсор в конец вставленного текста для следующей итерации
    СсылкаМакет.Application.Selection.Collapse(0); // 0 соответствует константе wdCollapseEnd
КонецЦикла;

// В конце работы можно удалить сам исходный шаблон, чтобы он не отображался в документе
СсылкаМакет.Bookmarks("ШаблонСтроки").Range.Delete();

Этот способ позволяет сохранить абсолютно любое форматирование, настроенное пользователем в файле Word, избавляя программиста от написания громоздкого кода настройки шрифтов.

Решение №3: Современный стандарт на базе БСП (без использования COM)

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

В современных конфигурациях 1С, использующих Библиотеку стандартных подсистем (БСП), реализован механизм печати документов в формате Office Open XML (файлы .docx). Этот механизм работает напрямую с XML-структурой файла и выполняется на сервере без привлечения стороннего ПО.

Давайте проанализируем, как реализовать нашу задачу с помощью БСП:

  1. В макете Word создаем область разметки с помощью специальных тегов БСП. Область должна открываться тегом {v8 Область.СтрокаПриказа} и закрываться тегом {/v8 Область.СтрокаПриказа}.
  2. Внутри области размещаем параметры в фигурных скобках, например: С {v8 ДатаНачала} по {v8 ДатаОкончания} поручить {v8 Сотрудник}....
  3. В самом шаблоне Word настраиваем форматирование параметров: если выделить параметр {v8 Сотрудник} жирным шрифтом прямо в Word, он останется жирным и после заполнения данными из 1С.

Для вывода списка строк используется стандартный программный интерфейс БСП. Нам больше не нужны циклы для ручного заполнения каждого параметра. Посмотрим, как лаконично выглядит код в 1С:


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

// Формируем таблицу значений для вывода динамического списка
ТаблицаСтрок = Новый ТаблицаЗначений;
ТаблицаСтрок.Колонки.Добавить("ДатаНачала");
ТаблицаСтрок.Колонки.Добавить("ДатаОкончания");
ТаблицаСтрок.Колонки.Добавить("Сотрудник");

Для Каждого Стр Из ЗамещениеСотрудников Цикл
    НоваяСтрока = ТаблицаСтрок.Добавить();
    НоваяСтрока.ДатаНачала = Стр.ДатаНачала;
    НоваяСтрока.ДатаОкончания = Стр.ДатаОкончания;
    НоваяСтрока.Сотрудник = Стр.ЗамещающийСотрудникФИО;
КонецЦикла;

// Получаем макет из двоичных данных
Макет = ПолучитьМакет("МакетПриказаWord");

// Запускаем печать БСП
ПечатнаяФорма = УправлениеПечатью.ИнициализироватьПечатнуюФормуMSWord(Макет);

// Заполняем простые реквизиты шапки документа
УправлениеПечатью.ЗаполнитьПараметры(ПечатнаяФорма, ДанныеОбъекта);

// Выводим динамический список сотрудников. 
// БСП автоматически размножит область "СтрокаПриказа" столько раз, сколько строк в нашей таблице!
УправлениеПечатью.ПрисоединитьИЗаполнитьКоллекцию(ПечатнаяФорма, "СтрокаПриказа", ТаблицаСтрок);

// Завершаем формирование печатной формы
ДокументРезультат = УправлениеПечатью.СформироватьПечатнуюФорму(ПечатнаяФорма);

Использование БСП — это наиболее надежный, быстрый и отказоустойчивый метод формирования документов Word из 1С на сегодняшний день. Он полностью решает проблему форматирования и избавляет от необходимости отлаживать сложные макросы VBA/COM.

← На главную