Как изменить существующий файл Excel из 1С и исправить ошибку метода SaveAs

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

При автоматизации работы с прайс-листами, заказами или отчетами программисты часто сталкиваются с необходимостью изменить данные в уже существующем файле Excel напрямую из кода 1С. Традиционный подход с использованием COM-соединения часто преподносит неприятные сюрпризы в виде трудночитаемых системных ошибок. Одной из самых популярных является ошибка: "Метод SaveAs из класса Workbook завершен неверно".

В этой статье мы подробно разберем, почему возникает эта ошибка при попытке сохранить измененный файл Excel из среды 1С:Предприятие, как правильно передавать параметры в COM-объекты и как раз и навсегда избавиться от подобных проблем с помощью современных альтернативных методов платформы.

Причина 1: Передача объекта 1С вместо строки в метод SaveAs

Проанализируем фрагмент исходного кода, который вызывает аварийное завершение работы метода:


НовыйФайл = Новый Файл(Путь + "new.xlsx");
КнигаЗагрузки.SaveAs(НовыйФайл);

Выясним причину сбоя. В переменную НовыйФайл записывается объект встроенного языка 1С типа Файл. Когда этот объект передается в метод SaveAs внешнего приложения Microsoft Office, COM-сервер Excel не может его интерпретировать. Программа ожидает получить на вход обычное строковое представление пути к файлу на диске, а не специфический объект базы данных 1С.

Решить эту проблему можно очень просто. Нам необходимо передавать в метод строго строковый путь. Рассмотрим два правильных варианта исправления кода:


// Вариант 1: Использование свойства "ПолноеИмя" объекта Файл
НовыйФайл = Новый Файл(Путь + "new.xlsx");
КнигаЗагрузки.SaveAs(НовыйФайл.ПолноеИмя);

// Вариант 2: Передача строки напрямую без создания объекта Файл
ИмяНовогоФайла = Путь + "new.xlsx";
КнигаЗагрузки.SaveAs(ИмяНовогоФайла);

Причина 2: Несоответствие формата файла и его расширения

Часто в попытках решить проблему разработчики начинают экспериментировать со вторым параметром метода SaveAs, который отвечает за формат сохраняемого файла. Например, используют значение -4143, которое соответствует константе xlWorkbookNormal.

Однако важно помнить, что код формата должен строго соответствовать расширению файла в пути сохранения:

  1. Константа -4143 (или код 56) указывает Excel на старый формат 97-2003 (.xls).
  2. Если при этом вы указываете расширение файла .xlsx (современный формат OpenXML), MS Excel выдаст ошибку несовместимости типов или сохранит поврежденный документ.
  3. Для сохранения современного формата .xlsx необходимо использовать код формата 51 (константа xlOpenXMLWorkbook).

Посмотрим на пример корректного вызова метода сохранения для формата XLSX:


// Код 51 гарантирует сохранение в валидном формате XLSX
КнигаЗагрузки.SaveAs(Путь + "new.xlsx", 51);

Причина 3: Зависание процессов excel.exe в операционной системе

В процедурах работы с COM-объектами критически важно правильно завершать сеанс работы с приложением. Если в коде происходит ошибка до выполнения финальных строк закрытия или если эти строки вовсе отсутствуют, в диспетчере задач Windows будут накапливаться запущенные процессы excel.exe.

При повторном запуске обработки заблокированный старым "зависшим" процессом файл не сможет перезаписаться, из-за чего метод SaveAs снова вернет ошибку. Чтобы этого избежать, всегда оборачивайте работу с внешними объектами в блоки обработки исключений и корректно освобождайте память.

Рассмотрим пример правильного завершения работы с COM-объектом:


Попытка
    // ... логика изменения ячеек ...
    КнигаЗагрузки.SaveAs(Путь + "new.xlsx", 51);
Исключение
    Сообщить("Произошла ошибка: " + ОписаниеОшибки());
КонецПопытки;

