Visual Studio + Roslyn = конфигуратор для 1С: Предприятие

в 11:00, , рубрики: .net, 1c 8.3, roslyn, Visual Studio

Roslyn позволяет C# проект преобразовать в открытый XML-формат конфигурации 1С: Предприятие. C#-проект при поддержке Visual Studio автоматически снабжается Intellisense, интерактивной проверкой синтаксиса и типов, рефакторингом, расширенным поиском по проекту, поддержкой XmlDoc. Настраиваемое расположение документов проекта на диске и более выразительный и экономный формат делает C#-проект на Visual Studio лучшим выбором в системах версионирования.

Понятно, что от чистой теории до реализации всех особенностей 1С очень далеко. Приведенный в статье пример обладает следующими ограничениями. В примере реализована поддержка нескольких типов объектов и нескольких часто встречающихся свойств. Атрибуты объектов могут быть одного типа, хотя 1С допускает составной тип. Трансляция кода в код 1С не поддерживается. Реализованы англоязычные наименования.

XML-выгрузка конфигурации 1С

Начиная с версии 8.3 1С научилась выгружать/загружать конфигурацию в/из открытый формат XML. Делает она это в своей самобытной манере. Выгрузка, например, УТ 11.0.7 займет примерно 6 минут. При этом все файлы конфигурации будут свалены в единый каталог. Файлов будет около 10K (десяти тысяч) размером примерно 430 Мбайт. Такая выгрузка считается огромным достижением в области открытости, потому что ранее конфигурация запаковывалась в закрытый cf-формат, распознать который можно было только специализированными средствами: через .Net-совместимый cfProject или v8unpack.

Так выглядит один из файлов XML Language.Русский.xml

<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<Language uuid="7a630662-cf14-44d7-a01c-08006bb5bffa">
		<Properties>
			<Name>Русский</Name>
			<Synonym/>
			<Comment/>
			<LanguageCode>ru</LanguageCode>
		</Properties>
	</Language>
</MetaDataObject>

Замена XML-конструкций на определения C#

В проекте C# за каждый объект 1С отвечает класс. Если это справочник, то класс наследуется от класса Catalog, перечисление наследуется от класса Enum. Свойства объектов и атрибутов обозначаются через C#-атрибуты.

В результате XML-определение язык Русский примет такой вид Русский.cs

using System;
using Elisy.Configuration.DataAnnotations;
using Elisy.Configuration;
namespace Test.Languages
{
    [Uuid("7a630662-cf14-44d7-a01c-08006bb5bffa")]
    [LanguageCode("ru")]
    public class Русский : Language
    {
    }
}

Ссылки на объекты определяются через типы вида CatalogRef<>. Например, так

[Uuid("e140b824-c8f9-4155-87e6-d408e73ccc69")]
[Synonym("ru", "Рубрика")]
public CatalogRef<рубрики> Рубрика;

Табличная часть определяется как TabularSection<строкатабличнойчасти>

[Uuid("bb2c088e-8fb3-4469-abd7-ba6b4104ae88")]
[GeneratedTypeAttribute(GeneratedTypeCategory.TabularSection, "8ef1c35e-8b55-488f-8e24-61f73d287458", "b300dc36-fe12-41d5-a462-792716a1e508")]
[GeneratedTypeAttribute(GeneratedTypeCategory.TabularSectionRow, "92e3cead-a61b-474c-8515-56e4911339c7", "dfa007ab-4185-443f-8d11-f7468a7c195e")]
[Synonym("ru", "Рубрики")]
public TabularSection<РубрикаИзображения> Рубрики;

События модуля объекта и модуля менеджера реализуются как override-методы. Могут выглядеть так:

public override void BeforeWrite(ref bool cancel)
{
    var ДубликатПоНаименованию = Catalog.FindByDescription<Изображения>(Description, true);
    if (!IsFolder && IsNew() && G.ValueIsFilled(ДубликатПоНаименованию))
    {
        G.Message("Найден дубликат изображения с наименованием: " + Description + ". Запись не производится");
        cancel = true;
        return;
    }
    if (!IsFolder)
        Рубрики.GroupBy("Рубрика");
	
    //Убрать ненужные рубрики, обработанные групповой обработкой
    var НайденнаяСтрока = Рубрики.Find(Catalogs.Рубрики.УдалитьРубрику, "Рубрика");
    if (G.ValueIsFilled(НайденнаяСтрока))
        Рубрики.Delete(НайденнаяСтрока);
}

Следует отметить, что методы сейчас никак не транслируются в код 1С. Не понятным остается, что делать с директивами препроцессора 1С и нужно ли множественное наследование основных объектов 1С. G-обозначает глобальный контекст, так как вызов функций в C# может выполняться только в привязке к классу.

Компилирование проекта CSPROJ

