При разработке печатных форм в 1С часто возникает задача вывода документов со сложной версткой в формат Microsoft Word (.doc или .docx). Это могут быть трудовые договоры, приказы, соглашения или дополнительные соглашения. Трудность заключается в том, что состав и количество выводимых строк (например, список замещающих сотрудников в приказе) заранее неизвестны, а отдельные элементы текста (ФИО, даты, должности) должны выделяться полужирным шрифтом или иметь другое специфическое форматирование.
В этой статье мы подробно разберем, почему возникает наслоение текста («каша») при использовании стандартных закладок через COM-соединение, как правильно управлять объектами Word из 1С и какие современные альтернативные методы существуют для решения этой задачи.
Рассмотрим типичную ошибку, с которой сталкиваются разработчики при попытке динамического вывода строк в цикле. Посмотрим на код, который часто используют для решения этой задачи:
Для Каждого Параметр Из СтруктураПараметров.Значение Цикл
ТекстСтрокиПриказа = Строка(Параметр.Номер) + ". С [ДатаНачала] по [ДатаОкончания] поручить...";
СсылкаМакет.Bookmarks("ТекстСтрокиПриказа").Select();
СсылкаМакет.Application.Selection.TypeText(ТекстСтрокиПриказа);
СсылкаМакет.Bookmarks.Add("ДатаНачала", Word.Application.Selection.Range);
СсылкаМакет.Bookmarks("ДатаНачала").Select();
СсылкаМакет.Application.Selection.BoldRun();
СсылкаМакет.Application.Selection.TypeText(Параметр.ДатаНачала);
// ... и так далее для остальных параметров
КонецЦикла;
Выясним причину, почему этот код приводит к появлению «каши» и наложению текста (когда даты и фамилии выводятся слипшимся текстом в конце документа). Дело в логике работы глобального объекта Selection (Выделение) в объектной модели Microsoft Word:
TypeText() вставляет текст в текущую позицию курсора (выделения), после чего само выделение «сжимается» и курсор перемещается в самый конец только что вставленного фрагмента.Bookmarks.Add("ИмяЗакладки", Selection.Range), закладка создается в текущей позиции курсора (фактически, ее длина равна нулю символов).Для решения этой проблемы нам необходимо отказаться от постоянного использования объекта Selection в пользу точечной работы с объектами Range (Диапазон).
Объект Range представляет собой непрерывный фрагмент документа и, в отличие от Selection, позволяет изменять текст и форматирование без физического перемещения курсора на экране. Это значительно повышает скорость работы скрипта и предотвращает сдвиги разметки.
Разберем по шагам правильный алгоритм работы с закладками:
Range существующей в шаблоне закладки.Range.Text сама закладка в документе Word удаляется.Посмотрим на пример кода, реализующего этот подход:
// Предположим, у нас есть ТаблицаЗначений "ТаблицаСотрудников"
Для Каждого СтрокаДанных Из ТаблицаСотрудников Цикл
// 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 гарантирует, что жирным шрифтом выделится только измененный параметр, а остальной текст строки приказа останется обычного начертания.
Если структура строки приказа очень сложная (содержит разные шрифты, подчеркивания, табуляцию), программировать форматирование каждого фрагмента в 1С крайне трудоемко. В таком случае рассмотрим метод копирования готового отформатированного шаблона строки через буфер обмена Word.
Разберем этот метод подробнее:
[Номер]. С [ДатаНачала] по [ДатаОкончания]...).ШаблонСтроки.Посмотрим на программную реализацию этого алгоритма:
// Шаг 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, избавляя программиста от написания громоздкого кода настройки шрифтов.
Использование COM-соединения имеет огромный недостаток: оно требует обязательного наличия установленного Microsoft Office (Word) на компьютере пользователя или на сервере 1С. Это делает невозможной генерацию документов на серверах под управлением Linux, а также в веб-клиенте.
В современных конфигурациях 1С, использующих Библиотеку стандартных подсистем (БСП), реализован механизм печати документов в формате Office Open XML (файлы .docx). Этот механизм работает напрямую с XML-структурой файла и выполняется на сервере без привлечения стороннего ПО.
Давайте проанализируем, как реализовать нашу задачу с помощью БСП:
{v8 Область.СтрокаПриказа} и закрываться тегом {/v8 Область.СтрокаПриказа}.С {v8 ДатаНачала} по {v8 ДатаОкончания} поручить {v8 Сотрудник}....{v8 Сотрудник} жирным шрифтом прямо в Word, он останется жирным и после заполнения данными из 1С.Для вывода списка строк используется стандартный программный интерфейс БСП. Нам больше не нужны циклы для ручного заполнения каждого параметра. Посмотрим, как лаконично выглядит код в 1С:
// Инициализируем макет и структуру печатной формы средствами БСП
ДанныеОбъекта = Новый Структура;
ДанныеОбъекта.Вставить("ДатаДокумента", ТекущаяДата());
// Формируем таблицу значений для вывода динамического списка
ТаблицаСтрок = Новый ТаблицаЗначений;
ТаблицаСтрок.Колонки.Добавить("ДатаНачала");
ТаблицаСтрок.Колонки.Добавить("ДатаОкончания");
ТаблицаСтрок.Колонки.Добавить("Сотрудник");
Для Каждого Стр Из ЗамещениеСотрудников Цикл
НоваяСтрока = ТаблицаСтрок.Добавить();
НоваяСтрока.ДатаНачала = Стр.ДатаНачала;
НоваяСтрока.ДатаОкончания = Стр.ДатаОкончания;
НоваяСтрока.Сотрудник = Стр.ЗамещающийСотрудникФИО;
КонецЦикла;
// Получаем макет из двоичных данных
Макет = ПолучитьМакет("МакетПриказаWord");
// Запускаем печать БСП
ПечатнаяФорма = УправлениеПечатью.ИнициализироватьПечатнуюФормуMSWord(Макет);
// Заполняем простые реквизиты шапки документа
УправлениеПечатью.ЗаполнитьПараметры(ПечатнаяФорма, ДанныеОбъекта);
// Выводим динамический список сотрудников.
// БСП автоматически размножит область "СтрокаПриказа" столько раз, сколько строк в нашей таблице!
УправлениеПечатью.ПрисоединитьИЗаполнитьКоллекцию(ПечатнаяФорма, "СтрокаПриказа", ТаблицаСтрок);
// Завершаем формирование печатной формы
ДокументРезультат = УправлениеПечатью.СформироватьПечатнуюФорму(ПечатнаяФорма);
Использование БСП — это наиболее надежный, быстрый и отказоустойчивый метод формирования документов Word из 1С на сегодняшний день. Он полностью решает проблему форматирования и избавляет от необходимости отлаживать сложные макросы VBA/COM.