Pull to refresh

1С, Linux, Excel, Word, OpenXML,ADO и Net Core

Reading time 7 min
Views 9.2K
Доброго времени суток хабратчане! Сегодня я продолжу мучить вас великим и могучим Руслишем. Это продолжение статей:

» Разработка → Кроссплатформенное использование классов .Net из неуправляемого кода. Или аналог IDispatch на Linux
» Разработка → Кроссплатформенное использование классов .Net в 1С через Native ВК. Или замена COM на Linux
» Кроссплатформенное использование классов .Net в 1С через Native ВК. Или замена COM на Linux II
» Асинхронное программирование в 1С через .Net Native ВК

С того времени я добавил использование расширений Linq. В этой статье я коснусь практического использования моей компоненты. А именно кроссплатформенной работой с файлами Excel и Word c помощью OpenXML и NetStandart.

Собственно ради чего эта разработка и задумывалась. Исходники были взяты отсюда. К сожалению без Nuget подключить библиотеку к проекту нельзя. Но через CoreClr его можно подключить. Справочную информацию по работе с OpenXML можно посмотреть здесь.
Как мне… (Open XML SDK)

Итак начнем с чтения страниц Excel. Задача преобразовать данные в ТаблицуЗначений.

Процедура ПрочитатьExcel(ИмяФайла)
	
	// Загрузим сборку 
	СборкаOpenXml=ъ(Врап.Сборка("DocumentFormat.OpenXml.dll"));
	
	// Получим типы
	SpreadsheetDocument=ъ(СборкаOpenXml.GetType("DocumentFormat.OpenXml.Packaging.SpreadsheetDocument"));
	SharedStringTablePart=ъ(СборкаOpenXml.GetType("DocumentFormat.OpenXml.Packaging.SharedStringTablePart"));
	
	CellValues=ъ(СборкаOpenXml.GetType("DocumentFormat.OpenXml.Spreadsheet.CellValues"));
	
	SharedString=ъ(CellValues.SharedString);
	Cell=ъ(СборкаOpenXml.GetType("DocumentFormat.OpenXml.Spreadsheet.Cell"));
	Sheet=ъ(СборкаOpenXml.GetType("DocumentFormat.OpenXml.Spreadsheet.Sheet"));
	Regex=ъТип("System.Text.RegularExpressions.Regex","System.Text.RegularExpressions",истина);
	
	// Вот чего не хватает в 1С так это  Regex
	ШаблонКолонки=ъ(Врап.Новый(Regex.ПолучитьСсылку(),"[A-Za-z]+"));
	ШаблонСтроки=ъ(Врап.Новый(Regex.ПолучитьСсылку(),"\d+"));
	
	// Откроем файл Excel
	doc = ъ(SpreadsheetDocument.Open(ИмяФайла, false));
	workbookPart = ъ(doc.WorkbookPart);
	
	// Строки хранятся отдельно
	// В ячейках хранится индекс
	pt=ъ(ъ(workbookPart.in(SharedStringTablePart.ПолучитьСсылку())).GetPartsOfType());
	sstpart = ъ(pt.First());
	sst = ъ(sstpart.SharedStringTable);
	ОбщиеСтроки=ъ(sst.ChildElements);
	
	workbook = ъ(workbookPart.Workbook);
	
	// Получим список страниц
	sheets = ъ(ъ(workbook.in(Sheet.ПолучитьСсылку())).Descendants());
	sheets=ъ(Врап.ПолучитьЭнумератор(sheets.ПолучитьСсылку()));
	
	
	Пока sheets.MoveNext() Цикл
		sheet= ъ(sheets.Current);
		id=ъ(sheet.Id).Value;
		ИмяСтраницы=ъ(sheet.Name).Value;
		Сообщить(ИмяСтраницы);
		worksheetPart = ъ(workbookPart.GetPartById(id));
		
		Worksheet=ъ(worksheetPart.Worksheet);
		// Так как данные хранятся только те которые имеют значения 
		// Получим тз с колонками ИмяЯчейки,ИмяКолонки,НомСтроки,ЗначениеЯчейки
		//Где  ИмяЯчейки= A1, ИмяКолонки=A,НомСтроки=1;
		Тз=ЗаписатьСтраницуВТЗ(Worksheet,ОбщиеСтроки);		// через Найти  используя ИмяЯчейки или
		// НайтиСтроки используя ИмяКолонки и  НомСтроки
		
		// Получим таблицу с колонками A,B,D..AA,AB..
		Тз=ПреобразоватьВТаблицу(Тз);
		Тз.ВыбратьСтроку(ИмяСтраницы);
		
	КонецЦикла
	
