Загрузка данных из внешних файлов — одна из самых частых задач, с которыми сталкивается разработчик 1С. В среде управляемых форм эта задача требует особого внимания к архитектуре "клиент-сервер". Разберем по шагам, как создать универсальную обработку, которая позволит пользователю выбирать файл, конкретный лист и загружать данные с произвольным набором колонок и типизацией. При необходимости можно воспользоваться готовой универсальной обработкой для импорта данных из Excel в справочники без установки MS Excel.
Начнем с интерфейса. Нам необходимо предоставить пользователю возможность выбрать файл .xls или .xlsx. Рассмотрим, как это реализовать на клиенте. Основная сложность здесь заключается в том, что сервер «не видит» файлы на диске пользователя, поэтому файл нужно сначала выбрать, а затем передать во временное хранилище.
Проанализируем пример функции для выбора файла:
&НаКлиенте
Процедура ВыбратьФайл(Команда)
ПараметрыДиалога = Новый ПараметрыДиалогаВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
ПараметрыДиалога.Заголовок = "Выберите файл Excel";
ПараметрыДиалога.Фильтр = "Файлы Excel (*.xls;*.xlsx)|*.xls;*.xlsx";
ПараметрыДиалога.МножественныйВыбор = Ложь;
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВыбораФайла", ЭтотОбъект);
НачатьПомещениеФайлаНаСервер(ОписаниеОповещения, , , , ПараметрыДиалога, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ПослеВыбораФайла(Результат, Адрес, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
Если Результат Тогда
Объект.ПутьКФайлу = ВыбранноеИмяФайла;
Объект.АдресФайлаВоВременномХранилище = Адрес;
// После выбора файла можно сразу получить список листов
ПолучитьСписокЛистовНаСервере();
КонецЕсли;
КонецПроцедуры
Использование метода НачатьПомещениеФайлаНаСервер позволяет нам одновременно выбрать файл и загрузить его в временное хранилище. Это критически важно для работы в клиент-серверном варианте, чтобы избежать ошибки «Файл не найден» при выполнении кода на сервере.
Часто в Excel-файле содержится несколько листов. Чтобы соответствовать требованиям, мы должны дать пользователю выбор. Рассмотрим, как программно получить имена листов на сервере, используя встроенный объект ТабличныйДокумент. Этот метод не требует установленного Microsoft Excel на сервере, что делает его предпочтительным.
&НаСервере
Процедура ПолучитьСписокЛистовНаСервере()
Если ПустаяСтрока(Объект.АдресФайлаВоВременномХранилище) Тогда
Возврат;
КонецЕсли;
ДанныеФайла = ПолучитьИзВременногоХранилища(Объект.АдресФайлаВоВременномХранилище);
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xlsx");
ДанныеФайла.Записать(ИмяВременногоФайла);
ТабДок = Новый ТабличныйДокумент;
Попытка
// Метод ПолучитьИменаЛистов доступен в новых версиях платформы
СписокЛистов = ТабДок.ПолучитьИменаЛистов(ИмяВременногоФайла);
Объект.СписокЛистов.Очистить();
Для Каждого ИмяЛиста Из СписокЛистов Цикл
Объект.СписокЛистов.Добавить(ИмяЛиста);
КонецЦикла;
Исключение
Сообщить("Ошибка при чтении файла: " + ОписаниеОшибки());
КонецПопытки;
УдалитьФайлы(ИмяВременногоФайла);
КонецПроцедуры
Здесь мы используем метод ПолучитьИменаЛистов(). Если вы работаете на старой версии платформы, где этот метод недоступен, можно прочитать файл целиком в ТабличныйДокумент, но это может быть медленно для больших файлов.
Когда пользователь выбрал конкретный лист, мы приступаем к чтению данных. Выясним причину, почему метод с ПостроительЗапроса, предложенный в обсуждении, считается удобным. Он позволяет быстро преобразовать область табличного документа в Таблицу Значений. Для такой задачи подойдёт универсальная обработка для чтения листов файлов Excel и CSV с возвратом данных в виде таблиц значений.
Рассмотрим детально процесс чтения:
&НаСервере
Функция ПрочитатьДанныеСЛиста(ИмяЛиста)
ДанныеФайла = ПолучитьИзВременногоХранилища(Объект.АдресФайлаВоВременномХранилище);
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xlsx");
ДанныеФайла.Записать(ИмяВременногоФайла);
ТабДок = Новый ТабличныйДокумент;
// Читаем конкретный лист
ТабДок.Прочитать(ИмяВременногоФайла, СпособЧтенияЗначенийТабличногоДокумента.Значение, ИмяЛиста);
Высота = ТабДок.ВысотаТаблицы;
Ширина = ТабДок.ШиринаТаблицы;
Если Высота = 0 Тогда
Возврат Неопределено;
КонецЕсли;
// Создаем область для построителя
Область = ТабДок.Область(1, 1, Высота, Ширина);
Построитель = Новый ПостроительЗапроса;
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(Область);
Построитель.Выполнить();
ТЗ = Построитель.Результат.Выгрузить();
УдалитьФайлы(ИмяВременногоФайла);
Возврат ТЗ;
КонецФункции
Обратите внимание на параметр СпособЧтенияЗначенийТабличногоДокумента.Значение. Он позволяет 1С пытаться автоматически определить типы данных (число, дата) еще на этапе чтения из Excel.
Пользователь по условию задачи должен сам задать типы данных. Разберем, как реализовать механизм сопоставления. Для этого на форме удобно использовать таблицу, где в одной колонке будет «Имя колонки в Excel», а в другой — «Тип данных» (через объект ОписаниеТипов).
Проанализируем ситуацию загрузки данных в итоговую таблицу:
&НаСервере
Процедура ЗагрузитьДанныеВБазу(ТаблицаДанныхExcel)
// Создаем результирующую таблицу значений с типами, выбранными пользователем
ИтоговаяТЗ = Новый ТаблицаЗначений;
Для Каждого Настройка Из Объект.НастройкиКолонок Цикл
// Настройка.ТипДанных - это объект ОписаниеТипов, выбранный пользователем
ИтоговаяТЗ.Колонки.Добавить(Настройка.ИмяКолонки, Настройка.ТипДанных);
КонецЦикла;
// Заполняем данными с приведением типов
Для Каждого СтрокаExcel Из ТаблицаДанныхExcel Цикл
НоваяСтрока = ИтоговаяТЗ.Добавить();
Для Каждого Настройка Из Объект.НастройкиКолонок Цикл
ИмяКолонкиExcel = "Колонка" + Настройка.НомерКолонки;
Значение = СтрокаExcel[ИмяКолонкиExcel];
// Приведение типа происходит автоматически при присваивании в типизированную колонку
Попытка
НоваяСтрока[Настройка.ИмяКолонки] = Значение;
Исключение
Сообщить("Ошибка приведения типа в строке " + СтрокаExcel.НомерСтроки);
КонецПопытки;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Как отмечалось в сообщении 20, существует способ работы через COMОбъект("Excel.Application"). Посмотрим на этот вариант подробнее. Его главное преимущество — возможность работать со старыми форматами и сложной структурой файлов. Однако помните, что этот метод требует установленного Excel на рабочем месте (если выполняется &НаКлиенте) или на сервере.
Пример быстрого чтения через COMSafeArray:
&НаКлиенте
Функция ПолучитьДанныеЧерезCOM(ПутьКФайлу)
Попытка
Excel = Новый COMОбъект("Excel.Application");
Книга = Excel.Workbooks.Open(ПутьКФайлу);
Лист = Книга.Sheets(1); // Или выбор по имени
// Получаем все данные листа в массив одним вызовом (очень быстро)
ДанныеМассива = Лист.UsedRange.Value;
Массив = ДанныеМассива.Выгрузить();
Книга.Close(Ложь);
Excel.Quit();
Возврат Массив;
Исключение
// Не забываем закрыть Excel при ошибке
Если Excel <> Неопределено Тогда
Excel.Quit();
КонецЕсли;
Возврат Неопределено;
КонецПопытки;
КонецФункции
УдалитьФайлы() для временных копий, созданных на сервере из временного хранилища. Засорение папки Temp может привести к сбоям сервера..xlsx является XML-структурой и читается 1С нативно. Формат .xls (97-2003) более капризен и иногда требует наличия установленных библиотек провайдера (например, ACE OLEDB).UsedRange может включать строки, которые выглядят пустыми, но содержат форматирование. Проверяйте значимые поля (например, артикул или наименование) на заполненность перед созданием объектов в базе.ТабличныйДокумент или библиотеки для чтения XML.Таким образом, мы рассмотрели комплексный подход к загрузке данных. Сочетание НачатьПомещениеФайлаНаСервер для передачи данных, ТабличныйДокумент для универсального чтения и программное создание ОписаниеТипов позволяет создать мощный инструмент, отвечающий всем требованиям современных конфигураций на управляемых формах.