Доступ к моделям из текстовых шаблонов
С помощью текстовых шаблонов можно создавать файлы отчетов, файлы исходного кода и другие текстовые файлы, основанные на языковых моделях конкретного домена. Основные сведения о текстовых шаблонах см. в разделе "Создание кода" и "Шаблоны текста T4". Текстовые шаблоны будут работать в экспериментальном режиме при отладке DSL, а также будут работать на компьютере, на котором развернут DSL.
Примечание.
При создании решения DSL в проекте отладки создаются примеры текстовых шаблонов *.tt . При изменении имен классов домена эти шаблоны больше не будут работать. Тем не менее, они включают основные директивы, необходимые, и предоставляют примеры, которые можно обновить для соответствия DSL.
Чтобы получить доступ к модели из текстового шаблона:
Задайте для наследуемого свойства директивы шаблона значение Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation. Это обеспечивает доступ к Магазину.
Укажите процессоры директив для DSL, к которому требуется получить доступ. Это загружает сборки для DSL, чтобы использовать его классы домена, свойства и связи в коде текстового шаблона. Он также загружает указанный файл модели.
Файл
.tt
, аналогичный следующему примеру, создается в проекте отладки при создании нового решения Visual Studio из шаблона DSL Minimal Language.
<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" #>
<#@ output extension=".txt" #>
<#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1'" #>
This text will be output directly.
This is the name of the model: <#= this.ModelRoot.Name #>
Here is a list of elements in the model:
<#
// When you change the DSL Definition, some of the code below may not work.
foreach (ExampleElement element in this.ExampleModel.Elements)
{#>
<#= element.Name #>
<#
}
#>
Обратите внимание на следующие моменты этого шаблона:
Шаблон может использовать классы домена, свойства и связи, определенные в определении DSL.
Шаблон загружает файл модели, указанный в свойстве
requires
.Свойство, содержащее
this
корневой элемент. Оттуда код может перейти к другим элементам модели. Имя свойства обычно совпадает с корневым классом домена dsL. В нашем примере поисковый запрос будет выглядеть так:this.ExampleModel
.Хотя язык, на котором написаны фрагменты кода, — C#, можно создать текст любого вида. Можно также написать код в Visual Basic, добавив свойство
language="VB"
в директивуtemplate
.Чтобы выполнить отладку шаблона, добавьте
debug="true"
в директивуtemplate
. Шаблон откроется в другом экземпляре Visual Studio, если возникает исключение. Если вы хотите разбить отладчик в определенный момент в коде, вставьте инструкцию.System.Diagnostics.Debugger.Break();
Дополнительные сведения см. в разделе Отладка шаблона текста T4.
Сведения о обработчике директив DSL
Шаблон может использовать классы домена, определенные в определении DSL. Это связано с директивой, которая обычно отображается ближе к началу шаблона. В предыдущем примере это следующее.
<#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1'" #>
Имя директивы ( MyLanguage
в этом примере) является производным от имени DSL. Он вызывает обработчик директив, созданный в рамках DSL. Исходный код можно найти в dsl\GeneratedCode\DirectiveProcessor.cs.
Обработчик директив DSL выполняет две основные задачи:
Он эффективно вставляет директивы сборки и импорта в шаблон, ссылающийся на DSL. Это позволяет использовать классы домена в коде шаблона.
Он загружает файл, указанный в параметре
requires
, и задает свойство,this
которое ссылается на корневой элемент загруженной модели.
Проверка модели перед запуском шаблона
Вы можете вызвать проверку модели перед выполнением шаблона.
<#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1';validation='open|load|save|menu'" #>
Обратите внимание на указанные ниже моменты.
validation
Параметрыfilename
разделены ";" и не должны быть других разделителей или пробелов.Список категорий проверки определяет, какие методы проверки будут выполняться. Несколько категорий должны быть разделены на "|" и не должно быть других разделителей или пробелов.
Если обнаружена ошибка, в окне ошибок будет сообщено сообщение об ошибке, а в результирующем файле появится сообщение об ошибке.
Доступ к нескольким моделям из текстового шаблона
Примечание.
Этот метод позволяет читать несколько моделей в одном шаблоне, но не поддерживает ссылки ModelBus. Сведения о чтении моделей, которые связаны ссылками modelBus, см. в разделе "Использование Visual Studio ModelBus" в текстовом шаблоне.
Если вы хотите получить доступ к нескольким моделям из одного текстового шаблона, необходимо вызвать созданный обработчик директив один раз для каждой модели. Необходимо указать имя файла каждой модели в параметре requires
. Необходимо указать имена, которые необходимо использовать для корневого класса домена в параметре provides
. Необходимо указать разные значения provides
параметров в каждом из вызовов директивы. Например, предположим, что у вас есть три файла модели с именем Library.xyz, School.xyz и Work.xyz. Чтобы получить доступ к ним из одного текстового шаблона, необходимо написать три вызова директивы, которые похожи на следующие.
<#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='Library.xyz'" provides="ExampleModel=LibraryModel" #>
<#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='School.xyz'" provides="ExampleModel=SchoolModel" #>
<#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='Work.xyz'" provides="ExampleModel=WorkModel" #>
Примечание.
Этот пример кода предназначен для языка, основанного на шаблоне решения "Минимальный язык".
Чтобы получить доступ к моделям в текстовом шаблоне, теперь можно написать код, аналогичный коду в следующем примере.
<#
foreach (ExampleElement element in this.LibraryModel.Elements)
...
foreach (ExampleElement element in this.SchoolModel.Elements)
...
foreach (ExampleElement element in this.WorkModel.Elements)
...
#>
Динамическое загрузка моделей
Если вы хотите определить во время выполнения, какие модели нужно загрузить, можно динамически загрузить файл модели в коде программы, а не использовать директиву DSL.
Однако одной из функций директивы DSL является импорт пространства имен DSL, поэтому код шаблона может использовать классы домена, определенные в этом DSL. Так как директива не используется, необходимо добавить <директивы сборки> и< импорта> для всех моделей, которые можно загрузить. Это легко, если разные модели, которые могут быть загружены, являются всеми экземплярами одного и того же DSL.
Для загрузки файла наиболее эффективным методом является использование Visual Studio ModelBus. В типичном сценарии текстовый шаблон будет использовать директиву DSL для загрузки первой модели обычным способом. Эта модель будет содержать ссылки ModelBus на другую модель. С помощью ModelBus можно открыть эталонную модель и получить доступ к конкретному элементу. Дополнительные сведения см. в разделе "Использование Visual Studio ModelBus" в текстовом шаблоне.
В менее обычном сценарии может потребоваться открыть файл модели, для которого у вас есть только имя файла, и который может не находиться в текущем проекте Visual Studio. В этом случае файл можно открыть с помощью метода, описанного в разделе "Практическое руководство. Открытие модели из файла в коде программы".
Создание нескольких файлов из шаблона
Если вы хотите создать несколько файлов, например для создания отдельного файла для каждого элемента в модели, существует несколько возможных подходов. По умолчанию создается только один файл из каждого файла шаблона.
Разделение длинного файла
В этом методе используется шаблон для создания одного файла, разделенного разделителем. Затем файл разделяется на его части. Существует два шаблона, один для создания одного файла, а другой — для разделения.
LoopTemplate.t4 создает длинный один файл. Обратите внимание, что его расширение файла — "t4", так как оно не должно обрабатываться непосредственно при нажатии кнопки "Преобразовать все шаблоны". Этот шаблон принимает параметр, указывающий строку разделителя, разделяющую сегменты:
<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" #>
<#@ parameter name="delimiter" type="System.String" #>
<#@ output extension=".txt" #>
<#@ MyDSL processor="MyDSLDirectiveProcessor" requires="fileName='SampleModel.mydsl1';validation='open|load|save|menu'" #>
<#
// Create a file segment for each element:
foreach (ExampleElement element in this.ExampleModel.Elements)
{
// First item is the delimiter:
#>
<#= string.Format(delimiter, element.Id) #>
Element: <#= element.Name #>
<#
// Here you generate more content derived from the element.
}
#>
LoopSplitter.tt
LoopTemplate.t4
вызывается, а затем разбивает полученный файл на его сегменты. Обратите внимание, что этот шаблон не должен быть шаблоном моделирования, так как он не считывает модель.
<#@ template hostspecific="true" language="C#" #>
<#@ output extension=".txt" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ import namespace="System.Runtime.Remoting.Messaging" #>
<#@ import namespace="System.IO" #>
<#
// Get the local path:
string itemTemplatePath = this.Host.ResolvePath("LoopTemplate.t4");
string dir = Path.GetDirectoryName(itemTemplatePath);
// Get the template for generating each file:
string loopTemplate = File.ReadAllText(itemTemplatePath);
Engine engine = new Engine();
// Pass parameter to new template:
string delimiterGuid = Guid.NewGuid().ToString();
string delimiter = "::::" + delimiterGuid + ":::";
CallContext.LogicalSetData("delimiter", delimiter + "{0}:::");
string joinedFiles = engine.ProcessTemplate(loopTemplate, this.Host);
string [] separateFiles = joinedFiles.Split(new string [] {delimiter}, StringSplitOptions.None);
foreach (string nameAndFile in separateFiles)
{
if (string.IsNullOrWhiteSpace(nameAndFile)) continue;
string[] parts = nameAndFile.Split(new string[]{":::"}, 2, StringSplitOptions.None);
if (parts.Length < 2) continue;
#>
Generate: [<#= dir #>] [<#= parts[0] #>]
<#
// Generate a file from this item:
File.WriteAllText(Path.Combine(dir, parts[0] + ".txt"), parts[1]);
}
#>