В данной статье приведен функционал, с помощью которого в обработке
"Импорт из EXCEL и др.источников (xls,xlsx,ods,sxc,dbf,mxl,csv,sql) в 1С": //infostart.ru/public/120961/
производится считывание данных из файлов табличного типа *.xlsx средствами 1С (метод "NativeXLSX").
Метод позволяет обрабатывать данные EXCEL типов: "Строка", "Число", "Дата" ("Время"), "Булево", "Процент", а также изображения.
Примечание: Изображение должны быть единственным значением в ячейке. Принадлежность изображения ячейке определяется по левому верхнему углу этого изображения.
Методы загрузки из внешнего источника:
- Метод "MS ADO" (Чтение файлов xls, xlsx средствами Microsoft ADO): //infostart.ru/public/163640/
- Метод "MS EXCEL" (Чтение файлов xls, xlsx с картинками средствами Microsoft Office): //infostart.ru/public/163641/
- Метод "LO CALC" (Чтение файлов xls, xlsx, ods, sxc с картинками средствами LibreOffice): //infostart.ru/public/163642/
- Метод "NativeXLSX" (Чтение файлов xlsx с картинками средствами 1С. ПостроительDOM): //infostart.ru/public/300092/
- Метод "NativeXLSX". Предыдущий вариант (Чтение файлов xlsx средствами 1С. ЧтениеXML)://infostart.ru/public/225624/
- Метод "Excel1C" (Загрузка на платформе 8.3.6 с картинками. Чтение файлов xls, xlsx, ods): //infostart.ru/public/341855/
- Список листов файла: //infostart.ru/public/163724/
Функционал, представленный в статье, позволяет организовать импорт (загрузку) из файла типа XLSX в таблицу значений.
При этом учитывается, что XLSX-файл - это некий контейнер, который можно распаковать средствами 1С в набор XML-файлов.
В конечном счете задача сводится к чтению xml-файлов, содержащих различную информаци:
- строковые значения [sharedStrings.xml];
- индексы строковых значений, числовые значения, формулы [собственно страницы EXCEL-таблицы sheet...xml];
- информация об изображениях [drawing...xml];
- стили форматирования [styles.xml].
Чтение XML-файлов осуществляется с помощью связки ЧтениеXML-ПостроительDOM-ДокументDOM (встроенные объекты платформы 1С), т.е. ДокументDOM получается как результат чтения ПостроителемDOM объекта ЧтениеXML - источника данных XML.
В дальнейшем осуществляется обход дочерних узлов ДокументаDOM, чтение их значений и различных атрибутов.
Данная методика на мой взгляд проще и удобнее использования объекта ЧтениеXML для разбора структурированных XML-файлов, коими представляется XLSX-файл.
Публикаций на тему загрузки из EXCEL - множество, но
"
— Вам билетёр нужен?
— Был нужен, да уже взяли.
— Может и я на что сгожусь?
— Может и сгодишься, если скалиться не будешь ...
"
"НЕУЛОВИМЫЕ МСТИТЕЛИ" (1966).
В первом комментарии к публикации прикреплен файл, содержащий текст модуля.
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ФайлEXCEL= "D:\Товар.xlsx";
ИмяЛиста = Новый Структура ("ИмяЛиста, НомерЛиста","Alfa2", 2);
СтрокаЗаголовка = 1;
НачСтрока = 0;
КонСтрока = 0;
КолвоСтрокExcel = 0;
ЗагрузитьМетодом_NativeXLSX_НаСервере(ФайлEXCEL, ИмяЛиста, СтрокаЗаголовка, НачСтрока, КонСтрока, КолвоСтрокExcel);
КонецПроцедуры
&НаСервере
Процедура ЗагрузитьМетодом_NativeXLSX_НаСервере(ФайлEXCEL, ИмяЛиста, СтрокаЗаголовка, НачСтрока, КонСтрока, КолвоСтрокExcel)
ТаблицаРезультат = ЗагрузитьМетодом_NativeXLSX(ФайлEXCEL, ИмяЛиста, СтрокаЗаголовка, НачСтрока, КонСтрока, КолвоСтрокExcel);
КонецПроцедуры
// Метод "NativeXLSX" (ПостроительDOM).
// Преобразует текст формата XML (файл типа XLSX) в таблицу значений,
// при этом колонки таблицы формируются на основе описания в XML.
//
// Параметры:
// ФайлEXCEL - Полное имя файла (путь к файлу с именем файла и расширением)
// ИмяНомерЛиста - Структура Имя и Номер выбранного листа файла EXCEL.
// СтрокаЗаголовка (по умолчанию = 1) - Номер строки EXCEL, в которой расположены заголовки колонок.
// Не используется.
// В обработке 1-я строка анализируется для сопоставления колонок EXCEL с реквизитами 1С (справочники, докуметны, регистры).
// НачСтрока (по-умолчанию = 0) - Номер начальной строки, начиная с которой считываются данные из EXCEL.
// КонСтрока (по-умолчанию = 0) - Номер конечной строки, которой заканчиваются считываемые данные из EXCEL.
// Если НачСтрока=0 и КонСтрока=0, то считывается вся таблица, находящаяся на листе EXCEL.
// КолвоСтрокEXCEL - Количество строк на листе "ИмяЛиста" EXCEL. Возвращается в вызываемую процедуру.
//
// Возвращаемые значения:
// ТаблицаРезультат - Результат считывания с листа "ИмяЛиста" EXCEL.
//
&НаСервере
Функция ЗагрузитьМетодом_NativeXLSX(Знач ФайлEXCEL, Знач ИмяНомерЛиста, Знач СтрокаЗаголовка = 1, Знач НачСтрока = 0, Знач КонСтрока = 0, КолвоСтрокEXCEL = 0)
Перем ZIPКаталог, ФайлИмяЛиста, ФайлНомерЛиста, КолвоКолонокEXCEL, НомерСтроки, НомерКолонки, ИмяКолонки, ИндексКолонки, ШиринаКолонки, ДлинаСтроки;
Перем МассивИменКолонокXLSX, МассивSharedStrings, МассивNumFmtId, СоответствиеNumFmtIdFormatCode, ТаблицаIMAGE, СтрокаIMAGE, КаталогMEDIA;
Перем ДокументDOM, ЭлементыROW, ЭлементСтрока, ЭлементыCOL, ЭлементКолонка;
Перем ЗначениеЯчейки, ТипЗначения, ФорматЯчейки, ФорматСтиля;
Перем ТаблицаРезультат, НоваяСтрока;
ФайлИмяЛиста = ИмяНомерЛиста.ИмяЛиста;
ФайлНомерЛиста = ИмяНомерЛиста.НомерЛиста;
ФайлНомерЛиста = ?(ФайлНомерЛиста = 0, 1, ФайлНомерЛиста);
ZIPКаталог = ПолучитьИмяКаталогаВременныхФайловДляРаспаковкиXLSX();
Если НЕ ПредварительнаяОбработкаФайлаXLSX(ФайлEXCEL, ZIPКаталог) Тогда
Возврат Новый ТаблицаЗначений;
КонецЕсли;
СоответствиеNumFmtIdFormatCode = Новый Соответствие;
МассивNumFmtId = ИзвлечьДанныеИзФайла_StylesXML(ZIPКаталог, СоответствиеNumFmtIdFormatCode);
МассивSharedStrings = ИзвлечьДанныеИзФайла_SharedStringsXML(ZIPКаталог);
КаталогMEDIA = "media";
ТаблицаIMAGE = ИзвлечьДанныеИзФайла_DrawingXML(ZIPКаталог, ФайлНомерЛиста, КаталогMEDIA);
ДокументDOM = ПолучитьДокументDOM_NativeXLSX(ZIPКаталог + "XL\WorkSheets\Sheet" + ФайлНомерЛиста + ".xml");
ЭлементыROW = ПолучитьЭлементыROW_NativeXLSX(ДокументDOM);
Если ЭлементыROW = Неопределено ИЛИ ЭлементыROW.Количество() = 0 Тогда
// Завершение работы.
// Закрытие Объектов.
ДокументDOM = Неопределено;
Возврат Новый ТаблицаЗначений;
КонецЕсли;
МассивИменКолонокXLSX = ПолучитьМассивИменКолонокНаЛистеXLSX(ДокументDOM, ЭлементыROW);
КолвоКолонокEXCEL = МассивИменКолонокXLSX.Количество();
// Создание результирующей таблицы, в которую будут записываться считанные из EXCEL данные.
ТаблицаРезультат = Новый ТаблицаЗначений;
// Формирование колонок результирующей таблицы.
// "НомерСтроки" - для наглядности и удобства.
// В зависимости от разрабатываемой обработки.
// "Сопоставлено" - может быть другим.
// Здесь же могут быть добавлены другие колонки, не формируемые из содержимого файла EXCEL.
ТаблицаРезультат.Колонки.Добавить("НомерСтроки", Новый ОписаниеТипов("Число"), "№", 4);
ТаблицаРезультат.Колонки.Добавить("Сопоставлено", Новый ОписаниеТипов("Булево"), "Сопоставлено", 1);
ТаблицаРезультат.Колонки.Добавить("УровеньГруппировки", Новый ОписаниеТипов("Число"), "Гр", 2); // Группировка строк в файле EXCEL.
Для ит = 1 ПО КолвоКолонокEXCEL Цикл
ИмяКолонки = "N" + ит;
Колонка = ТаблицаРезультат.Колонки.Добавить(ИмяКолонки);
КонецЦикла;
// Строки XLSX.
НомерСтроки = 0;
Для Каждого ЭлементСтрока ИЗ ЭлементыROW Цикл
НомерСтроки = НомерСтроки + 1;
// Начало диапазона считываемых данных.
Если НЕ НачСтрока = 0 И НЕ НомерСтроки = 1 И НомерСтроки < НачСтрока Тогда
Продолжить;
КонецЕсли;
// Окончание диапазона считываемых данных.
Если КонСтрока > 0 И НомерСтроки > КонСтрока Тогда
Прервать;
КонецЕсли;
// Окончание данных.
Если КолвоСтрокEXCEL > 0 И НомерСтроки > КолвоСтрокEXCEL Тогда
Прервать;
КонецЕсли;
НоваяСтрока = ТаблицаРезультат.Добавить();
НоваяСтрока.НомерСтроки = НомерСтроки;
НоваяСтрока.УровеньГруппировки = ЗначениеАтрибутаУзлаDOM(ЭлементСтрока, "outlineLevel");
// Колонки XLSX.
НомерКолонки = 0;
ЭлементыCOL = ЭлементСтрока.ДочерниеУзлы;
Для ит = 0 ПО КолвоКолонокEXCEL-1 Цикл
НомерКолонки = НомерКолонки + 1;
ЭлементКолонка = Неопределено;
Попытка
ЭлементКолонка = ЭлементыCOL[ит];
ТипЗначения = ЗначениеАтрибутаУзлаDOM(ЭлементКолонка, "t");
ФорматЯчейки = ЗначениеАтрибутаУзлаDOM(ЭлементКолонка, "s");
ИмяКолонки = ЗначениеАтрибутаУзлаDOM(ЭлементКолонка, "r");
ИмяКолонкиБезЦифр = ЗаменитьОдниСимволыДругими("0123456789", ИмяКолонки, "");
ИндексКолонки = МассивИменКолонокXLSX.Найти(ИмяКолонкиБезЦифр) + 1;
Исключение
ЭлементКолонка = Неопределено;
ИндексКолонки = НомерКолонки;
КонецПопытки;
ИмяКолонки = "N" + ИндексКолонки;
ЗначениеЯчейки = Неопределено;
Если НЕ ЭлементКолонка = Неопределено И НЕ ЭлементКолонка.ПервыйДочерний = Неопределено И (ВРег(ЭлементКолонка.ПервыйДочерний.ИмяУзла) = "V" ИЛИ ВРег(ЭлементКолонка.ПервыйДочерний.ИмяУзла) = "F") Тогда // "V" - Значение, "F" - Формула.
Если ВРег(ЭлементКолонка.ПервыйДочерний.ИмяУзла) = "F" Тогда
Если НЕ ЭлементКолонка.ПоследнийДочерний = Неопределено И ВРег(ЭлементКолонка.ПоследнийДочерний.ИмяУзла) = "V" Тогда
ЗначениеЯчейки = ЭлементКолонка.ПоследнийДочерний.ТекстовоеСодержимое;
Иначе
Сообщить("Строка " + НомерСтроки + " Колонка " + ИндексКолонки + " значение формулы не определено.");
КонецЕсли;
Иначе
ЗначениеЯчейки = ЭлементКолонка.ТекстовоеСодержимое;
КонецЕсли;
ФорматСтиля = "";
Если (НЕ ФорматЯчейки = "" И НЕ ФорматЯчейки = Неопределено) Тогда
Попытка
ФорматСтиля = СоответствиеNumFmtIdFormatCode.Получить(МассивNumFmtId[Число(ФорматЯчейки)]);
Исключение
ФорматСтиля = "";
КонецПопытки;
КонецЕсли;
Если ЗначениеЗаполнено(ЗначениеЯчейки) Тогда
Если ТипЗначения = Неопределено ИЛИ ВРег(ТипЗначения) = "N" ИЛИ ВРег(ТипЗначения) = "B" Тогда // Тип "Число", "Булево".
Попытка
Значение1 = Число(ЗначениеЯчейки);
Исключение
Значение1 = ЗначениеЯчейки;
КонецПопытки;
ЗначениеЯчейки = Значение1;
Если (ФорматСтиля = "" ИЛИ ФорматСтиля = Неопределено) Тогда
// ФорматСтиля = Неопределено - Атрибут "s" отсутствует.
// MS Office может не формировать в xml-файле описание стиля форматирования для ячейки.
// LibreOffice формирует в xml-файле необходимые описания стиля форматирования ячейки.
// Сообщить("Не определен стиль форматирования для кода " + ФорматЯчейки + " значения " + ЗначениеЯчейки);
КонецЕсли;
Если ТипЗнч(ЗначениеЯчейки) = Тип("Строка")
И (Найти(ЗначениеЯчейки, "E-") > 0 ИЛИ Найти(ЗначениеЯчейки, "E+") > 0) Тогда
ЗначениеЯчейки = ПолучитьЧислоВСтепениИзСтроковогоЗначения(ЗначениеЯчейки);
КонецЕсли;
Если ТипЗнч(ЗначениеЯчейки) = Тип("Число") Тогда
// ПРОЦЕНТ.
Если ЭтоПроцентXLSX(ЗначениеЯчейки, ФорматСтиля) Тогда
ЗначениеЯчейки = Окр(ЗначениеЯчейки * 100, 3);
// БУЛЕВО.
ИначеЕсли ЭтоБулевоXLSX(ЗначениеЯчейки, ФорматСтиля) Тогда
ЗначениеЯчейки = Булево(ЗначениеЯчейки);
// ВРЕМЯ.
ИначеЕсли ЭтоВремяXLSX(ЗначениеЯчейки, ФорматСтиля) Тогда
ЗначениеЯчейки = КонвертироватьЧислоXLSXвДатуВремя(ЗначениеЯчейки);
// ДАТА.
ИначеЕсли ЭтоДатаXLSX(ЗначениеЯчейки, ФорматСтиля) Тогда
ЗначениеЯчейки = КонвертироватьЧислоXLSXвДату(ЗначениеЯчейки);
// ЧИСЛО.
ИначеЕсли ТипЗначения = Неопределено
ИЛИ ( ЭтоЧислоXLSX(ЗначениеЯчейки, ФорматСтиля)
И НЕ ЭтоБулевоXLSX(ЗначениеЯчейки, ФорматСтиля)
И НЕ ЭтоВремяXLSX(ЗначениеЯчейки, ФорматСтиля)
И НЕ ЭтоДатаXLSX(ЗначениеЯчейки, ФорматСтиля) )
Тогда
// Без преобразования.
Иначе
// Прочие форматы.
КонецЕсли;
Иначе
Сообщить("Не удалось преобразовать значение к ""числовому""(число, процент, время, дата, булево) типу.");
КонецЕсли;
ИначеЕсли ВРег(ТипЗначения) = "S" Тогда
// МассивSharedStrings может быть пустым.
Попытка
ЗначениеЯчейки = СокрЛП(МассивSharedStrings[Число(ЗначениеЯчейки)]);
Исключение
ЗначениеЯчейки = "";
КонецПопытки;
ИначеЕсли ВРег(ТипЗначения) = "STR" Тогда
Если ТипЗнч(ЗначениеЯчейки) = Тип("Строка") Тогда
ЗначениеЯчейки = СокрЛП(ЗначениеЯчейки);
КонецЕсли;
КонецЕсли;
КонецЕсли;
НоваяСтрока[ИмяКолонки] = ЗначениеЯчейки;
// Используется при формировании таблицы на форме обработки.
ШиринаКолонки = ТаблицаРезультат.Колонки[ИмяКолонки].Ширина;
ДлинаСтроки = СтрДлина(СокрЛП(НоваяСтрока[ИмяКолонки]));
ТаблицаРезультат.Колонки[ИмяКолонки].Ширина = ?(ШиринаКолонки < ДлинаСтроки, ДлинаСтроки, ШиринаКолонки);
ИначеЕсли ЭлементКолонка = Неопределено ИЛИ (ЭлементКолонка.ПервыйДочерний = Неопределено И ТаблицаIMAGE.Количество() > 0) Тогда
Для Каждого СтрокаIMAGE ИЗ ТаблицаIMAGE Цикл
Если СтрокаIMAGE.ROW = НомерСтроки И СтрокаIMAGE.COL = НомерКолонки Тогда
НоваяСтрока[ИмяКолонки] = ZIPКаталог + "XL\" + КаталогMEDIA + "\" + СтрокаIMAGE.FILE;
Прервать;
КонецЕсли;
КонецЦикла;
// Используется при формировании таблицы на форме обработки.
ШиринаКолонки = ТаблицаРезультат.Колонки[ИмяКолонки].Ширина;
ДлинаСтроки = СтрДлина(СокрЛП(НоваяСтрока[ИмяКолонки]));
ТаблицаРезультат.Колонки[ИмяКолонки].Ширина = ?(ШиринаКолонки < ДлинаСтроки, ДлинаСтроки, ШиринаКолонки);
КонецЕсли;
КонецЦикла;
КонецЦикла;
// Завершение работы.
// Закрытие Объектов.
ДокументDOM = Неопределено;
УдалитьКолонкиСНулевойШириной(ТаблицаРезультат);
Возврат ТаблицаРезультат;
КонецФункции
&НаСервере
Функция ПолучитьИмяКаталогаВременныхФайловДляРаспаковкиXLSX()
Возврат КаталогВременныхФайлов() + "XLSX\";
КонецФункции
&НаСервере
Функция ПредварительнаяОбработкаФайлаXLSX(ФайлEXCEL, ZIPКаталог)
Перем Файл;
Файл = ПолучитьОбъектФайл(ФайлEXCEL);
Если Файл = Неопределено Тогда
Сообщить("Невозможно загрузить данные, т.к. невозможно открыть для чтения файл:
|" + ФайлEXCEL);
Возврат Ложь;
КонецЕсли;
Если НЕ ВРег(Файл.Расширение) = ".XLSX" Тогда
Сообщить("Файл с расширением " + Файл.Расширение + " не поддерживается методом NativeXLSX:
|" + ФайлEXCEL);
Возврат Ложь;
КонецЕсли;
Если НЕ РаспаковатьXLSXвКаталогВременныхФайлов(ФайлEXCEL, ZIPКаталог) Тогда
Сообщить("Ошибка распаковки файла.
|Возможно каталог временных файлов занят.
|" + ФайлEXCEL);
Возврат Ложь;
КонецЕсли;
Возврат Истина;
КонецФункции
&НаСервере
Функция ПолучитьОбъектФайл(Знач ФайлEXCEL)
Перем Файл;
Если НЕ ЗначениеЗаполнено(ФайлEXCEL) Тогда
Возврат Неопределено;
КонецЕсли;
Файл = Новый Файл(ФайлEXCEL);
Если НЕ ФайлСуществуетИДоступен(Файл.ПолноеИмя) Тогда
Сообщить("Файл не существует/не доступен:
|" + ФайлEXCEL);
Возврат Неопределено;
КонецЕсли;
Возврат Файл;
КонецФункции
&НаСервере
Функция ФайлСуществуетИДоступен(ПолноеИмяФайла)
Перем Файл;
Файл = Новый Файл(ПолноеИмяФайла);
Если НЕ Файл.Существует() Тогда
Возврат Ложь;
КонецЕсли;
// Проверка: Занят ли файл другим процессом?
Попытка
ПереместитьФайл(Файл.ПолноеИмя, Файл.ПолноеИмя);
Исключение
Сообщить("Файл не доступен:
|" + Файл.ПолноеИмя);
Возврат Ложь;
КонецПопытки;
Возврат Истина;
КонецФункции
&НаСервере
Функция РаспаковатьXLSXвКаталогВременныхФайлов(ФайлEXCEL, ZIPКаталог)
Перем ZIPФайл;
Попытка
УдалитьФайлы(ZIPКаталог);
ZIPФайл = Новый ЧтениеZipФайла;
ZIPФайл.Открыть(ФайлEXCEL);
ZIPФайл.ИзвлечьВсе(ZIPКаталог, РежимВосстановленияПутейФайловZIP.Восстанавливать);
Возврат Истина;
Исключение
Возврат Ложь;
КонецПопытки;
Возврат Истина;
КонецФункции
&НаСервере
Функция ПолучитьДокументDOM_NativeXLSX(ФайлSheetXML)
Перем ЧтениеXML, ПостроительDOM, ДокументDOM;
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ФайлSheetXML);
ПостроительDOM = Новый ПостроительDOM;
Попытка
ДокументDOM = ПостроительDOM.Прочитать(ЧтениеXML);
//Сообщить("Версия XML: " + ДокументDOM.ВерсияXML);
Исключение
Сообщить(ОписаниеОшибки());
ДокументDOM = Неопределено;
КонецПопытки;
// Завершение работы.
// Закрытие Объектов.
ЧтениеXML.Закрыть();
Возврат ДокументDOM;
КонецФункции
&НаСервере
Функция ПолучитьЭлементыROW_NativeXLSX(Знач ДокументDOM)
Перем ЭлементыROW;
Попытка
ЭлементыROW = ДокументDOM.ПолучитьЭлементыПоИмени("row");
Исключение
Сообщить(ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
// Если строки пусты, то они не включаются в список элементов.
// Если строки пусты и отдельные ячейки содержат оформление (например задан тип "Текст"),
// то эти строки включаются в список (содержание: пустые).
Возврат ЭлементыROW;
КонецФункции
// Возвращает строковое значение атрибута узла DOM-документа.
//
// Параметры:
// УзелDOM (УзелDOM) - узел DOM-документа;
// ИмяАтрибута (Строка) - полное имя атрибута;
// ЗначениеЕслиНеНайдено (Произвольный) - значение, если атрибут не найден;
//
// Возвращаемое значение:
// Строка:
// Строковое значение атрибута узла;
//
&НаСервере
Функция ЗначениеАтрибутаУзлаDOM(УзелDOM, ИмяАтрибута, ЗначениеЕслиНеНайдено = Неопределено)
Перем АтрибутDOM, Атрибут;
АтрибутDOM = УзелDOM.Атрибуты.ПолучитьИменованныйЭлемент(ИмяАтрибута);
Если АтрибутDOM = Неопределено Тогда
Для Каждого Атрибут ИЗ УзелDOM.Атрибуты Цикл
Если Атрибут.ИмяУзла = ИмяАтрибута Тогда
Возврат Атрибут.Значение;
КонецЕсли;
КонецЦикла;
КонецЕсли;
Если АтрибутDOM = Неопределено Тогда
Возврат ЗначениеЕслиНеНайдено;
Иначе
Возврат АтрибутDOM.Значение;
КонецЕсли;
КонецФункции
&НаСервере
Функция ПолучитьМассивИменКолонокНаЛистеXLSX(ДокументDOM, ЭлементыROW)
Перем DIMENSION, ДиапазонДанных, ДиапазонКолонок, ПерваяКолонка, ПоследняяКолонка, НомерПервойК, НомерПоследнейК;
Перем ЭлементыCOL, Колонка;
Перем МассивИменКолонокXLSX, КолвоКолонокEXCEL;
// DIMENSION.
Попытка
DIMENSION = ДокументDOM.ПолучитьЭлементыПоИмени("DIMENSION");
ДиапазонДанных = ЗначениеАтрибутаУзлаDOM(DIMENSION[0], "ref");
ДиапазонКолонок = ЗаменитьОдниСимволыДругими("0123456789", ДиапазонДанных, "");
ПерваяКолонка = Лев(ДиапазонКолонок, Найти(ДиапазонКолонок ,":") - 1);
ПоследняяКолонка= Сред(ДиапазонКолонок, Найти(ДиапазонКолонок ,":") + 1);
НомерПервойК = НомерКолонкиДесятичный(ПерваяКолонка);
НомерПоследнейК = НомерКолонкиДесятичный(ПоследняяКолонка);
Исключение
НомерПоследнейК = Неопределено;
Сообщить("Не удалось определить конечную колонку диапазона файла XLSX");
КонецПопытки;
МассивИменКолонокXLSX = Новый Массив;
ЭлементыCOL = ЭлементыROW[0].ДочерниеУзлы;
Для Каждого Колонка ИЗ ЭлементыCOL Цикл
ИмяКолонки = ЗначениеАтрибутаУзлаDOM(Колонка, "r");
ИмяКолонки = ЗаменитьОдниСимволыДругими("0123456789", ИмяКолонки, "");
МассивИменКолонокXLSX.Добавить(ИмяКолонки);
КонецЦикла;
КолвоКолонокEXCEL = МассивИменКолонокXLSX.Количество();
Если НЕ НомерПоследнейК = Неопределено Тогда
Если КолвоКолонокEXCEL < НомерПоследнейК Тогда
Сообщить("Строка заголовков колонок (1-я строка) заполнена не полностью:
|К-во колонок строки меньше объявленной: " + КолвоКолонокEXCEL + "<" + НомерПоследнейК);
ДополнитьМассивИменКолонокXLSX(НомерПоследнейК, МассивИменКолонокXLSX);
ИначеЕсли МассивИменКолонокXLSX.Количество() > НомерПоследнейК Тогда
Сообщить("Строка заголовков колонок (1-я строка) содержит больше колонок, чем объявлено:
|К-во колонок строки больше объявленной:" + КолвоКолонокEXCEL + ">" + НомерПоследнейК);
КонецЕсли;
КонецЕсли;
Возврат МассивИменКолонокXLSX;
КонецФункции
&НаСервере
Функция НомерКолонкиДесятичный(ИмяКолонкиXLSX)
Перем Латиница, ДлинаНомера, Поз, ит;
Перем НомерКолонки;
Латиница = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 26.
ДлинаНомера = СтрДлина(ИмяКолонкиXLSX);
НомерКолонки = 0;
Для ит = 1 ПО ДлинаНомера Цикл
Поз = Найти(Латиница, Сред(ИмяКолонкиXLSX, (ДлинаНомера + 1 - ит), 1));
НомерКолонки = НомерКолонки + Поз * Pow(26, ит - 1);
КонецЦикла;
Возврат НомерКолонки;
КонецФункции
&НаСервере
Процедура ДополнитьМассивИменКолонокXLSX(Знач КолвоКолонокExcel, МассивИменКолонокXLSX, Индекс = - 1)
Перем Алфавит, ит, Буква;
Алфавит = РазложитьСтрокуВМассивПодстрок("A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z", ",");
Если МассивИменКолонокXLSX.Количество() >= КолвоКолонокExcel Тогда
Возврат;
КонецЕсли;
Если Индекс > Алфавит.Количество() - 1 Тогда
Возврат;
КонецЕсли;
Буква = ?(Индекс = -1, "", Алфавит[Индекс]);
Для ит = 0 ПО Алфавит.Количество() - 1 Цикл
Если МассивИменКолонокXLSX.Найти(Буква + Алфавит[ит]) = Неопределено Тогда
МассивИменКолонокXLSX.Добавить(Буква + Алфавит[ит]);
КонецЕсли;
Если МассивИменКолонокXLSX.Количество() >= КолвоКолонокExcel Тогда
Возврат;
КонецЕсли;
КонецЦикла;
Если МассивИменКолонокXLSX.Количество() > КолвоКолонокExcel Тогда
Возврат;
Иначе
Индекс = Индекс + 1;
ДополнитьМассивИменКолонокXLSX(КолвоКолонокExcel, МассивИменКолонокXLSX, Индекс);
КонецЕсли;
Возврат;
КонецПроцедуры
&НаСервере
Функция ИзвлечьДанныеИзФайла_StylesXML(ZIPКаталог, СоответствиеNumFmtIdFormatCode)
Перем Файл, ДокументDOM, ЭлементыNnumFmts, ЭлементыCellXfs, ЭлементыКоллекции, Элемент;
Перем МассивNumFmtId, ит;
МассивNumFmtId = Новый Массив;
СоответствиеNumFmtIdFormatCode = Новый Соответствие;
Файл = Новый Файл(ZIPКаталог + "XL\Styles.xml");
Если НЕ Файл.Существует() Тогда
Возврат Новый Массив;
КонецЕсли;
ДокументDOM = ПолучитьДокументDOM_NativeXLSX(Файл.ПолноеИмя);
Попытка
ЭлементыNnumFmts = ДокументDOM.ПолучитьЭлементыПоИмени("numFmts");
Исключение
Сообщить(ОписаниеОшибки());
Возврат Новый Массив;
КонецПопытки;
Если ЭлементыNnumFmts.Количество() > 0 Тогда
ЭлементыКоллекции = ЭлементыNnumFmts[0].ДочерниеУзлы;
Для Каждого Элемент ИЗ ЭлементыКоллекции Цикл
СоответствиеNumFmtIdFormatCode.Вставить(ЗначениеАтрибутаУзлаDOM(Элемент, "numFmtId"), ВРег(ЗначениеАтрибутаУзлаDOM(Элемент, "formatCode")));
КонецЦикла;
КонецЕсли;
Попытка
ЭлементыCellXfs = ДокументDOM.ПолучитьЭлементыПоИмени("cellXfs");
Исключение
Сообщить(ОписаниеОшибки());
Возврат Новый Массив;
КонецПопытки;
Если ЭлементыCellXfs.Количество() > 0 Тогда
ЭлементыКоллекции = ЭлементыCellXfs[0].ДочерниеУзлы;
Для Каждого Элемент ИЗ ЭлементыКоллекции Цикл
МассивNumFmtId.Добавить(ЗначениеАтрибутаУзлаDOM(Элемент, "numFmtId"));
КонецЦикла;
КонецЕсли;
// Проверка сопоставления кодов массива и соответствия.
// MS Office может не формировать в xml-файле описание стиля форматирования для ячейки.
// LibreOffice формирует в xml-файле необходимые описания стиля форматирования ячейки.
Для Каждого ит ИЗ МассивNumFmtId Цикл
Если СоответствиеNumFmtIdFormatCode.Получить(ит) = Неопределено Тогда
Если ит = "0" Тогда // Стандарт для числа (Целое число).
СоответствиеNumFmtIdFormatCode.Вставить(ит, "GENERAL");
ИначеЕсли ит = "9" ИЛИ ит = "10" Тогда // Форматы для % ("0%", "0.00%").
СоответствиеNumFmtIdFormatCode.Вставить(ит, "0%");
ИначеЕсли ит = "14" ИЛИ ит = "16" Тогда // Форматы для даты.
СоответствиеNumFmtIdFormatCode.Вставить(ит, "DD.MM.YYYY");
ИначеЕсли ит = "45" ИЛИ ит = "46" ИЛИ ит = "47" Тогда // Форматы для времени.
СоответствиеNumFmtIdFormatCode.Вставить(ит, "HH:MM:SS");
КонецЕсли;
КонецЕсли;
КонецЦикла;
Возврат МассивNumFmtId;
КонецФункции
&НаСервере
Функция ИзвлечьДанныеИзФайла_SharedStringsXML(ZIPКаталог)
Перем Файл, ДокументDOM, ЭлементыSharedStrings, ЭлементыКоллекции, Элемент;
Перем МассивSharedStrings;
// Если в файле EXCEL не содержится значений, имеющих тип "СТРОКА", то файл "SharedStrings.xml" не формируется.
МассивSharedStrings = Новый Массив;
Файл = Новый Файл(ZIPКаталог + "XL\SharedStrings.xml");
Если НЕ Файл.Существует() Тогда
Возврат Новый Массив;
КонецЕсли;
ДокументDOM = ПолучитьДокументDOM_NativeXLSX(Файл.ПолноеИмя);
Попытка
ЭлементыSharedStrings = ДокументDOM.ПолучитьЭлементыПоИмени("sst");
Исключение
Сообщить(ОписаниеОшибки());
Возврат Новый Массив;
КонецПопытки;
Если ЭлементыSharedStrings.Количество() > 0 Тогда
ЭлементыКоллекции = ЭлементыSharedStrings[0].ДочерниеУзлы;
Для Каждого Элемент ИЗ ЭлементыКоллекции Цикл
МассивSharedStrings.Добавить(Элемент.ТекстовоеСодержимое);
КонецЦикла;
КонецЕсли;
Возврат МассивSharedStrings;
КонецФункции
&НаСервере
Функция ИзвлечьДанныеИзФайла_DrawingXML(Знач ZIPКаталог, Знач ФайлНомерЛиста, Знач КаталогMEDIA)
Перем Файл, ДокументRELS, ЭлементыRELS, НомерDRAWING, ДокументDRAWING, ЭлементыDRAWING, Атрибут, ФайлDRAWING;
Перем ЭлементыКоллекции1, Элемент1, ЭлементыКоллекции2, Элемент2, ЭлементыКоллекции3, Элемент3;
Перем ТаблицаIMAGE, НоваяСтрока, СтруктураПараметров, НайденныеСтрокиТЗ, СтрокаТЗ;
// Если в файле EXCEL не содержится значений, имеющих тип "ИЗОБРАЖЕНИЕ", то файл "drawing...xml" не формируется.
ТаблицаIMAGE = Новый ТаблицаЗначений;
ТаблицаIMAGE.Колонки.Добавить("ID");
ТаблицаIMAGE.Колонки.Добавить("rID");
ТаблицаIMAGE.Колонки.Добавить("NAME");
ТаблицаIMAGE.Колонки.Добавить("ROW");
ТаблицаIMAGE.Колонки.Добавить("COL");
ТаблицаIMAGE.Колонки.Добавить("FILE");
Файл = Новый Файл(ZIPКаталог + "XL\WorkSheets\_rels\sheet" + ФайлНомерЛиста + ".xml.rels");
Если НЕ Файл.Существует() Тогда
Возврат Новый ТаблицаЗначений;
КонецЕсли;
ДокументRELS = ПолучитьДокументDOM_NativeXLSX(Файл.ПолноеИмя);
Попытка
ЭлементыRELS = ДокументRELS.ПолучитьЭлементыПоИмени("Relationships");
Исключение
Сообщить(ОписаниеОшибки());
Возврат Новый ТаблицаЗначений;
КонецПопытки;
НомерDRAWING = "/Неопределено/";
Если ЭлементыRELS.Количество() > 0 Тогда
Элемент1 = ЭлементыRELS[0].ПервыйДочерний;
НомерDRAWING = ЗначениеАтрибутаУзлаDOM(Элемент1, "Target"); // ="../drawings/drawing2.xml".
Если Найти(ВРег(НомерDRAWING), ".XML") > 0 Тогда
НомерDRAWING = СтрЗаменить(НомерDRAWING, "../drawings/drawing", "");
НомерDRAWING = СтрЗаменить(НомерDRAWING, ".xml", "");
Попытка
НомерDRAWING = Число(НомерDRAWING);
Исключение
Сообщить("Не удалось получить номер файла Drawing.xml");
КонецПопытки;
КонецЕсли;
КонецЕсли;
Файл = Новый Файл(ZIPКаталог + "XL\Drawings\Drawing" + НомерDRAWING + ".xml");
Если НЕ Файл.Существует() Тогда
Возврат Новый ТаблицаЗначений;
КонецЕсли;
ДокументDRAWING = ПолучитьДокументDOM_NativeXLSX(Файл.ПолноеИмя);
Попытка
ЭлементыDRAWING = ДокументDRAWING.ПолучитьЭлементыПоИмени("wsDr");
Исключение
Сообщить(ОписаниеОшибки());
Возврат Новый ТаблицаЗначений;
КонецПопытки;
Если ЭлементыDRAWING.Количество() > 0 Тогда
ЭлементыКоллекции1 = ЭлементыDRAWING[0].ДочерниеУзлы;
Для Каждого Элемент1 ИЗ ЭлементыКоллекции1 Цикл
НоваяСтрока = ТаблицаIMAGE.Добавить();
ЭлементыКоллекции2 = Элемент1.ДочерниеУзлы;
Для Каждого Элемент2 ИЗ ЭлементыКоллекции2 Цикл
ЭлементыКоллекции3 = Элемент2.ДочерниеУзлы;
Для Каждого Элемент3 ИЗ ЭлементыКоллекции3 Цикл
Если Элемент2.ИмяУзла = "xdr:from" Тогда
Если Элемент3.ИмяУзла = "xdr:row" Тогда
НоваяСтрока.ROW = Число(Элемент3.ТекстовоеСодержимое)+1;
КонецЕсли;
Если Элемент3.ИмяУзла = "xdr:col" Тогда
НоваяСтрока.COL = Число(Элемент3.ТекстовоеСодержимое)+1;
КонецЕсли;
КонецЕсли;
Если Элемент2.ИмяУзла = "xdr:pic" Тогда
Если Элемент3.ИмяУзла = "xdr:nvPicPr" И Элемент3.ПервыйДочерний.ИмяУзла = "xdr:cNvPr" Тогда
НоваяСтрока.ID = ЗначениеАтрибутаУзлаDOM(Элемент3.ПервыйДочерний, "id");
НоваяСтрока.NAME = ЗначениеАтрибутаУзлаDOM(Элемент3.ПервыйДочерний, "name");
КонецЕсли;
Если Элемент3.ИмяУзла = "xdr:blipFill" И Элемент3.ПервыйДочерний.ИмяУзла = "a:blip" Тогда
НоваяСтрока.rID = ЗначениеАтрибутаУзлаDOM(Элемент3.ПервыйДочерний, "r:embed");
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
КонецЕсли;
Файл = Новый Файл(ZIPКаталог + "XL\Drawings\_rels\Drawing" + НомерDRAWING + ".xml.rels");
Если НЕ Файл.Существует() Тогда
Возврат Новый ТаблицаЗначений;
КонецЕсли;
ДокументRELS = ПолучитьДокументDOM_NativeXLSX(Файл.ПолноеИмя);
Попытка
ЭлементыRELS = ДокументRELS.ПолучитьЭлементыПоИмени("Relationships");
Исключение
Сообщить(ОписаниеОшибки());
Возврат Новый ТаблицаЗначений;
КонецПопытки;
Если ЭлементыRELS.Количество() > 0 Тогда
ЭлементыКоллекции1 = ЭлементыRELS[0].ДочерниеУзлы;
Для Каждого Элемент1 ИЗ ЭлементыКоллекции1 Цикл
Атрибут = ЗначениеАтрибутаУзлаDOM(Элемент1, "Id");
ФайлDRAWING = ЗначениеАтрибутаУзлаDOM(Элемент1, "Target");
СтруктураПараметров = Новый Структура("rID", Атрибут);
НайденныеСтрокиТЗ = ТаблицаIMAGE.НайтиСтроки(СтруктураПараметров);
Если Найти(ФайлDRAWING, "../"+КаталогMEDIA+"/") > 0 Тогда
Для Каждого СтрокаТЗ ИЗ НайденныеСтрокиТЗ Цикл
СтрокаТЗ.File = СтрЗаменить(ФайлDRAWING, "../"+КаталогMEDIA+"/", "");
КонецЦикла;
Иначе
Сообщить("Преобразование имени файла не произведено: " + ФайлDRAWING);
КонецЕсли;
КонецЦикла;
КонецЕсли;
Возврат ТаблицаIMAGE;
КонецФункции
&НаСервере
Функция ЭтоЧислоXLSX(Знач ЗначениеЯчейки, Знач ФорматСтиля)
Если ( ВРег(ФорматСтиля) = "GENERAL"
ИЛИ ВРег(ФорматСтиля) = "STANDARD"
ИЛИ Найти(ФорматСтиля, "0") > 0 )
Тогда
Возврат Истина;
КонецЕсли;
Возврат Ложь;
КонецФункции
&НаСервере
Функция ЭтоПроцентXLSX(Знач ЗначениеЯчейки, Знач ФорматСтиля)
Если ( Найти(ФорматСтиля, "%") > 0 )
Тогда
Возврат Истина;
КонецЕсли;
Возврат Ложь;
КонецФункции
&НаСервере
Функция ЭтоБулевоXLSX(ЗначениеЯчейки, ФорматСтиля)
Если (ЗначениеЯчейки = 0 ИЛИ ЗначениеЯчейки = 1)
И Найти(ВРег(ФорматСтиля), "ИСТИНА") > 0 ИЛИ Найти(ВРег(ФорматСтиля), "ЛОЖЬ") > 0
ИЛИ Найти(ВРег(ФорматСтиля), "TRUE") > 0 ИЛИ Найти(ВРег(ФорматСтиля), "FALSE") > 0
Тогда
Возврат Истина;
КонецЕсли;
Возврат Ложь;
КонецФункции
&НаСервере
Функция ЭтоДатаXLSX(Знач ЗначениеЯчейки, Знач ФорматСтиля)
Если ЗначениеЯчейки = Цел(ЗначениеЯчейки) И
( Найти(ФорматСтиля, "DD") > 0
ИЛИ Найти(ФорматСтиля, "MM") > 0
ИЛИ Найти(ФорматСтиля, "YY") > 0
ИЛИ Найти(ФорматСтиля, "QQ") > 0
ИЛИ Найти(ФорматСтиля, "WW") > 0 )
Тогда
Возврат Истина;
КонецЕсли;
Возврат Ложь;
КонецФункции
&НаСервере
Функция ЭтоВремяXLSX(Знач ЗначениеЯчейки, Знач ФорматСтиля)
Если ЗначениеЯчейки < 1 И
( Найти(ФорматСтиля, "HH:") > 0
ИЛИ Найти(ФорматСтиля, "[HH]:") > 0
ИЛИ Найти(ФорматСтиля, "[H]:") > 0
ИЛИ Найти(ФорматСтиля, "MM:") > 0
ИЛИ Найти(ФорматСтиля, ":SS") > 0 )
Тогда
Возврат Истина;
КонецЕсли;
Возврат Ложь;
КонецФункции
&НаСервере
Функция КонвертироватьЧислоXLSXвДату(Знач Число)
Перем Дата1900, Разница, ДатаРезультат;
Дата1900 = Дата("19000101");
Разница = Число - 2; // EXCEL ошибочно считает 1900-й год високосным.
Разница = ?(Разница < 0, 0, Разница);
ДатаРезультат = Дата1900 + Разница * 24 * 60 * 60;
Возврат ДатаРезультат;
КонецФункции
&НаСервере
Функция КонвертироватьЧислоXLSXвДатуВремя(Знач Число)
Перем КВоСекунд;
Перем ВремяРезультат;
// 0,0000115740740740741 = 1 сек.
// 1 = 24 часа 00 мин 00 сек.
Если ТипЗнч(Число) = Тип("Число") Тогда
КВоСекунд = Число * 100000 / 1.15740740740741;
КВоСекунд = Окр(КВоСекунд);
Иначе
Сообщить("Ошибка определения значения типа ""Время"".");
КонецЕсли;
ВремяРезультат = Дата("19000101000000") + КВоСекунд;
Возврат ВремяРезультат;
КонецФункции
&НаСервере
Функция ПолучитьЧислоВСтепениИзСтроковогоЗначения(Знач ЗначениеЯчейки)
Перем Поз1Е, Степень;
Перем Значение;
Если Найти(ЗначениеЯчейки, "E-") > 0 Тогда
Поз1Е = Найти(ЗначениеЯчейки, "E-");
Степень = Сред(ЗначениеЯчейки, Поз1Е+2);
Значение = Лев(ЗначениеЯчейки, Поз1Е-1);
Попытка
Степень = Число(Степень);
Значение = Число(Значение) / Pow(10, Степень);
Исключение
Значение = ЗначениеЯчейки;
КонецПопытки;
ИначеЕсли Найти(ЗначениеЯчейки, "E+") > 0 Тогда
Поз1Е = Найти(ЗначениеЯчейки, "E+");
Степень = Сред(ЗначениеЯчейки, Поз1Е+2);
Значение = Лев(ЗначениеЯчейки, Поз1Е-1);
Попытка
Степень = Число(Степень);
Значение = Число(Значение) * Pow(10, Степень);
Исключение
Значение = ЗначениеЯчейки;
КонецПопытки;
Иначе
Значение = ЗначениеЯчейки;
КонецЕсли;
Возврат Значение;
КонецФункции
// Выполняет замену символов в строке.
//
// Параметры:
// ЗаменяемыеСимволы - Строка - строка символов, каждый из которых требует замены;
// Строка - Строка - исходная строка, в которой требуется замена символов;
// СимволыЗамены - Строка - строка символов, на каждый из которых нужно заменить символы параметра ЗаменяемыеСимволы.
//
// Возвращаемое значение:
// Строка - строка после замены символов.
//
// Примечание: функция предназначена для простых случаев, например, для замены латиницы на похожие кириллические символы.
// Функция не анализирует повторную замену символов, поэтому такой вызов:
// ЗаменитьОдниСимволыДругими("кр", "карета", "гз") вернёт слово "газета", а
// ЗаменитьОдниСимволыДругими("кр", "карета", "рк") не вернёт слово "ракета".
//
&НаСервере
Функция ЗаменитьОдниСимволыДругими(ЗаменяемыеСимволы, Строка, СимволыЗамены)
Перем Результат, НомерСимвола;
Результат = Строка;
Для НомерСимвола = 1 ПО СтрДлина(ЗаменяемыеСимволы) Цикл
Результат = СтрЗаменить(Результат, Сред(ЗаменяемыеСимволы, НомерСимвола, 1), Сред(СимволыЗамены, НомерСимвола, 1));
КонецЦикла;
Возврат Результат;
КонецФункции
// Функция "расщепляет" строку на подстроки, используя заданный
// разделитель. Разделитель может иметь любую длину.
// Если в качестве разделителя задан пробел, рядом стоящие пробелы
// считаются одним разделителем, а ведущие и хвостовые пробелы параметра Стр
// игнорируются.
// Например,
// РазложитьСтрокуВМассивПодстрок(",один,,,два", ",") возвратит массив значений из пяти элементов,
// три из которых - пустые строки, а
// РазложитьСтрокуВМассивПодстрок(" один два", " ") возвратит массив значений из двух элементов
//
// Параметры:
// Стр - строка, которую необходимо разложить на подстроки.
// Параметр передается по значению.
// Разделитель - строка-разделитель, по умолчанию - запятая.
//
// Возвращаемое значение:
// массив значений, элементы которого - подстроки
//
&НаСервере
Функция РазложитьСтрокуВМассивПодстрок(Знач Стр, Разделитель = ",")
МассивСтрок = Новый Массив();
Если Разделитель = " " Тогда
Стр = СокрЛП(Стр);
Пока 1 = 1 Цикл
Поз = Найти(Стр, Разделитель);
Если Поз = 0 Тогда
МассивСтрок.Добавить(СокрЛП(Стр));
Возврат МассивСтрок;
КонецЕсли;
МассивСтрок.Добавить(СокрЛП(Лев(Стр, Поз - 1)));
Стр = СокрЛ(Сред(Стр, Поз));
КонецЦикла;
Иначе
ДлинаРазделителя = СтрДлина(Разделитель);
Пока 1 = 1 Цикл
Поз = Найти(Стр, Разделитель);
Если Поз = 0 Тогда
Если (СокрЛП(Стр) <> "") Тогда
МассивСтрок.Добавить(СокрЛП(Стр));
КонецЕсли;
Возврат МассивСтрок;
КонецЕсли;
МассивСтрок.Добавить(СокрЛП(Лев(Стр,Поз - 1)));
Стр = Сред(Стр, Поз + ДлинаРазделителя);
КонецЦикла;
КонецЕсли;
КонецФункции
&НаСервере
Процедура УдалитьКолонкиСНулевойШириной(ТаблицаРезультат)
Перем МассивПустыхКолонок;
// Найдем пустые колонки.
МассивПустыхКолонок = Новый Массив;
Для Каждого Колонка ИЗ ТаблицаРезультат.Колонки Цикл
Если Колонка.Ширина = 0 Тогда
МассивПустыхКолонок.Добавить(Колонка.Имя);
КонецЕсли;
КонецЦикла;
// Удалим пустые колонки.
Для Каждого ПустаяКолонка ИЗ МассивПустыхКолонок Цикл
ТаблицаРезультат.Колонки.Удалить(ПустаяКолонка);
КонецЦикла;
КонецПроцедуры
С уважением к сообществу МА!