Sdílet prostřednictvím


Návod: Vytvoření vlastního procesoru direktiv

Procesory direktiv fungují přidáním kódu do vygenerované transformační třídy. Pokud voláte direktivu z textové šablony, zbytek kódu, který píšete v textové šabloně, může záviset na funkcích, které direktiva poskytuje.

Můžete si vytvořit své vlastní procesory direktiv. To vám pak umožní textové šablony přizpůsobit. Chcete-li vytvořit vlastní procesor direktiv, vytvoříte třídu, která dědí z nebo DirectiveProcessor RequiresProvidesDirectiveProcessor.

Mezi úlohy popsané v tomto návodu patří:

  • Vytvoření vlastního procesoru direktiv

  • Registrace procesoru direktiv

  • Testování procesoru direktiv

Vytvoření vlastního procesoru direktiv

V tomto návodu vytvoříte vlastní procesor direktiv. Přidáte vlastní direktivu, která čte soubor XML, uloží ho XmlDocument do proměnné a zpřístupní ji prostřednictvím vlastnosti. V části „Testování procesoru direktiv“ získáte pomocí této vlastnosti v textové šabloně přístup k tomuto souboru XML.

Volání vlastní direktivy vypadá následovně:

<#@ CoolDirective Processor="CustomDirectiveProcessor" FileName="<Your Path>DocFile.xml" #>

Vlastní procesor direktiv přidá proměnnou a vlastnost do vygenerované třídy transformace. Direktiva, kterou napíšete, používá System.CodeDom třídy k vytvoření kódu, který modul přidá do vygenerované transformační třídy. Třídy System.CodeDom vytvářejí kód v jazyce Visual C# nebo Visual Basic v závislosti na jazyce určeném language v parametru direktivy template . Jazyk procesoru direktiv a jazyk textové šablony, ke které tento procesor direktiv přistupuje, se nemusejí shodovat.

Kód, který direktiva vytvoří, vypadá takto:

private System.Xml.XmlDocument document0Value;

public virtual System.Xml.XmlDocument Document0
{
  get
  {
    if ((this.document0Value == null))
    {
      this.document0Value = XmlReaderHelper.ReadXml(<FileNameParameterValue>);
    }
    return this.document0Value;
  }
}

