» Разработка > Кроссплатформенное использование классов .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)
artem_b89
22.08.2016 13:52+1Простите, этот код правда работает?
if (Элемент = null) Тогда возврат "" КонецЕсли;
Serginio1
22.08.2016 14:00+1Да Native API возвращает Null а не неопределено
artem_b89
22.08.2016 14:41Даже так)) Я немного про другое. Видимо Руслиш на столько велик и могуч, что позволяет писать часть условного оператора на английском, а часть на русском.
Serginio1
22.08.2016 14:43+1Да. Особенно, когда приспосабливаешь код написанный на C# в 1С.
В реалии код
if (Элемент ==null) return ""
D 1C можно использовать англоязычные синонимы
oxidmod
22.08.2016 14:09+2>> Элемент = ъ(ъ(ъ(Пакет.MainDocumentPart).Document).Body)
ужас…
вооьще вымораживает эта смесь русского и английского
Serginio1
22.08.2016 14:20-2Это великий и могучий Руслиш «Руслиш»: официальный язык МКС — МИР24
Это с непревычки. Мне например долго приходилось соображать, что к чему когда код 1С на английском Сценарное тестирование в помощь программисту 1С
Ну, а кроме Русслиша, неинтересно?
1. Использование классов .Net в нативе
2. Кроссплатформенность как замена COM
3. Использование сахара как методы расширения, вывод типа в дженерик методах, асинхронное программирование?
Tiamon
23.08.2016 10:13Спасибо
Изучаю 1с после питон, конечно жесть ), очень не привычно, но интересноSerginio1
23.08.2016 10:18+1Спасибо. Вот если в неё встроить доступ к классам .Net, добавить замыкания аналоги await…
Но на ней действительно достаточно легко решать задачи учета. И самое главное это куча типовых конфигураций которые сейчас можно расширять не снимая с поддержкиTiamon
23.08.2016 10:40+11с почти гениальна в своей области, очень удобна, к коду можно привыкнуть.
Единственное пока не понял как на ней работать разработчику под Linux, я уже лет пять на Винде не работаю, только виртуалки для тестов.
К сожалению версии для разработчиков под Linux нет. Под вайн не работает отладчик. Короче в этом смысле печаль… + проблемы с com объектами, но тут только со слов разработчиков, сам пока до этого не дорос )
marzhin
23.08.2016 13:02С версии 8.3.3.641 прекрасно работает под nix ( и сервер и клиент и конфигуратор).
Serginio1
23.08.2016 13:18http://v8.1c.ru/requirements/ Пока .Net Core для nix только под 64 разрядные. Хотя 1С сейчас выпускает 64 разрядного клиента в 8.3.9
http://www.forum.mista.ru/topic.php?id=778385&page=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-систем
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.ПолучитьСсылку());
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("Наименование")); КонецЦикла;
ns5d
фу, какашка…