КонецПроцедуры

Рассмотрим более подробно методы для получения данных о ячейках таблицы

Функция ИмяКолонки(ИмяЯчейки)
	//Получим Имя колнки ячейки
	match = ъ(ШаблонКолонки.Match(ИмяЯчейки));
	return match.Value;
	
КонецФункции
		// Можно можно эту тз проиндексировать и получать доступ к ячейкам


Функция НомерСтроки(ИмяЯчейки)
	// Получим Номер строки ячейки
	match = ъ(ШаблонСтроки.Match(ИмяЯчейки));
	return число(match.Value);
КонецФункции

Процедура ЗаписатьДанныеЯчейки(знач Тз,знач Ячейка,знач ОбщиеСтроки)
	Перем ТипДанных;
	
	// Получим адрес ячейки и Тип данных
	адрес=ъ(Ячейка.CellReference).InnerText;
	ТипДанных=Ячейка.DataType;
	ФлЕстьДанные=ложь;				
	Если ТипДанных<> null Тогда
		ТипДанных=ъ(ъ(ТипДанных).Value);
		// Проверим тип данных на Строку
		Если ТипДанных.Equals(SharedString.ПолучитьСсылку()) Тогда
			//В CellValue хранится индек строки в таблице общих строк
			CellValue=ъ(ячейка.CellValue);
			Text=CellValue.Text;
			ssid = Число(Text);
			ChildElement=ъ(ОбщиеСтроки.get_Item(ssid));
			ЗначениеЯчейки = ChildElement.InnerText;
			ФлЕстьДанные=истина;
		КонецЕсли;
	КонецЕсли;
	
	// Если это не строка то получим, то получим значение из  CellValue
	Если не  ФлЕстьДанные Тогда
		ЗначениеЯчейки= ячейка.CellValue;
		Если (ЗначениеЯчейки<> null) Тогда
			ЗначениеЯчейки=ъ(ЗначениеЯчейки);
			ЗначениеЯчейки=ЗначениеЯчейки.Text;
			ФлЕстьДанные=истина;
		КонецЕсли;
		
	КонецЕсли;
	
	Если   ФлЕстьДанные Тогда
		Стр=Тз.Добавить();
		Стр.ИмяЯчейки=адрес;
		Стр.ИмяКолонки=ИмяКолонки(адрес);
		Стр.НомСтроки =НомерСтроки(адрес);
		Стр.ЗначениеЯчейки=ЗначениеЯчейки;
	КонецЕсли;
	
КонецПроцедуры
Функция СоздатьТз()
	// Так как данные хранятся не ввиде таблицы
	// То создадим ТЗ с данными по ячейкам
	ТипЧисла = Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(10,0,ДопустимыйЗнак.Неотрицательный)); 
	Тз=новый ТаблицаЗначений;
	Колонки=Тз.Колонки;
	Колонки.Добавить("ИмяЯчейки",ОписаниеСтроки());
	Колонки.Добавить("ИмяКолонки",ОписаниеСтроки());
	Колонки.Добавить("НомСтроки",ТипЧисла);
	Колонки.Добавить("ЗначениеЯчейки",ОписаниеСтроки());
	
	возврат тз;
КонецФункции

Функция ЗаписатьСтраницуВТЗ(знач sheet,Знач ОбщиеСтроки)
	
	// Прочитаем все ячейки страницы
	cells = ъ(ъ(sheet.in(Cell.ПолучитьСсылку())).Descendants());
	cells=ъ(Врап.ПолучитьЭнумератор(cells.ПолучитьСсылку()));
	
	Тз= СоздатьТз();
	// Запишем данные ячейки в ТЗ
	Пока cells.MoveNext() Цикл
		Ячейка=ъ(cells.Current);
		ЗаписатьДанныеЯчейки(Тз,Ячейка,ОбщиеСтроки)		
	КонецЦикла;
	
	возврат Тз;
