При разработке механизмов печати договоров и коммерческих предложений программисты часто используют макеты в формате Microsoft Word (файлы .docx), хранящиеся в конфигурации как двоичные данные. Однако при реализации выгрузки таких файлов на управляемых формах нередко возникает неприятное исключение: «Произошла исключительная ситуация (Microsoft Word): Файл не найден». При этом в процессе отладки видно, что путь к временному файлу успешно формируется, но MS Word наотрез отказывается его открывать. Выясним причину этой проблемы и подробно разберем правильные архитектурные решения.
Давайте проанализируем исходный код, приведенный в начале обсуждения. В нем функция получения макета выполняется в контексте сервера (директива &НаСервере), а создание COM-объекта и открытие документа — на стороне клиента (директива &НаКлиенте). Проследим путь создания файла:
ПолучитьИмяВременногоФайла("docx"). Платформа генерирует уникальный путь во временной директории операционной системы, установленной на серверном компьютере (например, C:\Windows\Temp\v8_A1B2_1.docx).ДвоичныеДанныеМакета.Записать(ИмяФайла) физически создает и записывает файл именно по этому серверному пути.Word.Application, который пытается выполнить команду Documents.Add(ИмяФайла).Поскольку клиентское приложение (компьютер пользователя) физически не имеет доступа к папкам временных файлов сервера (особенно в клиент-серверном варианте работы базы данных 1С), MS Word на локальной машине выдает логичную ошибку: «Файл не найден». Путь, переданный с сервера, просто не существует в файловой системе клиента.
Чтобы решить эту проблему, нам необходимо перенести физическое создание файла на сторону клиента. Однако напрямую прочитать макет из базы данных на клиенте невозможно. Для обмена двоичными данными между сервером и клиентом мы должны использовать механизм Временного хранилища.
Рассмотрим подробнее скорректированный алгоритм совместной работы клиента и сервера:
"e1cib/tempstorage/...").Посмотрим на пример правильной программной реализации этого процесса:
&НаСервере
Функция ПолучитьАдресМакетаНаСервере(СсылкаНаДоговор)
// Получаем объект документа или справочника, где хранится макет
ДокументОбъект = СсылкаНаДоговор.ПолучитьОбъект();
// Получаем двоичные данные макета из конфигурации
ДвоичныеДанныеМакета = ДокументОбъект.ПолучитьМакет("ШаблонПечати");
// Помещаем двоичные данные во временное хранилище и возвращаем адрес
АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанныеМакета, УникальныйИдентификатор);
Возврат АдресХранилища;
КонецФункции
&НаКлиенте
Процедура ЗаполнитьШаблонВорд(СсылкаНаДоговор)
// Шаг 1. Получаем уникальный адрес данных с сервера
АдресХранилища = ПолучитьАдресМакетаНаСервере(СсылкаНаДоговор);
Если ПустаяСтрока(АдресХранилища) Тогда
ВызватьИсключение "Не удалось получить двоичные данные макета.";
КонецЕсли;
// Шаг 2. Генерируем путь к файлу в локальной Temp-папке пользователя на клиенте
ИмяФайлаНаКлиенте = ПолучитьИмяВременногоФайла("docx");
// Шаг 3. Извлекаем данные из хранилища и записываем их на локальный диск клиента
ДвоичныеДанныеСКлиента = ПолучитьИзВременногоХранилища(АдресХранилища);
ДвоичныеДанныеСКлиента.Записать(ИмяФайлаНаКлиенте);
// Шаг 4. Работаем с COM-объектом Microsoft Word локально
Попытка
ОбъектВорд = Новый COMОбъект("Word.Application");
Исключение
Сообщить("Ошибка при запуске MS Word: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
// Открываем локально созданный файл шаблона
ОбъектВорд.Documents.Add(ИмяФайлаНаКлиенте);
ОбъектВорд.Visible = Истина;
// Дальнейший алгоритм заполнения параметров в Word...
КонецПроцедуры
Ранее рассмотренный метод ПолучитьИзВременногоХранилища работает синхронно. В современных версиях платформы 1С:Предприятие 8.3 (особенно при работе в веб-клиенте через браузеры) использование синхронных вызовов для работы с локальной файловой системой считается устаревшим и может блокироваться политиками безопасности.
Для обеспечения стабильной работы приложения на любых клиентах рекомендуется использовать асинхронные методы работы с файлами. Проанализируем, как переписать процедуру получения файла на клиенте с использованием асинхронного метода ПолучитьФайлССервераАсинхронно:
&НаКлиенте
Асинх Процедура ЗаполнитьШаблонВордАсинхронно(СсылкаНаДоговор)
// Получаем адрес данных на сервере
АдресХранилища = ПолучитьАдресМакетаНаСервере(СсылкаНаДоговор);
// Генерируем имя временного локального файла
ЛокальноеИмяФайла = ПолучитьИмяВременногоФайла("docx");
// Асинхронно получаем файл с сервера и сохраняем его локально
ПараметрыПолучения = Новый ПараметрыПолученияФайлаССервера;
Ждать ПолучитьФайлССервераАсинхронно(АдресХранилища, ЛокальноеИмяФайла, ПараметрыПолучения);
// Запуск COM-объекта
Попытка
ОбъектВорд = Новый COMОбъект("Word.Application");
Исключение
ПоказатьПредупреждение(, "Не удалось запустить MS Word. Проверьте, установлено ли приложение.");
Возврат;
КонецПопытки;
ОбъектВорд.Documents.Add(ЛокальноеИмяФайла);
ОбъектВорд.Visible = Истина;
// Продолжаем обработку файла...
КонецПроцедуры
Решение с передачей файла через хранилище и открытием его через COM-клиент является полностью рабочим, но имеет ряд критических недостатков, о которых важно знать разработчику:
Как справедливо отмечено в сообщении №7 обсуждения форума, в современных конфигурациях на базе Библиотеки Стандартных Подсистем (БСП) реализован великолепный кроссплатформенный механизм работы с офисными макетами. Подсистема «Печать» позволяет формировать документы DOCX и ODT на стороне сервера вообще без использования установленного MS Word.
С технической стороны файлы .docx представляют собой обычные ZIP-архивы, содержащие файлы разметки XML. Механизмы БСП умеют на сервере распаковывать этот архив, находить внутри него специальные теги параметров вида {v8 ИмяПараметра}, заменять их на реальные данные из базы данных 1С с помощью средств работы с XML, упаковывать файлы обратно в ZIP-контейнер и отдавать готовый файл пользователю.
Рассмотрим по шагам, как подготовить и вывести такой макет:
ПФ_DOC_.{v8 НомерДоговора}, {v8 ДатаДоговора}, {v8 КонтрагентНаименование}.{v8 Область.СтрокаТаблицы} ... структура строки таблицы ... {/v8 Область.СтрокаТаблицы}.Посмотрим на пример серверного кода по заполнению такого шаблона с использованием программного интерфейса БСП (модуль УправлениеПечатью):
Функция СформироватьДоговорБСП(СсылкаНаДоговор) Экспорт
// Получаем макет из конфигурации средствами БСП
МакетДоговора = УправлениеПечатью.ИнициализироватьМакетОфисногоДокумента(
"Документ.ДоговорКонтрагента.ПФ_DOC_Договор",
ТипМакетОфисногоДокумента.DOCX
);
// Создаем структуру для хранения параметров заполнения
ДанныеДокумента = Новый Структура;
ДанныеДокумента.Вставить("НомерДоговора", СсылкаНаДоговор.Номер);
ДанныеДокумента.Вставить("ДатаДоговора", Формат(СсылкаНаДоговор.Дата, "ДФ=dd.MM.yyyy"));
ДанныеДокумента.Вставить("КонтрагентНаименование", СсылкаНаДоговор.Контрагент.НаименованиеПолное);
// Инициализируем печатную форму
ПечатнаяФорма = УправлениеПечатью.ИнициализироватьПечатнуюФорму(
ТипМакетОфисногоДокумента.DOCX,
ДанныеДокумента,
МакетДоговора
);
// Заполняем основную область шаблона данными
ОбластьШаблона = УправлениеПечатью.ОбластьМакета(МакетДоговора, "ОсновнойТекст");
УправлениеПечатью.ВывестиОбласть(ПечатнаяФорма, ОбластьШаблона, ДанныеДокумента);
// Завершаем формирование печатной формы и получаем двоичные данные файла
РезультатДвоичныеДанные = УправлениеПечатью.СформироватьДокумент(ПечатнаяФорма);
Возврат РезультатДвоичныеДанные;
КонецФункции
Этот способ имеет колоссальные преимущества: формирование происходит на сервере (даже под управлением ОС Linux), работает в десятки раз быстрее COM-соединения, не требует наличия установленного пакета Microsoft Office на сервере и клиенте, а готовый результат в виде двоичных данных можно легко отправить на email, сохранить в присоединенные файлы или вывести пользователю на экран для просмотра.