// Обязательное закрытие файлов и выход из приложения Excel
Если КнигаЗагрузки <> Неопределено Тогда
    КнигаЗагрузки.Close(Ложь); // Ложь означает "закрыть без сохранения текущих изменений"
    КнигаЗагрузки = Неопределено;
КонецЕсли;

Если Эксель <> Неопределено Тогда
    Эксель.Quit();
    Эксель = Неопределено;
КонецЕсли;

Причина 4: Специфика серверного контекста и папки Desktop

Если ваша обработка выполняется под директивой &НаСервере (особенно в случае клиент-серверной базы или при запуске в регламентном задании), COM-объект Excel инициализируется от имени системного пользователя службы 1С (например, USR1CV8).

Программа Microsoft Excel при запуске требует обязательного наличия каталога Desktop в профиле пользователя Windows, под которым она выполняется. Если этой папки нет, метод SaveAs упадет с ошибкой инициализации.

Для исправления этой проблемы системному администратору необходимо вручную создать пустые папки Desktop по следующим путям на сервере (в зависимости от разрядности операционной системы и пакета Office):

Также следует убедиться, что у пользователя, от имени которого запущена служба "1C:Enterprise Server", есть полные права на чтение и запись в эти папки.

Альтернативное современное решение: Работа через ТабличныйДокумент (без COM)

Использование COM-объектов в современных реалиях считается устаревшим и неэффективным подходом. Он медленный, нестабильный, требует обязательной установки Microsoft Office непосредственно на сервере или компьютере пользователя и не работает на серверах под управлением ОС Linux.

Начиная с версии 8.3, платформа 1С умеет нативно читать, изменять и записывать файлы Excel (включая формат .xlsx) с помощью встроенного объекта ТабличныйДокумент. Этот способ работает в разы быстрее, полностью кроссплатформенен и не зависит от стороннего софта.

Разберем по шагам, как переписать исходный алгоритм изменения цен в файле Excel без использования COM:


&НаСервере
Процедура ИзменитьЦеныНаСайтеНаСервере()
    
    // Шаг 1: Создаем ТабличныйДокумент и читаем в него исходный файл Excel
    ТабДок = Новый ТабличныйДокумент;
    Попытка
        ТабДок.Прочитать(ИмяФайлаЗагрузки, СпособЧтенияЗначенийТабличногоДокумента.Значение);
    Исключение
        Сообщить("Не удалось прочитать файл Excel: " + ОписаниеОшибки());
        Возврат;
    КонецПопытки;
    
    // Шаг 2: В цикле находим нужные строки и перезаписываем значения ячеек
    Для Каждого Строка Из Номенклатура Цикл
        Если Строка.ИзменитьЦенуНаСайте = Истина Тогда
            Если Строка.Номенклатура = ПолучитьСпрНомПустой() Тогда
                Сообщить("Пустая ссылка: " + Строка.НаименованиеЭксель);
            Иначе
                // Обращаемся к ячейке напрямую по номеру строки и колонки (колонке 16 соответствует цена)
                // Записываем новое значение в свойство "Текст" или "Значение"
                ТабДок.Область(Строка.НомерСтрокиЭксель, 16).Текст = Строка.ЦенаВБазе;        
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    
    // Шаг 3: Определяем путь сохранения и записываем измененный файл
    ФайлЭксель = Новый Файл(ИмяФайлаЗагрузки);
    Путь = ФайлЭксель.Путь;
    НовыйФайлПуть = Путь + "new.xlsx";
    
    Попытка
        ТабДок.Записать(НовыйФайлПуть, ТипФайлаТабличногоДокумента.XLSX);
        Сообщить("Файл успешно сохранен по пути: " + НовыйФайлПуть);
    Исключение
        Сообщить("Ошибка при записи измененного файла: " + ОписаниеОшибки());
    КонецПопытки;
    
КонецПроцедуры

Используя этот подход, мы полностью исключаем влияние внешних факторов (таких как зависшие процессы excel.exe, отсутствие прав доступа, несовместимость версий MS Office или отсутствие папок в профиле Windows) на стабильность работы нашей программы.

← На главную