КонецФункции

Теперь нам нужно преобразовать ТЗ с данными о ячейках в Таблицу Здначений аналогичной Странице Excel

Функция ЗаписатьКолонки(Колонки,НачСтр,Разряд,КоличествоРазрядов,Сравнивать,ПоследняяКолонка)
// Процедура создает колонки которые меньше или равны имени последней колоки
// A,B,..,AA..ABC
	Для сч=КодСимвола("A") по  КодСимвола("Z") Цикл
		НовСтр=НачСтр+Символ(сч);
		
		Если  Разряд<КоличествоРазрядов Тогда
			рез= ЗаписатьКолонки(Колонки,НовСтр,Разряд+1,КоличествоРазрядов,Сравнивать,ПоследняяКолонка);
			Если Сравнивать и Рез Тогда
				возврат истина
			КонецЕсли;	
		Иначе
			Колонки.Добавить(НовСтр,ОписаниеСтроки());
			
			Если Сравнивать и НовСтр=ПоследняяКолонка Тогда
				возврат  истина
			КонецЕсли
		КонецЕсли;
	КонецЦикла;
	
	
	возврат ложь;
КонецФункции

Процедура СоздатьКолонки(Колонки,ПоследняяКолонка)
	
	// Создадим колонки учитывая разряды
	// Например если имя последней колоки ABC то колонки идут по разрядно
	//A..Z
	//AA..ZZ
	//AAA..ABC
	КоличествоРазрядов=СтрДлина(ПоследняяКолонка);
	
	Для сч=1 По  КоличествоРазрядов Цикл
		Сравнивать=сч=КоличествоРазрядов;
		рез= ЗаписатьКолонки(Колонки,"",1,сч,Сравнивать,ПоследняяКолонка);
		Если Сравнивать и рез Тогда
			возврат;
		КонецЕсли;	
	КонецЦикла;	
КонецПроцедуры

Функция НайтиИмяПоследнейКолонки(Тз)
	рез="";
	ДлинаРез=0;
	Для каждого стрТз из Тз Цикл 
		Стр=стрТз.ИмяКолонки;
		СтрДл=СтрДлина(стр);
		
		Если СтрДл>ДлинаРез Тогда
			ДлинаРез=СтрДл;
			рез=Стр;
		ИначеЕсли СтрДл=ДлинаРез и  Стр>рез Тогда
			рез=Стр;
		КонецЕсли;	
	КонецЦикла;
	возврат рез;
КонецФункции

Функция ПреобразоватьВТаблицу(Тз)
	рез=новый ТаблицаЗначений;
	ПоследняяКолонка=НайтиИмяПоследнейКолонки(Тз);
	СоздатьКолонки(рез.Колонки,ПоследняяКолонка);
	Колонки=рез.Колонки;
	// Часто исползую данную функцию
	// Код можно посмотреть здесь  http://infostart.ru/public/371762/
	// Сгруппируем данные ТЗ по номеру строки
	Тз=глСгруппироватьТзПоПолю(тз,"НомСтроки");
	сч=1;
	
	Для каждого стрТз из Тз Цикл 
		НомСтроки=стрТз.НомСтроки;
           // Добавим строки номера которых меньше НомСтроки
		Пока сч<НомСтроки Цикл
			сч=сч+1;
			рез.Добавить();
		КонецЦикла;
                сч=сч+1;
		стр=рез.Добавить();
		ТзГр=стрТз.ТзПоГруппе;
		
		
		Для каждого стрТзГр из ТзГр Цикл
			// Можно конечно получить индекс зная смещение символа 64 относительно 1 и 26 разрядную систему
			// но найдем колонку по имени и её индекс
			Колонка=Колонки.Найти(стрТзГр.ИмяКолонки);
			стр.Установить(Колонки.Индекс(Колонка),стрТзГр.ЗначениеЯчейки); 
		КонецЦикла;	
	КонецЦикла;
	возврат рез;
КонецФункции

Теперь перейдем к чтению данных файла Word.

