Table of Contents
The symbol returns the reader to the top of the Table of Contents.
1. Background
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
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
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
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
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
Parsing is intrinsic to XML2HTML. However, there are two flavors:
- Parsing the C# source file.
- Parsing the XML comments.
4.1. Parsing the C# Source File
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
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
The tool was developed in three incarnations.
- The user specifies a C# filename and the tool generates an HTML page from the embedded XML comments.
- The user specifies the XML filename that the C# compiler produced and the tool generates HTML pages from the XML tree contained therein.
- 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
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.)
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.
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.
When the project file has been chosen, the Select Files to Process button appears.
5.2. Extraction Phase
When the user specifies the C# project file of interest, XML2HTML extracts data from that file.
5.2.1. C# Project File Data
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
Color colorize ( string filename )
{
Color color = Color.Black;
string html_directory = String.Empty;
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 );
}
At this point in the processing, a colorized list of C# files has been collected into the list available.
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.
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
XML2HTML processes the chosen files one at a time. There are two distinct operations:
- Extract XML Comments and Signatures
- 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
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.
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 = "‡";
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 = "†";
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 );
}
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.
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 ( );
}
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.
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
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:
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.
namespace XML2HTML
{
public class Object_Type
{
public enum Type
{
CLASS,
INTERFACE,
STRUCT,
ENUM,
DELEGATE,
CONSTRUCTOR,
METHOD,
FIELD,
PROPERTY,
EVENT,
NAMESPACE,
UNKNOWN,
NUMBER_TYPES
}
}
}
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
parse_XML_comment_text is presented without comment.
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 );
}
}
}
The Text_Node is declared as:
public class Text_Node
{
public Symbol symbol = Symbol.syunknown;
public string text = String.Empty;
}
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
Again the System.Linq sort is invoked to sort the nodes of the text_list on symbol.
var ordered = Global.text_list.
OrderBy ( x => x.symbol ).
AsEnumerable ( );
5.3.2.3. Parsing text nodes
Each text node has the form
public class Text_Node
{
public Symbol symbol = Symbol.syunknown;
public string text = String.Empty;
}
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
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.
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 );
}
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.
string process_internal_XML_symbol ( Symbol internal_symbol )
{
string prefix = String.Empty;
string suffix = String.Empty;
StringBuilder text = new StringBuilder ( );
switch ( internal_symbol )
{
case Symbol.syXMLc:
prefix_suffix ( internal_symbol,
ref prefix,
ref suffix ) ;
text.Append ( extract_to_end_symbol (
internal_symbol ) );
break;
case Symbol.syXMLcode:
prefix_suffix ( internal_symbol,
ref prefix,
ref suffix ) ;
text.Append ( process_XML_code ( ) );
break;
case Symbol.syXMLpara:
prefix_suffix ( internal_symbol,
ref prefix,
ref suffix ) ;
text.Append ( extract_to_end_symbol (
internal_symbol ) );
break;
case Symbol.syXMLparamref:
prefix_suffix ( internal_symbol,
ref prefix,
ref suffix ) ;
text.Append ( extract_to_end_symbol (
internal_symbol ) );
break;
case Symbol.syXMLlist:
text.Append ( process_XML_list ( ) );
break;
case Symbol.syXMLdescription:
case Symbol.syXMLitem:
case Symbol.syXMLlistheader:
case Symbol.syXMLterm:
break;
default:
break;
}
text.Insert ( 0, prefix + Environment.NewLine );
text.Append ( Environment.NewLine +
suffix +
Environment.NewLine );
return ( text.ToString ( ) );
}
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.
string process_XML_code ( )
{
int at = 0;
string text = String.Empty;
text = Lexical_Scanner.contents;
at = text.IndexOf ( "</code>" );
if ( at < 0 )
{
return ( String.Empty );
}
Lexical_Scanner.position = ( at + "</code>".Length );
text = text.Substring ( 0, at );
at = text.IndexOf ( "<code>" );
if ( at < 0 )
{
return ( String.Empty );
}
text = text.Substring ( at + "<code>".Length );
text = StringManipulation.trim_newlines ( text );
if ( replace_HTML_entities )
{
text.Replace ( "&", "&" );
text.Replace ( "<", "<" ).
Replace ( ">", ">" ).
Replace ( "\"", """ ).
Replace ( "'", "'" );
}
Lexical_Scanner.contents = Lexical_Scanner.contents.
Remove ( 0,
Lexical_Scanner.position );
Lexical_Scanner.position = 0;
return ( text );
}
process_XML_list performs its parse using the XML tree of the XML comment text.
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 );
while ( ( ( symbol = Global.scanner.next_symbol (
false ) ) != Symbol.syeof ) &&
( symbol != end_symbol ) )
{
sb.Append ( Global.spelling );
}
sb.Append ( Global.spelling );
XML_document.LoadXml ( sb.ToString ( ) );
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;
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 ) );
foreach ( XmlNode description in descriptions )
{
sb.AppendFormat (
String.Format (
" {1}{0} {2}{0} {3}{0}",
Environment.NewLine,
item_prefix,
StringManipulation.trim_newlines (
description.InnerText ).Trim ( ),
item_suffix ) );
}
sb.AppendFormat (
String.Format (
"{1}{0}",
Environment.NewLine,
suffix ) );
return ( sb.ToString ( ) );
}
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
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.
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 );
}
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.)
When a member is selected (by clicking the button with the member's name), the member's HTML page is displayed.
6. Conclusion
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
8. Development Environment
The XML2HTML tool was developed in the following environment:
9. Directory Structure
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:
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
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
8/4/2024 | Original article |