/*       Copyright (c) Eric Ledoux.  All rights reserved.       */
/* See http://www.dwell.net/terms for code sharing information. */

// DefaultTopicRenderer.cs
//
// Implements class DefaultTopicRenderer and related types.
//

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using System.Xml;
using DwellNet.CodeDoc;
using CodeDocRenderer.Properties;

namespace DwellNet.CodeDoc
{

DefaultTopicRenderer Class

Converts a CodeDoc Topic to HTML.

public class DefaultTopicRenderer : TopicRenderer
{
    //////////////////////////////////////////////////////////////////////////
    // Public Methods
    //

    
DefaultTopicRenderer Constructor

Initializes an instance of this class.

    public DefaultTopicRenderer()
    {
    }

    
DefaultTopicRenderer.GenerateDocHtml Method

Executes one of two documentation generation passes: In the first pass, each section (e.g. "<summary>") of a given Topic's Xml property is converted to HTML, stored in Topic.XmlCommentSections. In the second pass, HTML documentation for the topic is written to an HtmlTextWriterHelper.

Parameters

embedded

If true "embedded" HTML is generated, i.e. a "<div>" designed to fit compactly within formatted source code. If false, a complete HTML document is generated. Ignored for the first pass.

htmlWriter

For the first pass, this is null. For the second path, this is HtmlTextWriterHelper to write to.

Remarks

The first pass of the documentation generation process must be peformed for all documentation topics before the second pass is performed on any topic.

    public override void GenerateDocHtml(bool embedded,
        HtmlTextWriterHelper htmlWriter)
    {
        // make sure we have unique topic HTML file names etc.
        Topic.DocumentationSet.BeginUsingSources();
        
        // execute the appropriate pass
        if (htmlWriter == null)
        {
            // first pass...
            
            // generate XML comment section HTML
            foreach (XmlCommentSection section in Topic.XmlCommentSections)
            {
                using (XmlReader xmlReader = section.GetXmlReader())
                {
                    // generate HTML for this section, stored in <html>
                    StringBuilder html = new StringBuilder(2000);
                    using (StringWriter stringWriter = new StringWriter(html))
                    {
                        using (HtmlTextWriterHelper sectionWriter =
                                new HtmlTextWriterHelper(stringWriter))
                        {
                            try
                            {
                                TopicWriter topicWriter = CreateTopicWriter(
                                    Topic, sectionWriter, this);
                                topicWriter.WarningInSourceFile +=
                                    delegate(SourceFile sourceFile,
                                        int lineNumber, string message)
                                {
                                    FireWarningInSourceFile(lineNumber, "{0}",
                                        message);
                                };
                                topicWriter.GenerateXmlSectionHtml(section,
                                    xmlReader);
                            }
                            catch (XmlException ex)
                            {
                                FireWarningInSourceFile(
                                    ((IXmlLineInfo) xmlReader).LineNumber,
                                    Resources.UnexpectedXmlError, ex.Message);
                            }
                        }
                    }

                    // do special processing, if required, based on what kind
                    // of section this is
                    if (section.SectionKind == XmlCommentSectionKind.Param)
                    {
                        // this is a "<param>" section -- make sure there's a
                        // matching parameter in the source code
                        if (String.IsNullOrEmpty(section.NameAttribute))
                        {
                            FireWarningInSourceFile(
                                ((IXmlLineInfo) xmlReader).LineNumber,
                                Resources.MissingAttribute, "param", "name");
                        }
                        else
                        {
                            Parameter parameter =
                                Topic.GetParameter(section.NameAttribute);
                            if (parameter == null)
                            {
                                FireWarningInSourceFile(
                                    ((IXmlLineInfo) xmlReader).LineNumber,
                                    Resources.NoParameter,
                                    section.NameAttribute,
                                    Topic.QualifiedMemberNameWithoutNamespace);
                            }
                        }
                    }
                    else
                    if (section.SectionKind == XmlCommentSectionKind.TypeParam)
                    {
                        // this is a "<typeparam>" section -- make sure
                        // there's a matching type parameter in the source code
                        if (String.IsNullOrEmpty(section.NameAttribute))
                        {
                            FireWarningInSourceFile(
                                ((IXmlLineInfo) xmlReader).LineNumber,
                                Resources.MissingAttribute, "typeparam",
                                "name");
                        }
                        else
                        {
                            if (!Topic.HasTypeParameter(section.NameAttribute))
                            {
                                FireWarningInSourceFile(
                                    ((IXmlLineInfo) xmlReader).LineNumber,
                                    Resources.NoTypeParameter,
                                    section.NameAttribute,
                                    Topic.QualifiedMemberNameWithoutNamespace);
                            }
                        }
                    }
                    else
                    if (section.SectionKind == XmlCommentSectionKind.Exception)
                    {
                        // this is a "<exception>" section -- look up the
                        // exception type
                        if (String.IsNullOrEmpty(section.CrefAttribute))
                        {
                            FireWarningInSourceFile(
                                ((IXmlLineInfo) xmlReader).LineNumber,
                                Resources.MissingAttribute, "exception",
                                "cref");
                        }
                        else
                        {
                            section.ReferencedTopic =
                                Topic.DocumentationSet.FindDocumentedTopic(
                                    section.CrefAttribute, Topic,
                                    ((IXmlLineInfo) xmlReader).LineNumber,
                                    FindTopicSettings.TypeOnly);
                        }
                    }
                    else
                    if (section.SectionKind == XmlCommentSectionKind.Permission)
                    {
                        // this is a "<permission>" section -- look up the
                        // permission type
                        if (String.IsNullOrEmpty(section.CrefAttribute))
                        {
                            FireWarningInSourceFile(
                                ((IXmlLineInfo) xmlReader).LineNumber,
                                Resources.MissingAttribute, "permission",
                                "cref");
                        }
                        else
                        {
                            section.ReferencedTopic =
                                Topic.DocumentationSet.FindDocumentedTopic(
                                    section.CrefAttribute, Topic,
                                    ((IXmlLineInfo) xmlReader).LineNumber,
                                    FindTopicSettings.TypeOnly);
                        }
                    }
                    else
                    if (section.SectionKind == XmlCommentSectionKind.SeeAlso)
                    {
                        // this is a "<seealso>" section -- look up the
                        // reference, if "cref" is provided
                        bool hasCref = !String.IsNullOrEmpty(
                            section.CrefAttribute);
                        bool hasHref = !String.IsNullOrEmpty(
                            section.HrefAttribute);
                        if (hasCref && !hasHref)
                        {
                            section.ReferencedTopic =
                                Topic.DocumentationSet.FindDocumentedTopic(
                                    section.CrefAttribute, Topic,
                                    ((IXmlLineInfo) xmlReader).LineNumber, 0);
                        }
                        else
                        if (hasHref && !hasCref)
                        {
                            // this "<seealso>" has an "href", not a "cref"
                        }
                        else
                        {
                            FireWarningInSourceFile(
                                ((IXmlLineInfo) xmlReader).LineNumber,
                                Resources.CrefOrHrefAttribute, "seealso");
                        }
                    }

                    // store <html> in <section> 
                    Topic.AddXmlSectionHtml(section, html.ToString());
                }
            }
        }
        else
        {
            // second pass: generate the HTML page
            TopicWriter topicWriter = CreateTopicWriter(Topic, htmlWriter,
                this);
            topicWriter.WarningInSourceFile += delegate(SourceFile sourceFile,
                int lineNumber, string message)
            {
                FireWarningInSourceFile(lineNumber, "{0}", message);
            };
            if (embedded)
                topicWriter.WriteEmbeddedTopic();
            else
                topicWriter.WriteTopicPage();
        }
    }

    
DefaultTopicRenderer.GenerateCodeNamespaceHtml Method

Assuming Topic is a namespace topic, this method generates the HTML file, used in the formatted code directory, that represents the namespace and contains links to the formatted code HTML files. list.

Parameters

htmlWriter

The HtmlTextWriterHelper to write to.

    public override void GenerateCodeNamespaceHtml(
        HtmlTextWriterHelper htmlWriter)
    {
        // quit if this isn't a namespace topic
        NamespaceTopic namespaceTopic = Topic as NamespaceTopic;
        if (namespaceTopic == null)
            return; // this isn't an overload list topic

        // generate the HTML page
        Debug.Assert(CodeHtml == true);
        TopicWriter topicWriter = CreateTopicWriter(Topic, htmlWriter, this);
        topicWriter.WarningInSourceFile += delegate(SourceFile sourceFile,
            int lineNumber, string message)
        {
            FireWarningInSourceFile(lineNumber, "{0}", message);
        };
        topicWriter.WriteCodeNamespacePage();
    }

    
DefaultTopicRenderer.GenerateCodeOverloadListHtml Method

Assuming Topic is an overload list topic, this method generates the HTML file, used in the formatted code directory, that contains links to the documentation block of each overload topic in the overload list.

Parameters

htmlWriter

The HtmlTextWriterHelper to write to.

    public override void GenerateCodeOverloadListHtml(
        HtmlTextWriterHelper htmlWriter)
    {
        // quit if this isn't an overload list topic
        OverloadListTopic overloadListTopic = Topic as OverloadListTopic;
        if (overloadListTopic == null)
            return; // this isn't an overload list topic

        // generate the HTML page
        Debug.Assert(CodeHtml == true);
        TopicWriter topicWriter = CreateTopicWriter(Topic, htmlWriter, this);
        topicWriter.WarningInSourceFile += delegate(SourceFile sourceFile,
            int lineNumber, string message)
        {
            FireWarningInSourceFile(lineNumber, "{0}", message);
        };
        topicWriter.WriteCodeOverloadListPage();
    }

    
DefaultTopicRenderer.CreateTopicWriter Method

Creates an instance of TopicWriter or a derived class.

Parameters

topic

The topic to render.

textWriter

The TextWriter that renders the markup content.

topicRenderer

The TopicRenderer that created this TextWriter.

Remarks

The default implementation of CreateTopicWriter creates an instance of TopicWriter. This method is intended to be overridden by a derived class, which can cause an instance of a subclass of TopicWriter to be returned.

    public virtual TopicWriter CreateTopicWriter(Topic topic,
        TextWriter textWriter, DefaultTopicRenderer topicRenderer)
    {
        return new TopicWriter(topic, textWriter, topicRenderer);
    }
}

TopicWriter Class

Converts a topic to HTML.

public class TopicWriter : HtmlTextWriterHelper
{
    //////////////////////////////////////////////////////////////////////////
    // Private Constants
    //

    
TopicWriter.LBrace Field

Left brace (to make brace matching work better in text editor).

    const char LBrace = '{';

    
TopicWriter.RBrace Field

Right brace (to make brace matching work better in text editor).

    const char RBrace = '}';

    
TopicWriter.LParen Field

Left parenthesis (to make parenthesis matching work better in a text editor).

    const char LParen = '(';

    
TopicWriter.RParen Field

Right parenthesis (to make parenthesis matching work better in a text editor).

    const char RParen = ')';

    
TopicWriter.LSquare Field

Left square bracket (to make square bracket matching work better in a text editor).

    const char LSquare = '[';

    
TopicWriter.RSquare Field

Right square bracket (to make square bracket matching work better in a text editor).

    const char RSquare = ']';

    
TopicWriter.LAngle Field

Left angle bracket (to make angle bracket matching work better in a text editor).

    const char LAngle = '<';

    
TopicWriter.RAngle Field

Right angle bracket (to make angle bracket matching work better in a text editor).

    const char RAngle = '>';

    //////////////////////////////////////////////////////////////////////////
    // Private Fields
    //

    
TopicWriter.s_insignificantWhiteSpaceRegex Field

Matches what would be considered insignificant white space in HTML.

    static readonly Regex s_insignificantWhiteSpaceRegex =
        new Regex(@"[ \r\n\t]+");

    
TopicWriter.m_topic Field

Holds the value of the Topic property.

    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    Topic m_topic;

    
TopicWriter.m_topicRenderer Field

Holds the value of the TopicRenderer property.

    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    DefaultTopicRenderer m_topicRenderer;

