Snippets 'n XML

There is an extensive collection of Visual Studio 2005 C# snippets available for download.  In this post I'll take a look at these snippets, and show how you can use simple XML syntax to parse the raw code that lies behind the snippets. The code used in this program can be downloaded and run in Visual Studio 2005.

Snippets provide a useful means of learning about the C# language. They are a bit like an interactive tutorial, or an interactive version of a 101 tips document. Snippets are not a cure all. For one thing, you can't use them unless you know they exist, and it is difficult to remember all the existing snippets and the shortcuts that make them available.

I encountered this problem when I first loaded the nearly 300 snippets from the download into the IDE. There was no easy way to discover what snippets were available or what shortcuts would allow me to access them. To help mitigate this problem, I wrote a simple program to parse the snippets and display them in a single XHTML file. This file is relatively easy to view and relatively easy to search.

Snippet Basics

You can download and install the 300 snippets with a few clicks of the mouse. After running the installation program, you will want to enter the IDE, and press Ctrl-k, Ctrl-b to access the Snippet Manager. The manager should also be available on the Tools menu. If it is not, then right click on the menu bar, choose Customize from the popup, find the Tools category, and drag the Snippet Manager on to the tools menu.

In the Snippet Manager, press the Add button, browse to the place where the snippets were installed, and press the Open button. By default, the snippets should be found in a directory called MSDN located beneath your (My) Documents directory. You will probably need to restart the IDE before the snippets are available for use.

Access snippets by typing code in the IDE. For instance, type in the letters cw, and press tab. The following code should be printed out in the editor:

 Console.WriteLine();

I'm cheating a bit here, because the cw snippet ships with Visual Studio. To make things more interesting, let's try the first snippet in the list of snippets that were just installed. Create a default Console Application. Type the new snippet, called osUser. When you type it in and press tab, it resolves to code that looks like this.

string userName = Environment.UserName;

You can use both this snippet, and the cw snippet, in a single program:

    1:  using System;
    2:  using System.Collections.Generic;
    3:  using System.Text;
    4:   
    5:  namespace ConsoleApplication1
    6:  {
    7:      class Program
    8:      {
    9:          static void Main(string[] args)
   10:          {
   11:              string userName = Environment.UserName;
   12:              Console.WriteLine(userName);
   13:              Console.ReadLine();
   14:          }
   15:      }
   16:  }

Voila. Using Visual Studio and snippets you were able to create a whole program, and only had to type the single line of code found on line 13.

Is it C# or Is it VB?

Let's try another snippet, with the mellifluous name of appActNa.  This is a very strange little snippet, but I'm showing it to you precisely because it is bizarre and unexpected, and also because it is a bit tricky to use. In particular, you have to change your project's references before the code generated from this snippet will compile. Many snippets ask you to take this step, so it is worth taking a moment to see how it works.

Remove the code you created in the last section and type the snippet appActNa directly into the Main method of your console application. This snippet produces the following code:

Microsoft.VisualBasic.Interaction.AppActivate("Untitled - Notepad");

One can be forgiven for assuming that this code must be some kind of mistake. After all, we are in C#, not Visual Basic. It turns out, however, that one can run an assembly called Microsoft.VisualBasic from inside a C# program without ever writing VB code.

To start figuring out what is going on, run the Snippet Manager or reference the snippet HTML file I mentioned earlier. Here is the entry for appActNa:

appActNa Activates a running application using the name of the application. For this code snippet to compile, add a reference to Microsoft.VisualBasic.dll.

As you can see, the description of the snippet specifies that you must reference a file called Microsoft.VisualBasic.dll. To follow the instructions laid out here, bring up the Solution Explorer (Ctrl-Alt-L), and right click on the References node. Choose Add Reference from popup menu, and Browse down to Microsoft.VisualBasic. Click the OK button to add the reference to your project.

Now bring up an empty copy of NotePad. By default, the message bar at the top should read "Untitled - Notepad." If that is the case, then running your program should bring your copy of notepad to the foreground.

Examining Snippet XML

In this section we are going to look at snipped called dtTableAdaptPartial.  When triggered, it produces this code:

    1:      namespace NorthwindDataSetTableAdapters
    2:      {
    3:          public partial class CustomersTableAdapter
    4:          {
    5:              
    6:          }
    7:      }

The Snippets themselves are stored in XML files with an extension of .snippet. Here is the relatively simple one used to define dtTableAdaptPartial:

