Click here to Skip to main content
15,881,424 members
Articles / Database Development / SQL Server

SQL XML Documentation

Rate me:
Please Sign up or sign in to vote.
4.59/5 (12 votes)
29 Feb 2008CPOL5 min read 112.2K   1.4K   75  
How to create and compile SQL XML Documentation comments
// Originaly created by
//     Stephen Toub [stoub@microsoft.com]
// Modified by: 
//     Evaldas Jocys [evaldas@jocys.com]
//
// XmlComments.cs
// Retrieve the xml comments stored in the assembly's comments file
// for specific types or members of types.
using System;
using System.IO;
using System.Xml;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.Collections;
using System.Runtime.InteropServices;

namespace JocysCom.Sql.XmlDocumentation
{
	public partial class XmlComments
	{
		#region Static Variables
		/// <summary>Hashtable of all loaded XmlDocument comment files for assemblies.</summary>
		private static Hashtable _assemblyDocs = new Hashtable();
		/// <summary>
		/// Hashtable, indexed by Type, of all the accessors for a type.  Each entry is a Hashtable, 
		/// indexed by MethodInfo, that returns the MemberInfo for a given MethodInfo accessor.
		/// </summary>
		private static Hashtable _typeAccessors = new Hashtable();
		/// <summary>Binding flags to use for reflection operations.</summary>
		private static BindingFlags _bindingFlags =
			BindingFlags.Instance | BindingFlags.Static |
			BindingFlags.Public | BindingFlags.NonPublic;
		#endregion

		#region Construction
		/// <summary>Gets the XML comments for the calling method.</summary>
		public static XmlComments Current
		{
			get { return new XmlComments(new StackTrace().GetFrame(1).GetMethod()); }
		}

		/// <summary>Initializes the XML comments for the specified member.</summary>
		/// <param name="mi">The member for which we want to retrieve the XML comments.</param>
		public XmlComments(MemberInfo mi)
		{
			if (mi == null) throw new ArgumentNullException("mi", "A MemberInfo must be supplied in order to retrieve the comments.");

			// If this is an accessor, get the owner property/event of the accessor.
			if (mi is MethodInfo)
			{
				MemberInfo owner = IsAccessor((MethodInfo)mi);
				if (owner != null) mi = owner;
			}

			// Get the comments.  If we got any, parse out the important stuff.
			_comments = GetComments(mi);
			InitComments();
		}

		#endregion

		#region Parsing the XML Document for an Assembly
		/// <summary>Retrieve the XML comments for a type or a member of a type.</summary>
		/// <param name="mi">The type or member for which comments should be retrieved.</param>
		/// <returns>A string of xml containing the xml comments of the selected type or member.</returns>
		private static XmlNode GetComments(MemberInfo mi)
		{
			Type declType = (mi is Type) ? ((Type)mi) : mi.DeclaringType;
			XmlDocument doc = LoadAssemblyComments(declType.Assembly);
			if (doc == null) return null;
			string xpath;

			// The fullname uses plus signs to separate nested types from their declaring
			// types.  The xml documentation uses dotted-notation.  We need to change
			// from one to the other.
			string typeName = declType.FullName.Replace("+", ".");

			// Based on the member type, get the correct xpath query to lookup the 
			// member's comments in the assembly's documentation.
			switch (mi.MemberType)
			{
				case MemberTypes.NestedType:
				case MemberTypes.TypeInfo:
					xpath = "//member[@name='T:" + typeName + "']";
					break;

				case MemberTypes.Constructor:
					xpath = "//member[@name='M:" + typeName + "." +
						"#ctor" + CreateParamsDescription(((ConstructorInfo)mi).GetParameters()) + "']";
					break;

				case MemberTypes.Method:
					xpath = "//member[@name='M:" + typeName + "." +
						mi.Name + CreateParamsDescription(((MethodInfo)mi).GetParameters());
					if (mi.Name == "op_Implicit" || mi.Name == "op_Explicit")
					{
						xpath += "~{" + ((MethodInfo)mi).ReturnType.FullName + "}";
					}
					xpath += "']";
					break;

				case MemberTypes.Property:
					xpath = "//member[@name='P:" + typeName + "." +
						mi.Name + CreateParamsDescription(((PropertyInfo)mi).GetIndexParameters()) + "']"; // have args when indexers
					break;

				case MemberTypes.Field:
					xpath = "//member[@name='F:" + typeName + "." + mi.Name + "']";
					break;

				case MemberTypes.Event:
					xpath = "//member[@name='E:" + typeName + "." + mi.Name + "']";
					break;

				// Unknown type, nothing to do
				default:
					return null;
			}

			// Get the appropriate node from the document
			return doc.SelectSingleNode(xpath);
		}

