Изображения из xlsx в справочник 1С: чтение архива, привязка к строкам и запись в базу

· 9 мин

Содержание

Загрузить картинку из Excel в 1С надёжнее всего так: открыть файл .xlsx как ZIP-архив, взять изображения из папки xl/media, а номер строки для каждого рисунка прочитать из xl/drawings/drawing1.xml. Значения ячеек (артикул, наименование) читает обычный ТабличныйДокумент.Прочитать(). По артикулу находим элемент справочника и пишем картинку в реквизит типа ХранилищеЗначения или в присоединённые файлы.

Частое заблуждение — что достаточно прочитать книгу в табличный документ, и картинки «приедут» вместе с ячейками. Значения действительно приедут. А вот рисунки в коллекции Рисунки табличного документа появляются не всегда и без удобной привязки к строке. Поэтому для пошаговой загрузки изображений в справочник ниже используется разбор архива — он не зависит ни от Excel на компьютере, ни от того, как платформа распознала графику.

Как подготовить Excel-файл, чтобы картинки нашлись

Половина проблем с загрузкой решается ещё до написания кода. Договоритесь с тем, кто готовит файл, о простых правилах.

📌 Формат и структура:

  • только .xlsx — старый .xls не является ZIP-архивом, метод с разбором не сработает;
  • одна строка — один товар, первая строка — заголовки;
  • в первой колонке ключ для поиска: артикул или код, без пробелов по краям;
  • картинка лежит внутри ячейки своей строки и не залезает верхним краем на строку выше;
  • все данные на первом листе, без объединённых ячеек в шапке.

📌 Сами изображения:

  • вставлены через Вставка → Рисунки, а не как фон или примечание;
  • сжаты: в Excel выделите любой рисунок и на вкладке работы с рисунками выберите «Сжать рисунки», снимите галку «Применить только к этому рисунку»;
  • форматы PNG или JPEG. EMF и WMF 1С прочитает, но на формах они часто выглядят хуже.

В новых версиях Excel 365 есть режим «Поместить в ячейку». Такая картинка хранится не в xl/drawings, а в отдельном хранилище расширенных данных книги. Разбор из этой статьи её не увидит. Перед загрузкой выделите такие ячейки и через контекстное меню переведите рисунок в режим «Поместить поверх ячеек».

💡 Если картинок сотни, проще вообще не вставлять их в Excel. Положите файлы в папку, а в колонку B впишите имя файла: art-10025.jpg. Загрузка сведётся к Новый ДвоичныеДанные(ПутьКПапке + ИмяФайла) — без разбора XML.

Шаблон файла и форма обработки для загрузки изображений

Чтобы пользователи не присылали файлы «кто во что горазд», положите шаблон прямо в обработку. Создайте в конфигураторе внешнюю обработку и добавьте макет типа «Двоичные данные» с именем Шаблон. В него загрузите пустой .xlsx с правильными колонками.

Кнопка «Скачать шаблон» на форме отдаёт этот макет пользователю:

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

&НаКлиенте
Процедура СкачатьШаблон(Команда)
	ПолучитьФайл(АдресШаблона(), "Шаблон загрузки картинок.xlsx", Истина);
КонецПроцедуры

На саму форму добавьте:

  1. Реквизит АдресФайла (строка) — сюда попадёт адрес загруженной книги во временном хранилище.
  2. Команду «Выбрать файл», которая вызывает НачатьПомещениеФайла() и сохраняет полученный адрес.
  3. Таблицу значений Результат с колонками «Строка», «Артикул», «Статус» — чтобы после загрузки было видно, что не нашлось.
  4. По желанию — поле картинки для предпросмотра: реквизит-строка с адресом во временном хранилище, элемент формы «Поле картинки».

Файл передаём на сервер как двоичные данные. На сервере записываем их во временный файл — оба метода чтения ниже работают с путём на диске.

Чем 1С читает xlsx: табличный документ, ZIP-архив и COM

У разработчика три инструмента. Каждый закрывает свою часть задачи.

Табличный документ. ТабличныйДокумент.Прочитать(ИмяФайла) открывает .xlsx в актуальных версиях платформы 8.3 без установленного Excel. Работает на сервере и в Linux. Для текстовых колонок — идеально.