Listing One: The XML used to define a snippet

    1:  <?xml version="1.0"?>
    2:  <CodeSnippets xmlns="https://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    3:    <CodeSnippet Format="1.0.0">
    4:      <Header>
    5:        <Title>Extend a TableAdapter w/Partial Classes</Title>
    6:        <Author>Microsoft</Author>
    7:        <Description>Extends a TableAdapter using partial classes.</Description>
    8:        <Shortcut>dtTableAdaptPartial</Shortcut>
    9:        <SnippetTypes>
   10:          <SnippetType>Expansion</SnippetType>
   11:        </SnippetTypes>
   12:      </Header>
   13:      <Snippet>
   14:        <Declarations>
   15:          <Literal>
   16:            <ID>Namespace</ID>
   17:            <Type>String</Type>
   18:            <ToolTip>DataSet the TableAdapters function upon</ToolTip>
   19:            <Default>NorthwindDataSet</Default>
   20:          </Literal>
   21:          <Literal>
   22:            <ID>TableAdapter</ID>
   23:            <Type>String</Type>
   24:            <ToolTip>The name of the TableAdapter you wish to expand</ToolTip>
   25:            <Default>CustomersTableAdapter</Default>
   26:          </Literal>
   27:        </Declarations>
   28:        <Code Language="csharp">
   29:          <![CDATA[namespace $Namespace$TableAdapters
   30:          {
   31:            public partial class $TableAdapter$
   32:            {
   33:              $end$
   34:            }
   35:          }]]>
   36:        </Code>
   37:      </Snippet>
   38:    </CodeSnippet>
   39:  </CodeSnippets>

To save space, I shortened the text in the Description and in the first of the two ToolTip fields.

There is no need to go through the XML schema of the code in Listing One line by line. Certain obvious features, such as the Title (line 5), Description (line 7) and Shortcut (line 8) elements, all tend to stand out. Most of the XML is self explanatory. For instance, the Shortcut element defines the bit of code that you type into the IDE in order to trigger the snippet. In this case, that code would be dtTableAdaptPartial.

Looking down to line 28, you see the XML that defines the code that will be generated in the IDE. Again, the syntax is relatively self explanatory, except for the bits that have dollar signs around them. This is the syntax that snippets use to reference the Literals found in the Declaration section which starts on line 14. For instance, the $TableAdapter$ declaration begins on line 21.

After inserting the snippet in the IDE, you can tab back and forth between the $Namespace$ and $TableAdapter$ objects. These are the elements you will typically replace with variables specific to your own program. For instance, your TableAdapter is probably not called the CustomersTableAdapter, but something specific to your program such as MyTableAdapter. Holding your mouse over these objects brings up the tooltips defined in lines 18 and 24.

The value $end$ is used to designate the place in your code where the cursor should be located after the snippet is inserted.

Parsing the Snippet

The code to parse one of these XML files is quite simple. Some of the "tricky" parts can even be generated from the XML snippets found in the xml section at the bottom of our HTML page.

Listing Two: Simple code for parsing the XML in a snippet file.

    1:  using System;
    2:  using System.Collections.Generic;
    3:  using System.Text;
    4:  using System.Xml;
    5:   
    6:  namespace ReadXmlSnippets
    7:  {
    8:      class ParseSnippet
    9:      {
   10:          private CreateHtml createHtml;
   11:          private XmlDocument doc = null;
   12:   
   13:          public ParseSnippet(CreateHtml createHtml)
   14:          {
   15:              this.createHtml = createHtml;
   16:          }
   17:   
   18:          private void ShowNode(string elementName)
   19:          {
   20:              XmlNodeList nodes;
   21:              nodes = doc.GetElementsByTagName(elementName);            
   22:              createHtml.WriteTableDelimiter(nodes[0].FirstChild.Value, 1);
   23:          }
   24:   
   25:          public void Parse(string directory, string fileName)
   26:          {            
   27:              createHtml.StartTableRow("LightYellow");
   28:              doc = new XmlDocument();
   29:              doc.Load(fileName);
   30:              ShowNode("Shortcut");
   31:              ShowNode("Description");
   32:              createHtml.EndTableRow();
   33:          }
   34:      }
   35:  }

The CreateHtml class referenced here is custom code that helps you generate valid XHTML with a minimum of fuss. It has simple methods in it such as the following, which is used to designate the end of XHTML file:

    1:  public void StopHtml()
    2:  {            
    3:    AppendToTextFile("</body>");
    4:    AppendToTextFile("</html>");
    5:  }

