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

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


  1. ns5d
    22.08.2016 13:04
    +3

    фу, какашка…


  1. artem_b89
    22.08.2016 13:52
    +1

    Простите, этот код правда работает?

    if (Элемент = null) Тогда
    		возврат ""
    	КонецЕсли;
    


    1. Serginio1
      22.08.2016 14:00
      +1

      Да Native API возвращает Null а не неопределено


      1. artem_b89
        22.08.2016 14:41

        Даже так)) Я немного про другое. Видимо Руслиш на столько велик и могуч, что позволяет писать часть условного оператора на английском, а часть на русском.


        1. Serginio1
          22.08.2016 14:43
          +1

          Да. Особенно, когда приспосабливаешь код написанный на C# в 1С.

          В реалии код

          if (Элемент ==null) 		return ""
          


          D 1C можно использовать англоязычные синонимы


  1. oxidmod
    22.08.2016 14:09
    +2

    >> Элемент = ъ(ъ(ъ(Пакет.MainDocumentPart).Document).Body)
    ужас…
    вооьще вымораживает эта смесь русского и английского


  1. Serginio1
    22.08.2016 14:20
    -2

    Это великий и могучий Руслиш «Руслиш»: официальный язык МКС — МИР24

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

    Ну, а кроме Русслиша, неинтересно?
    1. Использование классов .Net в нативе
    2. Кроссплатформенность как замена COM
    3. Использование сахара как методы расширения, вывод типа в дженерик методах, асинхронное программирование?


  1. Tiamon
    23.08.2016 10:13

    Спасибо
    Изучаю 1с после питон, конечно жесть ), очень не привычно, но интересно


    1. Serginio1
      23.08.2016 10:18
      +1

      Спасибо. Вот если в неё встроить доступ к классам .Net, добавить замыкания аналоги await…
      Но на ней действительно достаточно легко решать задачи учета. И самое главное это куча типовых конфигураций которые сейчас можно расширять не снимая с поддержки


      1. Tiamon
        23.08.2016 10:40
        +1

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

        Единственное пока не понял как на ней работать разработчику под Linux, я уже лет пять на Винде не работаю, только виртуалки для тестов.
        К сожалению версии для разработчиков под Linux нет. Под вайн не работает отладчик. Короче в этом смысле печаль… + проблемы с com объектами, но тут только со слов разработчиков, сам пока до этого не дорос )


        1. Serginio1
          23.08.2016 10:55

          К сожалению у меня нет Linux. Но код кроссплатформен. Правда нужно перекомпилировать код на C++ и скачать .Net Core под Linux.
          https://www.microsoft.com/net/core#windows


          1. Serginio1
            23.08.2016 11:00

            https://github.com/dotnet/cli


  1. marzhin
    23.08.2016 13:02

    С версии 8.3.3.641 прекрасно работает под nix ( и сервер и клиент и конфигуратор).


    1. Serginio1
      23.08.2016 13:18

      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


  1. Serginio1
    23.08.2016 14:15

    Хотя для 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-систем


  1. Serginio1
    24.08.2016 16:45

    Просили про отправку почты. Заодно исправил ошибку. Кому интересно скачайте новую версию

     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.ПолучитьСсылку()); 
    
    
    
    


  1. Serginio1
    25.08.2016 14:15

    Вот 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("Наименование"));
        КонецЦикла;