При формировании печатных форм договоров, соглашений или спецификаций в формате Microsoft Word разработчики часто сталкиваются с задачей вывода табличной части (например, списка товаров, услуг или цен). В отличие от простых параметров (таких как дата документа или наименование контрагента), которые заменяются обычным поиском и автозаменой, табличная часть содержит заранее неизвестное количество строк. Если просто попытаться заменить один маркер в шаблоне, мы сможем вывести только первую строку данных.
В этой статье мы подробно разберем несколько практических способов решения этой задачи: от классического копирования областей через буфер обмена до прямого заполнения таблиц через COM-соединение и современных серверных механизмов без использования стороннего ПО.
Этот метод используется в Библиотеке стандартных подсистем (БСП) и является стандартным для современных конфигураций 1С. Суть метода заключается в разметке шаблона Word специальными тегами начала и окончания областей. Мы находим нужную область в макете, копируем её в буфер обмена, а затем последовательно вставляем в результирующий документ необходимое количество раз, заполняя параметры.
Для начала проанализируем разметку шаблона в Word. Нам необходимо пометить строку таблицы специальными тегами:
{v8 Область.СтрокаТЧ} и {/v8 Область.СтрокаТЧ}
Внутри ячеек этой строки мы размещаем параметры для автозамены, например: {НомерСтроки}, {Номенклатура}, {Цена}, {Сумма}.
Рассмотрим код, который выполняет поиск этих областей, их копирование и вставку. Обратите внимание на важный нюанс: при интенсивной работе с буфером обмена Windows операции могут выполняться асинхронно, что иногда приводит к ошибкам заполнения. Мы добавим небольшую программную задержку (так называемый «костыль»), чтобы буфер обмена успевал обновляться.
Функция ПолучитьОбластьМакетаMSWord(Знач Handler, Знач ИмяОбласти, Знач СмещениеНачало = 1, Знач СмещениеОкончание = -1) Экспорт
Результат = Новый Структура("Document,Start,End");
ПозицияНачало = СмещениеНачало + ПолучитьПозициюНачалаОбласти(Handler.COMСоединение, ИмяОбласти);
ПозицияОкончание = СмещениеОкончание + ПолучитьПозициюОкончанияОбласти(Handler.COMСоединение, ИмяОбласти);
Если ПозицияНачало >= ПозицияОкончание Или ПозицияНачало < 0 Тогда
Возврат Неопределено;
КонецЕсли;
Результат.Document = Handler.COMСоединение.ActiveDocument;
Результат.Start = ПозицияНачало;
Результат.End = ПозицияОкончание;
Возврат Результат;
КонецФункции
Функция ПолучитьПозициюНачалаОбласти(Знач COMСоединение, Знач ИдентификаторОбласти)
ИдентификаторОбласти = "{v8 Область." + ИдентификаторОбласти + "}";
ВесьДокумент = COMСоединение.ActiveDocument.Content;
ВесьДокумент.Select();
Поиск = COMСоединение.Selection.Find;
Поиск.Text = ИдентификаторОбласти;
Поиск.ClearFormatting();
Поиск.Forward = Истина;
Поиск.execute();
Если Поиск.Found Тогда
Возврат COMСоединение.Selection.End;
КонецЕсли;
Возврат -1;
КонецФункции
Функция ПолучитьПозициюОкончанияОбласти(Знач COMСоединение, Знач ИдентификаторОбласти)
ИдентификаторОбласти = "{/v8 Область." + ИдентификаторОбласти + "}";
ВесьДокумент = COMСоединение.ActiveDocument.Content;
ВесьДокумент.Select();
Поиск = COMСоединение.Selection.Find;
Поиск.Text = ИдентификаторОбласти;
Поиск.ClearFormatting();
Поиск.Forward = Истина;
Поиск.execute();
Если Поиск.Found Тогда
Возврат COMСоединение.Selection.Start;
КонецЕсли;
Возврат -1;
КонецФункции
Функция ПрисоединитьОбласть(Знач ПечатнаяФорма, Знач ОбластьHandler, Знач ПереходНаСледСтроку = Истина, Знач ПрисоединитьСтрокуТаблицы = Ложь) Экспорт
// Копируем область шаблона в буфер обмена
ОбластьHandler.Document.Range(ОбластьHandler.Start, ОбластьHandler.End).Copy();
// Искусственная пауза для синхронизации буфера обмена Windows
ТекущаяДата = ТекущаяДата();
ВремяОстановки = ТекущаяДата + 1;
Пока ТекущаяДата < ВремяОстановки Цикл
ТекущаяДата = ТекущаяДата();
КонецЦикла;
ПФ_ActiveDocument = ПечатнаяФорма.COMСоединение.ActiveDocument;
ПозицияОкончанияДокумента = ПФ_ActiveDocument.Range.End;
ОбластьВставки = ПФ_ActiveDocument.Range(ПозицияОкончанияДокумента - 1, ПозицияОкончанияДокумента - 1);
Если ПрисоединитьСтрокуТаблицы Тогда
// Метод вставляет скопированную строку в существующую таблицу
ОбластьВставки.PasteAppendTable();
Иначе
ОбластьВставки.Paste();
КонецЕсли;
Результат = Новый Структура("Document, Start, End", ПФ_ActiveDocument, ПозицияОкончанияДокумента - 1, ПФ_ActiveDocument.Range.End - 1);
Если ПереходНаСледСтроку Тогда
// Вставка символа перевода строки, если необходимо
ПФ_ActiveDocument.Range(ПФ_ActiveDocument.Range.End - 1, ПФ_ActiveDocument.Range.End - 1).InsertParagraphAfter();
КонецЕсли;
Возврат Результат;
КонецФункции
Этот способ достаточно гибок и позволяет формировать сложные многостраничные документы. Однако использование буфера обмена накладывает ограничения: во время формирования печатной формы пользователь не должен использовать функции копирования и вставки на своем рабочем месте, иначе данные могут перепутаться.
Выясним причину, почему разработчики часто избегают предыдущего метода. Главный минус работы через буфер обмена — его нестабильность в многопользовательской среде. Чтобы сделать печатную форму максимально надежной, мы можем напрямую обращаться к объектной модели MS Word, добавлять строки в существующую таблицу и записывать данные в конкретные ячейки.
Проанализируем алгоритм работы этого метода:
Посмотрим на практический пример реализации этого алгоритма:
Процедура ВывестиТабличнуюЧастьНапрямую(ДокументСсылка)
// Получаем макет из конфигурации
Макет = ПолучитьМакет("ДоговорСпецификация");
MsWord = Макет.Получить();
Попытка
ДокументWord = MsWord.Application.Documents(1);
ДокументWord.Activate();
// Предположим, что таблица спецификации — первая в документе
ТаблицаWord = ДокументWord.Tables(1);
// Получаем данные из табличной части документа 1С
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Ссылка", ДокументСсылка);
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказТовары.НомерСтроки КАК НомерСтроки,
| ЗаказТовары.Номенклатура.Наименование КАК Товар,
| ЗаказТовары.Количество КАК Количество,
| ЗаказТовары.Цена КАК Цена,
| ЗаказТовары.Сумма КАК Сумма
|ИЗ
| Документ.ЗаказПокупателя.Товары КАК ЗаказТовары
|ГДЕ
| ЗаказТовары.Ссылка = &Ссылка";
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
НомерСтрокиТаблицы = 2; // Первая строка — это шапка таблицы
Пока Выборка.Следующий() Цикл
// Если это не первая итерация, добавляем новую строку в таблицу Word
Если НомерСтрокиТаблицы > 2 Тогда
ТаблицаWord.Rows.Add();
КонецЕсли;
// Заполняем ячейки добавленной строки данными
ТаблицаWord.Cell(НомерСтрокиТаблицы, 1).Range.Text = Строка(Выборка.НомерСтроки);
ТаблицаWord.Cell(НомерСтрокиТаблицы, 2).Range.Text = Выборка.Товар;
ТаблицаWord.Cell(НомерСтрокиТаблицы, 3).Range.Text = Строка(Выборка.Количество);
ТаблицаWord.Cell(НомерСтрокиТаблицы, 4).Range.Text = Формат(Выборка.Цена, "ЧЦ=15; ЧДЦ=2");
ТаблицаWord.Cell(НомерСтрокиТаблицы, 5).Range.Text = Формат(Выборка.Сумма, "ЧЦ=15; ЧДЦ=2");
// Переходим к следующей строке таблицы Word
НомерСтрокиТаблицы = НомерСтрокиТаблицы + 1;
КонецЦикла;
MsWord.Application.Visible = Истина;
Исключение
Сообщить("Произошла ошибка: " + ОписаниеОшибки(), СтатусСообщения.Важное);
MsWord.Application.Quit();
КонецПопытки;
КонецПроцедуры
Этот способ работает стабильно, не зависит от состояния системного буфера обмена и гарантирует, что оформление строк (границы, цвет текста) унаследуется от первой шаблонной строки таблицы.
Рассмотрим современную альтернативу использованию COM-технологии. Начиная с платформы 8.3.5 и в составе БСП доступен функционал генерации файлов в формате .docx непосредственно на сервере 1С. Это особенно важно, если сервер 1С работает под управлением операционной системы Linux, где запустить MS Word через COM-объект физически невозможно.
При таком подходе макет договора загружается в 1С как двоичные данные (обычный файл .docx). Внутри него табличная часть размечается тегами вида {v8 Область.СтрокаТЧ}. 1С считывает этот файл на сервере как ZIP-архив, парсит XML-структуру (файл word/document.xml) и программно дублирует XML-узлы строк таблицы необходимое количество раз.
Разберем, как выглядит программный вызов этого механизма с помощью процедур БСП:
// Пример использования БСП для формирования печатной формы DOCX
ПараметрыПечати = Новый Структура;
ПараметрыПечати.Вставить("ВыводитьТабличнуюЧасть", Истина);
ДанныеОбъекта = Новый Структура;
ДанныеОбъекта.Вставить("НомерДоговора", Объект.Номер);
ДанныеОбъекта.Вставить("ДатаДоговора", Формат(Объект.Дата, "ДЛФ=DD"));
// Формируем таблицу для вывода
ТаблицаТоваров = Новый ТаблицаЗначений;
ТаблицаТоваров.Колонки.Добавить("НомерСтроки");
ТаблицаТоваров.Колонки.Добавить("Товар");
ТаблицаТоваров.Колонки.Добавить("Количество");
ТаблицаТоваров.Колонки.Добавить("Сумма");
Для Каждого Стр Из Объект.Товары Цикл
НоваяСтр = ТаблицаТоваров.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтр, Стр);
НоваяСтр.Товар = Стр.Номенклатура.Наименование;
КонецЦикла;
ДанныеОбъекта.Вставить("ТаблицаТоваров", ТаблицаТоваров);
// Вызов функции генерации печатной формы через подсистему УправлениеПечатью БСП
// МакетOffice - это имя макета с типом ДвоичныеДанные (docx)
АдресХранилищаПечатнойФормы = УправлениеПечатью.СформироватьПечатнуюФормуMsoffice(
"ДоговорКуплиПродажи",
"ДвоичныеДанныеМакета",
ДанныеОбъекта
);
Преимущества этого метода неоспоримы: высокая скорость работы (формирование происходит в разы быстрее, чем через COM), полная кроссплатформенность и отсутствие необходимости устанавливать дорогостоящий пакет MS Office на серверную машину.
Если вам требуется максимально быстро решить задачу без написания сложного кода по управлению COM-объектами или XML-структурами, проанализируем самый простой «лайфхак». Мы можем сформировать обычный текстовый документ с HTML-разметкой, вывести таблицу с помощью стандартных тегов <table>, <tr>, <td>, а затем сохранить полученный файл на диск с расширением .doc.
Microsoft Word умеет автоматически распознавать HTML-код внутри файлов с расширением .doc и преобразовывать их в красивый форматированный текст. Для пользователя этот процесс будет абсолютно бесшовным.
Посмотрим на пример реализации такого генератора:
Процедура СформироватьДоговорЧерезHTML(ДокументСсылка)
// Начинаем сборку HTML-текста договора
HTML_Текст = "<html><head><meta charset='utf-8'></head><body style='font-family: Arial; font-size: 12pt;'>";
HTML_Текст = HTML_Текст + "<h2 style='text-align: center;'>ДОГОВОР ПОСТАВКИ № " + ДокументСсылка.Номер + "</h2>";
HTML_Текст = HTML_Текст + "<p>Ниже представлена спецификация поставляемого оборудования:</p>";
// Строим HTML-таблицу
HTML_Текст = HTML_Текст + "<table border='1' cellspacing='0' cellpadding='5' style='width: 100%; border-collapse: collapse;'>";
HTML_Текст = HTML_Текст + "<tr style='background-color: #f2f2f2;'><th>№</th><th>Наименование товара</th><th>Кол-во</th><th>Цена</th><th>Сумма</th></tr>";
Для Каждого СтрокаТЧ Из ДокументСсылка.Товары Цикл
HTML_Текст = HTML_Текст + "<tr>";
HTML_Текст = HTML_Текст + "<td style='text-align: center;'>" + СтрокаТЧ.НомерСтроки + "</td>";
HTML_Текст = HTML_Текст + "<td>" + СтрокаТЧ.Номенклатура.Наименование + "</td>";
HTML_Текст = HTML_Текст + "<td style='text-align: right;'>" + СтрокаТЧ.Количество + "</td>";
HTML_Текст = HTML_Текст + "<td style='text-align: right;'>" + СтрокаТЧ.Цена + "</td>";
HTML_Текст = HTML_Текст + "<td style='text-align: right;'>" + СтрокаТЧ.Сумма + "</td>";
HTML_Текст = HTML_Текст + "<tr>";
КонецЦикла;
HTML_Текст = HTML_Текст + "</table>";
HTML_Текст = HTML_Текст + "</body></html>";
// Записываем полученный текст в файл с расширением .doc
ПутьКФайлу = КаталогВременныхФайлов() + "Договор_" + СокрЛП(ДокументСсылка.Номер) + ".doc";
ТекстовыйДокумент = Новый ТекстовыйДокумент;
ТекстовыйДокумент.УстановитьТекст(HTML_Текст);
ТекстовыйДокумент.Записать(ПутьКФайлу, КодировкаТекста.UTF8);
// Открываем созданный файл в MS Word на клиенте
ЗапуститьПриложение(ПутьКФайлу);
КонецПроцедуры
Этот метод невероятно прост в отладке. Вы можете использовать CSS-стили для точной настройки внешнего вида таблицы, изменения цвета заголовков, настройки границ и отступов. При этом вам не нужно изучать сложные свойства COM-объектов MS Word.
Подведем итоги: для старых конфигураций 1С на обычных формах (например, УТ 10.3) лучшим выбором будет прямое заполнение ячеек таблицы Word через COM (Метод 2) или генерация HTML-файла (Метод 4). Для современных конфигураций (ERP, КА 2, БП 3.0) на управляемых формах всегда стоит отдавать приоритет штатным механизмам БСП и серверной сборке файлов DOCX без использования COM-соединений.