ZIP-архив. Формат .xlsx — это обычный ZIP с XML-файлами внутри. В нём есть всё, что нужно:

Файл внутри архиваЧто в нём
xl/media/image1.png …сами картинки в исходном формате
xl/drawings/drawing1.xmlякоря рисунков: строка и колонка ячейки
xl/drawings/_rels/drawing1.xml.relsсвязь «идентификатор рисунка → файл в media»
xl/worksheets/_rels/sheet1.xml.relsкакой drawing относится к какому листу

Объект ЧтениеZipФайла распаковывает архив, ПостроительDOM читает XML. Excel не нужен.

COM. Через Новый COMОбъект("Excel.Application") можно обойти коллекцию Shapes листа и узнать строку рисунка через TopLeftCell.Row. Но выгрузить саму картинку COM не умеет напрямую — подробнее в разделе сравнения.

Рабочая связка: значения ячеек — табличным документом, картинки — из архива.

Код загрузки: картинки из Excel по строкам через ZIP и XML

Функция ниже принимает путь к .xlsx и возвращает соответствие «номер строки → двоичные данные картинки». Номер строки — как в Excel, начиная с 1.

&НаСервере
Функция КартинкиПоСтрокам(ИмяФайлаXlsx)

	NSxdr = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing";
	NSa   = "http://schemas.openxmlformats.org/drawingml/2006/main";
	NSr   = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
	NSrel = "http://schemas.openxmlformats.org/package/2006/relationships";

	Разд = ПолучитьРазделительПути();
	Каталог = КаталогВременныхФайлов() + Строка(Новый УникальныйИдентификатор) + Разд;
	СоздатьКаталог(Каталог);

	Zip = Новый ЧтениеZipФайла(ИмяФайлаXlsx);
	Zip.ИзвлечьВсе(Каталог, РежимВосстановленияПутейФайловZIP.Восстанавливать);
	Zip.Закрыть();

	Результат = Новый Соответствие;
	ПапкаDrawings = Каталог + "xl" + Разд + "drawings" + Разд;
	ФайлРисунков  = ПапкаDrawings + "drawing1.xml";
	ФайлСвязей    = ПапкаDrawings + "_rels" + Разд + "drawing1.xml.rels";

	Если Не (Новый Файл(ФайлРисунков)).Существует() Тогда
		УдалитьФайлы(Каталог);
		Возврат Результат; // в книге нет рисунков поверх ячеек
	КонецЕсли;

	// 1. Связи: rId -> имя файла в xl/media
	Связи = Новый Соответствие;
	СписокСвязей = ПрочитатьDOM(ФайлСвязей).ПолучитьЭлементыПоИмени(NSrel, "Relationship");
	Для Инд = 0 По СписокСвязей.Количество() - 1 Цикл
		Эл = СписокСвязей.Элемент(Инд);
		Цель = Эл.ПолучитьАтрибут("Target"); // например ../media/image3.png
		Связи.Вставить(Эл.ПолучитьАтрибут("Id"), СтрЗаменить(Цель, "../media/", ""));
	КонецЦикла;

	// 2. Якоря: строка ячейки и ссылка на картинку
	ДокРисунков = ПрочитатьDOM(ФайлРисунков);
	Для Каждого ИмяЯкоря Из СтрРазделить("twoCellAnchor,oneCellAnchor", ",") Цикл
		Якоря = ДокРисунков.ПолучитьЭлементыПоИмени(NSxdr, ИмяЯкоря);
		Для Инд = 0 По Якоря.Количество() - 1 Цикл
			Якорь = Якоря.Элемент(Инд);
			Blip = Якорь.ПолучитьЭлементыПоИмени(NSa, "blip");
			Если Blip.Количество() = 0 Тогда
				Продолжить; // фигура или надпись, а не картинка
			КонецЕсли;
			ИдСвязи = Blip.Элемент(0).ПолучитьАтрибутNS(NSr, "embed");
			ИмяМедиа = Связи.Получить(ИдСвязи);
			Если ИмяМедиа = Неопределено Тогда
				Продолжить;
			КонецЕсли;
			ОтКуда = Якорь.ПолучитьЭлементыПоИмени(NSxdr, "from").Элемент(0);
			СтрокаXML = ОтКуда.ПолучитьЭлементыПоИмени(NSxdr, "row").Элемент(0).ТекстовоеСодержимое;
			НомерСтроки = Число(СтрокаXML) + 1; // в XML строки считаются с нуля
			ПутьКартинки = Каталог + "xl" + Разд + "media" + Разд + ИмяМедиа;
			Результат.Вставить(НомерСтроки, Новый ДвоичныеДанные(ПутьКартинки));
		КонецЦикла;
	КонецЦикла;

	УдалитьФайлы(Каталог);
	Возврат Результат;