    //////////////////////////////////////////////////////////////////////////
    // Public Properties
    //

    
TopicWriter.Topic Property

The topic being rendered by this TopicWriter.

    public Topic Topic
    {
        [DebuggerStepThrough]
        get
        {
            return m_topic;
        }
        [DebuggerStepThrough]
        internal set
        {
            m_topic = value;
        }
    }

    
TopicWriter.TopicRenderer Property

The TopicRenderer that created this TopicWriter.

    public DefaultTopicRenderer TopicRenderer
    {
        [DebuggerStepThrough]
        get
        {
            return m_topicRenderer;
        }
        [DebuggerStepThrough]
        internal set
        {
            m_topicRenderer = value;
        }
    }

    //////////////////////////////////////////////////////////////////////////
    // Public Events
    //

    
TopicWriter.WarningInSourceFile Event

Fired to indicate a warning associated with a given line number in a source file.

Remarks

The application may want to use this event to provide feedback to the user.

    public event WarningInSourceFileEventDelegate WarningInSourceFile;

    //////////////////////////////////////////////////////////////////////////
    // Private Properties
    //

    
TopicWriter.ParamListStart Property

Returns LSquare if this is an indexer topic, LParen otherwise.

    char ParamListStart
    {
        get
        {
            if (Topic.PropertyKind == PropertyKind.Indexer)
                return LSquare;
            else
                return LParen;
        }
    }

    
TopicWriter.ParamListEnd Property

Returns RSquare if this is an indexer topic, RParen otherwise.

    char ParamListEnd
    {
        get
        {
            if (Topic.PropertyKind == PropertyKind.Indexer)
                return RSquare;
            else
                return RParen;
        }
    }

    //////////////////////////////////////////////////////////////////////////
    // Public Methods
    //

    
TopicWriter Constructor

Initializes an instance of this class.

Parameters

topic

The topic to render.

textWriter

The TextWriter that renders the markup content.

topicRenderer

The TopicRenderer that created this TextWriter.

    public TopicWriter(Topic topic, TextWriter textWriter,
            DefaultTopicRenderer topicRenderer)
        : base(textWriter)
    {
        m_topic = topic;
        m_topicRenderer = topicRenderer;
    }

    
TopicWriter.GenerateXmlSectionHtml Method

Generates HTML for a single section (e.g. "<summary>") within the XML of a single topic, and sets XmlCommentSection.NameAttribute, XmlCommentSection.CrefAttribute, and XmlCommentSection.LineNumber. Used in the first pass of documentation generation.

Parameters

section

The section being parsed.

xmlReader

After the first call to XmlReader.Read, this is positioned on the XML element that begins the section; for example, "<summary>".

    public void GenerateXmlSectionHtml(XmlCommentSection section,
        XmlReader xmlReader)
    {
        // initialize <xmlReader>
        int initialDepth = xmlReader.Depth;
        bool ok = xmlReader.Read();
        Debug.Assert(ok);
        Debug.Assert(String.Compare(xmlReader.Name,
            section.SectionName.ToString(), true) == 0);

        // set <section.LineNumber> to the line number of this XML section
        // within the source file, for error reporting purposes
        section.LineNumber = ((IXmlLineInfo) xmlReader).LineNumber;

        // get the "name", "cref", and "href" attributes
        section.NameAttribute = xmlReader.GetAttribute("name");
        section.CrefAttribute = xmlReader.GetAttribute("cref");
        section.HrefAttribute = xmlReader.GetAttribute("href");

        // if this element has no child elements there's nothing further to do
        if (xmlReader.IsEmptyElement)
            return;

        // keep track of the state of the "<list>" elements we're nested within
        Stack<ListState> listStateStack = new Stack<ListState>();

        // if this XML section has internal="true" specified and this is an
        // external-only documentation build, skip the XML
        bool skipSection = false;
        if (Topic.DocumentationSet.ExternalOnly)
        {
            try
            {
                string value = xmlReader.GetAttribute("internal");
                if (value != null)
                    skipSection = XmlConvert.ToBoolean(value);
            }
            catch (FormatException ex)
            {
                FireWarningInSourceFile(section.LineNumber,
                    Resources.InvalidXmlAttributeValue, "internal",
                    xmlReader.Name, ex.Message);
            }
        }

        // process the XML in the section; convert to HTML
        bool nextNodeAlreadyRead = false;
        string listType;
        ListState listState;
        while ((nextNodeAlreadyRead || xmlReader.Read()) &&
               (xmlReader.Depth > initialDepth))
        {
            if (skipSection)
                continue;
            nextNodeAlreadyRead = false;
            string element, text, name, cref;
            switch (xmlReader.NodeType)
            {
            case XmlNodeType.Element:

                switch (element = xmlReader.Name)
                {

                case "n": // name (unlinked)

                    // format as an unlinked name
                    if ((text = ReadXmlElementText(xmlReader)) != null)
                    {
                        FormatBeginTag("span,class", "n");
                        WriteEncodedText(text);
                        RenderEndTag();
                    }
                    nextNodeAlreadyRead = true;
                    break;

                case "r": // reference

                    // "<r>" has an optional "cref" attribute
                    if ((cref = xmlReader.GetAttribute("cref")) == null)
                    {
                        // no "cref" attribute
                        if ((text = ReadXmlElementText(xmlReader)) != null)
                        {
                            GenerateInlineReferenceHtml(section,
                                ((IXmlLineInfo) xmlReader).LineNumber,
                                text, null, false, 0);
                        }
                    }
                    else
                    {
                        // "cref" attribute present
                        if ((text = ReadXmlElementText(xmlReader)) != null)
                        {
                            GenerateInlineReferenceHtml(section,
                                ((IXmlLineInfo) xmlReader).LineNumber,
                                cref, text, false, 0);
                        }
                    }
                    nextNodeAlreadyRead = true;
                    break;

                case "ar": // ancestor reference

                    // "<ar>" has an optional "cref" attribute
                    if ((cref = xmlReader.GetAttribute("cref")) == null)
                    {
                        // no "cref" attribute
                        if ((text = ReadXmlElementText(xmlReader)) != null)
                        {
                            GenerateInlineReferenceHtml(section,
                                ((IXmlLineInfo) xmlReader).LineNumber,
                                text, null, false,
                                FindTopicSettings.SearchOnlyAncestors);
                        }
                    }
                    else
                    {
                        // "cref" attribute present
                        if ((text = ReadXmlElementText(xmlReader)) != null)
                        {
                            GenerateInlineReferenceHtml(section,
                                ((IXmlLineInfo) xmlReader).LineNumber,
                                cref, text, false,
                                FindTopicSettings.SearchOnlyAncestors);
                        }
                    }
                    nextNodeAlreadyRead = true;
                    break;

                case "s": // short reference

                    if ((text = ReadXmlElementText(xmlReader)) != null)
                    {
                        GenerateInlineReferenceHtml(section,
                            ((IXmlLineInfo) xmlReader).LineNumber,
                            text, null, true, 0);
                    }
                    nextNodeAlreadyRead = true;
                    break;

                case "pr": // same as <paramref>

                    if ((text = ReadXmlElementText(xmlReader)) != null)
                    {
                        GenerateInlineParameterReferenceHtml(section,
                            ((IXmlLineInfo) xmlReader).LineNumber, text);
                    }
                    nextNodeAlreadyRead = true;
                    break;

                case "tpr": // same as <typeparamref>

                    if ((text = ReadXmlElementText(xmlReader)) != null)
                    {
                        GenerateInlineTypeParameterReferenceHtml(section,
                            ((IXmlLineInfo) xmlReader).LineNumber, text);
                    }
                    nextNodeAlreadyRead = true;
                    break;

                case "see":

                    // "<see>" has a required "cref" attribute
                    if ((cref = xmlReader.GetAttribute("cref")) == null)
                    {
                        FireWarningInSourceFile(
                            ((IXmlLineInfo) xmlReader).LineNumber,
                            Resources.MissingAttribute, "see",
                            "cref");
                    }
                    else
                    {
                        GenerateInlineReferenceHtml(section,
                            ((IXmlLineInfo) xmlReader).LineNumber,
                            cref, null, false, 0);
                    }
                    break;

                case "para":

                    WriteLine();
                    RenderBeginTag("p");
                    break;

                case "c":

                    RenderBeginTag("code");
                    break;

                case "code":

                    string language = xmlReader.GetAttribute("language");
                    if (language == null)
                        language = Resources.RenderCSharp;
                    BeginGrid("Code", language);
                    RenderBeginTag("tr");
                    FormatBeginTag("td,class\n", "Content_");
                    int initialDepth2 = xmlReader.Depth;
                    Write("\r\n<pre>"); // avoid tabs
                    bool nextNodeAlreadyRead2 = false;
                    while ((nextNodeAlreadyRead2 || xmlReader.Read()) &&
                           (xmlReader.Depth > initialDepth2))
                    {
                        nextNodeAlreadyRead2 = false;
                        switch (xmlReader.NodeType)
                        {
                        case XmlNodeType.Element:
                            string xml = xmlReader.ReadOuterXml();
                            nextNodeAlreadyRead2 = true;
                            WriteEncodedText(xml);
                            break;
                        default:
                            WriteEncodedText(
                                xmlReader.Value.Replace("\n", "\r\n"));
                            break;
                        }
                    }
                    Write("</pre>");
                    WriteLine();
                    RenderEndTag(); // "td"
                    FormatEndTag("\n"); // "tr"
                    EndGrid();
                    break;

                case "paramref":

                    // set <name> to the value of the "name" attribute
                    if ((name = xmlReader.GetAttribute("name")) == null)
                    {
                        // missing "name" attribute
                        int lineNumber = ((IXmlLineInfo) xmlReader).LineNumber;
                        FireWarningInSourceFile(lineNumber,
                            Resources.MissingAttribute, "paramref", "name");
                    }
                    else
                    {
                        GenerateInlineParameterReferenceHtml(section,
                            ((IXmlLineInfo) xmlReader).LineNumber, name);
                    }
                    break;

                case "typeparamref":

                    // set <name> to the value of the "name" attribute
                    if ((name = xmlReader.GetAttribute("name")) == null)
                    {
                        // missing "name" attribute
                        int lineNumber = ((IXmlLineInfo) xmlReader).LineNumber;
                        FireWarningInSourceFile(lineNumber,
                            Resources.MissingAttribute, "typeparamref", "name");
                    }
                    else
                    {
                        GenerateInlineTypeParameterReferenceHtml(section,
                            ((IXmlLineInfo) xmlReader).LineNumber, name);
                    }
                    break;

                case "list":

                    // set <listType> to the value of the "type" attribute
                    if ((listType = xmlReader.GetAttribute("type")) == null)
                    {
                        // missing "type" attribute
                        int lineNumber = ((IXmlLineInfo) xmlReader).LineNumber;
                        FireWarningInSourceFile(lineNumber,
                            Resources.MissingAttribute, "list", "type");
                        break;
                    }

                    // set <tagName> to the name of the element to begin, or
                    // String.Empty if none, or null if <listType> is invalid
                    string tagName;
                    if (listType == "bullet")
                        tagName = "ul";
                    else
                    if (listType == "number")
                        tagName = "ol";
                    else
                    if (listType == "table")
                        tagName = String.Empty;
                    else
                    {
                        // invalid "type" attribute
                        int lineNumber = ((IXmlLineInfo) xmlReader).LineNumber;
                        FireWarningInSourceFile(lineNumber,
                            Resources.InvalidListType);
                        tagName = null;
                    }

                    // begin the list HTML (if any)
                    if (tagName != null)
                    {
                        // keep track of the "type" attribute of the "<list>"
                        // elements we're nested within
                        listStateStack.Push(new ListState(listType));

                        // render <tagName>
                        if (tagName.Length > 0)
                            RenderBeginTag(tagName);
                    }
                    WriteLine();
                    break;

                case "listheader":

                    // do nothing if we're not within a list -- this should
                    // only happen if the XML is incorrect
                    if (listStateStack.Count == 0)
                        break;

                    // set <listState> to information about the "<list>"
                    // element we're nested within
                    listState = listStateStack.Peek();

                    // process this element
                    if (listState.ListType == "table")
                        listState.InListHeader = true;
                    break;

                case "item":

                    // do nothing if we're not within a list -- this should
                    // only happen if the XML is incorrect
                    if (listStateStack.Count == 0)
                        break;

                    // set <listState> to information about the "<list>"
                    // element we're nested within
                    listState = listStateStack.Peek();

                    // process this element
                    if (listState.ListType == "table")
                    {
                        listState.BeginGridIfNeeded(this);
                        RenderBeginTag("tr");
                    }
                    break;

                case "term":

                    // do nothing if we're not within a list -- this should
                    // only happen if the XML is incorrect
                    if (listStateStack.Count == 0)
                        break;

                    // set <listState> to information about the "<list>"
                    // element we're nested within
                    listState = listStateStack.Peek();

                    // process this element
                    if (listState.ListType == "table")
                    {
                        if (listState.InListHeader)
                        {
                            if ((text = ReadXmlElementText(xmlReader)) != null)
                                listState.TermHeading = text;
                            nextNodeAlreadyRead = true;
                        }
                        else
                        {
                            listState.BeginGridIfNeeded(this);
                            RenderBeginTag("td");
                        }
                    }
                    break;

                case "description":

                    // do nothing if we're not within a list -- this should
                    // only happen if the XML is incorrect
                    if (listStateStack.Count == 0)
                        break;

                    // set <listState> to information about the "<list>"
                    // element we're nested within
                    listState = listStateStack.Peek();

                    // process this element
                    if ((listState.ListType == "bullet") ||
                        (listState.ListType == "number"))
                        RenderBeginTag("li");
                    else
                    if (listState.ListType == "table")
                    {
                        if (listState.InListHeader)
                        {
                            if ((text = ReadXmlElementText(xmlReader)) != null)
                                listState.DescriptionHeading = text;
                            nextNodeAlreadyRead = true;
                        }
                        else
                        {
                            listState.BeginGridIfNeeded(this);
                            RenderBeginTag("td");
                        }
                    }
                    break;

                default:

                    // unknown XML element...

                    // call the TransformCustomInsideXml custom hook, if any
                    string nodeName = xmlReader.Name;
                    try
                    {
                        if (TransformCustomInsideXml(section, xmlReader))
                        {
                            nextNodeAlreadyRead = true;
                            break;
                        }
                    }
                    catch (XmlException ex)
                    {
                        FireWarningInSourceFile(
                            ((IXmlLineInfo) xmlReader).LineNumber,
                            Resources.InvalidXmlElementSyntax, nodeName,
                            ex.Message);
                        break;
                    }
                    catch (WarningException ex)
                    {
                        FireWarningInSourceFile(
                            ((IXmlLineInfo) xmlReader).LineNumber,
                            "{0}", ex.Message);
                        break;
                    }

                    // write the XML element as-is (i.e. as HTML), but first
                    // check to see if it's in the "<AllowXmlElements>" section
                    // of the CodeDoc XML project file
                    string elementName = xmlReader.Name;
                    if (!Topic.DocumentationSet.IsAllowedXmlElement(
                        elementName))
                    {
                        int lineNumber = ((IXmlLineInfo) xmlReader).LineNumber;
                        FireWarningInSourceFile(lineNumber,
                            Resources.NotAllowedXmlElement, elementName);
                    }

                    // write element
                    Write(LAngle);
                    Write(elementName);
                    if (xmlReader.MoveToFirstAttribute())
                    {
                        while (true)
                        {
                            Write(' ');
                            Write(xmlReader.Name);
                            Write('=');
                            Write('"');
                            WriteEncodedText(xmlReader.Value);
                            Write('"');
                            if (!xmlReader.MoveToNextAttribute())
                                break;
                        }
                    }
                    Write(RAngle);                    
                    break;

                }
                break;

            case XmlNodeType.EndElement:

                switch (xmlReader.Name)
                {

                case "para":

                    RenderEndTag();
                    WriteLine();
                    break;

                case "c":

                    RenderEndTag();
                    break;

                case "list":

                    // do nothing if we're not within a list -- this should
                    // only happen if the XML is incorrect
                    if (listStateStack.Count == 0)
                        break;

                    // set <listState> to information about the "<list>"
                    // element we're nested within
                    listState = listStateStack.Peek();

                    // end the list HTML
                    if (listState.ListType == "table")
                    {
                        if (listState.BegunGrid)
                            EndGrid();
                    }
                    else
                        RenderEndTag();
                    WriteLine();
                    break;

                case "listheader":

                    // do nothing if we're not within a list -- this should
                    // only happen if the XML is incorrect
                    if (listStateStack.Count == 0)
                        break;

                    // set <listState> to information about the "<list>"
                    // element we're nested within
                    listState = listStateStack.Peek();

                    // process this element
                    if (listState.ListType == "table")
                        listState.InListHeader = false;
                    break;

                case "item":

                    // do nothing if we're not within a list -- this should
                    // only happen if the XML is incorrect
                    if (listStateStack.Count == 0)
                        break;

                    // set <listState> to information about the "<list>"
                    // element we're nested within
                    listState = listStateStack.Peek();

                    // process this element
                    if (listState.ListType == "table")
                    {
                        RenderEndTag(); // "tr"
                        WriteLine();
                    }
                    break;

                case "term":

                    // do nothing if we're not within a list -- this should
                    // only happen if the XML is incorrect
                    if (listStateStack.Count == 0)
                        break;

                    // set <listState> to information about the "<list>"
                    // element we're nested within
                    listState = listStateStack.Peek();

                    // process this element
                    if (listState.ListType == "table")
                    {
                        if (!listState.InListHeader)
                        {
                            RenderEndTag(); // "td"
                            WriteLine();
                        }
                    }
                    break;

                case "description":

                    // do nothing if we're not within a list -- this should
                    // only happen if the XML is incorrect
                    if (listStateStack.Count == 0)
                        break;

                    // set <listState> to information about the "<list>"
                    // element we're nested within
                    listState = listStateStack.Peek();

                    // process this element
                    if ((listState.ListType == "bullet") ||
                        (listState.ListType == "number"))
                        RenderEndTag();
                    else
                    if (listState.ListType == "table")
                    {
                        if (!listState.InListHeader)
                        {
                            RenderEndTag(); // "td"
                            WriteLine();
                        }
                    }
                    break;

                default:

                    // unknown XML element -- send it out as-is
                    Write(LAngle);
                    Write('/');
                    Write(xmlReader.Name);
                    Write(RAngle);                    
                    break;

                }
                break;

            case XmlNodeType.SignificantWhitespace:
            case XmlNodeType.Whitespace:
            case XmlNodeType.Text:

                WriteEncodedText(s_insignificantWhiteSpaceRegex
                    .Replace(xmlReader.Value, " ").Replace("\n", "\r\n"));
                break;

            }
        }
    }

    
TopicWriter.WriteTopicPage Method

Generates HTML corresponding to the topic specified in the constructor. The HTML is written to the TextWriter specified in the constructor. Generates a standalone page. Used in the second pass of documentation generation.