Функция GetPlainText(знач Элемент)
	ЗаписьXML = Новый ЗаписьXML;
	ЗаписьXML.УстановитьСтроку();

	// Получим секции и рекурсивно пройдемся по их значениям
	Секции=ъ(Элемент.Elements());         
	
	Секции=ъ(Врап.ПолучитьЭнумератор(Секции.ПолучитьСсылку()));
	
	Пока Секции.MoveNext() Цикл
		Секция= ъ(Секции.Current);
		ИмяСекции=Секция.LocalName;
		Если ИмяСекции= "t" Тогда
			Стр=Секция.InnerText;
			ЗаписьXML.ЗаписатьБезОбработки(Стр);
			
		ИначеЕсли    ИмяСекции= "cr" или ИмяСекции= "br"  Тогда
			ЗаписьXML.ЗаписатьБезОбработки(NewLine); 
		ИначеЕсли    ИмяСекции= "tab"  Тогда
			ЗаписьXML.ЗаписатьБезОбработки(Таб); 
			// Paragraph
		ИначеЕсли    ИмяСекции= "p"  Тогда
			ЗаписьXML.ЗаписатьБезОбработки(GetPlainText(Секция));
			ЗаписьXML.ЗаписатьБезОбработки(NewLine+NewLine);
		Иначе
			ЗаписьXML.ЗаписатьБезОбработки(GetPlainText(Секция));
		КонецЕсли;            
	КонецЦикла;
	
	возврат  ЗаписьXML.Закрыть();
КонецФункции

Функция ПрочитатьWord(ИмяФайла)
    СборкаOpenXml=ъ(Врап.Сборка("DocumentFormat.OpenXml.dll"));
	WordprocessingDocument=ъ(СборкаOpenXml.GetType("DocumentFormat.OpenXml.Packaging.WordprocessingDocument"));
	Пакет = ъ(WordprocessingDocument.Open(ИмяФайла, ложь));
	Элемент = ъ(ъ(ъ(Пакет.MainDocumentPart).Document).Body);
	if (Элемент = null) Тогда
		возврат ""
	КонецЕсли;
	
	возврат GetPlainText(Элемент);
КонецФункции // 


Очень удобно использовать Productivity Tool", она умеет генерировать код Генерируем OfficeOpenXML-документы за 5 минут

Кроме того есть множество провайдеров к различным базам данных, как MS SQL так и другим, в том числе NoSQL

Приведу пример доступа к MS SQL


   СборкаSqlClient=ъ(Врап.Сборка("System.Data.SqlClient.dll"));
    SqlConnection=ъ(СборкаSqlClient.GetType("System.Data.SqlClient.SqlConnection"));
    SqlCommand=ъ(СборкаSqlClient.GetType("System.Data.SqlClient.SqlCommand")); 
    
    connection =ъ(Врап.Новый(SqlConnection.ПолучитьСсылку(),ConnectionString));
    connection.Open();
    
    ТекстЗапроса = "Select Номенклатура.DESCR Наименование  From sc84 Номенклатура where DESCR Like '%'+@Строка+'%'
    |order by Номенклатура.DESCR";
    
    
    command = ъ(Врап.Новый(SqlCommand.ПолучитьСсылку(),ТекстЗапроса,connection.ПолучитьСсылку()));
    
    Parameters=ъ(command.Parameters);
    Parameters.AddWithValue("@Строка", "ДСП");
    dr = ъ(command.ExecuteReader());
    
    Пока dr.Read() Цикл
        Сообщить(dr.get_Item("Наименование"));
    КонецЦикла;



При этом можно сделать обертку DynamicObject над SqlDataReader и использовать так

 Пока dr.Read() Цикл
        Сообщить(dr.Наименование);
    КонецЦикла;




В своих статьях я хочу донести прежде всего до 1С, что есть кроссплатформенная замена COM с помощью NetStadart. Но к моему большому сожалению пока данный подход никого не интересует. Привлекает внимание только Руслиш. Если у кого будут идеи чем можно привлечь внимание к замене COM пишите. Буду только рад.

Примеры и исходники можно скачать здесь.
Tags:
Hubs:
+3
Comments 17
Comments Comments 17

Articles