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





5.00/5 (7 votes)
This article presents a tool that convert XML comments in a C# source file to HTML pages.
Table of Contents
- 1. Background
- 2. Introduction
- 3. XML Comments
- 4. A Note on Parsing
- 5. XML2HTML Tool
- 6. Conclusion
- 7. References
- 8. Development Environment
- 9. Directory Structure
- 10. The Download
- 11. History
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
// ************************************************** 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.
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.
// ******************************************** 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.
// ******************************************** 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.
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
{
// ********************************************* 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
parse_XML_comment_text is presented without comment.
// ************************************ 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:
// *********************************************** 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
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
// *********************************************** 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
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.
// ********************************************** 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.
// ******************************* 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.
// ****************************************** 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 ( &, <, >, ", and '). 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 ( "&", "&" ); // must come first
text.Replace ( "<", "<" ).
Replace ( ">", ">" ).
Replace ( "\"", """ ).
Replace ( "'", "'" );
}
// 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.
// ****************************************** 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
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.
// ************************************* 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.)
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
- C# IntelliSense [^]
- Documentation comments [^]
- Example XML documentation comments [^]
- Recommended XML tags for C# documentation comments [^]
8. Development Environment
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
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 |