    public void WriteTopicPage()
    {
        // begin the HTML document, including beginning the "<html>" block
        BeginHtmlDocument(HtmlDocType.XhtmlTransitional,
            TopicRenderer.MarkOfTheWeb);

        // begin the "<head>" block
        FormatBeginTag("head\n");

        // write the "<title>" block
        RenderBeginTag("title");
        WriteEncodedText(Topic.ShortTitle);
        FormatEndTag("\n\n");

        // write the "<link>" element
        FormatTag("link,rel,href\n", "stylesheet", "_CodeDoc.css");

        // end the "<head>" block
        FormatEndTag("\n\n");

        // begin the "<body>" block
        FormatBeginTag("body,id\n","DocBrowserTopic");

        // write the topic HTML that goes on the page
        WriteTopicInnerHtml(false, true);

        // write the page footer, if any (e.g. copyright)
        WriteTopicFooterHtml();

        // write script code which, when the page loads, notifies the parent
        // window (if any) that the topic page has loaded
        FormatBeginTag("script,type,defer", "text/javascript", "defer");
        Write("if (parent.OnTopicLoaded != undefined)");
        WriteLine();
        Write(String.Format("  parent.OnTopicLoaded({0}, {1});", Topic.Id,
            ((Topic.OverloadListTopic != null)
                ? Topic.OverloadListTopic.Id.ToString() : "null")));
        FormatEndTag("\n\n");

        // end the "<body>" and "<html>" blocks
        FormatEndTag("\n");
        FormatEndTag("\n");
    }

    
TopicWriter.WriteEmbeddedTopic Method

Generates HTML corresponding to the topic specified in the constructor. The HTML is written to the TextWriter specified in the constructor. Generates "embedded" HTML, i.e. a "<div>" designed to fit compactly within formatted source code.

    public void WriteEmbeddedTopic()
    {
        // begin the "<div>"
        FormatBeginTag("div,class\n", "EmbeddedTopic_");

        // write the topic HTML
        WriteTopicInnerHtml(true, false);

        // end the "<div>"
        FormatEndTag("\n");
    }

    
TopicWriter.WriteCodeNamespacePage Method

Generates HTML corresponding to the topic specified in the constructor. The HTML is written to the TextWriter specified in the constructor. Assuming the current topic is a namespace topic, this method generates the HTML file, used in the formatted code directory, that represents the namespace and contains links to the formatted code HTML files.

    public void WriteCodeNamespacePage()
    {
        // begin the HTML document, including beginning the "<html>" block
        BeginHtmlDocument(HtmlDocType.XhtmlTransitional,
            TopicRenderer.MarkOfTheWeb);

        // begin the "<head>" block
        FormatBeginTag("head\n");

        // write the "<title>" block
        RenderBeginTag("title");
        string title = String.Format(Resources.CodeOverloadTitle,
            Topic.ShortTitle);
        WriteEncodedText(title);
        FormatEndTag("\n\n");

        // write the "<link>" element
        FormatTag("link,rel,href\n", "stylesheet", "_CodeDoc.css");

        // end the "<head>" block
        FormatEndTag("\n\n");

        // begin the "<body>" block
        FormatBeginTag("body,id\n","DocBrowserTopic");

        // write the namespace member list sections
        WriteTopicInnerHtml(false, false);

        // write the page footer, if any (e.g. copyright)
        WriteTopicFooterHtml();

        // end the "<body>" and "<html>" blocks
        FormatEndTag("\n");
        FormatEndTag("\n");
    }

    
TopicWriter.WriteCodeOverloadListPage Method

Generates HTML corresponding to the topic specified in the constructor. The HTML is written to the TextWriter specified in the constructor. Assuming the current topic is an overload list, this method generates a code overload list page, i.e. a page, designed to be used within a directory of formatted source code, containing a list of links to embedded code blocks, one link per overload in the overload list.