The complete code for the CreateHtml.cs file is included in the sample codee associated with this post.

The code on lines 28 and 29 of Listing Two shows how to create an XmlDocument, and how to load a snippet into it. The XmlDocument class provides many utility methods that make it easy for you to load and parse an XML file. For instance, up on line 21 you can see the GetElementsByTagName method,  which is used to retrieve a particular node from an XML file. (You can use the xmlFind snippet to generate most of the code in the ShowNode method that contains the call to GetElementsByTagName. )

Finding the Snippet Files

Since there are nearly 300 snippet files in the download from the C# site, one needs some reasonable way to locate each file that needs to be parsed. The code found in Listing Three shows the simple solution to this problem.

Listing Three: Simple code for iterating over the directories that contain the snippet files.

    1:  using System;
    2:  using System.Collections.Generic;
    3:  using System.Text;
    4:  using System.Collections;
    5:   
    6:  namespace ReadXmlSnippets
    7:  {
    8:      class XmlSnippets
    9:      {
   10:          private CreateHtml createHtml = 
   11:              new CreateHtml(@"..\..\snippets.html");
   12:          private string saveDirectory = "";       
   13:          private string snippetDirectory; 
   14:   
   15:          private String GetMyDocumentsDir() 
   16:          { 
   17:              return Environment.GetFolderPath(
   18:                  Environment.SpecialFolder.Personal); 
   19:          }
   20:   
   21:          public void RunSearch()
   22:          {            
   23:              snippetDirectory = GetMyDocumentsDir() + 
   24:                  @"\MSDN\Visual C# 2005 Code Snippets\";
   25:              List<FileData> files = new List<FileData>();
   26:              DirSearch(snippetDirectory, ref files);
   27:   
   28:              ShowCollection(files);
   29:              Console.ReadLine();
   30:          }
   31:   
   32:          private void ShowCollection(List<FileData> files)
   33:          {
   34:              createHtml.StartHtml();
   35:              createHtml.StartTable();
   36:              
   37:              int count = 0;
   38:              ParseSnippet parseSnippet = new ParseSnippet(createHtml);
   39:              foreach (FileData fileData in files)
   40:              {
   41:                  if (!fileData.Directory.Equals(saveDirectory))
   42:                  {
   43:                      createHtml.WriteSingleRow(
   44:                          fileData.Directory.Substring(snippetDirectory.Length));
   45:                      saveDirectory = fileData.Directory;
   46:                  }
   47:                  parseSnippet.Parse(fileData.Directory, fileData.FileName);
   48:                  count++;
   49:                  Console.WriteLine(fileData.FileName);
   50:              }
   51:   
   52:              createHtml.EndTable();
   53:              createHtml.StopHtml();
   54:          }
   55:   
   56:          private void DirSearch(string sDir, ref List<FileData> files)
   57:          {
   58:              try
   59:              {
   60:                  string[] dirs = System.IO.Directory.GetDirectories(sDir);
   61:   
   62:                  foreach (string foundDirectoryName in dirs)
   63:                  {
   64:                      foreach (string foundFileName in 
   65:                          System.IO.Directory.GetFiles(foundDirectoryName, 
   66:                          "*.*"))
   67:                      {
   68:                          files.Add(new FileData(foundDirectoryName, 
   69:                              foundFileName));
   70:                      }
   71:                      DirSearch(foundDirectoryName, ref files);
   72:   
   73:                  }
   74:              }
   75:              catch (System.Exception excpt)
   76:              {
   77:                  Console.WriteLine(excpt.Message);
   78:              }
   79:          }
   80:      }
   81:  }

The code shown here is designed to run unchanged inside of the IDE without the user having to enter any parameters. This means there are several compromises that you may want to customize in your own versions of this code.

I'm careful not to hardcode in the path to the snippets, but instead use a method, shown in lines 15 through 19, that retrieves the user's My Document directory. This should work, so long as you installed the snippets into the default directory. In line eleven I place the the HTML file that is output by the program in the root of the project directory.

The code in the DirSearch method, found on line 56, is nearly straight snippet code accessed by typing filSearchDir. It uses the GetDirectories and GetFiles methods of the System.IO.Directory namespace.

Summary

This article explains a few facts about snippets, and shows how you can use the XML API's that ship with Visual Studio to simply and easily parse a file. You can learn more about Code Snippets by reading the MSDN documentation.

kick it on DotNetKicks.com

ReadXmlSnippets.zip