|
// 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.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.