    public void WriteCodeOverloadListPage()
    {
        // begin the HTML document, including beginning the "<html>" block
        BeginHtmlDocument(HtmlDocType.XhtmlTransitional,
            TopicRenderer.MarkOfTheWeb);

        // begin the "<head>" block
        FormatBeginTag("head\n");

        // write the "<title>" block
        RenderBeginTag("title");
        string title = String.Format(Resources.CodeOverloadTitle,
            Topic.ShortTitle);
        WriteEncodedText(title);
        FormatEndTag("\n\n");

        // write the "<link>" element
        FormatTag("link,rel,href\n", "stylesheet", "_CodeDoc.css");

        // end the "<head>" block
        FormatEndTag("\n\n");

        // begin the "<body>" block
        FormatBeginTag("body,id\n","DocBrowserTopic");

        // write the topic header
        FormatBeginTag("div,class", "TopicHeader");
        WriteEncodedText(Topic.ShortTitle);
        FormatEndTag("\n\n");

        // cast to the derived type
        OverloadListTopic overloadListTopic = (OverloadListTopic) Topic;

        // begin the "Overloads List" section
        BeginSection(title);

        // begin the table
        BeginGrid("Overloads", Resources.RenderName,
            Resources.RenderDescription);

        // sort overloads in <overloadListTopic>
        overloadListTopic.SortOverloadTopics();

        // write each row
        foreach (Topic overloadTopic
            in overloadListTopic.OverloadTopics)
        {
            // begin the row
            RenderBeginTag("tr");

            // write the first cell
            RenderBeginTag("td");
            FormatBeginTag("a,href",
                overloadTopic.CodeHtmlUrl);
            WriteEncodedText(overloadTopic.ShortSignature);
            RenderEndTag(); // "a"
            FormatEndTag("\n"); // "td"

            // write the second cell
            RenderBeginTag("td");
            overloadTopic.WriteSummaryHtml(this);
            RenderEndTag(); // "td"

            // end the row
            RenderEndTag(); // "tr"
        }

        // end the table
        EndGrid();

        // end the "Overloads List" section
        EndSection();

        // write the space at the bottom of the page
        FormatBeginTag("div,class", "TopicFooter");
        FormatEndTag("\n\n");

        // write the page footer, if any (e.g. copyright)
        WriteTopicFooterHtml();

        // end the "<body>" and "<html>" blocks
        FormatEndTag("\n");
        FormatEndTag("\n");
    }

    //////////////////////////////////////////////////////////////////////////
    // Protected Methods
    //

    
TopicWriter.GetTopicUrl Method

Returns the URL to a given topic, which varies depending on the value of TopicRenderer.CodeHtml.

Parameters

topic

The topic to return the URL of.

    protected string GetTopicUrl(Topic topic)
    {
        if (TopicRenderer.CodeHtml)
            return topic.CodeHtmlUrl;
        else
            return topic.DocHtmlFileName;
    }

    
TopicWriter.GenerateInlineReferenceHtml Method

Generates HTML corresponding to a "<r>...</r>" (reference) "<s>...</s>" (short reference) XML construct.

Parameters

section

The section being parsed.

lineNumber

The line number of the line of the source file containing this XML construct.

name

The contents of "<r>...</r>" or "<s>...</s>".

text

The text to include in the generated HTML. If text is null, text is derived from name and isShortReference.

isShortReference

true if name is a "<s>...</s>" construct; false if it's a "<r>...</r>" construct.

settings

Settings that control the search for name.

    protected void GenerateInlineReferenceHtml(XmlCommentSection section,
        int lineNumber, string name, string text, bool isShortReference,
        FindTopicSettings settings)
    {
        // treat "{" and "}" like "<" and ">" in <name> (so user doesn't have
        // to type e.g. "&lt;" and "&gt;" in XML)
        name = name.Replace('{', '<').Replace('}', '>');

        // if <text> is not explicitly specified, set <text> to the text to
        // display -- short text for "<s>", full text for "<r>"
        if (text == null)
        {
            if (isShortReference)
            {
                // remove leading types; e.g. "A<>.B<>" --> "B<>" 
                int ich = name.LastIndexOf('.');
                if ((ich > 0) && (ich < (name.Length - 1)))
                    text = name.Substring(ich + 1);
                else
                    text = name;

                // remove trailing type parameters, e.g. "B<>" --> "B"
                if ((ich = text.IndexOf('<')) > 0)
                    text = text.Substring(0, ich);
            }
            else
                text = name;
        }

        // set <targetTopic> to the referenced topic, or null if none; for
        // error reporting purposes, set <lineNumber> to the line number of
        // this XML element
        Topic targetTopic = Topic.DocumentationSet.FindDocumentedTopic(
            name, section.Topic, lineNumber, settings);

        // format <text>
        if (targetTopic != null)
        {
            // format as a hyperlink to the target topic
            FormatBeginTag("a,class,href,title", "r",
                GetTopicUrl(targetTopic), targetTopic.LongTitle);
            WriteEncodedText(text);
            RenderEndTag();
        }
        else
        {
            // format as an unlinked name
            FormatBeginTag("span,class", "n");
            WriteEncodedText(text);
            RenderEndTag();
        }
    }

    
TopicWriter.GenerateInlineParameterReferenceHtml Method

Generates HTML corresponding to a "<pr>...</pr>" or "<paramref> name="..."/>" (parameter reference) XML construct.

Parameters

section

The section being parsed.

lineNumber

The line number of the line of the source file containing this XML construct.

name

The name of the parameter being referenced.

    protected void GenerateInlineParameterReferenceHtml(
        XmlCommentSection section, int lineNumber, string name)
    {
        // check to make sure there is a parameter named <name>; generate a
        // warning if not
        if (!Topic.HasParameter(name))
        {
            FireWarningInSourceFile(lineNumber, Resources.NoParameter, name,
                Topic.ShortTitle);
        }

        // format <name> as a parameter reference
        FormatBeginTag("span,class", "pr");
        WriteEncodedText(name);
        RenderEndTag();
    }

    
TopicWriter.GenerateInlineTypeParameterReferenceHtml Method

Generates HTML corresponding to a "<tpr>...</tpr>" or "<typeparamref> name="..."/>" (type parameter reference) XML construct.

Parameters

section

The section being parsed.

lineNumber

The line number of the line of the source file containing this XML construct.

name

The name of the type parameter being referenced.

    protected void GenerateInlineTypeParameterReferenceHtml(
        XmlCommentSection section, int lineNumber, string name)
    {
        // check to make sure there is a type parameter named <name>; generate
        // a warning if not
        if (!Topic.HasTypeParameter(name))
        {
            FireWarningInSourceFile(lineNumber, Resources.NoTypeParameter,
                name, Topic.ShortTitle);
        }

        // format the <name> as a type parameter reference
        FormatBeginTag("span,class", "tpr");
        WriteEncodedText(name);
        RenderEndTag();
    }

    
TopicWriter.GenerateCodeBlockHtml Method

Generates HTML corresponding to a block of code.

Parameters

xmlReader

The XML element to read.

    protected void GenerateCodeBlockHtml(XmlReader xmlReader)
    {
        int initialDepth2 = xmlReader.Depth;
        Write("\r\n<pre>"); // avoid tabs
        bool nextNodeAlreadyRead2 = false;
        while ((nextNodeAlreadyRead2 || xmlReader.Read()) &&
               (xmlReader.Depth > initialDepth2))
        {
            nextNodeAlreadyRead2 = false;
            switch (xmlReader.NodeType)
            {
            case XmlNodeType.Element:
                string xml = xmlReader.ReadOuterXml();
                nextNodeAlreadyRead2 = true;
                WriteEncodedText(xml);
                break;
            default:
                WriteEncodedText(
                    xmlReader.Value.Replace("\n", "\r\n"));
                break;
            }
        }
        Write("</pre>");
    }

    
TopicWriter.WriteTopicInnerHtml Method

Generates HTML corresponding to the topic specified in the constructor. The HTML is written to the TextWriter specified in the constructor. Generates a "<div>" appropriate for inclusion on an existing HTML page.

Parameters

embedded

If true "embedded" HTML is generated, i.e. a "<div>" designed to fit compactly within formatted source code. If false, complete HTML is generated, designed for use in standalone documentation pages.

writeViewTableOfContentsLink

If true, a "View Table of Contents" link is written (or, rather, script is written which display that link if the table of contents is not visible, i.e. if the page is not running in the DocBrowser frameset).

    protected void WriteTopicInnerHtml(bool embedded,
        bool writeViewTableOfContentsLink)
    {
        // write some comments for debugging purposes
        Write(String.Format("<!-- Id: {0} -->", Topic.Id));
        WriteLine();
        Write(String.Format("<!-- Name: {0} -->", Topic.Name));
        WriteLine();
        Write(String.Format("<!-- CanonicalName: {0} -->",
            Topic.CanonicalName));
        WriteLine();
        WriteLine();

        // write the topic header
        FormatBeginTag("div,class", "TopicHeader");
        WriteEncodedText(Topic.ShortTitle);
        FormatEndTag("\n\n");

        if (writeViewTableOfContentsLink)
        {
            // write script code which, when the page loads, detects if the
            // page is being displayed within the context of the parent
            // frameset and, if not, displays a link which, when clicked, opens
            // the frameset
            FormatBeginTag("script,type", "text/javascript");
            Write("if (parent.OnTopicLoaded == undefined)");
            WriteLine();
            Write(String.Format(
                "  document.write('<div class=\"ViewToc\"><a href=\"_SyncToc.htm#{0}\">{1}</a></div>');",
                Topic.DocHtmlFileName, Resources.ViewTableOfContents));
            WriteLine();
            FormatEndTag("\n\n");
        }

        // write the "Summary" section, if any
        if (Topic.TopicKind != TopicKind.NamespaceTopic)
            WriteSummarySection(embedded);

        // if this is an overload list topic, write the "Overloads List"
        // section and then quit
        if (Topic is OverloadListTopic)
        {
            WriteOverloadListSection();
            return;
        }

        // write the "Syntax" section, if any
        if (Topic.TopicKind != TopicKind.NamespaceTopic)
            WriteSyntaxSection(embedded);

        // write the "Members" section, if any
        if (Topic.TopicKind == TopicKind.EnumTopic)
            WriteEnumerationMemberListSection();
        else
        if (!embedded)
        {
            if ((Topic.TopicKind == TopicKind.ClassTopic) ||
                (Topic.TopicKind == TopicKind.StructTopic) ||
                (Topic.TopicKind == TopicKind.NamespaceTopic) ||
                (Topic.TopicKind == TopicKind.InterfaceTopic))
                WriteMemberListSections();
        }

        // write the "Exceptions" section, if any
        if (Topic.HasExceptionsHtml)
            WriteExceptionsSection();

        // write the "Permissions" section, if any
        if (Topic.HasPermissionsHtml)
            WritePermissionsSection();

        // write the "Remarks" section, if any
        if (Topic.HasRemarksHtml)
        {
            BeginSection(Resources.RenderRemarks);
            Topic.WriteRemarksHtml(this);
            EndSection();
        }

        // write the "Example" section, if any
        if (Topic.HasExampleHtml)
        {
            BeginSection(Resources.RenderExample);
            Topic.WriteExampleHtml(this);
            EndSection();
        }

        // write the "See Also" section, if any
        if (Topic.HasSeeAlsoHtml)
            WriteSeeAlsoSection();

        // write the space at the bottom of the page, if any
        if (!embedded)
        {
            FormatBeginTag("div,class", "TopicFooter");
            FormatEndTag("\n\n");
        }
    }

    
TopicWriter.WriteSummarySection Method

Writes the "Summary" section of the topic.

Parameters

embedded

If true "embedded" HTML is generated, designed to fit compactly within formatted source code. If false, complete HTML is generated, designed for use in standalone documentation pages.

    protected void WriteSummarySection(bool embedded)
    {
        // begin the Summary section
        FormatBeginTag("div,class", "Summary");

        // write the topic summary text
        Topic.WriteSummaryHtml(this);

        if (!embedded)
        {
            // write the parent type name (if any)
            TokenList parentTypeName = Topic.ParentTypeName;
            if (parentTypeName != null)
            {
                WriteLabeledParagraph(Resources.RenderParentTypeLabel,
                    LinkHtml(parentTypeName.ToString(), Topic.ParentTopic));
            }

            // write the namespace name (if any)
            TokenList namespaceName = Topic.NamespaceName;
            if (namespaceName != null)
            {
                WriteLabeledParagraph(Resources.RenderNamespaceLabel,
                    LinkHtml(namespaceName.ToString(), Topic.NamespaceTopic));
            }

            // write the assembly name (if any)
            if ((Topic.SourceFile != null) &&
                (Topic.SourceFile.AssemblyName != null))
            {
                WriteLabeledParagraph(Resources.RenderAssemblyLabel,
                    Html(Topic.SourceFile.AssemblyName));
            }
        }

        // end the Summary section
        FormatEndTag("\n"); // "div"
    }

    
TopicWriter.WriteOverloadListSection Method

Writes the "Overload List" section of the topic.

Remarks

This method can only be called if this topic is of type OverloadListTopic.