КонецФункции

&НаСервере
Функция ПрочитатьDOM(ИмяФайла)
	Чтение = Новый ЧтениеXML;
	Чтение.ОткрытьФайл(ИмяФайла);
	Построитель = Новый ПостроительDOM;
	Документ = Построитель.Прочитать(Чтение);
	Чтение.Закрыть();
	Возврат Документ;
КонецФункции

Три момента, на которых чаще всего ошибаются:

  1. Строки с нуля. В drawing1.xml первая строка листа — это 0. Отсюда + 1. Без него все картинки съедут на строку вверх, и шапка «получит» фото первого товара.
  2. Два вида якорей. Картинка, которая перемещается вместе с ячейками, записана как twoCellAnchor, «плавающая» — как oneCellAnchor. Обходим оба.
  3. Номер drawing. У первого листа почти всегда drawing1.xml. Если в книге несколько листов с рисунками, точное соответствие лежит в xl/worksheets/_rels/sheet1.xml.rels.

Если в одной строке две картинки, в соответствии останется последняя. Нужны все — храните в значении массив двоичных данных.

Запись в реквизит ХранениеКартинки и работа с двоичными данными

Теперь соединяем картинки с ячейками. Пример для собственной конфигурации, где у справочника Номенклатура есть реквизит Артикул и реквизит ХранениеКартинки типа ХранилищеЗначения:

&НаСервере
Процедура ЗагрузитьНаСервере()

	ИмяФайла = ПолучитьИмяВременногоФайла("xlsx");
	ПолучитьИзВременногоХранилища(АдресФайла).Записать(ИмяФайла);

	ТабДок = Новый ТабличныйДокумент;
	ТабДок.Прочитать(ИмяФайла);
	Картинки = КартинкиПоСтрокам(ИмяФайла);
	УдалитьФайлы(ИмяФайла);

	Результат.Очистить();
	Для НомерСтроки = 2 По ТабДок.ВысотаТаблицы Цикл

		Артикул = СокрЛП(ТабДок.Область(НомерСтроки, 1, НомерСтроки, 1).Текст);
		ДанныеКартинки = Картинки.Получить(НомерСтроки);
		Если ПустаяСтрока(Артикул) Или ДанныеКартинки = Неопределено Тогда
			Продолжить;
		КонецЕсли;

		НоваяСтрока = Результат.Добавить();
		НоваяСтрока.Строка  = НомерСтроки;
		НоваяСтрока.Артикул = Артикул;

		Ссылка = Справочники.Номенклатура.НайтиПоРеквизиту("Артикул", Артикул);
		Если Ссылка.Пустая() Тогда
			НоваяСтрока.Статус = "Не найден артикул";
			Продолжить;
		КонецЕсли;

		ТоварОбъект = Ссылка.ПолучитьОбъект();
		ТоварОбъект.ХранениеКартинки = Новый ХранилищеЗначения(ДанныеКартинки, Новый СжатиеДанных(9));
		ТоварОбъект.Записать();
		НоваяСтрока.Статус = "Загружено";

	КонецЦикла;

КонецПроцедуры

Что важно про двоичные данные:

  • 🗜️ Новый СжатиеДанных(9) — максимальное сжатие. PNG и JPEG уже сжаты, выигрыш небольшой, но для BMP и EMF заметный.
  • 🔙 Обратно картинку достают так: Объект.ХранениеКартинки.Получить() вернёт ДвоичныеДанные. Для показа на форме поместите их во временное хранилище и отдайте адрес полю картинки.
  • 🖼️ Нужен объект Картинка (например, для печатной формы) — Новый Картинка(ДанныеКартинки).

