Как исправить ошибку «Файл не найден» при открытии макета Word через двоичные данные в 1С?

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

При разработке механизмов печати договоров и коммерческих предложений программисты часто используют макеты в формате Microsoft Word (файлы .docx), хранящиеся в конфигурации как двоичные данные. Однако при реализации выгрузки таких файлов на управляемых формах нередко возникает неприятное исключение: «Произошла исключительная ситуация (Microsoft Word): Файл не найден». При этом в процессе отладки видно, что путь к временному файлу успешно формируется, но MS Word наотрез отказывается его открывать. Выясним причину этой проблемы и подробно разберем правильные архитектурные решения.

Выясняем причину ошибки: клиент-серверный конфликт

Давайте проанализируем исходный код, приведенный в начале обсуждения. В нем функция получения макета выполняется в контексте сервера (директива &НаСервере), а создание COM-объекта и открытие документа — на стороне клиента (директива &НаКлиенте). Проследим путь создания файла:

  1. На сервере вызывается метод ПолучитьИмяВременногоФайла("docx"). Платформа генерирует уникальный путь во временной директории операционной системы, установленной на серверном компьютере (например, C:\Windows\Temp\v8_A1B2_1.docx).
  2. Метод ДвоичныеДанныеМакета.Записать(ИмяФайла) физически создает и записывает файл именно по этому серверному пути.
  3. Функция возвращает строку пути обратно на клиентский компьютер.
  4. На клиенте инициализируется локальный COM-объект Word.Application, который пытается выполнить команду Documents.Add(ИмяФайла).

Поскольку клиентское приложение (компьютер пользователя) физически не имеет доступа к папкам временных файлов сервера (особенно в клиент-серверном варианте работы базы данных 1С), MS Word на локальной машине выдает логичную ошибку: «Файл не найден». Путь, переданный с сервера, просто не существует в файловой системе клиента.

Способ 1. Правильная передача файла через временное хранилище

Чтобы решить эту проблему, нам необходимо перенести физическое создание файла на сторону клиента. Однако напрямую прочитать макет из базы данных на клиенте невозможно. Для обмена двоичными данными между сервером и клиентом мы должны использовать механизм Временного хранилища.

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

  1. На сервере: извлекаем макет, преобразуем его в двоичные данные, помещаем их во Временное хранилище и возвращаем на клиент уникальный адрес временного хранилища (строку вида "e1cib/tempstorage/...").
  2. На клиенте: генерируем локальный путь для временного файла, извлекаем данные из хранилища по адресу, записываем их в локальный файл и открываем его с помощью COM.

Посмотрим на пример правильной программной реализации этого процесса:


&НаСервере
Функция ПолучитьАдресМакетаНаСервере(СсылкаНаДоговор)
    
    // Получаем объект документа или справочника, где хранится макет
    ДокументОбъект = СсылкаНаДоговор.ПолучитьОбъект();
    
    // Получаем двоичные данные макета из конфигурации
    ДвоичныеДанныеМакета = ДокументОбъект.ПолучитьМакет("ШаблонПечати");
    
    // Помещаем двоичные данные во временное хранилище и возвращаем адрес
    АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанныеМакета, УникальныйИдентификатор);
    
    Возврат АдресХранилища;
    
КонецФункции

&НаКлиенте
Процедура ЗаполнитьШаблонВорд(СсылкаНаДоговор)
    
    // Шаг 1. Получаем уникальный адрес данных с сервера
    АдресХранилища = ПолучитьАдресМакетаНаСервере(СсылкаНаДоговор);
    
    Если ПустаяСтрока(АдресХранилища) Тогда
        ВызватьИсключение "Не удалось получить двоичные данные макета.";
    КонецЕсли;
    
    // Шаг 2. Генерируем путь к файлу в локальной Temp-папке пользователя на клиенте
    ИмяФайлаНаКлиенте = ПолучитьИмяВременногоФайла("docx");
    
    // Шаг 3. Извлекаем данные из хранилища и записываем их на локальный диск клиента
    ДвоичныеДанныеСКлиента = ПолучитьИзВременногоХранилища(АдресХранилища);
    ДвоичныеДанныеСКлиента.Записать(ИмяФайлаНаКлиенте);
    
    // Шаг 4. Работаем с COM-объектом Microsoft Word локально
    Попытка 
        ОбъектВорд = Новый COMОбъект("Word.Application");
    Исключение
        Сообщить("Ошибка при запуске MS Word: " + ОписаниеОшибки());
        Возврат;
    КонецПопытки;    
    
    // Открываем локально созданный файл шаблона
    ОбъектВорд.Documents.Add(ИмяФайлаНаКлиенте);
    ОбъектВорд.Visible = Истина;
    
    // Дальнейший алгоритм заполнения параметров в Word...
    
КонецПроцедуры

Переход на асинхронную работу с файлами в современных версиях 1С

Ранее рассмотренный метод ПолучитьИзВременногоХранилища работает синхронно. В современных версиях платформы 1С:Предприятие 8.3 (особенно при работе в веб-клиенте через браузеры) использование синхронных вызовов для работы с локальной файловой системой считается устаревшим и может блокироваться политиками безопасности.

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


