Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

How to Convert XML comments in a C# Source File into HTML Pages

5.00/5 (7 votes)
12 Aug 2024CPOL13 min read 8.3K   119  
This article presents a tool that convert XML comments in a C# source file to HTML pages.

Table of Contents

The symbol Table of Contents returns the reader to the top of the Table of Contents.

1. Background Table of Contents

There have been a number of recent comments on CodeProject that discuss the conversion of XML comments in C# source file into HTML pages. This article presents a tool that will perform such a service.

2. Introduction Table of Contents

During my career, I have written enumerable APIs that either support my work or support the work of others. Until recently (about 2010), I was forced to write commentary that would guide others and me in the use of the APIs. The process was tedious. Worse, there were times that the commentary no longer reflected the contents of an API.

Unbeknownst to me, Microsoft had developed what was called "XML Comments" and had incorporated them into the C# compiler. They are a factor in C# IntelliSense [^] providing completion lists as methods are entered into source code.

3. XML Comments Table of Contents

XML comments are structured comments that can be used to produce API documentation. In Microsoft's view, the C# compiler uses these comments to generate an XML file that contains structured data representing the comments and the API signatures. Microsoft suggests that "Other tools can process that XML output to create human-readable documentation in the form of web pages or PDF files."

The problem with the compiler-generated XML file is that it is incomplete. It fails to generate XML elements for namespaces and classes without XML comments preceding them. Furthermore, the XML element for class is the same as the XML elements for interface, struct, enum, and delegate without disambiguation. Lastly, the documentation for XML comments is poorly written with some portions wholly inadequate. It was for these reasons that I decided to develop a tool that would convert XML comments imbedded in C# source files into an HTML document.

3.1. Recognized and processed XML Comment Tags Table of Contents

