В процессе работы с 1С часто возникает задача массово выгрузить присоединенные файлы, например, изображения товаров из справочника «Номенклатура», чтобы использовать их на сайте или передать контрагенту. Напрямую извлечь данные и сохранить их на диск может оказаться нетривиальной задачей, сопровождающейся ошибками. Рассмотрим по шагам, как правильно организовать такую выгрузку, и разберем типичные проблемы, с которыми можно столкнуться.
Основная проблема, с которой столкнулся автор исходной темы, — ошибка «метод объекта (Записать) не обнаружен» при попытке сохранить полученные данные в файл. Это указывает на то, что переменная, к которой применяется метод Записать(), имеет неверный тип. Проанализируем, как получить данные в правильном формате и корректно их сохранить.
Для начала нам нужно получить сами данные файла. В современных конфигурациях 1С информация о присоединенных файлах хранится в специальном справочнике (в нашем случае, НоменклатураПрисоединенныеФайлы), а сами двоичные данные — либо в регистре сведений ДвоичныеДанныеФайлов, либо во внешних томах на диске. Самый прямой способ — получить их запросом.
Составим запрос, который выберет для нужной номенклатуры ее присоединенный файл (картинку) и его двоичные данные.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| НоменклатураПрисоединенныеФайлы.ВладелецФайла КАК ВладелецФайла,
| НоменклатураПрисоединенныеФайлы.Расширение КАК Расширение,
| ДвоичныеДанныеФайлов.ДвоичныеДанныеФайла КАК ДвоичныеДанныеФайла
|ИЗ
| Справочник.НоменклатураПрисоединенныеФайлы КАК НоменклатураПрисоединенныеФайлы
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДвоичныеДанныеФайлов КАК ДвоичныеДанныеФайлов
| ПО НоменклатураПрисоединенныеФайлы.Ссылка = ДвоичныеДанныеФайлов.Файл
|ГДЕ
| НоменклатураПрисоединенныеФайлы.ВладелецФайла.Ссылка В (&СписокНоменклатуры)";
// Здесь можно установить параметр, например, для выгрузки файлов по списку номенклатуры
// Запрос.УстановитьПараметр("СписокНоменклатуры", ВашСписокНоменклатуры);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// ... дальнейшая обработка
КонецЦикла;
В результате выполнения запроса в поле Выборка.ДвоичныеДанныеФайла мы получаем не сами данные, а ссылку на ХранилищеЗначения. Это специальный объект 1С для хранения больших объемов данных. Чтобы получить из него непосредственно двоичные данные, нужно использовать метод Получить().
Именно на этом этапе часто допускают ошибку. Попытка записать в файл само хранилище (объект типа ХранилищеЗначения) приведет к ошибке, так как у него нет метода Записать(). Этот метод есть у объекта типа ДвоичныеДанные, который мы получим после вызова метода Получить().
Правильный код для получения и сохранения данных будет выглядеть так:
// ... внутри цикла обхода выборки ...
Пока Выборка.Следующий() Цикл
// Получаем объект типа ХранилищеЗначения
Хранилище = Выборка.ДвоичныеДанныеФайла;
// Проверяем, что хранилище не пустое
Если Хранилище <> Неопределено Тогда
// Получаем из хранилища объект типа ДвоичныеДанные
ФайлДанные = Хранилище.Получить();
Если ФайлДанные <> Неопределено Тогда
// Формируем путь для сохранения файла
ИмяФайла = "c:\temp\" + Выборка.ВладелецФайла + "." + Выборка.Расширение;
// Теперь у объекта ФайлДанные (тип ДвоичныеДанные) есть метод Записать()
ФайлДанные.Записать(ИмяФайла);
КонецЕсли;
КонецЕсли;
КонецЦикла;
После выполнения этого кода в папке c:\temp\ появится файл с именем, соответствующим наименованию номенклатуры, и ее расширением.
На предыдущем шаге мы столкнемся с новой проблемой, если в наименовании номенклатуры есть символы, недопустимые в именах файлов операционной системы. Автор темы обнаружил, что символ слэша / в названии товара (например, "Ботинки мужские 42/43") воспринимается системой как разделитель каталогов, и происходит попытка создать папку "Ботинки мужские 42" и записать туда файл "43.jpg", что, скорее всего, приведет к ошибке.
Для решения этой проблемы необходимо "очистить" имя файла от всех недопустимых символов перед сохранением. Самый простой способ — использовать функцию СтрЗаменить().
Доработаем наш код, добавив замену слэша:
// ... внутри цикла ...
Если ФайлДанные <> Неопределено Тогда
// Получаем "сырое" имя номенклатуры
НаименованиеНоменклатуры = СокрЛП(Выборка.ВладелецФайла);
// Заменяем недопустимый символ
ИмяДляФайла = СтрЗаменить(НаименованиеНоменклатуры, "/", "_"); // Заменим слэш на подчеркивание
// Формируем итоговый путь к файлу
ПолныйПуть = "c:\temp\" + ИмяДляФайла + "." + Выборка.Расширение;
// Сохраняем файл
ФайлДанные.Записать(ПолныйПуть);
КонецЕсли;
// ...
Этот код успешно справится с проблемой слэша. Однако стоит помнить, что в именах файлов Windows недопустимы и другие символы: \ / : * ? " < > |. Для надежной работы лучше создать отдельную функцию, которая будет заменять все эти символы.
Посмотрим на пример такой функции:
&НаСервереБезКонтекста
Функция ПодготовитьИмяФайла(ИсходнаяСтрока)
ОбрабатываемаяСтрока = ИсходнаяСтрока;
ОбрабатываемаяСтрока = СтрЗаменить(ОбрабатываемаяСтрока, "\", "_");
ОбрабатываемаяСтрока = СтрЗаменить(ОбрабатываемаяСтрока, "/", "_");
ОбрабатываемаяСтрока = СтрЗаменить(ОбрабатываемаяСтрока, ":", "_");
ОбрабатываемаяСтрока = СтрЗаменить(ОбрабатываемаяСтрока, "*", "_");
ОбрабатываемаяСтрока = СтрЗаменить(ОбрабатываемаяСтрока, "?", "_");
ОбрабатываемаяСтрока = СтрЗаменить(ОбрабатываемаяСтрока, """", "_");
ОбрабатываемаяСтрока = СтрЗаменить(ОбрабатываемаяСтрока, "<", "_");
ОбрабатываемаяСтрока = СтрЗаменить(ОбрабатываемаяСтрока, ">", "_");
ОбрабатываемаяСтрока = СтрЗаменить(ОбрабатываемаяСтрока, "|", "_");
Возврат СокрЛП(ОбрабатываемаяСтрока);
КонецФункции
Теперь наш код сохранения станет еще более надежным:
// ...
ИмяДляФайла = ПодготовитьИмяФайла(Строка(Выборка.ВладелецФайла));
ПолныйПуть = "c:\temp\" + ИмяДляФайла + "." + Выборка.Расширение;
ФайлДанные.Записать(ПолныйПуть);
// ...
Прямой запрос к регистру ДвоичныеДанныеФайлов работает, но он не учитывает ситуацию, когда файлы хранятся в томах на диске. Чтобы сделать код универсальным и не зависеть от настроек хранения файлов, рекомендуется использовать стандартные функции из общих модулей Библиотеки стандартных подсистем (БСП).
В большинстве современных конфигураций есть общий модуль РаботаСФайлами, который содержит функцию ПолучитьДвоичныеДанныеФайла(). Эта функция сама определяет, где хранится файл (в базе или в томе), и возвращает его двоичные данные. Это предпочтительный метод.
Для его использования нам понадобится только ссылка на сам присоединенный файл (элемент справочника НоменклатураПрисоединенныеФайлы).
Перепишем наш код с использованием этого подхода:
// Запрос можно упростить, нам не нужны двоичные данные из регистра
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| НоменклатураПрисоединенныеФайлы.Ссылка КАК СсылкаНаФайл,
| НоменклатураПрисоединенныеФайлы.ВладелецФайла КАК ВладелецФайла,
| НоменклатураПрисоединенныеФайлы.Расширение КАК Расширение
|ИЗ
| Справочник.НоменклатураПрисоединенныеФайлы КАК НоменклатураПрисоединенныеФайлы
|ГДЕ
| НоменклатураПрисоединенныеФайлы.ВладелецФайла.Ссылка В (&СписокНоменклатуры)";
// ...
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
Попытка
// Используем универсальную функцию БСП
ФайлДанные = РаботаСФайлами.ПолучитьДвоичныеДанныеФайла(Выборка.СсылкаНаФайл);
Если ФайлДанные <> Неопределено Тогда
// Очищаем имя и формируем путь
ИмяДляФайла = ПодготовитьИмяФайла(Строка(Выборка.ВладелецФайла));
ПолныйПуть = "c:\temp\" + ИмяДляФайла + "." + Выборка.Расширение;
// Сохраняем
ФайлДанные.Записать(ПолныйПуть);
КонецЕсли;
Исключение
Сообщить("Не удалось обработать файл для номенклатуры: " + Выборка.ВладелецФайла);
Сообщить(ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
Такой подход не только более правильный с точки зрения архитектуры, но и более устойчив к изменениям в конфигурации и настройках хранения файлов.
Итак, мы разобрали полный цикл выгрузки присоединенных файлов: от получения данных из базы до их корректного сохранения на диск с учетом всех нюансов именования. Используя эти методы, вы сможете легко автоматизировать экспорт любых вложенных файлов в 1С.