При формировании печатных форм и отчетов в формате Microsoft Word на стороне сервера часто возникает ограничение: офисный пакет не установлен на серверной операционной системе (особенно под управлением Linux), либо использование COM-соединения через COMОбъект("Word.Application") работает нестабильно, блокирует интерфейс и расходует много ресурсов. Документ формата docx представляет собой обычный zip-архив, содержащий XML-файлы разметки OpenXML. Разберем по шагам, как программно распаковать такой документ, заполнить обычные реквизиты, динамически сформировать строки таблицы товаров и корректно собрать файл обратно средствами платформы 1С:Предприятие.
Основное содержимое документа Word находится внутри архива в файле word/document.xml. При прямой работе с данным файлом как со строкой разработчики часто сталкиваются с неочевидными проблемами OpenXML, которые приводят к ошибкам при открытии файла:
<w:r> и <w:t>. Если вы ввели метку {Товар}, в XML она может превратиться в цепочку из трех узлов: <w:t>{</w:t>, <w:t>Товар</w:t> и <w:t>}</w:t>. Простой вызов метода СтрЗаменить() в таком случае не найдет метку. Для предотвращения дробления рекомендуется вставлять метки в шаблон единым блоком из буфера обмена и отключать проверку правописания для меток.&, <, >, ", ', нарушают валидность XML. Перед подстановкой значений их необходимо обязательно экранировать (например, заменять & на &).<w:tr>, ячейка — тегом <w:tc>. Внутри ячейки обязательно должен присутствовать параграф <w:p>. Если ячейка пустая, параграф внутри нее все равно должен быть объявлен, иначе Word сочтет файл поврежденным.Проанализируем конфигурации на базе Библиотеки стандартных подсистем (включая 1С:КА, 1С:ERP, 1С:УТ, 1С:ДО). В подсистеме печати БСП уже реализован готовый механизм формирования документов docx без COM-объекта:
Этот способ является наиболее предпочтительным и безопасным, так как штатные механизмы БСП берут на себя контроль валидности структуры XML и работу с архивом.
Если требуется нестандартная верстка, колонки разной ширины, объединенные строки итогов или перенос логики в собственную конфигурацию, эффективным решением является замена единого параметра в шаблоне на готовый XML-код всей таблицы. Разберем этот алгоритм подробно.
Чтобы понять точную XML-структуру сложной таблицы, создадим в Microsoft Word пустой документ, добавим требуемую таблицу с нужным оформлением (ширина столбцов, шрифты, границы, объединение ячеек в подвале) и сохраним файл. Затем разархивируем docx и изучим файл word/document.xml:
<w:tcPr> объединение колонок по горизонтали задается тегом <w:gridSpan w:val="N"/>, где N — количество объединенных столбцов.<w:vMerge w:val="restart"/> в первой ячейке и <w:vMerge/> в последующих строках той же колонки.w:rsidR, w:rsidTr) можно безопасно исключить из генерации, чтобы не перегружать XML.Рассмотрим пример кода 1С, который распаковывает архив, генерирует XML-таблицу товаров и выполняет замену маркера:
// 1. Распаковка архива docx во временный каталог
КаталогВременныхФайловШаблона = ПолучитьИмяВременногоФайла();
СоздатьКаталог(КаталогВременныхФайловШаблона);
ЧтениеАрхива = Новый ЧтениеZipФайла(ПутьКШаблонуDocx);
ЧтениеАрхива.ИзвлечьВсе(КаталогВременныхФайловШаблона, РежимВосстановленияПутейФайловZIP.Восстанавливать);
ЧтениеАрхива.Закрыть();
ПутьКDocumentXML = КаталогВременныхФайловШаблона + "/word/document.xml";
// 2. Чтение исходного XML документа
ТекстовыйФайл = Новый ТекстовыйДокумент;
ТекстовыйФайл.Прочитать(ПутьКDocumentXML, КодировкаТекста.UTF8);
ТекстДокумента = ТекстовыйФайл.ПолучитьТекст();
// 3. Формирование XML-кода таблицы товаров
СтрТаблица = "";
СтрТаблица = СтрТаблица + "<w:tbl><w:tblPr><w:tblW w:w=""0"" w:type=""auto""/></w:tblPr>";
// Шапка таблицы
СтрТаблица = СтрТаблица + "<w:tr>"
+ "<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>№</w:t></w:r></w:p></w:tc>"
+ "<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Товар</w:t></w:r></w:p></w:tc>"
+ "<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Количество</w:t></w:r></w:p></w:tc>"
+ "<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Сумма</w:t></w:r></w:p></w:tc>"
+ "</w:tr>";
// Строки с товарами
ИтогоСумма = 0;
Для Каждого Стр Из ТаблицаТоваров Цикл
ИтогоСумма = ИтогоСумма + Стр.Сумма;
// Экранируем спецсимволы в наименовании
БезопасноеНаименование = СтрЗаменить(Стр.Номенклатура, "&", "&");
БезопасноеНаименование = СтрЗаменить(БезопасноеНаименование, "<", "<");
БезопасноеНаименование = СтрЗаменить(БезопасноеНаименование, ">", ">");
СтрТаблица = СтрТаблица + "<w:tr>"
+ "<w:tc><w:p><w:r><w:t>" + Стр.НомерСтроки + "</w:t></w:r></w:p></w:tc>"
+ "<w:tc><w:p><w:r><w:t>" + БезопасноеНаименование + "</w:t></w:r></w:p></w:tc>"
+ "<w:tc><w:p><w:r><w:t>" + Формат(Стр.Количество, "ЧДЦ=2") + "</w:t></w:r></w:p></w:tc>"
+ "<w:tc><w:p><w:r><w:t>" + Формат(Стр.Сумма, "ЧДЦ=2") + "</w:t></w:r></w:p></w:tc>"
+ "</w:tr>";
КонецЦикла;
// Итоговая строка с объединением трех колонок
СтрТаблица = СтрТаблица + "<w:tr>"
+ "<w:tc><w:tcPr><w:gridSpan w:val=""3""/></w:tcPr><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>ИТОГО:</w:t></w:r></w:p></w:tc>"
+ "<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>" + Формат(ИтогоСумма, "ЧДЦ=2") + "</w:t></w:r></w:p></w:tc>"
+ "</w:tr>";
СтрТаблица = СтрТаблица + "</w:tbl>";
// 4. Подстановка таблицы вместо метки в документе
// Метка в шаблоне должна быть обернута отдельным параграфом, который замещается таблицей
ТекстДокумента = СтрЗаменить(ТекстДокумента, "<w:p><w:r><w:t>[ТаблицаТоваров]</w:t></w:r></w:p>", СтрТаблица);
// Заполнение обычных реквизитов документа
ТекстДокумента = СтрЗаменить(ТекстДокумента, "[НомерДокумента]", НомерДокумента);
ТекстДокумента = СтрЗаменить(ТекстДокумента, "[ДатаДокумента]", Формат(ДатаДокумента, "ДЛФ=DD"));
// 5. Запись обновленного XML
ТекстовыйФайл.УстановитьТекст(ТекстДокумента);
ТекстовыйФайл.Записать(ПутьКDocumentXML, КодировкаТекста.UTF8);
// 6. Сборка архива docx обратно
ЗаписьАрхива = Новый ЗаписьZipФайла(ПутьКФинальномуDocx);
ЗаписьАрхива.Добавить(КаталогВременныхФайловШаблона + "/*", РежимСохраненияПутейZIP.СохранятьОтносительныеПути, РежимОбработкиПодкаталоговZIP.ОбрабатыватьРекурсивно);
ЗаписьАрхива.Записать();
// Удаление временных файлов
УдалитьФайлы(КаталогВременныхФайловШаблона);
Когда таблица в шаблоне уже имеет сложное визуальное оформление (заливки, определенные отступы, границы, кастомные стили), вручную генерировать полный XML таблицы неудобно. В этом случае надежнее использовать объектную модель документа ДокументDOM:
[Номер], [Номенклатура], [Количество]).word/document.xml загружается через ЧтениеXML и объект ПостроительDOM.<w:tr>, содержащего наши метки.Клонировать(Истина).ВставитьПеред().УдалитьВложенный().ЗаписьDOM и ЗаписьXML.Работа через DOM исключает синтаксические ошибки закрытия тегов и повреждение разметки, однако для документов объемом в сотни страниц требует повышенного объема оперативной памяти.
Если задача не требует жесткого соответствия специфическим возможностям верстки Microsoft Word, рассмотрим другие стандартные способы выгрузки данных:
ТабличныйДокумент.Записать(ПутьКФайлу, ТипФайлаТабличногоДокумента.DOCX) или ТипФайлаТабличногоДокумента.PDF. Метод работает полностью нативно, без COM и офисных пакетов на серверах Windows и Linux.<table>, <tr>, <td> и сохраняется с расширением .doc. Microsoft Word открывает такой документ прозрачно для пользователя.