    protected void WriteOverloadListSection()
    {
        // cast to the derived type
        OverloadListTopic overloadListTopic = (OverloadListTopic) Topic;

        // begin the "Overloads List" section
        BeginSection(Resources.RenderOverloadList);

        // begin the table
        BeginGrid("Overloads", Resources.RenderName,
            Resources.RenderDescription);

        // sort overloads in <overloadListTopic>
        overloadListTopic.SortOverloadTopics();

        // write each row
        foreach (Topic overloadTopic
            in overloadListTopic.OverloadTopics)
        {
            // begin the row
            RenderBeginTag("tr");

            // write the first cell
            RenderBeginTag("td");
            FormatBeginTag("a,href", GetTopicUrl(overloadTopic));
            WriteEncodedText(overloadTopic.ShortSignature);
            RenderEndTag(); // "a"
            FormatEndTag("\n"); // "td"

            // write the second cell
            RenderBeginTag("td");
            overloadTopic.WriteSummaryHtml(this);
            RenderEndTag(); // "td"

            // end the row
            RenderEndTag(); // "tr"
        }

        // end the table
        EndGrid();

        // end the "Overloads List" section
        EndSection();
    }

    
TopicWriter.WriteSyntaxSection Method

Writes the "Syntax" section of the topic.

Parameters

embedded

If true "embedded" HTML is generated, designed to fit compactly within formatted source code. If false, complete HTML is generated, designed for use in standalone documentation pages.

    protected void WriteSyntaxSection(bool embedded)
    {
        // begin the "Syntax" section
        BeginSection(embedded ? null : Resources.RenderSyntax);

        // retrieve this topic's type parameters and regular parameters
        TokenList[] typeParameters = Topic.TypeParameters;
        IList<Parameter> parameters = Topic.Parameters;

        if (!embedded)
        {
            // begin the table (one row, one column)
            BeginGrid("Code", Resources.RenderCSharp);
            RenderBeginTag("tr");
            FormatBeginTag("td,class\n", "Content_");

            // write attributes, if any; skip attributes that match any regular
            // expresion in <TopicRenderer.HideAttributesRegexList>
            if (Topic.Attributes != null)
            {
                foreach (TokenList attribute in Topic.Attributes)
                {
                    string attributeString = attribute.ToString();
                    bool hideAttribute = false;
                    foreach (Regex regex in
                        TopicRenderer.HideAttributesRegexList)
                    {
                        if (regex.Match(attributeString).Success)
                        {
                            hideAttribute = true;
                            break;
                        }
                    }
                    if (!hideAttribute)
                    {
                        RenderBeginTag("p");
                        Write(LSquare);
                        WriteEncodedText(attributeString);
                        Write(RSquare);
                        FormatEndTag("\n"); // "p"
                    }
                }
            }

            // begin the next paragraph of the declaration -- first line is
            // left-justified, following lines are indented
            RenderBeginTag("p");

            // write modifiers (e.g. "public static")
            WriteEncodedTokenList(Topic.Modifiers, " ");

            // write the appropriate C# type declaration keyword, if any
            switch (Topic.TopicKind)
            {
            case TopicKind.ClassTopic:
                Write("class ");
                break;
            case TopicKind.StructTopic:
                Write("struct ");
                break;
            case TopicKind.InterfaceTopic:
                Write("interface ");
                break;
            case TopicKind.EventTopic:
                Write("event ");
                break;
            case TopicKind.DelegateTopic:
                Write("delegate ");
                break;
            case TopicKind.EnumTopic:
                Write("enum ");
                break;
            }

            // write the member type, if any
            if (Topic.MemberType != null)
            {
                WriteTypeNameHtml(Topic.MemberType);
                Write(' ');
            }

            // write the member name
            if (Topic.ExplicitPrefix != null)
                WriteEncodedText(Topic.ExplicitPrefix.ToString());
            if (Topic.PropertyKind == PropertyKind.Indexer)
                WriteEncodedText(Topic.MemberName.Replace("Item", "this"));
            else
                WriteEncodedText(Topic.MemberName);

            // if this topic has a type parameter list, write it; set
            // <typeParameters> to the list of type parameters
            if (typeParameters != null)
            {
                WriteEncodedText(String.Format("<{0}>",
                    TokenList.Join(',', typeParameters).ToString()));
            }

            // add a blank
            Write(' ');

            // if this topic has a base type list, write it
            TokenList[] baseTypes = Topic.BaseTypes;
            if (baseTypes != null)
            {
                Write(": ");
                bool first = true;
                foreach (TokenList baseType in baseTypes)
                {
                    if (first)
                        first = false;
                    else
                        Write(", ");
                    WriteTypeNameHtml(baseType);
                }
            }

            // if this topic has a parameter list, write it; set <parameters>
            // to the list of parameters and <lastParamIndex> to the 0-based
            // index of the last parameter
            int lastParamIndex;
            if (parameters != null)
            {
                // topic has a parameter list with zero or more parameters
                lastParamIndex = parameters.Count - 1;
                if (lastParamIndex >= 0)
                {
                    // one or more parameters -- write them
                    Write(ParamListStart);
                    FormatTag("br\n");
                    for (int paramIndex = 0;
                         paramIndex <= lastParamIndex;
                         paramIndex++)
                    {
                        Parameter parameter = parameters[paramIndex];
                        string kind = parameter.CSharpKind;
                        if (kind != null)
                            WriteEncodedText(String.Format("{0} ", kind));
                        WriteTypeNameHtml(parameter.ParameterType);
                        Write(' ');
                        FormatBeginTag("span,class", "ParameterName");
                        WriteEncodedText(parameter.ParameterName);
                        RenderEndTag();
                        if (paramIndex != lastParamIndex)
                            Write(',');
                        FormatTag("br\n");
                    }
                }
                else
                {
                    // zero parameters -- write "()" on a single line
                    Write(ParamListStart);
                    Write(ParamListEnd);
                }
            }
            else
                lastParamIndex = -1;

            // end this paragraph of the declaration
            FormatEndTag("\n"); // "p"

            // begin the second paragraph of the declaration (if any)
            if (lastParamIndex >= 0)
            {
                RenderBeginTag("p");
                Write(ParamListEnd);
                Write(' ');
            }

            // if this topic has get and/or set accessors, write them
            Accessor getAccessor = Topic.GetAccessor;
            Accessor setAccessor = Topic.SetAccessor;
            if ((getAccessor != null) || (setAccessor != null))
            {
                Write(LBrace);
                Write(' ');
                if ((getAccessor != null) &&
                    (!Topic.DocumentationSet.ExternalOnly ||
                     getAccessor.IsExternallyAccessible))
                {
                    WriteEncodedText(getAccessor.ToString());
                    Write("; ");
                }
                if ((setAccessor != null) &&
                    (!Topic.DocumentationSet.ExternalOnly ||
                     setAccessor.IsExternallyAccessible))
                {
                    WriteEncodedText(setAccessor.ToString());
                    Write("; ");
                }
                Write(RBrace);
            }

            // end the second paragraph of the declaration (if any)
            if (lastParamIndex >= 0)
                FormatEndTag("\n"); // "p"

            // if this topic has constraints ("where" in C#), write them
            if (Topic.Constraints != null)
            {
                // put each "where" clause on a new line
                RenderBeginTag("p");
                bool first = true;
                foreach (Token token in Topic.Constraints)
                {
                    if (token.IsReservedWord("where") && !first)
                    {
                        FormatEndTag("\n"); // "p"
                        RenderBeginTag("p");
                    }
                    WriteEncodedText(token.ToString());
                    first = false;
                }
                FormatEndTag("\n"); // "p"
            }

            // end the table
            RenderEndTag(); // "td"
            FormatEndTag("\n"); // "tr"
            EndGrid();
        }

        // write the "Type Parameters" subsection, if there are one or more
        // type parameters
        if (typeParameters != null)
        {
            BeginSubsection(Resources.RenderTypeParameters);
            foreach (TokenList typeParameter in typeParameters)
            {
                // set <name> to the type parameter name
                string name = typeParameter.ToString();

                // write the type parameter name
                FormatBeginTag("p,class", "TypeParameterName");
                WriteEncodedText(name);
                FormatEndTag("\n"); // "p"

                // set <descriptionHtml> to the type parameter description HTML
                string descriptionHtml = Topic.GetTypeParameterHtml(name);
                if (descriptionHtml == null)
                {
                    FireWarningInSourceFile(
                        Topic.FirstXmlComment.GetLineNumber(),
                        Resources.NoDocForTypeParameter, name);
                    descriptionHtml = "";
                }

                // write the type parameter description
                FormatBeginTag("p,class", "TypeParameterDescription");
                Write(descriptionHtml);
                RenderEndTag(); // "p"
            }
            EndSubsection();
        }

        // write the "Parameters" subsection, if there are one or more
        // parameters
        if ((parameters != null) && (parameters.Count > 0))
        {
            BeginSubsection(Resources.RenderParameters);
            foreach (Parameter parameter in parameters)
            {
                // write the parameter name
                FormatBeginTag("p,class", "ParameterName");
                WriteEncodedText(parameter.ParameterName);
                FormatEndTag("\n"); // "p"

                // set <descriptionHtml> to the parameter description HTML
                string descriptionHtml =
                    Topic.GetParameterHtml(parameter.ParameterName);
                if (descriptionHtml == null)
                {
                    FireWarningInSourceFile(
                        Topic.FirstXmlComment.GetLineNumber(),
                        Resources.NoDocForParameter, parameter.ParameterName);
                    descriptionHtml = "";
                }

                // write the parameter description
                FormatBeginTag("p,class", "ParameterDescription");
                Write(descriptionHtml);
                RenderEndTag(); // "p"
            }
            EndSubsection();
        }

        // write the "Returns" section, if any
        if (Topic.HasReturnsHtml)
        {
            BeginSubsection(Resources.RenderReturns);
            Topic.WriteReturnsHtml(this);
            EndSubsection();
        }

        // write the "Value" section, if any
        if (Topic.HasValueHtml)
        {
            BeginSubsection(Resources.RenderValue);
            Topic.WriteValueHtml(this);
            EndSubsection();
        }

        // end the "Syntax" section
        EndSection();
    }

    
TopicWriter.WriteEnumerationMemberListSection Method

Writes the "Members" section of the topic, assuming the topic is TopicKind.EnumTopic.

    protected void WriteEnumerationMemberListSection()
    {
        // begin the "Members" section
        BeginSection(Resources.RenderMembers);

        // begin the table
        BeginGrid("Members", Resources.RenderName,
            Resources.RenderDescription);

        // sort child topics
        Topic.SortChildTopics();

        // write each row
        foreach (Topic memberTopic in Topic.ChildTopics)
        {
            // begin the row
            RenderBeginTag("tr");

            // write the first cell
            RenderBeginTag("td");
            FormatBeginTag("span,class", "n");
            WriteEncodedText(memberTopic.MemberName);
            RenderEndTag();
            FormatEndTag("\n"); // "td"

            // write the second cell
            RenderBeginTag("td");
            memberTopic.WriteSummaryHtml(this);
            memberTopic.WriteRemarksHtml(this);
            RenderEndTag(); // "td"

            // end the row
            RenderEndTag(); // "tr"
        }

        // end the table
        EndGrid();

        // end the "Members" section
        EndSection();
    }

    
TopicWriter.WriteMemberListSections Method

Writes the members sections of the topic, assuming the topic is not a TopicKind.EnumTopic.