The following depicts the XML comment tags that are processed by XML2HTML (the items in Red are not processed.

<summary>description</summary>
<remarks>description</remarks>
<returns>description</returns>
<param name="name">description</param>
<paramref name="name"/>
<exception cref="member">description</exception>
<value>property-description</value>
<para>paragraph</para>
<list type="bullet|number|table">
    <listheader>
        <term>term</term>
        <description>description</description>
    </listheader>
    <item>
        <term>Assembly</term>
        <description>description</description>
    </item>
</list>
<c>text</c>
<code>
    var index = 5;
    index++;
</code>
<example>
This shows how to increment an integer.
<code>
    var index = 5;
    index++;
</code>
</example>
<inheritdoc [cref=""] [path=""]/>
<include file='filename' path='tagpath[@name="id"]'/>
<see cref="member"/>
<see cref="member">Link text</see>
<see href="link">Link Text</see>
<see langword="keyword"/>
<seealso cref="member"/>
<seealso href="link">Link Text</seealso>
<typeparam name="TResult">The type returned from this method</typeparam>
<typeparamref name="TKey"/>

Tags were eliminated because either the tag made a reference to a value known only to the C# compiler (that XML2HTML is not), such as "cref" or "TResult", or that the documentation describing the tag was poorly written with some portions wholly inadequate (<list type="table">...). <see href=... was eliminated because the same effect can be obtained with <seealso href=....

3.2. Format of HTML Documentation Table of Contents

There are no restrictions on the ordering of methods and constructors in C# source files. Private and public methods may be interspersed. However when it comes to documentation, ordering becomes important for reader understanding. With this in mind, an HTML page format was defined and is depicted below.

<class-name> CLASS
Definition
  Assembly: <library-name>.dll
  Namespace: <namespace>
  <description>
Remarks
  <remarks>
Example
  <example>
See Also
  <see-also>
<class-name> Constructors
  <class-name> Constructor
  <signature>  <description>
  Parameters
    <parameters>
  Remarks
    <remarks>
  Example
    <example>
  See Also
    <see-also>
<class-name> Methods
  <class-name> Method
  <signature>  <description>
  Parameters
    <parameters>
  Returns
    <returns>
  Remarks
    <remarks>
  Example
    <example>
  See Also
    <see-also>

This ordering is data-driven (by Object_Type, in Object_Type.cs, for the ordering of major items (CLASS, CONSTRUCTOR, METHOD, etc.) and by Symbol, in Symbols.cs for the ordering of XML tags).

4. A Note on Parsing Table of Contents

Parsing is intrinsic to XML2HTML. However, there are two flavors:

  1. Parsing the C# source file.
  2. Parsing the XML comments.

4.1. Parsing the C# Source File Table of Contents

The C# source file parsing is relatively straight-forward. All that needs to be recognized in the C# source file are lines containing XML comments and associated public signatures.

set in_XML_comment to false
repeat
    read a c# source line
    if ( not c# source file eof )
        trim line of leading spaces
        if ( the first three characters are "///" )
            if ( not in_XML_comment )
                set in_XML_comment to true
                create XML_comment object
            endif
            append line to text of XML_comment
        else if ( in_XML_comment )
            set in_XML_comment to false
            collect signature
            if ( signature contains "public" )
                add XML_comment to XML_comments_list
            else
                discard XML_comment object
            endif
        else
            ignore c# source line
        endif
    endif
until c# source file eof

The only symbols that must be recognized are:

syassignment '='
syclass "class"
syclosebracket ']'
sycolon ':'
sycomment "//"
sydelegate "delegate"
syenum "enum"
syeof End of file ("‡")
syeoln End of line ('†')
syinterface "interface"
synamespace "namespace"
syopenbrace '{'
syopenbracket '['
syopenparen '('
sypublic "public"
sysemicolon ';'
syslash '/'
syspace ' '
systruct "struct"
syXMLcomment "///"

4.2. Parsing the XML Comments Table of Contents

When the parsing of the C# source file is complete, all the data required to generate an HTML page for a C# file has been collected. The XML comments have been collected, in the order in which they appeared, into the XML_comment_list. Also included in each XML_comment block is the public signature that was preceded by the XML comments.

XML2HTML must now process the data found in the XML_comment nodes of the XML_comment_list. For this portion of the XML2HTML work, parsing takes on the full lexical analysis of a parser. The syntax analysis is somewhat simplified because XML2HTML is not parsing the input for correctness.

For these reasons, it is highly recommended that C# project files that will be submitted to the XML2HTML tool be successfully compiled. To insure that XML comments are valid, the project should specify that the compiler generate the project's XML documentation file. This compiler-generated XML documentation file should be examined for any compiler reported problems and any problems should be repaired before the project is submitted to XML2HTML.

5. XML2HTML Tool Table of Contents

The tool was developed in three incarnations.

  1. The user specifies a C# filename and the tool generates an HTML page from the embedded XML comments.
  2. The user specifies the XML filename that the C# compiler produced and the tool generates HTML pages from the XML tree contained therein.
  3. The user specifies a C# project filename and the tool generates HTML pages from the XML comments embedded in the C# files that make up the project.

Because the third iteration finally became the XML2HTML tool, this discussion will be limited to that iteration.

5.1. Setup Phase Table of Contents

The user must specify which C# project file to use. This is done on the Setup page of the tool.

(All images in this article are thumbnail images that, when clicked, expand to a full size version.)

XML2HTML Setup

At the top of this form, the user may specify certain HTML options. When the Browse button is clicked, the user is presented with a modal dialog box that allows the user to choose a C# project file.

The XML2HTML tool keeps track of the last directory chosen. On first execution, last_directory is initialized to "C:\". Once a C# project file has been specified, last_directory is assigned the project file directory and is saved in the Registry for later retrieval.

There is a requirement levied against the chosen directory: it must be writable by the user. If it is not, a message is presented. The test is performed by the following code.

C#
try
    {
    File.Open ( project_filename,
                FileMode.Open,
                FileAccess.ReadWrite ).
         Dispose ( );
    set_last_directory ( project_filename );
    output_directory = last_directory;
    project_directory =
        Path.GetDirectoryName ( project_filename );
    success = true;
    }
catch (IOException)
    {
    MessageBox.Show (
        String.Format (
            "Cannot write to directory {0}",
            last_directory ),
        "Write Permission Required",
        MessageBoxButtons.OK,
        MessageBoxIcon.Exclamation );
    get_project_filename ( );
    }

The reasons for this requirement are: XML2HTML writes the HTML output for the individual chosen files to the directory HTML (beneath the project file directory) and writes the HTML file, index.html, to the project file directory.

Project Directory Additions

When the project file has been chosen, the Select Files to Process button appears.

XML2HTML Setup 2

5.2. Extraction Phase Table of Contents

When the user specifies the C# project file of interest, XML2HTML extracts data from that file.

5.2.1. C# Project File Data Table of Contents

The C# project file is an XML document. What was needed from this file were:

  • OutputType - must be "Library"
  • AssemblyName - used as the dll name
  • RootNamespace - used as the namespace
  • File names found under the Compile tag

An example, extracting the project filenames, is:

XmlDocument document = new XmlDocument();
XmlNodeList nodes;
.
.
.
document.Load ( project_filename );
.
.
.
nodes = document.GetElementsByTagName ( "Compile" );
foreach ( XmlNode node in nodes )
    {
    if ( node.Attributes [ "Include" ] != null )
        {
        string  filename = node.Attributes [
                               "Include" ].Value;

        available.Add ( filename,
                        colorize ( filename ) );
        }
    }

where

using SLsc = System.Collections.Generic.SortedList<
                                            string,
                                            System.Drawing.Color>;
                .
                .
                .
        SLsc                        available = new SLsc ( );

and colorize is

C#
// ************************************************** colorize

Color colorize ( string filename )
    {
    Color   color = Color.Black;
    string  html_directory = String.Empty;
                                // last_directory ends with \
    html_directory = String.Format ( @"{0}HTML",
                                     last_directory );

    if ( Directory.Exists ( html_directory ) )
        {
        string   cs_filename = String.Empty;
        string   html_filename = String.Empty;

        cs_filename = String.Format ( @"{0}{1}",
                                      last_directory,
                                      filename );
        html_filename = String.Format (
                        @"{0}\{1}.html",
                        html_directory,
                        filename.Replace ( ".cs",
                                           String.Empty ) );

        if ( File.Exists ( html_filename ) )
            {
            color =
                ( File.GetLastWriteTime ( cs_filename ) >
                  File.GetLastWriteTime ( html_filename ) ) ?
                    color = Color.Red :
                    color = Color.Green;
            }
        }

    return ( color );

    } // colorize

At this point in the processing, a colorized list of C# files has been collected into the list available.

XML2HTML Select Files

With no files chosen for processing, the Create HTML Index File button is the only option. If one or more files are chosen for processing, the Execute button appears.

XML2HTML Select Files

Regardless of which button is clicked, XML2HTML executes as a separate thread. This worker thread executes two separate functions:

  • Process all of the chosen files.
  • Create the index.html file.

5.3. Process Chosen Files Table of Contents

XML2HTML processes the chosen files one at a time. There are two distinct operations:

  1. Extract XML Comments and Signatures
  2. Process XML Comments and Generate HTML Documentation

As it has been noted the XML2HTML tool is not, in the strictest sense, a C# compiler: it performs lexical analysis but it is very limited in its syntax analysis. C# errors are not recognized and flawed XML comments are not reported.

5.3.1. XML Comments/Signatures Extraction Table of Contents

The form of the XML comment recognized by XML2HTML is the single line XML comment delimiter, triple slashes (///). From Microsoft documentation:

You create documentation for your code by writing special comment fields indicated by triple slashes. The comment fields include XML elements that describe the code block that follows the comments. ... You set either the GenerateDocumentationFile or DocumentationFile option, and the compiler finds all comment fields with XML tags in the source code and creates an XML documentation file from those comments. When this option is enabled, the compiler generates the CS1591 warning for any publicly visible member declared in your project without XML documentation comments.

Each line in the file is read and processed. A line is defined as a sequence of characters followed by a line feed ('\n'), a carriage return ('\r'), or a carriage return immediately followed by a line feed ("\r\n"). The string that is returned by System.IO.StreamReader.ReadLine does not contain the terminating carriage return or line feed. The returned value is null if the end of the input stream is reached. The XML2HTML read_next_line method appends a double-dagger (‡) to a line at end of file; XML2HTML appends a dagger (†) to represent an end of line.

C#
// ******************************************** read_next_line

public static bool read_next_line ( ref string  message )
    {
    bool  success = false;

    Global.first_in_line = 0;
    Global.last_in_line = 0;

    message = String.Empty;

    if ( Global.sr == null )
        {
        message = String.Format ( "{0} is not open",
                                  Global.filename );
        }
    else if ( Global.sr.EndOfStream )
        {
        Global.line = "‡";      // syeof
        Global.first_in_line = 0;
        Global.last_in_line = 0;
        }
    else if ( ( ( Global.line =
                  Global.sr.ReadLine ( ).
                         TrimEnd ( ) ) == null ) ||
              ( Global.line.Length == 0 ) )
        {
        Global.line = "†";      // syeoln
        Global.line_number++;
        Global.first_in_line = 0;
        Global.last_in_line = 0;
        }
    else
        {
        success = true;
        Global.line += "†";
        Global.line_number++;
        Global.first_in_line = 0;
        Global.last_in_line = Global.line.Length - 1;
        }

    return ( success );

    } // read_next_line

The Global class holds persistent copies of variables used across XML2HTML methods.

As XML2HTML scans the file contents, each block of XML comments and the associated signature are collected into an XML_Comments object.

C#
// ******************************************** class XML_Comments

// XML_Comments are generated for each XML comments/signature pair
// found in the source file.

public class XML_Comments
    {

    public  string              name = String.Empty;
    public  Type                object_type =
                                    Object_Type.Type.UNKNOWN;
    public  int                 parameter_count = 0;
    public  bool                parameter_heading_emitted = false;
    public  Dictionary <
                string,
                Parameter >     parameter_dictionary = new
                                    Dictionary < string,
                                                 Parameter > ( );

    public  StringBuilder       signature = new StringBuilder ( );
    public  StringBuilder       text = new StringBuilder ( );

    } // class XML_Comments

For example, at the end of the collection of the block of XML comments and associated public signature for the set_placeholder method, the following data will have been recorded:

name = "set_placeholder"
object_type = METHOD
parameter_count = 2
parameter_dictionary = Count = 2
    [0] = {[control, XML2HTML.Parameter]}
          Key = "control"
          Value = {XML2HTML.Parameter}
            description = ""
            name = "control"
            referenced = false
            type = "Control"
    [1] = {[text, XML2HTML.Parameter]}
          Key = "text"
          Value = {XML2HTML.Parameter}
            description = ""
            name = "text"
            referenced = false
            type = "string"
parameter_heading_emitted = false
signature = {Label set_placeholder ( Control control,
                                     string  text )}
text =
<summary>
Sets placeholder text on a control (may not work for some
controls).
</summary>
<param name="control">
The control on which to set the placeholder.
</param>
<param name="text">
The text to display as the placeholder.
</param>
<returns>
The newly-created placeholder Label.
</returns>

As the C# file is scanned, each XML_comment is added to the XML_comments_list.

C#
public  static  LinkedList <
                XML_Comments >  XML_comments_list;

When the C# file is closed, the only artifact remaining is the XML_comments_list. It is upon this list that analysis and HTML generation is performed.

5.3.2. Process XML Comments and generate HTML Documentation Table of Contents

The XML_comments_list must first be ordered by XML_comment object_type ( CLASS, INTERFACE, STRUCT, ENUM, DELEGATE, CONSTRUCTOR, METHOD, FIELD, PROPERTY) and then by name and then by the parameter_count, descending. The operative System.Linq sort statement is:

C#
var ordered =
        Global.XML_comments_list.
              OrderBy ( x => x.object_type ).
              ThenBy ( x => x.name ).
              ThenByDescending ( x => x.parameter_count ).
              AsEnumerable ( );

Note that the ordering used in the declaration of object_type defines the order in which HTML page sections are to be generated.

C#
namespace XML2HTML
    {

    // ********************************************* class Object_Type

    public class Object_Type
        {

        public enum Type
                        { 
                        CLASS,
                        INTERFACE,
                        STRUCT,
                        ENUM,
                        DELEGATE,
                        CONSTRUCTOR,    // order CONSTRUCTOR before 
                        METHOD,         // METHOD
                        FIELD,
                        PROPERTY,
                        EVENT,
                        NAMESPACE,
                        UNKNOWN,
                        NUMBER_TYPES    // 12
                        }

        } // class Object_Type

    } // namespace XML2HTML

Once that the nodes of the XML_comments_list are sorted, the text object in each XML_comment node in the XML_comments_list is processed. This is a multi-step process.

  • The text in an XML_comment must be parsed, separating the contents of each XML tag from another. This produces a text node list in which each node contains one of the accepted XML tags (<summary>, <param>, <returns>, <remarks>, <exception>, <value>, <example>, and <seealso>).
  • The text node list must be ordered by the symbol appearing in the text node.
  • Each text node in the ordered list of text nodes is parsed.
5.3.2.1. Parsing XML Comment Text Table of Contents

parse_XML_comment_text is presented without comment.

C#
// ************************************ parse_XML_comment_text

void parse_XML_comment_text ( XML_Comments XML_comment )
    {
    Symbol          end_symbol = Symbol.systart;
    Symbol          next_symbol = Symbol.systart;
    StringBuilder   sb = new StringBuilder ( );
    Symbol          symbol = Symbol.systart;
    Text_Node       text_node;

    Global.text_list = new LinkedList < Text_Node > ( );
    while ( ( symbol = Global.scanner.next_symbol ( false ) ) !=
              Symbol.syeof )
        {
        if ( Symbols.XML_start_symbols.Contains ( symbol ) )
            {
            end_symbol = Global.scanner.get_XML_end_symbol (
                                                    symbol );
            text_node = new Text_Node ( );
            text_node.symbol = symbol;
            sb.Length = 0;
            while ( ( ( next_symbol =
                        Global.scanner.next_symbol ( false ) ) !=
                      Symbol.syeof ) &&
                    ( next_symbol != end_symbol ) )
                {
                sb.Append ( Global.spelling );
                }
            StringManipulation.trim_ends ( ref sb );
            sb.Append ( "‡" );
            text_node.text = sb.ToString ( );
            Global.text_list.AddLast ( text_node );
            }
        else
            {
            sb.Append ( Global.spelling );
            }
        }

    } // parse_XML_comment_text

The Text_Node is declared as:

C#
// *********************************************** class Text_Node

public class Text_Node
    {
    public  Symbol      symbol = Symbol.syunknown;
    public  string      text = String.Empty;

    } // Text_Node

The result of parsing the XML comment text is a list of text nodes in text_list.

5.3.2.2. Ordering text nodes in the text node list Table of Contents

Again the System.Linq sort is invoked to sort the nodes of the text_list on symbol.

C#
var ordered = Global.text_list.
                     OrderBy ( x => x.symbol ).
                     AsEnumerable ( );
5.3.2.3. Parsing text nodes Table of Contents

Each text node has the form

C#
// *********************************************** class Text_Node

public class Text_Node
    {
    public  Symbol      symbol = Symbol.syunknown;
    public  string      text = String.Empty;

    } // Text_Node

The symbol is one of syXMLsummary, syXMLparam, syXMLparamref, syXMLreturns, syXMLremarks, syXMLexception, syXMLvalue, syXMLexample, or syXMLseealso. The nodes with the symbols syXMLexception or syXMLvalue are ignored.

The text contains the XML comment tag contents, stripped of the XML comment tag (that now appears in the Text_Node symbol). For example, a syXMLparam node may contain

C#
name="pattern">
The pattern for which to search‡

The character '‡' is inserted by XML2HTML to indicate end of line (actually end-of-file). Parsing of the XML comment text is performed by process_text.

C#
// ********************************************** process_text

string process_text ( )
    {
    Symbol          symbol = Symbol.systart;
    StringBuilder   sb = new StringBuilder ( );
    string          text = String.Empty;

    while ( ( symbol = Global.scanner.next_symbol (
                                false ) ) != Symbol.syeof )
        {
        if ( Symbols.XML_internal_symbols.Contains (
                                                    symbol ) )
            {
            sb.Append ( process_internal_XML_symbol (
                                                symbol ) );
            }
       else
            {
            sb.Append ( Global.spelling );
            }
        }
    text = StringManipulation.trim_newlines (
                                        sb.ToString ( ) );

    return ( text );

    } // process_text

If the XML comment text contains pure text, then process_text returns just those contents. However, if an internal XML comment tag (i.e., <c>, <code>, <list>, <para>, or <paramref>) is encountered, process_internal_XML_symbol is invoked.

C#
// ******************************* process_internal_XML_symbol

string process_internal_XML_symbol ( Symbol  internal_symbol )
    {
    string          prefix = String.Empty;
    string          suffix = String.Empty;
    StringBuilder   text = new StringBuilder ( );

    switch ( internal_symbol )
        {
                    // <c>...</c>
        case Symbol.syXMLc:
            prefix_suffix (     internal_symbol,
                            ref prefix,
                            ref suffix ) ;
            text.Append ( extract_to_end_symbol (
                                        internal_symbol ) );
            break;
                    // <code>...</code>
        case Symbol.syXMLcode:
            prefix_suffix (     internal_symbol,
                            ref prefix,
                            ref suffix ) ;
            text.Append ( process_XML_code ( ) );
            break;
                    // <para>...</para>
        case Symbol.syXMLpara:
            prefix_suffix (     internal_symbol,
                            ref prefix,
                            ref suffix ) ;
            text.Append ( extract_to_end_symbol (
                                        internal_symbol ) );
            break;
                    // <paramref name="name"/>
        case Symbol.syXMLparamref:
            prefix_suffix (     internal_symbol,
                            ref prefix,
                            ref suffix ) ;
            text.Append ( extract_to_end_symbol (
                                        internal_symbol ) );
            break;
                    // <list type="bullet|number|table">
        case Symbol.syXMLlist:
            text.Append ( process_XML_list ( ) );
            break;
                                // following are internal
                                // components of <list>
        case Symbol.syXMLdescription:
        case Symbol.syXMLitem:
        case Symbol.syXMLlistheader:
        case Symbol.syXMLterm:
            // processed in process_XML_list
            break;

        default:
            // ERROR
            break;
        }

    text.Insert ( 0, prefix + Environment.NewLine );
    text.Append ( Environment.NewLine +
                  suffix +
                  Environment.NewLine );

    return ( text.ToString ( ) );

    } // process_internal_XML_symbol

Given a symbol, prefix_suffix returns the HTML tags that are to surround the text.

The more interesting parsing is performed in process_XML_code.

C#
        // ****************************************** process_XML_code

/*
From MSDN documentation:

 The <code> tag is used to indicate multiple lines of code. 

The tag is replaced by <pre>. This means that the author must insure 
that the characters &, <, >, ", and ' are replaced by their respective 
HTML entities ( &amp;, &lt;, &gt;, &quot;, and &apos;). Alternatively 
the user may select, during setup, to replace these HTML characters 
with their HTML entities automatically.

The processing of the <code>...</code> pair differs from that of other 
tag pairs. Effectively whatever is contained with the pair of tags is 
copied verbatim into the <pre>...</pre> pair. This requires that no 
processing of contained contents is performed. To accomplish this end, 
the following algorithm is employed:

1. The variable text is assigned the current Lexical_Scanner buffer.
2. The position, within text, of the first </code> tag is found. This 
   recognizes that there may be more than one <code>...</code> pair in 
   the text.
3. The Lexical_Scanner position is revised to point to the text 
   position immediately following the </code> tag. This allows 
   scanning to resume using the Lexical_Scanner.
4. The text is assigned the substring from the current start (0) to 
   the position of the first </code> tag.
5. The position of the first <code> tag is found.
6. Text is extracted from this position to the end of the text string.
7. If the user required that characters be replaced by their HTML 
   entities, the text will be modified accordingly.
8. The contents of the Lexical_Scanner buffer upto the </code> is 
   removed (allowing multiple <code>...</code> tags).
9. The text is returned.
*/
        string process_XML_code ( )
            {
            int             at = 0;
            string          text = String.Empty;

            text = Lexical_Scanner.contents;
            at = text.IndexOf ( "</code>" );
            if ( at < 0 )
                {
                // error
                return ( String.Empty );
                }
            Lexical_Scanner.position = ( at + "</code>".Length );
            text = text.Substring ( 0, at );
            at = text.IndexOf ( "<code>" );
            if ( at < 0 )
                {
                // error
                return ( String.Empty );
                }
            text = text.Substring ( at + "<code>".Length );
            text = StringManipulation.trim_newlines ( text );

            if ( replace_HTML_entities )
                {
                text.Replace ( "&", "&amp;" );  // must come first
                text.Replace ( "<", "&lt;" ).
                     Replace ( ">", "&gt;" ).
                     Replace ( "\"", "&quot;" ).
                     Replace ( "'", "&apos;" );
                }
                                        // revise 
                                        // Lexical_Scanner.contents 
                                        // and 
                                        // Lexical_Scanner.position
            Lexical_Scanner.contents = Lexical_Scanner.contents.
                                Remove ( 0,
                                         Lexical_Scanner.position );
            Lexical_Scanner.position = 0;

            return ( text );

            } // process_XML_code

process_XML_list performs its parse using the XML tree of the XML comment text.

C#
        // ****************************************** process_XML_list

/*
From MSDN documentation:

 The <listheader> block is used to define the heading row of either a 
 table or definition list. When defining a table, you only need to 
 supply an entry for term in the heading. Each item in the list is 
 specified with an <item> block. When creating a definition list, 
 you'll need to specify both term and description. However, for a 
 table, bulleted list, or numbered list, you only need to supply an 
 entry for description. A list or table can have as many <item> blocks 
 as needed.

This implies:

 General form:
 <list type="bullet|number|table">
     <listheader>
         <term>term</term>
         <description>description</description>
     </listheader>
     <item>
         <term>term</term>
         <description>description</description>
     </item>
 </list>

 Bulleted List form:
 <list type="bullet">                           <ul>
     <listheader>                                 <li>
         <description>desc</description>            desc
     </listheader>                                </li>
     <item>                                       <li>
         <description>desc</description>            desc
     </item>                                      </li>
 </list>                                        </ul>
 The <listheader> in type="bullet" will be ignored since it 
 does not make sense in the <ul> tag

 Numbered List form:
 <list type="number">                           <ol>
     <listheader>                                 <li>
         <description>desc</description>            desc
     </listheader>                                </li>
     <item>                                       <li>
         <description>desc</description>            desc
     </item>                                      </li>
 </list>                                        </ol>
 The <listheader> in type="number" will be ignored since it 
 does not make sense in the <ol> tag

 Table form:
 <list type="table">                            <table>
     <listheader>                                 <thead>
                                                    <tr>
                                                      <th>
         <term>term</term>                              term
                                                      </th>
                                                    <tr>
     </listheader>                                </thead>
                                                  <tbody>
     <item>                                         <tr>
                                                      <td>
         <description>desc</description>                desc
                                                      </td>
     </item>                                        </tr>
                                                  </tbody>
 </list>                                        </table>
 The type="table" is poorly conceived. It will not be implemented.
*/
        string process_XML_list ( )
            {
            XmlNodeList     descriptions = null;
            Symbol          end_symbol = Symbol.syXMLendlist;
            XmlElement      root = null;
            StringBuilder   sb = new StringBuilder ( );
            Symbol          symbol = Symbol.systart;
            string          type = String.Empty;
            XmlDocument     XML_document = new XmlDocument ( );

            sb.Append ( Global.spelling );     // "<list "
            while ( ( ( symbol = Global.scanner.next_symbol ( 
                        false ) ) != Symbol.syeof ) && 
                    ( symbol != end_symbol ) )
                {
                sb.Append ( Global.spelling );
                }
            sb.Append ( Global.spelling );     // "</list>"

            XML_document.LoadXml ( sb.ToString ( ) );
                                        // extract type
            root = XML_document.DocumentElement;
            type = root.Attributes [ "type" ].Value;
            if ( !set_prefix_suffix ( type ) )
                {
                html.AppendFormat (
"  <h3><span class='RedBold'>" +
"{1} is either not supported or unrecognized</span></h3>{0}",
                    Environment.NewLine,
                    type );
                return ( String.Empty );
                }

            sb.Length = 0;              // clear StringBuilder
            descriptions = XML_document.GetElementsByTagName ( 
                                    "description" );
            if ( descriptions.Count == 0 )
                {
                html.AppendFormat (
"  <h3><span class='RedBold'>" +
"There is no description for {1} list</span></h3>{0}",
                    Environment.NewLine,
                    type );
                return ( String.Empty );
                }
            sb.AppendFormat (
                String.Format ( 
"{1}{0}",
                    Environment.NewLine,
                    prefix ) );         // <ul> or <ol>

            foreach ( XmlNode description in descriptions )
                {
                sb.AppendFormat (
                    String.Format ( 
"  {1}{0}  {2}{0}  {3}{0}",
                    Environment.NewLine,
                    item_prefix,        // <li>
                    StringManipulation.trim_newlines ( 
                                    description.InnerText ).Trim ( ),
                    item_suffix ) );    // </li>
                }

            sb.AppendFormat (
                String.Format ( 
"{1}{0}",
                    Environment.NewLine,
                    suffix ) );         // </ul> or </ol>

            return ( sb.ToString ( ) );

            } // process_XML_list

At the conclusion of parsing each input file, the worker thread closes the HTML file and reports its status. When all input files have been processed, the worker thread creates the index.html, if desired by the user.

5.4. Creation of the index.html file Table of Contents

The index.html file is created in the project file directory. It provides a means to easily scan the contents of the project library collection. Creation of the index.html file is performed by build_html_index_file.

C#
// ************************************* build_html_index_file

bool build_html_index_file ( ref string  message )
    {
    string [ ]  html_files;
    bool        success = true;

    message = String.Empty;

    try
        {
        html_directory = project_directory + @"\HTML";
        html_files = Directory.GetFiles ( html_directory );
        members_present = new List < string > ( html_files );

        if ( members_present.Count > 0 )
            {
            string index_page_filename = project_directory +
                                         @"\index.html";

            if ( build_index_page ( ref index_page_filename,
                                    ref message ) )
                {
                File.WriteAllText ( index_page_filename,
                                    index_page_html.ToString ( ) );
                message =
                    String.Format (
                       "HTML index page written to {0}",
                       index_page_filename );
                success = true;
                }
            else
                {
                message =
                    String.Format (
                       "Failed to write HTML index file{0}{1}",
                       Environment.NewLine,
                       index_page_filename );
                success = false;
                }
            }
        else
            {
            members_present.Clear ( );
            success = false;
            message = String.Format (
                        "No HTML files in (0}",
                        html_directory );

            }
        }
    catch ( Exception ex )
        {
        members_present.Clear ( );
        success = false;
        message = String.Format (
                    "Failed to build_html_index_file{0}{1}",
                    Environment.NewLine,
                    ex.Message );
        }

    return ( success );

    } // build_html_index_file

The index.html page is relatively simple.

<!DOCTYPE html>
<html lang='en'>
  <head>
    <title>Utilities Library</title>
    <meta http-equiv='Content-type' 
          content='text/html;charset=UTF-8' >
    <meta name='viewport' 
          content='width=device-width, initial-scale=1' >
    <style>
      body
        {
        margin-left:10px;
        width:850px;
        }
      .container
        {
        display: grid;
        grid-template-columns: 150px 700px;
        }
      ul 
        {
        list-style-type: none;
        padding: 0;
        margin: 0;
        }
      li 
        {
        padding: 2% 4%;
        }
      button
        {
        text-decoration: underline;
        color: Blue;
        }
      .members_description
        {
        font-size: large; 
        font-weight: bold; 
        color: #000000;
        }
      .title
        {
        font-size: xx-large; 
        font-weight: bold;
        margin: 0px; 
        color: #0000FF; 
        padding-left: 150px;
        }
    </style>
  </head>
  <body>
    <div>
      <p class='title'>Utilities Library</p>
    </div>
    <div class='container'>
      <div id='sidebar'>
        <p class='members_description'>Members</p>
        <ul>
          <li>
            <button onclick="switch_page ( 'ASCIICodes' )">ASCIICodes</button>
          </li>
          <li>
            <button onclick="switch_page ( 'BoyerMoore' )">BoyerMoore</button>
          </li>
          <li>
            <button onclick="switch_page ( 'FileIO' )">FileIO</button>
          </li>
          <li>
            <button onclick="switch_page ( 'Placeholder' )">Placeholder</button>
          </li>
          <li>
            <button onclick="switch_page ( 'RegistryUtilities' )">RegistryUtilities</button>
          </li>
          <li>
            <button onclick="switch_page ( 'TabControls' )">TabControls</button>
          </li>
          <li>
            <button onclick="switch_page ( 'TextFileIO' )">TextFileIO</button>
          </li>
        </ul>
      </div>
      <div>
        <p class='members_description'>Description</p>
        <iframe
          id='webpage'
          src='about:blank'
          width='800'
          height='500'>
        </iframe>
      </div>
    </div>
    <div style='width:850px;'>
      <p style='font-size: small; text-align: right; font-weight: bold;'>
        XML2HTML 1.1.8978 07/31/2024-10:11
      </p>
    </div>
    <script>
      var webpage_id = document.getElementById ( 'webpage' );
      window.addEventListener ( 'beforeunload', page_refresh, false );
      window.onload = 
        function ( )
          {
          webpage_id.src = 'about:blank';
          };
      function switch_page ( new_page )
        {
        var reference = './HTML/' + 
                        new_page + 
                        '.html';
        webpage_id.src = reference;
        };
      function page_refresh ( ) 
        {
        webpage_id.src = 'about:blank';
        };
    </script>
  </body>
</html>

When first displayed, the HTML page displays an empty iframe with the list of HTML files displayed as buttons, one button for each HTML file. (This example displays the files created from the source files in the XML2HTML project.)

XML2HTML Index at Startup

When a member is selected (by clicking the button with the member's name), the member's HTML page is displayed.

XML2HTML Index with Member Chosen

6. Conclusion Table of Contents

This article has presented a tool, XML2HTML, that converts XML comments in C# source files that are part of a project into HTML pages.

7. References Table of Contents

8. Development Environment Table of Contents

The XML2HTML tool was developed in the following environment:

Microsoft Windows 7 Professional SP 1
Microsoft Visual Studio 2008 Professional SP1
Firefox Developer Browser 115.0b9
Nu Html Checker [^]
JSHint [^]
FileZilla 3.67.1 [^]

9. Directory Structure Table of Contents

When executed, XML2HTML creates HTML pages, one for each C# source file that contains at least one XML comment block that is immediately followed by a public signature. If the user so desires, XML2HTML also creates an index.html file than can access the generated HTML files.

The HTML pages are placed in the newly created HTML directory beneath the project file directory. The index.html file is placed in the project file directory. For this project, the Utilities directory structure becomes:

Copying to Website

The items in Blue were created by the XML2HTML tool.

For the index.html file to work as desired, the HTML directory and the index.html file must be copied to a web server. For example, the directory and files were copied to gggustafson.com [^].

10. The Download Table of Contents

The download consists of the XML2HTML Project Source Files in a ZIP file, excluding bin and obj directories. I recommend that the ZIP file be downloaded to the C:\ directory and extracted to an XML2HTML directory. Because the project was developed using Microsoft Visual Studio 2008, when you run your version of Visual Studio against XML_Comments_to_HTML.sln you may need to perform a conversion.

11. History Table of Contents

8/4/2024 Original article

License

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