ImmDoc .NET - a Lightweight Tool for Generating HTML Documentation

December 12, 2011

Update

The project is now hosted on GitHub: https://github.com/marek-stoj/ImmDoc.NET.

Introduction

Do you remember Visual Studio 2003 and its nice feature of generating HTML documentation for your projects? I bet many of you do and just as I miss it now because unfortunately in Visual Studio 2005 it's no longer present.

Recently I needed such functionality to document a .NET library I've been developing and just couldn't find the right tool to do the job. I'm aware of the existence of NDoc but as far as I know it's quite dormant these days and it lacks support for .NET Framework 2.0. So I decided to spare some time to develop such tool by myself hoping that I would learn something new and that it'll be useful - not only for me but also for other programmers out there.

Below I put the list of topics which I had to broaden my knowledge on to complete the project so you know what you can learn by analyzing the provided source code:

  • reflection mechanisms,
  • subtleties of C# and MSIL,
  • regular expressions,
  • syntax of the XML comment files generated by the compiler,
  • how to read XML files and programmatically validate them against a schema definition,
  • embedded resources.

Example

Before I give you more technical details about ImmDoc .NET you may want to visit this page and see the example output generated by this tool - documentation of three arbitrarily chosen assemblies from .NET Framework: System.Runtime.Remoting, System.Security and System.Transactions.

Usage

Currently there's only command-line interface available for ImmDoc .NET. One of the reasons for this is the fact that creating GUI requires some time :) However thanks to this approach one can easily use the program from batch scripts or tools supporting the build process such as NAnt.

The usage of ImmDoc .NET is pretty easy and straightforward. You can put all your assemblies and XML comment files in one folder along with the ImmDoc .NET executable, then run the program and after some processing you'll get a doc folder containing generated documentation. You can also explicitly give the names of the files you want ImmDoc .NET to process and you can use a few options which I list below:

  • -pn, -ProjectName:STRING - use this switch if you want to give your documentation project a meaningful name,
  • -ex, -Exclude:FILE - if you don't explicitly give the file names you can use this switch to exclude particular files from processing,
  • -od, -OutputDirectory:DIR - use this switch to specify the name of the directory where you want to have your documentation generated,
  • -fd, -ForceDelete - ImmDoc .NET will not delete the output directory if it already exists unless you use this switch,
  • -vl, -VerboseLevel:LEVEL - you can set various levels of verbosity (the greater the level is the more output from the program you'll get; LEVEL can be from 0 (no output) to 3 (full output).

There is also a feature which needs explicit mentioning. Because it's not possible to put XML comments on namespaces and because you can document multiple assemblies together, ImmDoc .NET provides a way to add additional comments. To do this you have to create XML file with .docs extension. Syntax of this file is very simple but note that it'll be validated against a schema (namely AdditionalDocumentation.xsd - you can find it in the zip archive with the source code). After you've created such file all you have to do is put it in one folder with your assemblies and XML comment files you want to process or just explicitly give its name as an argument to the program. Below is the example:

<?xml version="1.0" encoding="utf-8"?>
  <summaries>

    <assembly name="SomeAssembly">
      <summary>
        Description of the assembly goes here.
      </summary>
      <namespace name="Some.Namespace">
        <summary>
          Description of the namespace goes here.
        </summary>

      </namespace>
      ...
    </assembly>
    ...
  </summaries>

Project architecture and design

ImmDoc .NET comprises two assemblies (which are at the end merged by ILMerge tool, so that's why there's only one .exe file), namely ImmDocNetLib which actually does the whole job of analyzing assemblies and XML comment files and ImmDocNet - a console application which uses ImmDocNetLib and provides the interface for the user.

As I've already said ImmDocNetLib has two main responsibilities: analysis of given assemblies and generating the documentation. Let's have a closer look at the first task wich in principle involves using reflection to obtain detailed information about assemblies and modules they contain.

You'll find the most important classes in the Imm.ImmDocNetLib.MyReflection.MetaClasses namespace. These classes are lightweight and more or less exact equivalents of classes from System.Reflection namespace. Essentially almost all of them inherit from MetaClass class which among other things contains properties like Name, Summary and Remarks. Other classes add specific details concerning entities they represent, eg. MyMethodInfo holds information about return type and parameters. Such information is often represented in ImmDoc .NET by other classes which inherit from MetaClass (like for example MyParameterInfo, MyFieldInfo etc).

So in the first step of the processing information obtained using ordinary reflection mechanisms is combined with comments extracted from XML comment files to form an internal representation of given assemblies. Below is the UML diagram which will hopefully give you some overview of this representation (to avoid clutter not all classes are included):

After collecting all needed information it's finally time to actually start generating some documentation. Relevant classes are contained in the Imm.ImmDocNetLib.Documenters namespace. We have there some simple abstract class named Documenter from which we can derive other classes responsible for generating documentation in whatever format we like. I've implemented HTMLDocumenter class which creates set of HTML, CSS and JavaScript files which together constitute a nice MSDN-like documentation of given assemblies.

HTMLDocumenter is rather a big class so in order to reuse some functionality one would probably want to do some high-level refactoring before implementing one's own documenter.

To give you general overview of responsibilities of HTMLDocumenter I've created simple UML diagram which lists example methods implemented by this class.

Here are short descriptions of above methods:

bool GenerateDocumentation(
  string outputDirectory,
  DocumentationGenerationOptions options)

Initiates the process of generating documentation. Invokes such methods as PrepareOutputDirectory(), GenerateMainIndex(), ExtractStyleSheets(), ProcessAssemblies(), GenerateTableOfContents(), etc.

void CreateInvokableMembersOverloadsIndex(
    MyInvokableMembersOverloadsInfo myInvokableMembersOverloadsInfo,
    MyClassInfo declaringType,
    string dirName)

This method creates an HTML page which will contain index of all overloads of a particular method or constructor.

string CreateNamespaceMemberSyntaxString(MyClassInfo namespaceMember)

This method is responsible for creating a type declaration string. Such string comprises for example visibility modifier, base class, implemented interfaces, constraints on generic parameters, etc.

void ExtractBinaryResourceToFile(string resourceName, string fileName)

There are couple of graphics used in generated pages and this method is used to extract specified embedded resource to a physical file.

string ProcessComment(string contents)

This method processes every comment found in XML file in order to replace such tags as <code>, <c>, <see> and so on with appropriate HTML tags.

void WriteIndexHeader(
  StreamWriter sw,
  string pageTitle, 
  string[] sectionsNamesAndIndices)

This method writes a common HTML header used by almost all generated pages.

string ResolveLink(MetaClass metaClass)

For the generated documentation to be usable it is necessary to provide some navigation between pages and this method aids with the task of creating hyperlinks (for example to the particular member of a class).

Summary

I hope that someone will find ImmDoc .NET useful (I know I do ;). I'm waiting for some feedback and if there is some interest I'll continue to develop this project (it still lacks some minor features and after all a user-friendly front-end would be a nice thing to have). So if you have some suggestions, questions or maybe you've found a bug I'd be more than glad to "hear" from you - my e-mail is: marek.stoj@gmail.com.