		/// <summary>Determines if a MethodInfo represents an accessor.</summary>
		/// <param name="mi">The MethodInfo to check.</param>
		/// <returns>The MemberInfo that represents the property or event if this is an accessor; null, otherwise.</returns>
		private static MemberInfo IsAccessor(MethodInfo mi)
		{

			// Must be a special name in order to be an accessor
			if (!mi.IsSpecialName) return null;

			Hashtable accessors;
			lock (_typeAccessors.SyncRoot)
			{
				// We cache accessor information to speed things up, so check to see if the array
				// of accessors for this type has already been computed.
				accessors = (Hashtable)_typeAccessors[mi.DeclaringType];
				if (accessors == null)
				{
					// Retrieve the accessors for the declaring type
					_typeAccessors[mi.DeclaringType] = accessors = RetrieveAccessors(mi.DeclaringType);
				}
			}

			// Return the owning property or event for the accessor
			return (MemberInfo)accessors[mi];
		}

		/// <summary>Retrieve all property and event accessors on a given type.</summary>
		/// <param name="t">The type from which the accessors should be retrieved.</param>
		/// <returns>A dictionary of all accessors.</returns>
		private static Hashtable RetrieveAccessors(Type t)
		{
			// Build up list of accessors to exclude from method list
			Hashtable ht = new Hashtable();

			// Get all property accessors
			foreach (PropertyInfo pi in t.GetProperties(_bindingFlags))
			{
				foreach (MethodInfo mi in pi.GetAccessors(true)) ht[mi] = pi;
			}

			// Get all event accessors
			foreach (EventInfo ei in t.GetEvents(_bindingFlags))
			{
				MethodInfo addMethod = ei.GetAddMethod(true);
				MethodInfo removeMethod = ei.GetRemoveMethod(true);
				MethodInfo raiseMethod = ei.GetRaiseMethod(true);

				if (addMethod != null) ht[addMethod] = ei;
				if (removeMethod != null) ht[removeMethod] = ei;
				if (raiseMethod != null) ht[raiseMethod] = ei;
			}

			// Return the whole list
			return ht;
		}

		/// <summary>Generates a parameter string used when searching xml comment files.</summary>
		/// <param name="parameters">List of parameters to a member.</param>
		/// <returns>A parameter string used when searching xml comment files.</returns>
		private static string CreateParamsDescription(ParameterInfo[] parameters)
		{
			System.Collections.Generic.Dictionary<string, Type> pars;
			pars = new System.Collections.Generic.Dictionary<string, Type>();
			string[] keys = new string[parameters.Length];
			pars.Keys.CopyTo(keys, 0);
			for (int i = 0; i < keys.Length; i++)
				{
				pars.Add(parameters[i].ParameterType.FullName, parameters[i].ParameterType);
				}
			// Return the parameter list description
			return CreateParamsDescription(pars);
		}

		#endregion

		#region Loading the Assembly's XML Comments
		/// <summary>Get the xml documentation for an assembly.</summary>
		/// <param name="a">The assembly whose documentation is to be loaded.</param>
		/// <returns>The xml documentation for an assembly; null if none found.</returns>
		public static XmlDocument LoadAssemblyComments(Assembly a)
		{
			// Get xml dom for the assembly's documentation
			XmlDocument doc;
			lock (_assemblyDocs.SyncRoot)
			{
				if (!_assemblyDocs.ContainsKey(a.FullName))
				{
					string xmlPath = DetermineXmlPath(a);
					if (xmlPath == null) return null;

					// Load it and store it
					doc = new XmlDocument();
					doc.Load(xmlPath);
					_assemblyDocs[a.FullName] = doc;
				}
				// Get the docs from the cache
				else doc = (XmlDocument)_assemblyDocs[a.FullName];
			}

			// Return the docs
			return doc;
		}

		/// <summary>Gets the path to a valid xml comments file for the assembly.</summary>
		/// <param name="asm">The assembly whose documentation is to be found.</param>
		/// <returns>The path to documentation for an assembly; null if none found.</returns>
		private static string DetermineXmlPath(Assembly asm)
		{
			// Get a list of locations to examine for the xml
			// 1. The location of the assembly.
			// 2. The runtime directory of the framework.
			string[] locations = new string[]
				{   
					asm.Location,
					RuntimeEnvironment.GetRuntimeDirectory() + Path.GetFileName(asm.CodeBase)
				};

			// Checks each path to see if the xml file exists there; if it does, return it.
			foreach (string location in locations)
			{
				string newPath = Path.ChangeExtension(location, ".xml");
				if (File.Exists(newPath)) return newPath;
			}

			// No xml found; return null.
			return null;
		}
		#endregion

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions