Как получить картинки из Excel и связать их со строками в 1С

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

При разработке интеграций (например, загрузке прайс-листов или каталогов товаров из файлов поставщиков) разработчики 1С часто сталкиваются со следующей проблемой: в файле Excel есть колонка с изображениями товаров, но при загрузке документа стандартными средствами связать картинку с конкретной строкой оказывается крайне сложно. Объект ОбластьТабличногоДокумента не имеет встроенного свойства Картинки или Рисунки, а вся графика хранится в общей коллекции Рисунки табличного документа и позиционируется на отдельном графическом слое поверх ячеек.

Кроме того, при конвертации файлов Excel в формат табличного документа 1С (MXL) часто возникает дублирование картинок, а попытка прочитать некоторые графические элементы (рамки, логотипы, элементы разметки) вызывает ошибку "Поле объекта недоступно для чтения". В этой статье мы подробно разберем три надежных способа, которые позволяют извлечь изображения и точно сопоставить их с нужными строками табличной части.

Способ 1. Координатный метод через «миллиметровый трюк» в Табличном Документе

Этот способ уникален тем, что работает исключительно штатными средствами платформы 1С на любом сервере (даже под управлением Linux) и не требует установленного Microsoft Excel или сторонних библиотек. Идея заключается в сопоставлении физических координат ячеек и координат картинок.

Проблема в том, что у рисунков в коллекции ТабличныйДокумент.Рисунки координаты задаются в миллиметрах (свойства Верх, Лево, Высота, Ширина), тогда как ячейки адресуются по строкам и колонкам. Мы можем преодолеть это ограничение с помощью динамического создания временного текстового рисунка.

Алгоритм реализации метода:

  1. Создаем цикл по всем строкам загруженного табличного документа.
  2. Для целевой ячейки (где должна находиться картинка) создаем временный пустой рисунок типа Текст.
  3. Используем метод Расположить(), чтобы позиционировать временный рисунок точно по границам ячейки. При этом платформа автоматически пересчитает строковые координаты в миллиметры.
  4. Считываем полученные миллиметровые координаты временного рисунка и запоминаем их границы.
  5. Удаляем временный рисунок.
  6. Сравниваем координаты реальных картинок из коллекции Рисунки с сохраненными координатами ячеек. Если геометрический центр картинки попадает внутрь границ ячейки — мы нашли соответствие.

Рассмотрим пример кода, реализующий этот подход:


&НаСервере
Процедура СопоставитьКартинкиЧерезКоординаты(ТабДок)
    
    // Соответствие для хранения миллиметровых границ каждой строки
    КартаГраницСтрок = Новый Соответствие;
    НомерКолонкиСКартинками = 1; // Предположим, картинки находятся в первой колонке
    
    // Шаг 1: Вычисляем физические границы ячеек для каждой строки
    Для НомСтроки = 2 По ТабДок.ВысотаТаблицы Цикл
        
        ОбластьЯчейки = ТабДок.Область(НомСтроки, НомерКолонкиСКартинками);
        
        // Создаем временный невидимый объект для вычисления координат
        ВремРисунок = ТабДок.Рисунки.Добавить(ТипРисункаТабличногоДокумента.Текст);
        ВремРисунок.Расположить(ОбластьЯчейки);
        
        // Фиксируем координаты границ ячейки в миллиметрах
        ГраницыЯчейки = Новый Структура;
        ГраницыЯчейки.Вставить("Верх", ВремРисунок.Верх);
        ГраницыЯчейки.Вставить("Низ", ВремРисунок.Верх + ВремРисунок.Высота);
        ГраницыЯчейки.Вставить("Лево", ВремРисунок.Лево);
        ГраницыЯчейки.Вставить("Право", ВремРисунок.Лево + ВремРисунок.Ширина);
        
        КартаГраницСтрок.Вставить(НомСтроки, ГраницыЯчейки);
        
        // Удаляем временный рисунок из коллекции
        ТабДок.Рисунки.Удалить(ВремРисунок);
        
    КонецЦикла;
    
    // Шаг 2: Анализируем реальные картинки и сопоставляем их со строками
    Для Каждого Рисунок Из ТабДок.Рисунки Цикл
        
        // Отсекаем служебные объекты и ошибки чтения недоступных свойств
        Попытка
            Если Рисунок.ТипРисунка <> ТипРисункаТабличногоДокумента.Картинка Тогда
                Продолжить;
            КонецЕсли;
            ТекущаяКартинка = Рисунок.Картинка;
        Исключение
            // Сюда попадут служебные объекты с недоступными для чтения свойствами
            Продолжить;
        КонецПопытки;
        
        // Находим геометрический центр текущего рисунка
        ЦентрY = Рисунок.Верх + (Рисунок.Высота / 2);
        ЦентрX = Рисунок.Лево + (Рисунок.Ширина / 2);
        
        // Ищем, в какую строку попадает этот центр
        Для Каждого СтрокаКарты Из КартаГраницСтрок Цикл
            
            НомерСтроки = СтрокаКарты.Ключ;
            Границы = СтрокаКарты.Значение;
            
            Если (ЦентрY >= Границы.Верх И ЦентрY <= Границы.Низ) И 
                 (ЦентрX >= Границы.Лево И ЦентрX <= Границы.Право) Тогда
                
                // Картинка успешно сопоставлена со строкой
                ДвоичныеДанныеКартинки = ТекущаяКартинка.ПолучитьДвоичныеДанные();
                Если ДвоичныеДанныеКартинки <> Неопределено Тогда
                    ЗаписатьКартинкуДляТовара(НомерСтроки, ДвоичныеДанныеКартинки);
                КонецЕсли;
                
                Прервать; // Переходим к следующему рисунку
                
            КонецЕсли;
            
        КонецЦикла;
        
    КонецЦикла;
    
КонецПроцедуры

Способ 2. Распаковка XLSX как ZIP-архива и разбор XML-структуры (OpenXML)

Современные файлы Excel с расширением .xlsx представляют собой обычные ZIP-архивы, содержащие XML-файлы разметки и медиаресурсы. Мы можем распаковать файл средствами 1С с помощью объекта ЧтениеZipФайла и программно связать картинки с ячейками.

Структура файлов внутри архива .xlsx:

Внутри узла xdr:twoCellAnchor содержатся теги координат левого верхнего угла картинки xdr:from:

А также тег xdr:pic, в котором через атрибут r:embed указан идентификатор связи. В файле связей xl/drawings/_rels/drawing1.xml.rels этот идентификатор сопоставляется с конкретным файлом из папки xl/media/.

Проанализируем пример кода, выполняющего разбор структуры OpenXML:


&НаСервере
Процедура ИмпортИзXLSXЧерезOpenXML(ПутьКФайлу)
    
    // Создаем временную папку для распаковки архива
    ПутьКПапкеРаспаковки = КаталогВременныхФайлов() + "XLSX_Unpack_" + Формат(ТекущаяДата(), "ДФ=yyyyMMddHHmmss") + "/";
    СоздатьКаталог(ПутьКПапкеРаспаковки);
    
    // Распаковываем XLSX как ZIP-архив
    Архив = Новый ЧтениеZipФайла(ПутьКФайлу);
    Архив.ИзвлечьВсе(ПутьКПапкеРаспаковки, РежимВосстановленияПутейФайловZIP.Восстанавливать);
    Архив.Закрыть();
    
    // Пути к ключевым файлам разметки
    ПутьКРазметке = ПутьКПапкеРаспаковки + "xl/drawings/drawing1.xml";
    ПутьКСвязям = ПутьКПапкеРаспаковки + "xl/drawings/_rels/drawing1.xml.rels";
    
    // Проверим наличие файлов графики
    ФайлРазметки = Новый Файл(ПутьКРазметке);
    Если Не ФайлРазметки.Существовать() Тогда
        ВызватьИсключение "В файле Excel отсутствуют графические элементы или они расположены не на первом листе.";
    КонецЕсли;
    
    // Шаг 1: Читаем связи рисунков (определяем соответствие rId -> имя файла в media)
    СоответствиеСвязей = Новый Соответствие;
    ЧтениеXMLСвязей = Новый ЧтениеXML;
    ЧтениеXMLСвязей.ОткрытьФайл(ПутьКСвязям);
    ПостроительСвязей = Новый ПостроительDOM;
    ДокументСвязейDOM = ПостроительСвязей.Прочитать(ЧтениеXMLСвязей);
    ЧтениеXMLСвязей.Закрыть();
    
    УзлыСвязей = ДокументСвязейDOM.ПолучитьЭлементыПоИмени("Relationship");
    Для Каждого УзелСвязи Из УзлыСвязей Цикл
        Атрибуты = УзелСвязи.ПолучитьАтрибуты();
        ИдСвязи = Атрибуты.ПолучитьИменованныйУзел("Id").ЗначениеУзла;
        ЦелевойПуть = Атрибуты.ПолучитьИменованныйУзел("Target").ЗначениеУзла; // Пример: "../media/image1.png"
        
        // Очищаем путь до имени файла
        ИмяФайла = СтрЗаменить(ЦелевойПуть, "../media/", "");
        СоответствиеСвязей.Вставить(ИдСвязи, ИмяФайла);
    КонецЦикла;
    
    // Шаг 2: Читаем координаты размещения рисунков из drawing1.xml
    ЧтениеXMLРазметки = Новый ЧтениеXML;
    ЧтениеXMLРазметки.ОткрытьФайл(ПутьКРазметке);
    ПостроительРазметки = Новый ПостроительDOM;
    ДокументРазметкиDOM = ПостроительРазметки.Прочитать(ЧтениеXMLРазметки);
    ЧтениеXMLРазметки.Закрыть();
    
    АнкерныеУзлы = ДокументРазметкиDOM.ПолучитьЭлементыПоИмени("xdr:twoCellAnchor");
    Для Каждого Анкер Из АнкерныеУзлы Цикл
        
        Попытка
            // Извлекаем строку и колонку привязки (левый верхний угол картинки)
            УзелFrom = Анкер.ПолучитьЭлементыПоИмени("xdr:from")[0];
            СтрокаXML = УзелFrom.ПолучитьЭлементыПоИмени("xdr:row")[0].ТекстовоеСодержимое;
            КолонкаXML = УзелFrom.ПолучитьЭлементыПоИмени("xdr:col")[0].ТекстовоеСодержимое;
            
            // Excel считает индексы с 0, переводим в формат 1С (начиная с 1)
            НомерСтроки1С = Число(СтрокаXML) + 1;
            НомерКолонки1С = Число(КолонкаXML) + 1;
            
            // Находим ID связи картинки
            УзелPic = Анкер.ПолучитьЭлементыПоИмени("xdr:pic")[0];
            УзелBlip = УзелPic.ПолучитьЭлементыПоИмени("xdr:blipFill")[0].ПолучитьЭлементыПоИмени("a:blip")[0];
            ИдКартинки = УзелBlip.ПолучитьАтрибуты().ПолучитьИменованныйУзел("r:embed").ЗначениеУзла;
            
            // Составляем полный физический путь к файлу картинки на диске
            ИмяФайлаКартинки = СоответствиеСвязей.Получить(ИдКартинки);
            ПолныйПутьКФайлу = ПутьКПапкеРаспаковки + "xl/media/" + ИмяФайлаКартинки;
            
            // Загружаем картинку в двоичные данные
            ФайлИзображения = Новый Файл(ПолныйПутьКФайлу);
            Если ФайлИзображения.Существовать() Тогда
                ДвоичныеДанныеИзображения = Новый ДвоичныеДанные(ПолныйПутьКФайлу);
                ЗаписатьКартинкуДляТовара(НомерСтроки1С, ДвоичныеДанныеИзображения);
            КонецЕсли;
        Исключение
            // Игнорируем ошибки при разборе поврежденных или неподдерживаемых узлов разметки
            Продолжить;
        КонецПопытки;
        
    КонецЦикла;
    
    // Очищаем временные файлы
    УдалитьФайлы(ПутьКПапкеРаспаковки);
    