&НаКлиенте
Асинх Процедура ЗаполнитьШаблонВордАсинхронно(СсылкаНаДоговор)
    
    // Получаем адрес данных на сервере
    АдресХранилища = ПолучитьАдресМакетаНаСервере(СсылкаНаДоговор);
    
    // Генерируем имя временного локального файла
    ЛокальноеИмяФайла = ПолучитьИмяВременногоФайла("docx");
    
    // Асинхронно получаем файл с сервера и сохраняем его локально
    ПараметрыПолучения = Новый ПараметрыПолученияФайлаССервера;
    Ждать ПолучитьФайлССервераАсинхронно(АдресХранилища, ЛокальноеИмяФайла, ПараметрыПолучения);
    
    // Запуск COM-объекта
    Попытка 
        ОбъектВорд = Новый COMОбъект("Word.Application");
    Исключение
        ПоказатьПредупреждение(, "Не удалось запустить MS Word. Проверьте, установлено ли приложение.");
        Возврат;
    КонецПопытки;    
    
    ОбъектВорд.Documents.Add(ЛокальноеИмяФайла);
    ОбъектВорд.Visible = Истина;
    
    // Продолжаем обработку файла...
    
КонецПроцедуры

Недостатки использования COM-объекта (Word.Application)

Решение с передачей файла через хранилище и открытием его через COM-клиент является полностью рабочим, но имеет ряд критических недостатков, о которых важно знать разработчику:

Способ 2. Кроссплатформенная печать в DOCX средствами БСП (без COM)

Как справедливо отмечено в сообщении №7 обсуждения форума, в современных конфигурациях на базе Библиотеки Стандартных Подсистем (БСП) реализован великолепный кроссплатформенный механизм работы с офисными макетами. Подсистема «Печать» позволяет формировать документы DOCX и ODT на стороне сервера вообще без использования установленного MS Word.

С технической стороны файлы .docx представляют собой обычные ZIP-архивы, содержащие файлы разметки XML. Механизмы БСП умеют на сервере распаковывать этот архив, находить внутри него специальные теги параметров вида {v8 ИмяПараметра}, заменять их на реальные данные из базы данных 1С с помощью средств работы с XML, упаковывать файлы обратно в ZIP-контейнер и отдавать готовый файл пользователю.

Рассмотрим по шагам, как подготовить и вывести такой макет:

  1. Создайте макет в конфигурации с типом «Двоичные данные». Рекомендуется использовать для имени префикс ПФ_DOC_.
  2. В самом шаблоне Microsoft Word расставьте параметры автозаполнения. Они оформляются в фигурных скобках, например: {v8 НомерДоговора}, {v8 ДатаДоговора}, {v8 КонтрагентНаименование}.
  3. Для вывода списков или табличных частей выделите область внутри шаблона, ограничив её тегами: {v8 Область.СтрокаТаблицы} ... структура строки таблицы ... {/v8 Область.СтрокаТаблицы}.
  4. В модуле менеджера объекта или в печатной форме используйте штатные функции БСП.

Посмотрим на пример серверного кода по заполнению такого шаблона с использованием программного интерфейса БСП (модуль УправлениеПечатью):


Функция СформироватьДоговорБСП(СсылкаНаДоговор) Экспорт
    
    // Получаем макет из конфигурации средствами БСП
    МакетДоговора = УправлениеПечатью.ИнициализироватьМакетОфисногоДокумента(
        "Документ.ДоговорКонтрагента.ПФ_DOC_Договор", 
        ТипМакетОфисногоДокумента.DOCX
    );
    
    // Создаем структуру для хранения параметров заполнения
    ДанныеДокумента = Новый Структура;
    ДанныеДокумента.Вставить("НомерДоговора", СсылкаНаДоговор.Номер);
    ДанныеДокумента.Вставить("ДатаДоговора", Формат(СсылкаНаДоговор.Дата, "ДФ=dd.MM.yyyy"));
    ДанныеДокумента.Вставить("КонтрагентНаименование", СсылкаНаДоговор.Контрагент.НаименованиеПолное);
    
    // Инициализируем печатную форму
    ПечатнаяФорма = УправлениеПечатью.ИнициализироватьПечатнуюФорму(
        ТипМакетОфисногоДокумента.DOCX, 
        ДанныеДокумента, 
        МакетДоговора
    );
    
    // Заполняем основную область шаблона данными
    ОбластьШаблона = УправлениеПечатью.ОбластьМакета(МакетДоговора, "ОсновнойТекст");
    УправлениеПечатью.ВывестиОбласть(ПечатнаяФорма, ОбластьШаблона, ДанныеДокумента);
    
    // Завершаем формирование печатной формы и получаем двоичные данные файла
    РезультатДвоичныеДанные = УправлениеПечатью.СформироватьДокумент(ПечатнаяФорма);
    
    Возврат РезультатДвоичныеДанные;
    
КонецФункции

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

← На главную