Доброго времени суток хабратчане! Сегодня я продолжу мучить вас великим и могучим Руслишем. Это продолжение статей:
» Разработка → Кроссплатформенное использование классов .Net из неуправляемого кода. Или аналог IDispatch на Linux
» Разработка → Кроссплатформенное использование классов .Net в 1С через Native ВК. Или замена COM на Linux
» Кроссплатформенное использование классов .Net в 1С через Native ВК. Или замена COM на Linux II
» Асинхронное программирование в 1С через .Net Native ВК
1С, Linux, Excel, Word, OpenXML,ADO и Net Core
На данный момент в .Net Core 2 варианта динамической компиляции
1. Это аналог CodeDom Microsoft.CodeAnalysis.CSharp.CSharpCompilation
2. Roslyn Scripting Api. Примеры здесь
Этот подход хорош когда нужно обновить библиотеку. Но при этом создается DLL со всеми вытекающими.
Второй способ мне нравится больше. Возьмем на примере получения делегата.
Обязательно нужно указать ссылки на:
Для компиляции используются следующие библиотеки:
По динамической компиляции. В свое время работал с запчастями для автомобилей. А там куча поставщиков и клиентов. При этом прайсы на миллионы позиций. И каждый горазд давать данные в своем формате. Было проще для каждого клиента создавать код записывать в справочник и использовать через Выполнить или Вычислить. Правда при работе с миллионными прайсами этот подход тормозил.
Так или иначе приходилось писать DLL и работать через COM.
C помощью динамической компиляции можно хранить текст кода и применять его в зависимости от условий. В том числе динамически формировать по условиям, а скомпилированный делегат можно кэшировать для повторного использования.
Скорость компиляции достаточно высокая (кроме первого раза секунд 5).
Перейдем к 1С. Так воторй алгоритм на 1С выглядит так
В общем ничего особенного. Получили сборки, дали ссылки на них скомпилировали, вызвали.
Перейдем к более сложным примерам. Так в предыдущей статье я показывал примеры использования DocumentFormat.OpenXml на примере чтения Excel и Word.
Там была проблема в скорости из-за приведения строки к объекту через функцию ъ и сама скорость вызова из 1С в 5 раз медленнее её родного кода.
Поэтому вынесем большую часть кода в .Net. Аналогичный вариант есть на большом .Net
.Net в 1С. На примере использования HTTPClient,AngleSharp. Удобный парсинг сайтов с помощью библиотеки AngleSharp, в том числе с авторизацией аля JQuery с использованием CSS селекторов. Динамическая компиляция (пример в конце статьи).
Создадим класс и скопируем его в макет. Суть класса прочитать данные ячеек и сгруппировать их по номеру строки.
Мы описали класс чтения и возвращаем ссылку на делегат принимающий путь к файлу и возвращающий анонимный класс. Все равно в 1С мы будем работать с ним через рефлексию. Обратите внимание, что здесь описаны 2 класса.
Теперь вызовем этот код из 1С.
Теперь скорость обработки Экселя значительно увеличилась, а затраты на компиляцию соизмеримы с затратами на чтения файлов.
Посмотрим процесс чтения Word. Не мудрствуя лукаво я взял готовый класс здесь. Тем более там все на англицком.
Но вернемся к Руслишу:
Основная задача указать ссылки на используемые сборки и пространство имен.
В следующей статье я сделаю динамическое создание обертки над объектами использующих события по аналогии с
.NET(C#) для 1С. Динамическая компиляция класса обертки для использования .Net событий в 1С через ДобавитьОбработчик или ОбработкаВнешнегоСобытия
» Разработка → Кроссплатформенное использование классов .Net из неуправляемого кода. Или аналог IDispatch на Linux
» Разработка → Кроссплатформенное использование классов .Net в 1С через Native ВК. Или замена COM на Linux
» Кроссплатформенное использование классов .Net в 1С через Native ВК. Или замена COM на Linux II
» Асинхронное программирование в 1С через .Net Native ВК
1С, Linux, Excel, Word, OpenXML,ADO и Net Core
На данный момент в .Net Core 2 варианта динамической компиляции
1. Это аналог CodeDom Microsoft.CodeAnalysis.CSharp.CSharpCompilation
2. Roslyn Scripting Api. Примеры здесь
var compilation = Microsoft.CodeAnalysis.CSharp.CSharpCompilation.Create("a")
.WithOptions(new Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions(Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary))
.AddReferences(
Microsoft.CodeAnalysis.MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location))
.AddSyntaxTrees(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(
@"
using System;
public class C
{
public C(){}
public string M()
{
return ""Hello Roslyn."";
}
}"));
var fileName = @"d:\NetStandart\TestCoreNetApp\src\TestCoreNetApp\bin\Debug\netcoreapp1.0\a.dll";
compilation.Emit(fileName);
var a = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(fileName);
Type тип = a.GetType("C");
var obj = Activator.CreateInstance(тип);
var res = тип.GetMethod("M").Invoke(obj, null);
Console.WriteLine(res.ToString());
Этот подход хорош когда нужно обновить библиотеку. Но при этом создается DLL со всеми вытекающими.
Второй способ мне нравится больше. Возьмем на примере получения делегата.
string words = "надо заменить все первые буквы в словах на заглавные";
string pattern = @"\w+";
var scr = Microsoft.CodeAnalysis.Scripting.ScriptOptions.Default;
var mscorlib = Assembly.Load(System.Runtime.Loader.AssemblyLoadContext.GetAssemblyName(@"c:\Users\Smirnov_SA\.nuget\packages\Microsoft.NETCore.Portable.Compatibility\1.0.1\ref\netcore50\mscorlib.dll"));
scr =scr.WithReferences(mscorlib, typeof(MulticastDelegate).GetTypeInfo().Assembly, typeof(System.Runtime.CompilerServices.IStrongBox).GetTypeInfo().Assembly, typeof(MatchEvaluator).GetTypeInfo().Assembly, typeof(Regex).GetTypeInfo().Assembly)
.WithImports("System", "System.Text.RegularExpressions");
string КодДелегата = @"return (MatchEvaluator)((match) =>
{
string x = match.Value;
// If the first char is lower case...
if (char.IsLower(x[0]))
{
// Capitalize it.
return char.ToUpper(x[0]) + x.Substring(1, x.Length - 1);
}
return x;
});";
var result = Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync(КодДелегата, scr).Result;
MatchEvaluator evaluator = (MatchEvaluator)result;
Console.WriteLine(Regex.Replace(words, pattern, evaluator));
Обязательно нужно указать ссылки на:
mscorlib.dll
System.Private.CoreLib.ni.dll
System.Runtime.dll
Для компиляции используются следующие библиотеки:
«Microsoft.CodeAnalysis.CSharp»: «2.0.0-beta3»,
«Microsoft.CodeAnalysis.CSharp.Scripting»: «2.0.0-beta3»,
«Microsoft.CodeAnalysis.Scripting.Common»: «2.0.0-beta3»,
Microsoft.CodeAnalysis.Scripting
По динамической компиляции. В свое время работал с запчастями для автомобилей. А там куча поставщиков и клиентов. При этом прайсы на миллионы позиций. И каждый горазд давать данные в своем формате. Было проще для каждого клиента создавать код записывать в справочник и использовать через Выполнить или Вычислить. Правда при работе с миллионными прайсами этот подход тормозил.
Так или иначе приходилось писать DLL и работать через COM.
C помощью динамической компиляции можно хранить текст кода и применять его в зависимости от условий. В том числе динамически формировать по условиям, а скомпилированный делегат можно кэшировать для повторного использования.
Скорость компиляции достаточно высокая (кроме первого раза секунд 5).
Перейдем к 1С. Так воторй алгоритм на 1С выглядит так
стр = "return (MatchEvaluator)((match) =>
|{
| string x = match.Value;
|// If the first char is lower case...
|if (char.IsLower(x[0]))
|{
|// Capitalize it.
|return char.ToUpper(x[0]) + x.Substring(1, x.Length - 1);
|}
|return x;
|
|});";
текст = "надо заменить все первые буквы в словах на заглавные";
Образец = "\w+";
// MatchEvaluator evaluator = (MatchEvaluator)ПолучитьДелегат();
ScriptOptions=ъТип("Microsoft.CodeAnalysis.Scripting.ScriptOptions","Microsoft.CodeAnalysis.Scripting");
CSharpScript=ъТип("Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript","Microsoft.CodeAnalysis.CSharp.Scripting");
scr = ъ(ScriptOptions.Default);
mscorlibСборка = ъ(Врап.Сборка("mscorlib.dll",Истина));
Private_CoreLibСборка=ъ(Врап.Сборка("System.Private.CoreLib.ni",Истина));
System_RuntimeСборка=ъ(Врап.Сборка("System.Runtime",Истина));
RegularExpressionsСборка=ъ(Врап.Сборка("System.Text.RegularExpressions",Истина));
Regex=ъ(RegularExpressionsСборка.GetType("System.Text.RegularExpressions.Regex"));
scr =ъ(scr.WithReferences(mscorlibСборка.ПолучитьСсылку(), Private_CoreLibСборка.ПолучитьСсылку(), System_RuntimeСборка.ПолучитьСсылку(), RegularExpressionsСборка.ПолучитьСсылку()));
scr =ъ(scr.WithImports("System", "System.Text.RegularExpressions"));
evaluator = ъ(ъ(CSharpScript.EvaluateAsync(стр, scr.ПолучитьСсылку())).Result);
Сообщить(Regex.Replace(текст, Образец, evaluator.ПолучитьСсылку()));
В общем ничего особенного. Получили сборки, дали ссылки на них скомпилировали, вызвали.
Перейдем к более сложным примерам. Так в предыдущей статье я показывал примеры использования DocumentFormat.OpenXml на примере чтения Excel и Word.
Там была проблема в скорости из-за приведения строки к объекту через функцию ъ и сама скорость вызова из 1С в 5 раз медленнее её родного кода.
Поэтому вынесем большую часть кода в .Net. Аналогичный вариант есть на большом .Net
.Net в 1С. На примере использования HTTPClient,AngleSharp. Удобный парсинг сайтов с помощью библиотеки AngleSharp, в том числе с авторизацией аля JQuery с использованием CSS селекторов. Динамическая компиляция (пример в конце статьи).
Создадим класс и скопируем его в макет. Суть класса прочитать данные ячеек и сгруппировать их по номеру строки.
public class ДанныеЯчейки
{
public string ИмяЯчейки;
public string ИмяКолонки;
public int НомСтроки;
public string ЗначениеЯчейки;
}
public class ExcelReader
{
static Regex ШаблонКолонки = new Regex("[A-Za-z]+");
OpenXmlElementList ОбщиеСтроки;
void ЗаписатьДанныеАдреса(string адрес, ДанныеЯчейки данныеЯчейки)
{
данныеЯчейки.ИмяЯчейки = адрес;
var match = ШаблонКолонки.Match(адрес);
var ИмяКолонки = match.Value;
var НомерСтроки = int.Parse(адрес.Substring(ИмяКолонки.Length));
данныеЯчейки.ИмяКолонки = ИмяКолонки;
данныеЯчейки.НомСтроки = НомерСтроки;
}
void ДобавитьДанныеОбЯчейке(List<ДанныеЯчейки> ДанныеЭселя, Cell cell)
{
var адрес = cell.CellReference.InnerText;
var text = cell.CellValue?.Text;
var DataType = cell.DataType;
string res = text;
if (DataType != null && DataType == CellValues.SharedString)
{
int ssid = int.Parse(text);
res = ОбщиеСтроки[ssid].InnerText;
}
if (res == null) return;
var result = new ДанныеЯчейки();
ЗаписатьДанныеАдреса(адрес, result);
result.ЗначениеЯчейки = res;
ДанныеЭселя.Add(result);
}
public List<ДанныеЯчейки> ReadExcel(string fileName)
{
List<ДанныеЯчейки> ДанныеЭселя = new List<ДанныеЯчейки>();
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (SpreadsheetDocument doc = SpreadsheetDocument.Open(fs, false))
{
var workbookPart = doc.WorkbookPart;
// Строки хранятся отдельно
// В ячейках хранится индекс
var pt = workbookPart.GetPartsOfType<SharedStringTablePart>();
var sstpart = pt.First();
var sst = sstpart.SharedStringTable;
ОбщиеСтроки = sst.ChildElements;
var workbook = workbookPart.Workbook;
// Получим список страниц
var sheet = workbook.Descendants<Sheet>().First();
var worksheetPart = (DocumentFormat.OpenXml.Packaging.WorksheetPart)workbookPart.GetPartById(sheet.Id);
var worksheet = worksheetPart.Worksheet;
var cells = worksheet.Descendants<Cell>();
// One way: go through each cell in the sheet
foreach (Cell cell in cells)
{
ДобавитьДанныеОбЯчейке(ДанныеЭселя, cell);
}
}
}
return ДанныеЭселя;
}
static string НайтиИмяПоследнейКолонки(List<ДанныеЯчейки> Тз)
{
var рез = "";
var ДлинаРез = 0;
foreach (var стрТз in Тз)
{
var Стр = стрТз.ИмяКолонки;
var СтрДл = Стр.Length;
if (СтрДл > ДлинаРез)
{
ДлинаРез = СтрДл;
рез = Стр;
}
else if (СтрДл == ДлинаРез && string.Compare(Стр, рез, true) > 0)
рез = Стр;
}
return рез;
}
public static object ПолучитьДанныеЭкселя(string fileName)
{
var res = new ExcelReader();
var Данные = res.ReadExcel(fileName);
var данныеЭкселя = Данные.GroupBy(ё => ё.НомСтроки).Select(ъ => new { НомСтроки = ъ.Key, Ячейки = ъ.ToArray() }).OrderBy(ь => ь.НомСтроки);
var ИмяПоследнейКолонки = НайтиИмяПоследнейКолонки(Данные);
return new { ДанныеЁкселя = данныеЭкселя.ToList(), ИмяПоследнейКолонки = ИмяПоследнейКолонки };
}
}
return new Func<string, object>(ExcelReader.ПолучитьДанныеЭкселя);
Мы описали класс чтения и возвращаем ссылку на делегат принимающий путь к файлу и возвращающий анонимный класс. Все равно в 1С мы будем работать с ним через рефлексию. Обратите внимание, что здесь описаны 2 класса.
Теперь вызовем этот код из 1С.
ScriptOptions=ъТип("Microsoft.CodeAnalysis.Scripting.ScriptOptions","Microsoft.CodeAnalysis.Scripting");
CSharpScript=ъТип("Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript","Microsoft.CodeAnalysis.CSharp.Scripting");
scr = ъ(ScriptOptions.Default);
mscorlibСборка = ъ(Врап.Сборка("mscorlib.dll",Истина));
Private_CoreLibСборка=ъ(Врап.Сборка("System.Private.CoreLib.ni",Истина));
System_RuntimeСборка=ъ(Врап.Сборка("System.Runtime",Истина));
RegularExpressionsСборка=ъ(Врап.Сборка("System.Text.RegularExpressions",Истина));
OpenXmlСбока=ъ(Врап.Сборка("DocumentFormat.OpenXml.dll"));
LinqСбока=ъ(Врап.Сборка("System.Linq", истина));
FileSystemСбока=ъ(Врап.Сборка("System.IO.FileSystem", истина));
Regex=ъ(RegularExpressionsСборка.GetType("System.Text.RegularExpressions.Regex"));
scr =ъ(scr.WithReferences(mscorlibСборка.ПолучитьСсылку(), Private_CoreLibСборка.ПолучитьСсылку(), System_RuntimeСборка.ПолучитьСсылку(), RegularExpressionsСборка.ПолучитьСсылку(),OpenXmlСбока.ПолучитьСсылку(),LinqСбока.ПолучитьСсылку(),FileSystemСбока.ПолучитьСсылку()));
scr =ъ(scr.WithImports("System", "System.Collections.Generic", "System.Linq", "System.IO", "DocumentFormat.OpenXml", "DocumentFormat.OpenXml.Packaging", "DocumentFormat.OpenXml.Spreadsheet", "System.Text.RegularExpressions"));
Текст=ПолучитьМакет("ТекстКлассаЧтенияЕксель").ПолучитьТекст();
Делегат = ъ(ъ(CSharpScript.EvaluateAsync(Текст, scr.ПолучитьСсылку())).Result);
Данные= ъ(Делегат.DynamicInvoke(ИмяФайла));
Сообщить(Данные.ИмяПоследнейКолонки);
рез=новый ТаблицаЗначений;
ПоследняяКолонка=Данные.ИмяПоследнейКолонки;
СоздатьКолонки(рез.Колонки,ПоследняяКолонка);
Колонки=рез.Колонки;
Тз=ъ(Данные.ДанныеЁкселя);
Тз=ъ(Врап.ПолучитьЭнумератор(Тз.ПолучитьСсылку()));
сч=1;
// Получили сгруппированные данные по строкам
Пока Тз.MoveNext() Цикл
стрТз= ъ(Тз.Current);
НомСтроки=стрТз.НомСтроки;
Пока сч<НомСтроки Цикл
сч=сч+1;
рез.Добавить();
КонецЦикла;
сч=сч+1;
стр=рез.Добавить();
// получим ячейки по строке
ТзГр=ъ(стрТз.Ячейки);
ТзГр=ъ(Врап.ПолучитьЭнумератор(ТзГр.ПолучитьСсылку()));
Пока ТзГр.MoveNext() Цикл
стрТзГр= ъ(ТзГр.Current);
ИмяКолонки=стрТзГр.ИмяКолонки;
ЗначениеЯчейки=стрТзГр.ЗначениеЯчейки;
// Можно конечно получить индекс зная смещение символа 64 относительно 1 и 26 разрядную систему
// но найдем колонку по имени и её индекс
Колонка=Колонки.Найти(ИмяКолонки);
стр.Установить(Колонки.Индекс(Колонка),ЗначениеЯчейки);
КонецЦикла;
КонецЦикла;
Теперь скорость обработки Экселя значительно увеличилась, а затраты на компиляцию соизмеримы с затратами на чтения файлов.
Посмотрим процесс чтения Word. Не мудрствуя лукаво я взял готовый класс здесь. Тем более там все на англицком.
// Исходники можно скачать здесь
//https://code.msdn.microsoft.com/office/CSOpenXmlGetPlainText-554918c3/sourcecode?fileId=71592&pathId=851860130
public class GetWordPlainText : IDisposable
{
// Specify whether the instance is disposed.
private bool disposed = false;
// The word package
private WordprocessingDocument package = null;
/// <summary>
/// Get the file name
/// </summary>
private string FileName = string.Empty;
/// <summary>
/// Initialize the WordPlainTextManager instance
/// </summary>
/// <param name="filepath"></param>
public GetWordPlainText(string filepath)
{
this.FileName = filepath;
if (string.IsNullOrEmpty(filepath) || !File.Exists(filepath))
{
throw new Exception("The file is invalid. Please select an existing file again");
}
this.package = WordprocessingDocument.Open(filepath, true);
}
/// <summary>
/// Read Word Document
/// </summary>
/// <returns>Plain Text in document </returns>
public string ReadWordDocument()
{
StringBuilder sb = new StringBuilder();
OpenXmlElement element = package.MainDocumentPart.Document.Body;
if (element == null)
{
return string.Empty;
}
sb.Append(GetPlainText(element));
return sb.ToString();
}
/// <summary>
/// Read Plain Text in all XmlElements of word document
/// </summary>
/// <param name="element">XmlElement in document</param>
/// <returns>Plain Text in XmlElement</returns>
public string GetPlainText(OpenXmlElement element)
{
StringBuilder PlainTextInWord = new StringBuilder();
foreach (OpenXmlElement section in element.Elements())
{
switch (section.LocalName)
{
// Text
case "t":
PlainTextInWord.Append(section.InnerText);
break;
case "cr": // Carriage return
case "br": // Page break
PlainTextInWord.Append(Environment.NewLine);
break;
// Tab
case "tab":
PlainTextInWord.Append("\t");
break;
// Paragraph
case "p":
PlainTextInWord.Append(GetPlainText(section));
PlainTextInWord.AppendLine(Environment.NewLine);
break;
default:
PlainTextInWord.Append(GetPlainText(section));
break;
}
}
return PlainTextInWord.ToString();
}
#region IDisposable interface
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
// Protect from being called multiple times.
if (disposed)
{
return;
}
if (disposing)
{
// Clean up all managed resources.
if (this.package != null)
{
this.package.Dispose();
}
}
disposed = true;
}
#endregion
public static string GetText(string FileName)
{
using (var pt = new GetWordPlainText(FileName))
{
return pt.ReadWordDocument();
}
}
}
return new Func<string,string>(GetWordPlainText.GetText);
Но вернемся к Руслишу:
ScriptOptions=ъТип("Microsoft.CodeAnalysis.Scripting.ScriptOptions","Microsoft.CodeAnalysis.Scripting");
CSharpScript=ъТип("Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript","Microsoft.CodeAnalysis.CSharp.Scripting");
scr = ъ(ScriptOptions.Default);
mscorlibСборка = ъ(Врап.Сборка("mscorlib.dll",Истина));
Private_CoreLibСборка=ъ(Врап.Сборка("System.Private.CoreLib.ni",Истина));
System_RuntimeСборка=ъ(Врап.Сборка("System.Runtime",Истина));
RegularExpressionsСборка=ъ(Врап.Сборка("System.Text.RegularExpressions",Истина));
OpenXmlСбока=ъ(Врап.Сборка("DocumentFormat.OpenXml.dll"));
LinqСбока=ъ(Врап.Сборка("System.Linq", истина));
FileSystemСбока=ъ(Врап.Сборка("System.IO.FileSystem", истина));
Regex=ъ(RegularExpressionsСборка.GetType("System.Text.RegularExpressions.Regex"));
scr =ъ(scr.WithReferences(mscorlibСборка.ПолучитьСсылку(), Private_CoreLibСборка.ПолучитьСсылку(), System_RuntimeСборка.ПолучитьСсылку(),OpenXmlСбока.ПолучитьСсылку(),FileSystemСбока.ПолучитьСсылку()));
scr =ъ(scr.WithImports("System", "System.Text", "System.IO", "DocumentFormat.OpenXml", "DocumentFormat.OpenXml.Packaging"));
Текст=ПолучитьМакет("ТекстЧтенияВорд").ПолучитьТекст();
Делегат = ъ(ъ(CSharpScript.EvaluateAsync(Текст, scr.ПолучитьСсылку())).Result);
стр = Делегат.DynamicInvoke(ИмяФайла);
Текст=Новый ТекстовыйДокумент;
Текст.ДобавитьСтроку(стр);
Текст.Показать();
Основная задача указать ссылки на используемые сборки и пространство имен.
В следующей статье я сделаю динамическое создание обертки над объектами использующих события по аналогии с
.NET(C#) для 1С. Динамическая компиляция класса обертки для использования .Net событий в 1С через ДобавитьОбработчик или ОбработкаВнешнегоСобытия