    protected void WriteMemberListSections()
    {
        // divide each member topic into elements of <sections>
        List<MemberInfo>[] sections =
            new List<MemberInfo>[(int) MemberSection.Length];
        foreach (MemberInfo memberInfo in Topic.MemberTopics)
        {
            // categorize this topic
            if (memberInfo.Topic.ExplicitPrefix != null)
            {
                AddItem(memberInfo, ref sections[
                    (int) MemberSection.Implementations]);
            }
            else
            switch (memberInfo.Topic.TopicKind)
            {

            case TopicKind.MethodTopic:

                switch (memberInfo.Topic.MethodKind)
                {

                case MethodKind.Constructor:

                    AddItem(memberInfo, ref sections[
                        (int) MemberSection.Constructors]);
                    break;

                case MethodKind.Destructor:

                    AddItem(memberInfo, ref sections[
                        (int) MemberSection.Destructors]);
                    break;

                case MethodKind.Operator:

                    AddItem(memberInfo, ref sections[
                        (int) MemberSection.Operators]);
                    break;

                default:

                    AddItem(memberInfo, ref sections[
                        (int) MemberSection.Methods]);
                    break;

                }
                break;

            case TopicKind.FieldTopic:

                AddItem(memberInfo, ref sections[
                    (int) MemberSection.Fields]);
                break;

            case TopicKind.PropertyTopic:

                AddItem(memberInfo, ref sections[
                    (int) MemberSection.Properties]);
                break;

            case TopicKind.EventTopic:

                AddItem(memberInfo, ref sections[
                    (int) MemberSection.Events]);
                break;

            case TopicKind.ClassTopic:

                AddItem(memberInfo, ref sections[
                    (int) MemberSection.Classes]);
                break;

            case TopicKind.InterfaceTopic:

                AddItem(memberInfo, ref sections[
                    (int) MemberSection.Interfaces]);
                break;

            case TopicKind.StructTopic:

                AddItem(memberInfo, ref sections[
                    (int) MemberSection.Structures]);
                break;

            case TopicKind.DelegateTopic:

                AddItem(memberInfo, ref sections[
                    (int) MemberSection.Delegates]);
                break;

            case TopicKind.EnumTopic:

                AddItem(memberInfo, ref sections[
                    (int) MemberSection.Enumerations]);
                break;
            }
        }

        // loop once for each potential members section
        for (MemberSection memberSection = 0;
             memberSection < MemberSection.Length;
             memberSection++)
        {
            // set <sectionTopics> to the topics in this section
            List<MemberInfo> sectionTopics = sections[(int) memberSection];

            // skip this section if there are no topics
            if (sectionTopics == null)
                continue;

            // sort this section
            sectionTopics.Sort();

            // set <title> to the section title
            string title;
            switch (memberSection)
            {
            case MemberSection.Constructors:
                title = Resources.RenderConstructors;
                break;
            case MemberSection.Destructors:
                title = Resources.RenderDestructors;
                break;
            case MemberSection.Fields:
                title = Resources.RenderFields;
                break;
            case MemberSection.Properties:
                title = Resources.RenderProperties;
                break;
            case MemberSection.Methods:
                title = Resources.RenderMethods;
                break;
            case MemberSection.Operators:
                title = Resources.RenderOperators;
                break;
            case MemberSection.Events:
                title = Resources.RenderEvents;
                break;
            case MemberSection.Implementations:
                title = Resources.RenderImplementations;
                break;

            case MemberSection.Classes:
                title = Resources.RenderClasses;
                break;
            case MemberSection.Interfaces:
                title = Resources.RenderInterfaces;
                break;
            case MemberSection.Structures:
                title = Resources.RenderStructures;
                break;
            case MemberSection.Delegates:
                title = Resources.RenderDelegates;
                break;
            case MemberSection.Enumerations:
                title = Resources.RenderEnumerations;
                break;

            default:
                title = Resources.RenderMembers;
                break;
            }

            // begin the section
            BeginSection(title);

            // begin the table; write the access icon column unless we're in
            // external-only mode and this a namespace topic (in which case all
            // members are public
            bool writeAccessIconColumn;
            if (Topic.DocumentationSet.ExternalOnly &&
                (Topic.TopicKind == TopicKind.NamespaceTopic))
            {
                writeAccessIconColumn = false;
                BeginGrid("Members", Resources.RenderName,
                    Resources.RenderDescription);
            }
            else
            {
                writeAccessIconColumn = true;
                BeginGrid("Members", "", Resources.RenderName,
                    Resources.RenderDescription);
            }

            // write each row
            foreach (MemberInfo memberInfo in sectionTopics)
            {
                // begin the row
                RenderBeginTag("tr");

                // write the access icon cell, if any
                if (writeAccessIconColumn)
                {
                    RenderBeginTag("td");
                    string accessTitle = Resources.RenderPublic;
                    string accessCssClass = "PublicIcon";
                    string accessChar = "&#208;";
                    bool isConst = false, isStatic = false;
                    if (memberInfo.Topic.Modifiers != null)
                    {
                        foreach (Token modifier in memberInfo.Topic.Modifiers)
                        {
                            switch (modifier.Text)
                            {
                            case "protected":
                                accessTitle = Resources.RenderProtected;
                                accessCssClass = "ProtectedIcon";
                                accessChar = "&#207;";
                                break;
                            case "internal":
                                // special case: "protected internal"
                                if (accessCssClass != "ProtectedIcon")
                                {
                                    accessTitle = Resources.RenderInternal;
                                    accessCssClass = "InternalIcon";
                                    accessChar = "&#100;";
                                }
                                break;
                            case "private":
                                accessTitle = Resources.RenderPrivate;
                                accessCssClass = "PrivateIcon";
                                accessChar = "&#114;";
                                break;
                            case "const":
                                isConst = true;
                                break;
                            case "static":
                                isStatic = true;
                                break;
                            }
                        }
                    }
                    FormatBeginTag("span,class,title", accessCssClass,
                        accessTitle);
                    Write(accessChar);
                    RenderEndTag(); // "span"
                    if (isConst)
                    {
                        FormatBeginTag("span,class,title", "ConstIcon",
                            Resources.RenderConst);
                        Write('C');
                        RenderEndTag(); // "span"
                    }
                    if (isStatic)
                    {
                        FormatBeginTag("span,class,title", "StaticIcon",
                            Resources.RenderStatic);
                        Write('S');
                        RenderEndTag(); // "span"
                    }
                    FormatEndTag("\n"); // "td"
                }

                // write the second cell
                RenderBeginTag("td");
                FormatBeginTag("a,class,href,title", "r",
                    GetTopicUrl(memberInfo.Topic), memberInfo.Topic.LongTitle);
                WriteEncodedText(memberInfo.Topic.VeryShortTitleNoKind);
                FormatEndTag("\n"); // "a"
                FormatEndTag("\n"); // "td"

                // write the third cell
                RenderBeginTag("td");
                memberInfo.Topic.WriteSummaryHtml(this,
                    delegate(HtmlTextWriter htmlWriter)
                    {
                        if (memberInfo.InheritedFromTopic != null)
                        {
                            // write "Inherited from..."
                            htmlWriter.Write(
                              String.Format(Resources.RenderInheritedFrom,
                                LinkNameHtml(memberInfo.InheritedFromTopic)));
                        }
                    });
                RenderEndTag(); // "td"

                // end the row
                RenderEndTag(); // "tr"
            }

            // end the table
            EndGrid();

            // end the section
            EndSection();
        }
    }

    
TopicWriter.WriteExceptionsSection Method

Writes the "Exceptions" section of the topic.

Remarks

This method can only be called if Topic.HasExceptionsHtml is true.

    protected void WriteExceptionsSection()
    {
        // begin the "Exceptions" section
        BeginSection(Resources.RenderExceptions);

        // begin the table
        BeginGrid("Exceptions", Resources.RenderExceptionType,
            Resources.RenderExceptionCondition);

        // write each row
        foreach (XmlCommentSection section in
            Topic.GetRenderedXmlCommentSections(
                XmlCommentSectionKind.Exception))
        {
            // begin the row
            RenderBeginTag("tr");

            // write the first cell
            RenderBeginTag("td");
            if (section.ReferencedTopic != null)
            {
                // format as a hyperlink to the referenced topic
                FormatBeginTag("a,class,href,title", "r",
                    GetTopicUrl(section.ReferencedTopic),
                    section.ReferencedTopic.LongTitle);
                WriteEncodedText(section.CrefAttribute);
                RenderEndTag();
            }
            else
            {
                // format as an unlinked name
                FormatBeginTag("span,class", "n");
                WriteEncodedText(section.CrefAttribute);
                RenderEndTag();
            }
            FormatEndTag("\n"); // "td"

            // write the second cell
            RenderBeginTag("td");
            RenderBeginTag("p");
            Write(section.Html.Trim());
            RenderEndTag(); // "p"
            WriteLine();
            RenderEndTag(); // "td"

            // end the row
            RenderEndTag(); // "tr"
        }

        // end the table
        EndGrid();

        // end the "Exceptions" section
        EndSection();
    }

    
TopicWriter.WritePermissionsSection Method

Writes the "Permissions" section of the topic.

Remarks

This method can only be called if Topic.HasPermissionsHtml is true.

    protected void WritePermissionsSection()
    {
        // begin the "Permissions" section
        BeginSection(Resources.RenderPermissions);

        // begin the table
        BeginGrid("Permissions", Resources.RenderName,
            Resources.RenderDescription);

        // write each row
        foreach (XmlCommentSection section in
            Topic.GetRenderedXmlCommentSections(
                XmlCommentSectionKind.Permission))
        {
            // begin the row
            RenderBeginTag("tr");

            // write the first cell
            RenderBeginTag("td");
            if (section.ReferencedTopic != null)
            {
                // format as a hyperlink to the referenced topic
                FormatBeginTag("a,class,href,title", "r",
                    GetTopicUrl(section.ReferencedTopic),
                    section.ReferencedTopic.LongTitle);
                WriteEncodedText(section.CrefAttribute);
                RenderEndTag();
            }
            else
            {
                // format as an unlinked name
                FormatBeginTag("span,class", "n");
                WriteEncodedText(section.CrefAttribute);
                RenderEndTag();
            }
            FormatEndTag("\n"); // "td"

            // write the second cell
            RenderBeginTag("td");
            RenderBeginTag("p");
            Write(section.Html.Trim());
            RenderEndTag(); // "p"
            WriteLine();
            RenderEndTag(); // "td"

            // end the row
            RenderEndTag(); // "tr"
        }

        // end the table
        EndGrid();

        // end the "Permissions" section
        EndSection();
    }

    
TopicWriter.WriteSeeAlsoSection Method

Writes the "See Also" section of the topic.

Remarks

This method can only be called if Topic.HasSeeAlsoHtml is true.

    protected void WriteSeeAlsoSection()
    {
        // begin the "See Also" section
        BeginSection(Resources.RenderSeeAlso);

        // write each reference
        foreach (XmlCommentSection section in
            Topic.GetRenderedXmlCommentSections(
                XmlCommentSectionKind.SeeAlso))
        {
            FormatBeginTag("p,class", "SeeAlso");
            string html = section.Html;
            if (html != null)
                html = html.Trim();
            if ((html != null) && (html.Length == 0))
                html = null;
            if (section.ReferencedTopic != null)
            {
                // format as a hyperlink to the referenced topic
                FormatBeginTag("a,class,href,title", "r",
                    GetTopicUrl(section.ReferencedTopic),
                    section.ReferencedTopic.LongTitle);
                if (html != null)
                    Write(html);
                else
                    WriteEncodedText(section.ReferencedTopic.ShortTitle);
                RenderEndTag();
            }
            else
            if ((section.HrefAttribute != null) && (html != null))
            {
                // format non-topic link
                if (section.HrefAttribute.StartsWith("http") ||
                    section.HrefAttribute.StartsWith("ftp"))
                {
                    // external link
                    FormatBeginTag("a,class,href,title", "r",
                        section.HrefAttribute,
                        Resources.ExternalLink);
                    Write(html);
                    FormatBeginTag("span,class", "ExternalLinkIcon");
                    Write(" &#222;");
                    RenderEndTag();
                    RenderEndTag();
                }
                else
                {
                    // internal non-topic link
                    FormatBeginTag("a,class,href", "r",
                        section.HrefAttribute);
                    Write(html);
                    RenderEndTag();
                }
            }
            FormatEndTag("\n"); // "p"
        }

        // end the "See Also" section
        EndSection();
    }

    
TopicWriter.BeginSection Method

Begins a section (e.g. "Exceptions") within a topic.

Parameters

headerTitle

The text of the section title. If null, no section header is written.