Vytvoření vlastního procesoru direktiv

  1. V sadě Visual Studio vytvořte projekt knihovny tříd jazyka C# nebo Visual Basic s názvem CustomDP.

    Poznámka:

    Pokud chcete procesor direktiv nainstalovat na více než jeden počítač, je lepší použít projekt rozšíření sady Visual Studio (VSIX) a zahrnout do přípony soubor .pkgdef. Další informace naleznete v tématu Nasazení vlastního procesoru direktiv.

  2. Přidejte odkazy na tato sestavení:

    • Microsoft.VisualStudio.TextTemplating.*.0

    • Microsoft.VisualStudio.TextTemplating.Interfaces.*.0

  3. Nahraďte kód ve třídě 1 následujícím kódem. Tento kód definuje CustomDirectiveProcessor třídy, která dědí z DirectiveProcessor třídy a implementuje nezbytné metody.

    using System;
    using System.CodeDom;
    using System.CodeDom.Compiler;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using System.Xml;
    using System.Xml.Serialization;
    using Microsoft.VisualStudio.TextTemplating;
    
    namespace CustomDP
    {
        public class CustomDirectiveProcessor : DirectiveProcessor
        {
            // This buffer stores the code that is added to the
            // generated transformation class after all the processing is done.
            // ---------------------------------------------------------------------
            private StringBuilder codeBuffer;
    
            // Using a Code Dom Provider creates code for the
            // generated transformation class in either Visual Basic or C#.
            // If you want your directive processor to support only one language, you
            // can hard code the code you add to the generated transformation class.
            // In that case, you do not need this field.
            // --------------------------------------------------------------------------
            private CodeDomProvider codeDomProvider;
    
            // This stores the full contents of the text template that is being processed.
            // --------------------------------------------------------------------------
            private String templateContents;
    
            // These are the errors that occur during processing. The engine passes
            // the errors to the host, and the host can decide how to display them,
            // for example the host can display the errors in the UI
            // or write them to a file.
            // ---------------------------------------------------------------------
            private CompilerErrorCollection errorsValue;
            public new CompilerErrorCollection Errors
            {
                get { return errorsValue; }
            }
    
            // Each time this directive processor is called, it creates a new property.
            // We count how many times we are called, and append "n" to each new
            // property name. The property names are therefore unique.
            // -----------------------------------------------------------------------------
            private int directiveCount = 0;
    
            public override void Initialize(ITextTemplatingEngineHost host)
            {
                // We do not need to do any initialization work.
            }
    
            public override void StartProcessingRun(CodeDomProvider languageProvider, String templateContents, CompilerErrorCollection errors)
            {
                // The engine has passed us the language of the text template
                // we will use that language to generate code later.
                // ----------------------------------------------------------
                this.codeDomProvider = languageProvider;
                this.templateContents = templateContents;
                this.errorsValue = errors;
    
                this.codeBuffer = new StringBuilder();
            }
    
            // Before calling the ProcessDirective method for a directive, the
            // engine calls this function to see whether the directive is supported.
            // Notice that one directive processor might support many directives.
            // ---------------------------------------------------------------------
            public override bool IsDirectiveSupported(string directiveName)
            {
                if (string.Compare(directiveName, "CoolDirective", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    return true;
                }
                if (string.Compare(directiveName, "SuperCoolDirective", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    return true;
                }
                return false;
            }
    
            public override void ProcessDirective(string directiveName, IDictionary<string, string> arguments)
            {
                if (string.Compare(directiveName, "CoolDirective", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    string fileName;
    
                    if (!arguments.TryGetValue("FileName", out fileName))
                    {
                        throw new DirectiveProcessorException("Required argument 'FileName' not specified.");
                    }
    
                    if (string.IsNullOrEmpty(fileName))
                    {
                        throw new DirectiveProcessorException("Argument 'FileName' is null or empty.");
                    }
    
                    // Now we add code to the generated transformation class.
                    // This directive supports either Visual Basic or C#, so we must use the
                    // System.CodeDom to create the code.
                    // If a directive supports only one language, you can hard code the code.
                    // --------------------------------------------------------------------------
    
                    CodeMemberField documentField = new CodeMemberField();
    
                    documentField.Name = "document" + directiveCount + "Value";
                    documentField.Type = new CodeTypeReference(typeof(XmlDocument));
                    documentField.Attributes = MemberAttributes.Private;
    
                    CodeMemberProperty documentProperty = new CodeMemberProperty();
    
                    documentProperty.Name = "Document" + directiveCount;
                    documentProperty.Type = new CodeTypeReference(typeof(XmlDocument));
                    documentProperty.Attributes = MemberAttributes.Public;
                    documentProperty.HasSet = false;
                    documentProperty.HasGet = true;
    
                    CodeExpression fieldName = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), documentField.Name);
                    CodeExpression booleanTest = new CodeBinaryOperatorExpression(fieldName, CodeBinaryOperatorType.IdentityEquality, new CodePrimitiveExpression(null));
                    CodeExpression rightSide = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("XmlReaderHelper"), "ReadXml", new CodePrimitiveExpression(fileName));
                    CodeStatement[] thenSteps = new CodeStatement[] { new CodeAssignStatement(fieldName, rightSide) };
    
                    CodeConditionStatement ifThen = new CodeConditionStatement(booleanTest, thenSteps);
                    documentProperty.GetStatements.Add(ifThen);
    
                    CodeStatement s = new CodeMethodReturnStatement(fieldName);
                    documentProperty.GetStatements.Add(s);
    
                    CodeGeneratorOptions options = new CodeGeneratorOptions();
                    options.BlankLinesBetweenMembers = true;
                    options.IndentString = "    ";
                    options.VerbatimOrder = true;
                    options.BracingStyle = "C";
    
                    using (StringWriter writer = new StringWriter(codeBuffer, CultureInfo.InvariantCulture))
                    {
                        codeDomProvider.GenerateCodeFromMember(documentField, writer, options);
                        codeDomProvider.GenerateCodeFromMember(documentProperty, writer, options);
                    }
                }
    
                // One directive processor can contain many directives.
                // If you want to support more directives, the code goes here...
                // -----------------------------------------------------------------
                if (string.Compare(directiveName, "supercooldirective", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    // Code for SuperCoolDirective goes here...
                }
    
                // Track how many times the processor has been called.
                // -----------------------------------------------------------------
                directiveCount++;
    
            }
    
            public override void FinishProcessingRun()
            {
                this.codeDomProvider = null;
    
                // Important: do not do this:
                // The get methods below are called after this method
                // and the get methods can access this field.
                // -----------------------------------------------------------------
                // this.codeBuffer = null;
            }
    
            public override string GetPreInitializationCodeForProcessingRun()
            {
                // Use this method to add code to the start of the
                // Initialize() method of the generated transformation class.
                // We do not need any pre-initialization, so we will just return "".
                // -----------------------------------------------------------------
                // GetPreInitializationCodeForProcessingRun runs before the
                // Initialize() method of the base class.
                // -----------------------------------------------------------------
                return String.Empty;
            }
    
            public override string GetPostInitializationCodeForProcessingRun()
            {
                // Use this method to add code to the end of the
                // Initialize() method of the generated transformation class.
                // We do not need any post-initialization, so we will just return "".
                // ------------------------------------------------------------------
                // GetPostInitializationCodeForProcessingRun runs after the
                // Initialize() method of the base class.
                // -----------------------------------------------------------------
                return String.Empty;
            }
    
            public override string GetClassCodeForProcessingRun()
            {
                //Return the code to add to the generated transformation class.
                // -----------------------------------------------------------------
                return codeBuffer.ToString();
            }
    
            public override string[] GetReferencesForProcessingRun()
            {
                // This returns the references that we want to use when
                // compiling the generated transformation class.
                // -----------------------------------------------------------------
                // We need a reference to this assembly to be able to call
                // XmlReaderHelper.ReadXml from the generated transformation class.
                // -----------------------------------------------------------------
                return new string[]
                {
                    "System.Xml",
                    this.GetType().Assembly.Location
                };
            }
    
            public override string[] GetImportsForProcessingRun()
            {
                //This returns the imports or using statements that we want to
                //add to the generated transformation class.
                // -----------------------------------------------------------------
                //We need CustomDP to be able to call XmlReaderHelper.ReadXml
                //from the generated transformation class.
                // -----------------------------------------------------------------
                return new string[]
                {
                    "System.Xml",
                    "CustomDP"
                };
            }
        }
    
        // -------------------------------------------------------------------------
        // The code that we are adding to the generated transformation class
        // will call this method.
        // -------------------------------------------------------------------------
        public static class XmlReaderHelper
        {
            public static XmlDocument ReadXml(string fileName)
            {
                XmlDocument d = new XmlDocument();
    
                using (XmlReader reader = XmlReader.Create(fileName))
                {
                    try
                    {
                        d.Load(reader);
                    }
                    catch (System.Xml.XmlException e)
                    {
                        throw new DirectiveProcessorException("Unable to read the XML file.", e);
                    }
                }
                return d;
            }
        }
    }
    
  4. Pouze v jazyce Visual Basic otevřete nabídku Projekt a klepněte na příkaz Vlastní vlastnostiDP. Na kartě Aplikace v kořenovém oboru názvů odstraňte výchozí hodnotu . CustomDP

  5. V nabídce File (Soubor) klikněte na Save All (Uložit vše).

  6. V nabídce Sestavení klikněte na Sestavit řešení.

Sestavení projektu

Sestavte projekt. V nabídce Sestavení klikněte na Sestavit řešení.

Registrace procesoru direktiv

Než budete moct volat direktivu z textové šablony v sadě Visual Studio, musíte pro procesor direktiv přidat klíč registru.

Poznámka:

Pokud chcete procesor direktiv nainstalovat na více než jeden počítač, je lepší definovat rozšíření sady Visual Studio (VSIX), které obsahuje soubor .pkgdef spolu s sestavením. Další informace naleznete v tématu Nasazení vlastního procesoru direktiv.

Klíče procesoru direktiv se v registru nacházejí na následujícím místě:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\*.0\TextTemplating\DirectiveProcessors

64bitové systémy používají toto místo v registru:

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\*.0\TextTemplating\DirectiveProcessors

V tomto oddílu přidáte na stejné místo v registru klíč pro vlastní procesor direktiv.

Upozornění

Nesprávná úprava registru může vážně poškodit systém. Před prováděním změn registru zazálohujte všechna cenná data v počítači.

Přidání klíče registru pro procesor direktiv

  1. regedit Spusťte příkaz pomocí nabídka Start nebo příkazového řádku.

  2. Přejděte do umístění HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\*.0\TextTemplating\DirectiveProcessors a klikněte na uzel.

    V 64bitových systémech použijte HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\*.0\TextTemplating\DirectiveProcessors

  3. Přidejte nový klíč s názvem CustomDirectiveProcessor.

    Poznámka:

    Jedná se o název, který budete používat v poli Processor vlastních direktiv. Tento název se nemusí shodovat s názvem direktivy, názvem třídy procesoru direktiv ani s oborem názvů procesoru direktiv.

  4. Přidejte novou řetězcovou hodnotu s názvem Class, která má pro název nového řetězce hodnotu CustomDP.CustomDirectiveProcessor.

  5. Přidejte novou řetězcovou hodnotu s názvem CodeBase, jejíž hodnota se shoduje s cestou k souboru CustomDP.dll, který jste vytvořili dříve v tomto návodu.

    Například cesta může vypadat nějak C:\UserFiles\CustomDP\bin\Debug\CustomDP.dlltakto.

    Klíč registru by měl mít následující hodnoty:

    Name Type Data
    (Výchozí) REG_SZ (hodnota nenastavena)
    Třída REG_SZ CustomDP.CustomDirectiveProcessor
    CodeBase REG_SZ <Cesta k vašemu řešení>CustomDP\bin\Debug\CustomDP.dll

    Pokud jste sestavení vložili do mezipaměti GAC, měly by tyto hodnoty vypadat takto:

    Name Type Data
    (Výchozí) REG_SZ (hodnota nenastavena)
    Třída REG_SZ CustomDP.CustomDirectiveProcessor
    Sestavení REG_SZ CustomDP.dll
  6. Restartujte Visual Studio.

Testování procesoru direktiv

Při testování procesoru direktiv potřebujete napsat textovou šablonu, která ho zavolá.

V tomto příkladu volá textová šablona direktivu a předá název souboru XML, který obsahuje dokumentaci pro soubor třídy. Textová šablona používá XmlDocument vlastnost, kterou direktiva vytvoří, k navigaci xml a tisku komentářů dokumentace.

Vytvoření souboru XML pro testování procesoru direktiv

  1. Pomocí libovolného textového editoru (například Poznámkový blok) vytvořte soubor DocFile.xml.

    Poznámka:

    Tento soubor můžete vytvořit v libovolném umístění (například C:\Test\DocFile.xml).

  2. Do souboru XML přidejte následující kód:

    <?xml version="1.0"?>
    <doc>
        <assembly>
            <name>xmlsample</name>
        </assembly>
        <members>
            <member name="T:SomeClass">
                <summary>Class level summary documentation goes here.</summary>
                <remarks>Longer comments can be associated with a type or member through the remarks tag</remarks>
            </member>
            <member name="F:SomeClass.m_Name">
                <summary>Store for the name property</summary>
            </member>
            <member name="M:SomeClass.#ctor">
                <summary>The class constructor.</summary>
            </member>
            <member name="M:SomeClass.SomeMethod(System.String)">
                <summary>Description for SomeMethod.</summary>
                <param name="s">Parameter description for s goes here</param>
                <seealso cref="T:System.String">You can use the cref attribute on any tag to reference a type or member and the compiler will check that the reference exists.</seealso>
            </member>
            <member name="M:SomeClass.SomeOtherMethod">
                <summary>Some other method.</summary>
                <returns>Return results are described through the returns tag.</returns>
                <seealso cref="M:SomeClass.SomeMethod(System.String)">Notice the use of the cref attribute to reference a specific method</seealso>
            </member>
            <member name="M:SomeClass.Main(System.String[])">
                <summary>The entry point for the application.</summary>
                <param name="args">A list of command line arguments</param>
            </member>
            <member name="P:SomeClass.Name">
                <summary>Name property</summary>
                <value>A value tag is used to describe the property value</value>
            </member>
        </members>
    </doc>
    
  3. Soubor uložte a zavřete.

Vytvoření textové šablony pro testování procesoru direktiv

  1. V sadě Visual Studio vytvořte projekt knihovny tříd jazyka C# nebo Visual Basic s názvem TemplateTest.

  2. Přidejte nový soubor textové šablony s názvem TestDP.tt.

  3. Ujistěte se, že vlastnost Vlastní nástroj TestDP.tt je nastavena na TextTemplatingFileGeneratorhodnotu .

  4. Změňte obsah souboru TestDP.tt na následující text.

    Poznámka:

    Nahraďte řetězec <YOUR PATH> cestou k souboru DocFile.xml .

    Jazyk textové šablony se nemusí shodovat s jazykem procesoru direktiv.

    <#@ assembly name="System.Xml" #>
    <#@ template debug="true" #>
    <#@ output extension=".txt" #>
    
    <#  // This will call the custom directive processor. #>
    <#@ CoolDirective Processor="CustomDirectiveProcessor" FileName="<YOUR PATH>\DocFile.xml" #>
    
    <#  // Uncomment this line if you want to see the generated transformation class. #>
    <#  // System.Diagnostics.Debugger.Break(); #>
    
    <#  // This will use the results of the directive processor. #>
    <#  // The directive processor has read the XML and stored it in Document0. #>
    <#
        XmlNode node = Document0.DocumentElement.SelectSingleNode("members");
    
        foreach (XmlNode member in node.ChildNodes)
        {
            XmlNode name = member.Attributes.GetNamedItem("name");
            WriteLine("{0,7}:  {1}", "Name", name.Value);
    
            foreach (XmlNode comment in member.ChildNodes)
            {
                WriteLine("{0,7}:  {1}", comment.Name, comment.InnerText);
            }
            WriteLine("");
        }
    #>
    
    <# // You can call the directive processor again and pass it a different file. #>
    <# // @ CoolDirective Processor="CustomDirectiveProcessor" FileName="<YOUR PATH>\<Your Second File>" #>
    
    <#  // To use the results of the second directive call, use Document1. #>
    <#
        // XmlNode node2 = Document1.DocumentElement.SelectSingleNode("members");
    
        // ...
    #>
    

    Poznámka:

    V tomto příkladu je CustomDirectiveProcessorhodnota parametru Processor . Hodnota parametru Processor se musí shodovat s názvem klíče registru procesoru.

  5. V nabídce Soubor zvolte Uložit vše.

Testování procesoru direktiv

  1. V Průzkumník řešení klepněte pravým tlačítkem myši na TestDP.tt a potom klepněte na tlačítko Spustit vlastní nástroj.

    U uživatelů jazyka Visual Basic se ve výchozím nastavení nemusí testDP.txt zobrazovat v Průzkumník řešení. Pokud chcete zobrazit všechny soubory přiřazené k projektu, otevřete nabídku Projekt a klikněte na Zobrazit všechny soubory.

  2. V Průzkumník řešení rozbalte uzel TestDP.txt a poklikáním na TestDP.txt ho otevřete v editoru.

    Zobrazí se vygenerovaný textový výstup. Výstup by měl vypadat takto:

       Name:  T:SomeClass
    summary:  Class level summary documentation goes here.
    remarks:  Longer comments can be associated with a type or member through the remarks tag
    
       Name:  F:SomeClass.m_Name
    summary:  Store for the name property
    
       Name:  M:SomeClass.#ctor
    summary:  The class constructor.
    
       Name:  M:SomeClass.SomeMethod(System.String)
    summary:  Description for SomeMethod.
      param:  Parameter description for s goes here
    seealso:  You can use the cref attribute on any tag to reference a type or member and the compiler will check that the reference exists.
    
       Name:  M:SomeClass.SomeOtherMethod
    summary:  Some other method.
    returns:  Return results are described through the returns tag.
    seealso:  Notice the use of the cref attribute to reference a specific method
    
       Name:  M:SomeClass.Main(System.String[])
    summary:  The entry point for the application.
      param:  A list of command line arguments
    
       Name:  P:SomeClass.Name
    summary:  Name property
      value:  A value tag is used to describe the property value
    

Přidání KÓDU HTML do generovaného textu

Po otestování vlastního procesoru direktiv můžete k vygenerovanému textu přidat kód HTML.

Přidání kódu HTML k vygenerovanému textu

  1. Nahraďte kód v TestDP.tt následujícím kódem. Kód HTML je zvýrazněn. Nezapomeňte nahradit řetězec YOUR PATH cestou k souboru DocFile.xml .

    Poznámka:

    Další otevřené <# a zavřít značky #> odděluje kód příkazu od značek HTML.

    <#@ assembly name="System.Xml" #>
    <#@ template debug="true" #>
    <#@ output extension=".htm" #>
    
    <#  // This will call the custom directive processor #>
    <#@ CoolDirective Processor="CustomDirectiveProcessor" FileName="<YOUR PATH>\DocFile.xml" #>
    
    <#  // Uncomment this line if you want to see the generated transformation class #>
    <#  // System.Diagnostics.Debugger.Break(); #>
    
    <html><body>
    
    <#  // This will use the results of the directive processor #>.
    <#  // The directive processor has read the XML and stored it in Document0#>.
    <#
        XmlNode node = Document0.DocumentElement.SelectSingleNode("members");
    
        foreach (XmlNode member in node.ChildNodes)
        {
    #>
    <h3>
    <#
            XmlNode name = member.Attributes.GetNamedItem("name");
            WriteLine("{0,7}:  {1}", "Name", name.Value);
    #>
    </h3>
    <#
            foreach (XmlNode comment in member.ChildNodes)
            {
                WriteLine("{0,7}:  {1}", comment.Name, comment.InnerText);
    #>
    <br/>
    <#
            }
        }
    #>
    </body></html>
    
  2. V nabídce Soubor klepněte na tlačítko Uložit TestDP.txt.

  3. Chcete-li zobrazit výstup v prohlížeči, v Průzkumník řešení klikněte pravým tlačítkem na TestDP.htm a klepněte na příkaz Zobrazit v prohlížeči.

    Výstup by měl být stejný jako původní text s tím rozdílem, že má použitý formát HTML. Název každé položky se zobrazí tučně.