В прошлой статье habrahabr.ru/post/245453/ была описана возможность компилирования одного файла CS. Чаще требуется скомпилировать целый проект — несколько файлов.

В Roslyn есть встроенные классы, отвечающие за обработку sln-решений и csproj-проектов Visual Studio. Проблема состоит в том, что они ссылаются на отдельные сборки, поставляемые в составе MSBuild Tools. Последняя версия Roslyn ссылается на сборки MSBuild Tools 2014, которые отдельно скачать не получится. Инсталляция сейчас входит в состав дистрибутива Visual Studio 14 CTP.

Оказалось, что в состав .Net framework входит сборка Microsoft.Build.dll v.4. В ней есть реализация чтения файла проекта C#. Можно самостоятельно реализовать состыковку этой сборки с логикой Roslyn и получить изоляцию от MSBuild Tools. Реализация будет примерно такой:

var project = new Project(projectFilePath);
List<string> references = new List<string>();
List<SyntaxTree> trees = new List<SyntaxTree>();
foreach(var item in project.Items)
{
    if (item.ItemType == "Reference")
    {
        references.Add(item.EvaluatedInclude);
    }
    else if (item.ItemType == "Compile")
    {
        string filePathToCompile = System.IO.Path.Combine(project.DirectoryPath, item.EvaluatedInclude);
        var text = System.IO.File.ReadAllText(filePathToCompile);
        trees.Add(CSharpSyntaxTree.ParseText(text));
    }
}
var assemblyName = project.GetProperty("AssemblyName").EvaluatedValue;
var compilation = CSharpCompilation.Create(assemblyName)
    .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
    .AddReferences(new MetadataFileReference(typeof(Elisy.Configuration.Configuration).Assembly.Location))
    .AddReferences(new MetadataFileReference(typeof(object).Assembly.Location))
    .AddSyntaxTrees(trees.ToArray());        

Если потребуется реализовать больше свойств проекта, то их можно подсмотреть в проекте Roslyn

Получение XML-файлов совместимых с 1С: Предприятие

При генерации XML-файлов работа большей частью ведется с типами, их членами и атрибутами. Оказалось, что объекты Roslyn CSharpCompilation очень ограничены в средствах. Через них нельзя выйти на типы System.Type, нельзя получить список атрибутов с учетом наследования. Поэтому для полноценного анализа лучше создать сборку средствами Roslyn и загрузить ее для Reflection-анализа:

MemoryStream ms = new MemoryStream();
compilation.Emit(ms);
var assembly = Assembly.Load(ms.ToArray());

Странно, что не удалось загрузить сборку как ReflectionOnlyLoad из-за ошибки «missing api-ms-win-core-l1-2-0.dll». Ошибка возникала при попытке прочитать входящие в состав сборки типы. Тем не менее, сборка нормально загружается и работает через обычный вызов Load.

Генерация XML-файлов сводится к перебору всех типов, наследованных от Configuration и основных объектов, к перебору свойств объектов и атрибутов.

XML строится средствами XML LINQ, например, так:

XElement configurationElement = new XElement(Compiler.MdClasses + "Language",
    language.GetUuidXml(),
    new XElement(Compiler.MdClasses + "Properties",
        new XElement(Compiler.MdClasses + "Name", language.Name),
        language.GetSynonimXml(),
        language.GetCommentXml(),
        new XElement(Compiler.MdClasses + "LanguageCode", language.GetAttribute().Value)
        )
    );
var document = CreateMetadataObjectDocument(configurationElement);
document.Save(Path.Combine(outFolder, String.Format("Language.{0}.xml", language.Name)));

Удалось выяснить, что 1С лояльно относится к отсутствию многих тегов, заменяя их значениями по умолчанию. Поэтому пустые значения в XML-файлах можно пропустить, тем самым сократив занимаемый размер на диске. Размер экономии может быть весьма значительным – до 40 процентов. Использование же cs-файлов вместо XML дает еще большую экономию места – до 60%.

Выводы

Средствами Visual Studio теоретически возможно создать альтернативный конфигуратор 1С. Для дальнейшего продвижения идеи нужно принять соглашения относительно всех основных объектов, хранения форм, трансляции языка C# в 1С, директив препроцессора 1С, обычных макетов и макетов СКД;

Текущая XML-выгрузка от 1С неудобна для анализа, потому что все файлы собраны в единый каталог;

Текущая XML-выгрузка 1С избыточна, и ее размер может быть уменьшен без потери данных до 40%, а хранение исходных кодов в виде C# позволит экономить до 60 процентов дискового пространства;

Чтение csproj-проектов и sln-решений Visual Studio встроенными средствами Roslyn непросто реализуемо из-за сложной доступности MSBuild Tools 2014. Компенсировать сейчас можно классами .Net Framework 4 и собственной инициализацией Roslyn.

Elisy.Configuration.zip (2,19 mb)

Автор: Elisy

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js