    protected void BeginSection(string headerTitle)
    {
        // write the section header, if any
        if (headerTitle != null)
        {
            FormatBeginTag("div,class", "SectionHeader");
            WriteEncodedText(headerTitle);
            FormatEndTag("\n"); // "div"
        }

        // begin the section "<div>"
        FormatBeginTag("div,class", "Section");
    }

    
TopicWriter.EndSection Method

Ends an HTML block begun by BeginSection.

    protected void EndSection()
    {
        // end the section "<div>"
        FormatEndTag("\n");
    }

    
TopicWriter.BeginSubsection Method

Begins a subsection (e.g. "Parameters") within a topic.

Parameters

headerTitle

The text of the section title.

    protected void BeginSubsection(string headerTitle)
    {
        // write the subsection header
        FormatBeginTag("div,class", "SubsectionHeader");
        WriteEncodedText(headerTitle);
        FormatEndTag("\n"); // "div"

        // begin the subsection "<div>"
        FormatBeginTag("div,class", "Subsection");
    }

    
TopicWriter.EndSubsection Method

Ends an HTML block begun by BeginSubsection.

    protected void EndSubsection()
    {
        // end the subsection "<div>"
        FormatEndTag("\n");
    }

    
TopicWriter.BeginGrid Method

Begins an HTML table that uses CSS class "Grid".

Parameters

cssClassName

An additional CSS class name to apply to the table.

columnLabels

The labels that appear at the top of the table columns.

    protected void BeginGrid(string cssClassName, params string[] columnLabels)
    {
        // begin the "<table>"
        WriteLine();
        FormatBeginTag("table,class",
            String.Format("Grid {0}", cssClassName));

        // write the table "<col>" elements
        for (int i = 0; i < columnLabels.Length; i++)
            FormatTag("col,class\n", String.Format("Column{0}_", i + 1));

        // write the table header row
        FormatBeginTag("tr,class", "Header_");
        for (int i = 0; i < columnLabels.Length; i++)
        {
            FormatBeginTag("td,class\n", String.Format("Header{0}_", i + 1));
            WriteEncodedText(columnLabels[i]);
            FormatEndTag("\n"); // "td"
        }

        // end the table header row
        FormatEndTag("\n"); // "tr"
    }

    
TopicWriter.EndGrid Method

Ends an HTML block begun by BeginGrid.

    protected void EndGrid()
    {
        // end the grid "<table>"
        FormatEndTag("\n");
    }

    
TopicWriter.WriteMemberNameHtml Method

Finds a topic that has Topic.IsDocumented equal to true given a member name of the sort that might be found in XML comments, and writes a hyperlink to that topic if it's found, or non-link text if not.

Parameters

memberName

The qualified or unqualified member name. Examples: "Bar", "Foo.Bar", "Foo<T>.Bar".

settings

Settings that control the method.

    protected void WriteMemberNameHtml(string memberName,
        FindTopicSettings settings)
    {
        Topic targetTopic = Topic.DocumentationSet.FindDocumentedTopic(
            memberName, Topic, 0, settings);
        if (targetTopic != null)
        {
            FormatBeginTag("a,href,title", GetTopicUrl(targetTopic),
                targetTopic.LongTitle);
        }
        WriteEncodedText(memberName);
        if (targetTopic != null)
            RenderEndTag();
    }

    
TopicWriter.WriteTypeNameHtml Method

Finds a topic that has Topic.IsDocumented equal to true given a type name, and writes a hyperlink to that topic if it's found, or non-link text if not.

Parameters

typeName

The qualified or unqualified type name. Examples: "Bar", "Foo.Bar", "Foo<T>.Bar", "Foo<Abc<Def[,]>>.Bar".

    protected void WriteTypeNameHtml(TokenList typeName)
    {
        Topic targetTopic;
        DissectTypeReference(typeName.HeadToken.Next, Topic, this,
            out targetTopic);
    }

    
TopicWriter.ResolveTypeReference Method

Finds a topic that has Topic.IsDocumented equal to true given a type reference. For example, given "Outer<MyList>.Inner<MyPoint<bool>?,int[,]>.Inner2", a reference to the topic with canonical name "Outer<>.Inner<,>.Inner2" is returned.

Parameters

typeName

The qualified or unqualified type name. Examples: "Bar", "Foo.Bar", "Foo<T>.Bar".

Return Value

The Topic corresponding to typeName, or null if none.

Remarks

The topic specified by the Topic property is used as the "context topic", i.e. typeName is assumed to be a reference within that topic.

    protected Topic ResolveTypeReference(string typeName)
    {
#if false
        // this code works, but is slower
        Topic targetTopic;
        DissectTypeReference(typeName.HeadToken, Topic, null, out targetTopic);
        return targetTopic;
#else
        return Topic.DocumentationSet.FindDocumentedTopic(
            typeName, Topic, 0, FindTopicSettings.TypeOnly);
#endif
    }

    
TopicWriter.DissectTypeReference Method

Helps implement WriteTypeNameHtml and ResolveTypeReference: a given type name is broken up into each of its inner type components. Optionally generates hyperlinks to all types contained within the type name (in type parameters).

Parameters

token

The first token of the qualified or unqualified type name.

contextTopic

The topic that contains the type name reference.

htmlWriter

The HtmlTextWriterHelper to write to. If null, no output is generated -- this is useful if the caller is only interested in targetTopic.

targetTopic

Set to the Topic of the type, or null if token doesn't refer to a topic.

Return Value

The first token after the type name.

    protected static Token DissectTypeReference(Token token,
        Topic contextTopic, HtmlTextWriterHelper htmlWriter,
        out Topic targetTopic)
    {
        // Algorithm:
        //
        // Consider this <typeName> value:
        //
        //     Outer<MyList>.Inner<MyPoint<bool>?,int[,]>.Inner2[]
        //
        // This is divided into the following three "prefix types":
        //
        //     Outer<MyList>
        //     Outer<MyList>.Inner<MyPoint<bool>?,int[,]>
        //     Outer<MyList>.Inner<MyPoint<bool>?,int[,]>.Inner2[]
        //
        // (These are divided at outer type parameter list boundaries, not
        // periods.  For example, "Inner" could be "Abc.Def.Ghi" and the
        // result would still be three total "prefix types".)
        //
        // The algorithm is to parse one "prefix type" at a time, canonicalize
        // it and create the link for that prefix.  Assuming Outer is in
        // namespace MyNs, the text "Outer", "Inner", and "Inner2" needs to
        // be hyperlinked to the topics with the following Topic.CanonicalName
        // values:
        //
        //     "Outer"    --> MyNs.Outer<>
        //     "Inner"    --> MyNs.Outer<>.Inner<,>
        //     "Inner2"   --> MyNs.Outer<>.Inner<,>.Inner2
        //
        // This algorithm has to be run recursively, to get these hyperlinks
        // (assuming MyList and MyPoint are in namespace MyNs2):
        // 
        //     "MyList"   --> MyNs2.MyList
        //     "MyPoint"  --> MyNs2.MyPoint<>
        //

        // <memberName> will hold e.g. "Inner"
        StringBuilder memberName = new StringBuilder(100);

        // <canonicalPrefix> will hold e.g. "MyNs.Outer<>.Inner<<>,>"
        StringBuilder canonicalPrefix = new StringBuilder(200);

        // loop once for each "prefix type" (see comments above)
        while (true)
        {
            // reset <memberName>
            memberName.Length = 0;

            // advance to the first type parameter list or the end of the
            // entire type, whichever comes first; note that we may be called
            // recursively, so the entire type ends at either <RAngle> or
            // end of token list
            while (!token.IsTail && !token.IsCharacter(LAngle) &&
                   !token.IsCharacter(RAngle) &&
                   !token.IsCharacter(LSquare) &&
                   !token.IsCharacter(RSquare) &&
                   !token.IsCharacter('?') &&
                   !token.IsCharacter(','))
            {
                canonicalPrefix.Append(token.Text);
                memberName.Append(token.Text);
                token = token.Next;
            }

            // advance to the end of the type parameter list; only include
            // '<' and ',' and '>' characters in <canonicalPrefix>
            Token typeParameters;
#if false
            int trailingTypeParameterIndex = -1;
#endif
            if (token.IsCharacter(LAngle))
            {
                // track the beginning of the type parameters
                typeParameters = token;

                // canonicalize the type parameter list
                int angleNesting = 0; // in "<...>"
                int squareNesting = 0; // in "[...]"; ignore arrays, e.g. "[,]"
                while (!token.IsTail)
                {
                    if (token.IsCharacter(LAngle))
                    {
                        if (angleNesting == 0)
                        {
#if false
                            trailingTypeParameterIndex = canonicalPrefix.Length;
#endif
                            canonicalPrefix.Append(LAngle);
                        }
                        angleNesting++;
                    }
                    else
                    if (token.IsCharacter(RAngle))
                    {
                        angleNesting--;
                        if (angleNesting == 0)
                        {
                            canonicalPrefix.Append(RAngle);
                            token = token.Next;
                            break;
                        }
                    }
                    else
                    if (token.IsCharacter(LSquare))
                        squareNesting++;
                    else
                    if (token.IsCharacter(RSquare))
                        squareNesting--;
                    else
                    if (token.IsCharacter(',') && (squareNesting == 0) &&
                            (angleNesting == 1))
                        canonicalPrefix.Append(token.Text);
                    token = token.Next;
                }
                Debug.Assert(angleNesting == 0);
                Debug.Assert(squareNesting == 0);
            }
            else
                typeParameters = null;

#if false
            // remove trailing type parameters, if any; set <canonicalName>
            // to the result
            string canonicalName = canonicalPrefix.ToString();
            if (trailingTypeParameterIndex >= 0)
            {
                canonicalName = canonicalName.Substring(
                    0, trailingTypeParameterIndex);
            }
#else
            // set <canonicalName> to the precanonicalized name to look up
            string canonicalName = canonicalPrefix.ToString();
#endif

            // set <targetTopic> to the topic corresponding to <canonicalName>
            targetTopic = contextTopic.DocumentationSet.FindDocumentedTopic(
                    canonicalName, contextTopic, 0,
                    FindTopicSettings.TypeOnly |
                        FindTopicSettings.Precanonicalized);

            // write the hyperlink (or non-hyperlinked text, if we can't find a
            // target topic) for this "prefix type"
            if (htmlWriter != null)
            {
                if (targetTopic != null)
                {
                    htmlWriter.FormatBeginTag("a,href,title",
                        targetTopic.DocHtmlFileName, targetTopic.LongTitle);
                }
                htmlWriter.WriteEncodedText(memberName.ToString());
                if (targetTopic != null)
                    htmlWriter.RenderEndTag();
            }

            // if there were type parameters, write them recursively
            if (typeParameters != null)
            {
                // begin the type parameter list
                if (htmlWriter != null)
                    htmlWriter.WriteEncodedText(LAngle.ToString());

                // loop once for each type parameter
                Debug.Assert(typeParameters.IsCharacter(LAngle));
                Token token2 = typeParameters.Next;
                while (true)
                {
                    // write the type parameter
                    Topic unused;
                    token2 = DissectTypeReference(token2, contextTopic,
                        htmlWriter, out unused);

                    // if there are more type parameters, continue
                    if (!token2.IsCharacter(','))
                        break;
                    if (htmlWriter != null)
                        htmlWriter.Write(',');
                    token2 = token2.Next;
                    while (token2.IsWhiteSpace)
                    {
                        if (htmlWriter != null)
                            htmlWriter.Write(' ');
                        token2 = token2.Next;
                    }
                }
                Debug.Assert(token2.IsCharacter(RAngle));
                Debug.Assert(token2.Next == token);

                // end the type parameter list
                if (htmlWriter != null)
                    htmlWriter.WriteEncodedText(RAngle.ToString());
            }

            // write and advance past anything following the type, e.g. "[]"
            // or "?", until we get to either the start of the next "prefix
            // type" or the end of the entire type
            int squareNesting2 = 0; // in "[...]"; ignore arrays, e.g. "[,]"
            while (true)
            {
                // we're done if we hit the end of the type
                if (token.IsTail || token.IsCharacter(RAngle) ||
                        (token.IsCharacter(',') && (squareNesting2 == 0)))
                    return token; // end of the entire type

                // if this type has "[]" or "?" etc at the end of it, then we
                // can't return a target type because the actual type (e.g.
                // an array) is one we don't have a topic for
                targetTopic = null;

                // ignore commas inside square brackets
                if (token.IsCharacter(LSquare))
                    squareNesting2++;
                else
                if (token.IsCharacter(RSquare))
                    squareNesting2--;

                // stop if we hit the start of the next "prefix type"
                bool isDot = token.IsCharacter('.');
                if (htmlWriter != null)
                    htmlWriter.WriteEncodedText(token.Text);
                token = token.Next;
                if (isDot)
                {
                    canonicalPrefix.Append('.');
                    break; // start of the next "prefix type"
                }
            }
        }
    }

    
TopicWriter.ReadXmlElementText Method

Assuming a given XmlReader is positioned at the beginning of an XML element, this method reads the text inside the element. If the element contains any child elements, null is returned.

Parameters

xmlReader

The XML element to read.