КонецПроцедуры

Способ 3. Использование COM-соединения (через установленный MS Excel)

Если обработка выполняется на операционной системе Windows и на компьютере гарантированно установлен Microsoft Office Excel, наиболее простым и надежным решением будет использование COM-интерфейса. Этот способ избавляет разработчика от необходимости писать сложные XML-парсеры.

Для определения точного положения картинок мы используем свойства TopLeftCell.Row и TopLeftCell.Column, которые присутствуют у каждого графического объекта (Shape) в коллекции Excel.

Для экспорта картинок во внешние файлы применим быстрый и эффективный трюк — сохранение книги Excel в формате веб-страницы (HTML). При этом Excel автоматически конвертирует и сохраняет все изображения документа в отдельную папку на диске с последовательным наименованием.

Рассмотрим по шагам реализацию этой логики:


&НаКлиенте
Процедура ИмпортКартинокЧерезCOM(ПутьКФайлуExcel)
    
    // Инициализируем COM-объект Excel
    Попытка
        ExcelПриложение = Новый COMОбъект("Excel.Application");
    Исключение
        Предупреждение("На компьютере не установлен Microsoft Excel или у пользователя нет прав на его запуск.");
        Возврат;
    КонецПопытки;
    
    ExcelПриложение.Visible = Ложь;
    ExcelПриложение.DisplayAlerts = Ложь;
    
    Попытка
        Книга = ExcelПриложение.Workbooks.Open(ПутьКФайлуExcel);
        Лист = Книга.Sheets(1);
    Исключение
        ExcelПриложение.Quit();
        ВызватьИсключение "Не удалось открыть файл Excel: " + ОписаниеОшибки();
    КонецПопытки;
    
    // Массив структур для хранения связей индексов картинок с их ячейками
    КартаКартинок = Новый Массив;
    
    // Шаг 1: Фиксируем координаты каждой картинки из коллекции Pictures
    ИндексРисунка = 1;
    Для Каждого Рисунок Из Лист.Pictures Цикл
        
        ДанныеКартинки = Новый Структура;
        ДанныеКартинки.Вставить("ИндексРисунка", ИндексРисунка);
        ДанныеКартинки.Вставить("Строка", Рисунок.TopLeftCell.Row);
        ДанныеКартинки.Вставить("Колонка", Рисунок.TopLeftCell.Column);
        
        КартаКартинок.Добавить(ДанныеКартинки);
        ИндексРисунка = ИндексРисунка + 1;
        
    КонецЦикла;
    
    // Шаг 2: Сохраняем книгу как HTML-документ для автоматического экспорта картинок
    ВременныйКаталог = КаталогВременныхФайлов() + "ExcelHTML_" + Формат(ТекущаяДата(), "ДФ=yyyyMMddhhmmss") + "\";
    СоздатьКаталог(ВременныйКаталог);
    
    ФайлHTML = ВременныйКаталог + "page.html";
    ПапкаРесурсов = ВременныйКаталог + "page.files\"; // Эту папку Excel создаст сам
    
    // Код формата сохранения 44 соответствует константе xlHTML в Excel
    Книга.SaveAs(ФайлHTML, 44);
    
    // Закрываем Excel и освобождаем память
    Книга.Close(Ложь);
    ExcelПриложение.Quit();
    ExcelПриложение = Неопределено;
    
    // Шаг 3: Читаем файлы картинок с диска и сопоставляем их с ячейками
    Для Каждого ЭлементКарты Из КартаКартинок Цикл
        
        // Excel сохраняет картинки по маске "imageXXX.ext" (например, "image001.png", "image002.jpg")
        СтроковоеИмяФайла = "image" + Прав("000" + СокрЛП(ЭлементКарты.ИндексРисунка), 3);
        
        // Поиск файла с любым расширением графического формата
        МассивФайлов = НайтиФайлы(ПапкаРесурсов, СтроковоеИмяФайла + ".*");
        Если МассивФайлов.Количество() > 0 Тогда
            
            ФайлКартинки = МассивФайлов[0];
            ДвоичныеДанныеИзображения = Новый ДвоичныеДанные(ФайлКартинки.ПолноеИмя);
            
            // Передаем полученные двоичные данные на сервер для записи в базу данных
            СохранитьКартинкуНаСервере(ЭлементКарты.Строка, ЭлементКарты.Колонка, ДвоичныеДанныеИзображения);
            
        КонецЕсли;
        
    КонецЦикла;
    
    // Очищаем временные файлы
    УдалитьФайлы(ВременныйКаталог);
    ПоказатьПредупреждение(, "Импорт картинок успешно завершен!");
    
КонецПроцедуры

Выводы и рекомендации по выбору метода

Каждый из рассмотренных нами методов имеет свои достоинства и ограничения, поэтому выбор оптимального подхода зависит от архитектуры вашей информационной системы:

← На главную