Типовые конфигурации. В БП 3.0, УТ 11 и других решениях на БСП картинка номенклатуры хранится не в реквизите самого справочника, а как присоединённый файл; основное изображение указывается в карточке. Писать туда напрямую в таблицы не стоит: используйте программный интерфейс подсистемы «Работа с файлами» из БСП, а основной файл назначайте через реквизит карточки. Загрузку в рабочую базу оформляйте внешней обработкой, без снятия конфигурации с поддержки.

Перед первым запуском на рабочей базе сделайте выгрузку .dt или копию. Обработка массово перезаписывает элементы справочника, а старые картинки будут заменены без возможности отката.

Табличный документ, ZIP или COM: какой способ выбрать

Код через COM выглядит так — для сравнения:

Excel = Новый COMОбъект("Excel.Application");
Книга = Excel.Workbooks.Open(ИмяФайла);
Лист = Книга.Worksheets(1);
Для Инд = 1 По Лист.Shapes.Count Цикл
	Фигура = Лист.Shapes.Item(Инд);
	НомерСтроки = Фигура.TopLeftCell.Row; // здесь уже с единицы
	// выгрузка: Фигура.CopyPicture(), вставка в временную диаграмму,
	// затем Chart.Export(ПутьКФайлу) и удаление диаграммы
КонецЦикла;
Книга.Close(Ложь);
Excel.Quit();

Сама картинка через COM добывается обходным путём: копирование в буфер обмена и экспорт временной диаграммы. На сервере без интерактивного сеанса буфер обмена обычно недоступен, и способ падает.

СпособКогда подходитРиски и ограничения
Табличный документтолько значения ячеек, любой серверрисунки ненадёжны, нет привязки к строке
ZIP + XMLкартинки поверх ячеек, сервер Windows или Linuxне видит режим «Поместить в ячейку» и .xls
COM Excelстарый .xls, разовая загрузка на машине с Excelнужен установленный Excel, только Windows, буфер обмена, потеря качества при экспорте
Папка с файлами + имя в колонкесотни и тысячи картинокнужно отдельно передать папку

Для регулярной загрузки от поставщика выбирайте ZIP. COM оставьте для разовых задач со старыми файлами — а лучше попросите пересохранить их в .xlsx.

Ошибки при загрузке изображений и скорость на больших файлах

Диагностика: выберите симптом

Чтобы загрузка тысяч строк не растягивалась на час:

  1. Не читайте объект зря. Сравнивайте хеш новой картинки со старой и пропускайте одинаковые. Хеш даёт объект ХешированиеДанных с функцией ХешФункция.MD5.
  2. Ограничьте размер. Проверяйте ДанныеКартинки.Размер() и откладывайте в отчёт всё, что больше, например, 2 МБ.
  3. Пишите порциями. Оборачивайте по 100-200 записей в НачатьТранзакцию() / ЗафиксироватьТранзакцию() с обработкой исключения. Одна гигантская транзакция держит блокировки на справочнике и мешает пользователям.
  4. Кешируйте поиск. Для 5 000 строк один запрос «артикул → ссылка» заранее быстрее, чем 5 000 вызовов НайтиПоРеквизиту.
  5. Убирайте за собой. Временный каталог с распакованным архивом удаляйте и при ошибке — оберните разбор в Попытка … Исключение.

Частые вопросы

Можно ли загрузить картинки из Excel без программирования?
В типовых конфигурациях есть загрузка табличных данных из файла, но она переносит значения ячеек, а не рисунки. Без кода реально только вручную, по одному товару, через карточку.
Будет ли код работать в файловой базе?
Да. Распаковка ZIP и чтение XML идут на стороне сервера, а в файловом варианте это тот же компьютер. Excel устанавливать не нужно.
Работает ли разбор на сервере 1С под Linux?
Да, ЧтениеZipФайла и ПостроительDOM от операционной системы не зависят. Разделитель пути берите через ПолучитьРазделительПути(), а не пишите обратный слеш.
Как загрузить картинки из Google Таблиц?
Скачайте таблицу как .xlsx. Картинки, вставленные функцией IMAGE, в архив не попадают — для них выгрузите ссылки и скачивайте файлы по URL отдельно.
Можно ли сразу уменьшить картинку при загрузке?
Встроенного удобного масштабирования изображений в платформе нет. Проще сжать рисунки в самом Excel до загрузки или подготовить их внешней утилитой.