    protected string ReadXmlElementText(XmlReader xmlReader)
    {
        string nodeName = xmlReader.Name;
        try
        {
            return xmlReader.ReadElementContentAsString();
        }
        catch (XmlException ex)
        {
            FireWarningInSourceFile(
                ((IXmlLineInfo) xmlReader).LineNumber,
                Resources.InvalidXmlElementSyntax, nodeName, ex.Message);
            return null;
        }
    }

    
TopicWriter.WriteLabeledParagraph Method

Generates HTML for a paragraph containing a label followed by text.

Parameters

label

The label text.

html

The HTML of the rest of the paragraph.

    protected void WriteLabeledParagraph(string label, string html)
    {
        FormatBeginTag("p,class", "LabeledParagraph");
        FormatBeginTag("span,class", "Label_");
        WriteEncodedText(label);
        RenderEndTag();
        Write(html);
        FormatEndTag("\n");
    }

    
TopicWriter.WriteEncodedTokenList Method

Writes a TokenList, converted to HTML.

Parameters

tokenList

The TokenList to write. If null, nothing is written.

after

If not null, then this string is written after tokenList is written.

    protected void WriteEncodedTokenList(TokenList tokenList, string after)
    {
        if (tokenList != null)
            WriteEncodedText(tokenList.ToString() + (after ?? " "));
    }

    
TopicWriter.WriteEncodedText Method

Writes a string, converted from plain text to HTML.

Parameters

text

The string to write. If null, nothing is written.

after

If not null, then this string is written after text is written.

    protected void WriteEncodedText(string text, string after)
    {
        if (text != null)
            WriteEncodedText(text + (after ?? " "));
    }

    
TopicWriter.FireWarningInSourceFile Method

Fires the WarningInSourceFile event.

Parameters

lineNumber

The line number within the source file that the warning relates to.

format

A String.Format-style format string used to format the warning message text.

args

Arguments for the format string.

    protected void FireWarningInSourceFile(int lineNumber, string format,
        params object[] args)
    {
        if (WarningInSourceFile != null)
        {
            WarningInSourceFile(Topic.SourceFile, lineNumber,
                String.Format(format, args));
        }
    }

    
TopicWriter.LinkHtml Method

Constructs a link to a topic. If the linked-to topic does not have Topic.IsDocumented set to true, non-linked text is returned instead.

Parameters

text

The plain text of the link.

topic

The topic to link to.

Return Value

The HTML of the link or non-linked text.

    protected static string LinkHtml(string text, Topic topic)
    {
        if (topic.IsDocumented)
        {
            return String.Format("<a href=\"{0}\">{1}</a>",
                Html(topic.DocHtmlFileName), Html(text));
        }
        else
            return Html(text);
    }

    
TopicWriter.LinkNameHtml Method

Constructs a link to a topic. If the linked-to topic does not have Topic.IsDocumented set to true, non-linked text is returned instead, formatted with class="n".

Parameters

topic

The topic to link to.

Return Value

The HTML of the link or non-linked text.

    protected static string LinkNameHtml(Topic topic)
    {
        if (topic.IsDocumented)
        {
            // format as a hyperlink to the specified topic
            return String.Format("<a href=\"{0}\" title=\"{1}\">{2}</a>",
                Html(topic.DocHtmlFileName), Html(topic.LongTitle),
                Html(topic.MemberName));
        }
        else
        {
            // format as an unlinked name
            return String.Format("<span class=\"n\">{0}</span>",
                Html(topic.MemberName));
        }
    }

    
TopicWriter.WriteTopicFooterHtml Method

Writes the footer HTML, if any (for example, a copyright notice).

    protected void WriteTopicFooterHtml()
    {
        if (TopicRenderer.TopicFooterHtml != null)
        {
            FormatBeginTag("div,class", "PageFooter");
            Write(TopicRenderer.TopicFooterHtml);
            FormatEndTag("\n");
        }
    }

    
TopicWriter.Html Method

Converts plain text to HTML.

Parameters

text

The plain text to convert.

Return Value

The converted HTML.

    protected static string Html(string text)
    {
        return HttpUtility.HtmlEncode(text);
    }

    
TopicWriter.AddItem Method

Adds an item to a list. Allocates the list if necessary.

Type Parameters

T

The type of item in the list.

Parameters

item

The item to add to the list.

list

The list. If list is null, a new list is allocated.

    protected static void AddItem<T>(T item, ref List<T> list)
    {
        if (list == null)
            list = new List<T>();
        list.Add(item);
    }

    //////////////////////////////////////////////////////////////////////////
    // Protected Helper Types
    //

    
TopicWriter.MemberSection Enumeration

Identifies a member list section of a topic; for example, "Constructors".

Members
Name Description
Classes

"Classes" section.

Constructors

"Constructors" section.

Delegates

"Classes" section.

Destructors

"Destructors" section.

Enumerations

"Classes" section.

Events

"Events" section.

Fields

"Fields" section.

Implementations

"Implementations" section.

Interfaces

"Classes" section.

Length

The count of possible member sections.

Methods

"Methods" section.

Operators

"Operators" section.

Properties

"Properties" section.

Structures

"Structures" section.

    protected enum MemberSection
    {
        
TopicWriter.MemberSection.Constructors Enumeration Value

"Constructors" section.

        Constructors = 0,

        
TopicWriter.MemberSection.Destructors Enumeration Value

"Destructors" section.

        Destructors = 1,

        
TopicWriter.MemberSection.Fields Enumeration Value

"Fields" section.

        Fields = 2,

        
TopicWriter.MemberSection.Properties Enumeration Value

"Properties" section.

        Properties = 3,

        
TopicWriter.MemberSection.Methods Enumeration Value

"Methods" section.

        Methods = 4,

        
TopicWriter.MemberSection.Operators Enumeration Value

"Operators" section.

        Operators = 5,

        
TopicWriter.MemberSection.Events Enumeration Value

"Events" section.

        Events = 6,

        
TopicWriter.MemberSection.Implementations Enumeration Value

"Implementations" section.

        Implementations = 7,

        
TopicWriter.MemberSection.Classes Enumeration Value

"Classes" section.

        Classes = 8,

        
TopicWriter.MemberSection.Interfaces Enumeration Value

"Classes" section.

        Interfaces = 9,

        
TopicWriter.MemberSection.Structures Enumeration Value

"Structures" section.

        Structures = 10,

        
TopicWriter.MemberSection.Delegates Enumeration Value

"Classes" section.

        Delegates = 11,

        
TopicWriter.MemberSection.Enumerations Enumeration Value

"Classes" section.

        Enumerations = 12,

        
TopicWriter.MemberSection.Length Enumeration Value

The count of possible member sections.

        Length = 13 // must be last element
    }

    
TopicWriter.ListState Class

State of the current "<list>" XML documentation construct.

    protected class ListState
    {
        
TopicWriter.ListState.ListType Field

The value of the "type" attribute of the "<list>" XML documentation element. One of the following: "bullet", "number", "table".

        public string ListType;

        
TopicWriter.ListState.InListHeader Field

For ListType equal to "table", InListHeader is set to true while we're inside "<listheader>...</listheader>".

        public bool InListHeader;

        
TopicWriter.ListState.BegunGrid Field

For ListType equal to "table", BegunGrid is set to true once BeginGrid has been called.

        public bool BegunGrid;

        
TopicWriter.ListState.TermHeading Field

For ListType equal to "table", TermHeading is set to the column heading (if one is specified) for the term column.

        public string TermHeading;

        
TopicWriter.ListState.DescriptionHeading Field

For ListType equal to "table", DescriptionHeading is set to the column heading (if one is specified) for the description column.

        public string DescriptionHeading;

        
ListState Constructor

Initializes an instance of this class.

Parameters

listType

The value to initialize ListType to.

        public ListState(string listType)
        {
            Debug.Assert((listType == "bullet") || (listType == "number") ||
                (listType == "table"));
            ListType = listType;
        }

        
TopicWriter.ListState.BeginGridIfNeeded Method

For ListType equal to "table", BeginGrid is called if it hasn't been called yet for this list, i.e. if BegunGrid is still false.

Parameters

writer

The TopicWriter to write to.

        public void BeginGridIfNeeded(TopicWriter writer)
        {
            if (!BegunGrid)
            {
                writer.BeginGrid("List",
                    TermHeading ?? Resources.DefaultTermHeading,
                    DescriptionHeading ?? Resources.DefaultDescriptionHeading);
                BegunGrid = true;
            }
        }
    }

    
TopicWriter.TransformCustomInsideXml Method

When overridden by a derived class, this method can transform an XML element that's inside a section (e.g. within "<summary>...lt;/summary>") within an XML comment into corresponding HTML.

Parameters

section

The section being parsed.

xmlReader

The input XML. On entry, xmlReader refers to the opening tag of the XML element to process. On exit, if true is returned, the method is responsible for positioning xmlReader to the first XML element after the element being processed. If false is returned, or an exception is thrown, the method must not reposition xmlReader.

Return Value

true if the element was processed, false if not. If false is returned, xmlReader must not be repositioned away from its initial value -- in other words, the method must be able to determine whether or not the XML element is one it can process purely by looking at its name and attributes.

Exceptions
Exception type Condition
WarningException

Indicates a problem that should be displayed to the user as a warning.

Remarks

The default implementation of TransformCustomInsideXml does nothing except return false. This method is intended to be overridden by a derived class.

    protected virtual bool TransformCustomInsideXml(XmlCommentSection section,
        XmlReader xmlReader)
    {
        // the default implementation does nothing
        return false;
    }
}

WarningException Class

Indicates an error in a custom hook that should be displayed as a warning to the user.

public class WarningException : Exception
{
    
WarningException Constructor

Initializes an instance of this class.

Parameters

format

A formatting string for an error message to include with the exception.

args

Formatting arguments for the error message.

    public WarningException(string format, params object[] args) :
        base(String.Format(format, args))
    {
    }
}

}