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

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

    » Разработка → Кроссплатформенное использование классов .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 пишите. Буду только рад.

    Примеры и исходники можно скачать здесь.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 17
    • +3
      фу, какашка…
      • +1
        Простите, этот код правда работает?
        if (Элемент = null) Тогда
        		возврат ""
        	КонецЕсли;
        
        • +1
          Да Native API возвращает Null а не неопределено
          • 0
            Даже так)) Я немного про другое. Видимо Руслиш на столько велик и могуч, что позволяет писать часть условного оператора на английском, а часть на русском.
            • +1
              Да. Особенно, когда приспосабливаешь код написанный на C# в 1С.

              В реалии код
              if (Элемент ==null) 		return ""
              


              D 1C можно использовать англоязычные синонимы
        • +2
          >> Элемент = ъ(ъ(ъ(Пакет.MainDocumentPart).Document).Body)
          ужас…
          вооьще вымораживает эта смесь русского и английского
          • –2
            Это великий и могучий Руслиш «Руслиш»: официальный язык МКС — МИР24

            Это с непревычки. Мне например долго приходилось соображать, что к чему когда код 1С на английском Сценарное тестирование в помощь программисту 1С

            Ну, а кроме Русслиша, неинтересно?
            1. Использование классов .Net в нативе
            2. Кроссплатформенность как замена COM
            3. Использование сахара как методы расширения, вывод типа в дженерик методах, асинхронное программирование?
            • 0
              Спасибо
              Изучаю 1с после питон, конечно жесть ), очень не привычно, но интересно
              • +1
                Спасибо. Вот если в неё встроить доступ к классам .Net, добавить замыкания аналоги await…
                Но на ней действительно достаточно легко решать задачи учета. И самое главное это куча типовых конфигураций которые сейчас можно расширять не снимая с поддержки
                • +1
                  1с почти гениальна в своей области, очень удобна, к коду можно привыкнуть.

                  Единственное пока не понял как на ней работать разработчику под Linux, я уже лет пять на Винде не работаю, только виртуалки для тестов.
                  К сожалению версии для разработчиков под Linux нет. Под вайн не работает отладчик. Короче в этом смысле печаль… + проблемы с com объектами, но тут только со слов разработчиков, сам пока до этого не дорос )
                  • 0
                    К сожалению у меня нет Linux. Но код кроссплатформен. Правда нужно перекомпилировать код на C++ и скачать .Net Core под Linux.
                    https://www.microsoft.com/net/core#windows
            • 0
              С версии 8.3.3.641 прекрасно работает под nix ( и сервер и клиент и конфигуратор).
              • 0
                http://v8.1c.ru/requirements/ Пока .Net Core для nix только под 64 разрядные. Хотя 1С сейчас выпускает 64 разрядного клиента в 8.3.9
                http://www.forum.mista.ru/topic.php?id=778385&page=1
              • 0
                Хотя для Linux есть 64 разрядные клиенты
                Тонкий клиент 1С: Предприятия (64-bit) для DEB-based Linux-систем
                Тонкий клиент 1С: Предприятия (64-bit) для RPM-based Linux-систем
                Клиент 1С: Предприятия (64-bit) для DEB-based Linux-систем
                Клиент 1С: Предприятия (64-bit) для RPM-based Linux-систем
                • 0
                  Просили про отправку почты. Заодно исправил ошибку. Кому интересно скачайте новую версию

                   smtp = "smtp.yandex.ru";
                      login = "XXXX@yandex.ru";
                      password = "YYYYYY";
                      Кому = "YYYYYYY@XXXXXXXX.ru";
                      
                      
                      СборкаMailKit=ъ(Врап.Сборка("MailKit.dll"));
                      СборкаMimeKit=ъ(Врап.Сборка("MimeKit.dll"));
                      
                      MimeMessage=ъ(СборкаMimeKit.GetType("MimeKit.MimeMessage"));
                      MailboxAddress=ъ(СборкаMimeKit.GetType("MimeKit.MailboxAddress"));
                      TextPart=ъ(СборкаMimeKit.GetType("MimeKit.TextPart"));
                      
                      SmtpClient=ъ(СборкаMailKit.GetType("MailKit.Net.Smtp.SmtpClient"));
                      
                      
                      
                      message = ъНовый(MimeMessage.ПолучитьСсылку());
                      From= ъ(Врап.Новый(MailboxAddress.ПолучитьСсылку(),"Сергей Смирнов", login));
                      ъ(message.From).Add( From.ПолучитьСсылку());
                      
                      ToMail=ъ(Врап.Новый(MailboxAddress.ПолучитьСсылку(),"Сергей Смирнов", Кому));
                      ъ(message.To).Add(ToMail.ПолучитьСсылку());
                      message.Subject = "Как дела?";
                      
                      ТелоСообщения=  ъ(Врап.Новый(TextPart.ПолучитьСсылку(),"plain"));
                      ТелоСообщения.Text = "Здесь любое сообщение
                      | что фантазия подскажет
                      | Это тест отправки почты";
                      
                      message.Body=ТелоСообщения.ПолучитьСсылку();
                      client =ъНовый(SmtpClient.ПолучитьСсылку());
                      client.Connect(smtp, 465, true);
                      
                      // Note: since we don't have an OAuth2 token, disable
                      // the XOAUTH2 authentication mechanism.
                      ъ(client.AuthenticationMechanisms).Remove("XOAUTH2");
                      
                      // Note: only needed if the SMTP server requires authentication
                      client.Authenticate(login, password);
                      
                      client.Send(message.ПолучитьСсылку());
                      client.Disconnect(true);
                      Врап.ЗакрытьРесурс(client.ПолучитьСсылку()); 
                  
                  
                  
                  
                  • 0
                    Вот SQL запрос к 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();
                        
                        ТекстЗапроса = "Sel ect Номенклатура.DESCR Наименование  Fr om sc84 Номенклатура where DESCR Like '%'+@Строка+'%'
                        |order by Номенклатура.DESCR";
                        
                        
                        command = ъ(Врап.Новый(SqlCommand.ПолучитьСсылку(),ТекстЗапроса,connection.ПолучитьСсылку()));
                        
                        Parameters=ъ(command.Parameters);
                        Parameters.AddWithValue("@Строка", "ДСП");
                        dr = ъ(command.ExecuteReader());
                        
                        Пока dr.Read() Цикл
                            Сообщить(dr.get_Item("Наименование"));
                        КонецЦикла;
                    
                    
                    


                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.