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

// App.cs
//
// Application entry point.
//

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

// TODO (deferred): change code to use typed XmlReader calls to read the
// CodeDoc XML project file, now that there's an XML schema defined

namespace DwellNet.CodeDoc.Application
{

App Class

Created documentation from source code.

class App
{
    //////////////////////////////////////////////////////////////////////////
    // Private Fields - Maintained Across Multiple CodeDoc XML Project Files
    //

    
App.m_warningCount Field

The number of warning messages displayed.

    int m_warningCount;

    
App.m_exePath Field

The full path of this .exe file.

    string m_exePath;

    
App.s_app Field

For debugging purposes.

    static App s_app;

    
App.s_docBrowserSupportFiles Field

The names of static (i.e. non-generated) files that are used in DocBrowser (i.e. what WriteDocumentation() writes)

    static readonly string[] s_docBrowserSupportFiles = new string[]
    {
        "_1px.gif",
        "_Closed.gif",
        "_CodeDoc.css",
        "_DocBrowser.js",
        "_Leaf.gif",
        "_Maximize.gif",
        "_Opened.gif",
        "_SyncToc.gif",
        "_SyncToc.htm",
        "_TabLeft.gif",
        "_TabRight.gif"
    };

    
App.s_formattedCodeSupportFiles Field

The names of static (i.e. non-generated) files that are used in the HTML files that WriteFormattedCode() generates.

    static readonly string[] s_formattedCodeSupportFiles = new string[]
    {
        "_CodeDoc.css"
    };

    //////////////////////////////////////////////////////////////////////////
    // Private Fields - Reset For Each CodeDoc XML Project File
    //

    
App.m_projectFilePath Field

The full path to the CodeDoc XML project file currently being processed, or null if none.

    string m_projectFilePath;

    
App.m_supportFilesPath Field

The full path to directory containing CodeDoc support files.

    string m_supportFilesPath;

    
App.m_docSet Field

The documentation set being generated. Consists of one or more source code files.

    DocumentationSet m_docSet;

    
App.m_documentedTopicCount Field

The count of documented topics.

    int m_documentedTopicCount;

    
App.m_overloadLists Field

When a DocumentationSet.DuplicateCanonicalName event is fired, an entry is added or updated in m_overloadLists with the key being the canonical name and the value being the number of overloads for that canonical name.

    Dictionary<string,int> m_overloadLists;

    
App.m_referencesNotFound Field

The list of potential references from one topic to another that were not found. These do not necessarily indicate errors.

    List<ReferenceNotFound> m_referencesNotFound;

    
App.m_externalReferences Field

The contents of the <ExternalReferences> section of the CodeDoc XML project file. Each line of that section is a key in this dictionary; the values are all true. These are external references, which means that they are not reported as "references not found".

    Dictionary<string, bool> m_externalReferences;

    
App.m_verbose Field

The verbosity level. 0 is minimal output, positive integers produce more output.

    int m_verbose;

    
App.m_docBrowserNodes Field

The collection of DocBrowserNode objects that have NodeID values; the key is the NodeId value. A value may be DocBrowser object (i.e. the root of a DocBrowser tree) or a regular DocBrowserNode object.

    Dictionary<string, DocBrowserNode> m_docBrowserNodes;

    
App.m_processedTopics Field

true once ProcessTopics was called.

    bool m_processedTopics;

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

    
App.FalseTrue Property

An IEnumerable which yields false and then true.

    IEnumerable<bool> FalseTrue
    {
        get
        {
            yield return false;
            yield return true;
        }
    }

    //////////////////////////////////////////////////////////////////////////
    // Methods
    //

    
App.Main Method

Main entry point for the application.

Parameters

args

Command-line arguments.

    public static int Main(string[] args)
    {
        bool pauseOnExit = false; // true if "/Pause" specifed on command line
        bool success;
        try
        {
            s_app = new App();
            success = s_app.Run(args, ref pauseOnExit);
        }
        catch (UsageException)
        {
            // display command-line usage information; in a debug build include
            // debug-only options
            Console.Error.WriteLine(Resources.Usage,
#if DEBUG
                Resources.DebugUsage
#else
                String.Empty
#endif
                );
            return 2;
        }
#if DEBUG
        catch (UnitTests.TestFailedException ex)
        {
            Console.Error.WriteLine("\n" + Resources.TestsFailed, ex.Message);
            return 2;
        }
#endif
#if !DEBUG
        catch (Exception ex)
        {
            Console.Error.WriteLine("\n" + Resources.Error, ex.Message);
            return 2;
        }
#endif

        // execute "/Pause" command-line argument, if present
        if (pauseOnExit)
        {
            Console.Error.WriteLine(Resources.PressAnyKey);
            Console.ReadKey(false);
        }

        // done; return exit code 1 if there were warnings
        return (success ? 0 : 1);
    }

    
App.Run Method

Executes the application.

Parameters

args

Command-line arguments.

pauseOnExit

Set to true if the "/Pause" command-line argument is given. The caller should initialize this parameter to false.

Return Value

true if the run succeeded with no warning messages, false if one or more warnings were displayed.

    public bool Run(string[] args, ref bool pauseOnExit)
    {
        // initialize <m_exePath> to be the full path of this .exe file.
        m_exePath = System.Reflection.Assembly.GetExecutingAssembly().Location;

        // parse command-line arguments
        System.Collections.IEnumerator argEnum = args.GetEnumerator();
        while (argEnum.MoveNext())
        {
            string arg = (string) argEnum.Current;
#if DEBUG
            Match match;
#endif
            if (arg == "/Pause")
            {
                pauseOnExit = true;
            }
#if DEBUG
            else
            if ((match = Regex.Match(arg, @"/RunTests(?::([A-Za-z0-9,]+))?"))
                .Success)
            {
                string[] tests = match.Groups[1].Value.Split(
                    new char[] {','}, StringSplitOptions.RemoveEmptyEntries);
                bool unused;
                string testDataDirPath = MakePath(NextArg(argEnum),
                    FileFlags.Directory, out unused);
                UnitTests.RunTests(testDataDirPath, tests);
            }
            else
            if (arg == "/UpdateTests")
            {
                bool unused;
                string testDataDirPath = MakePath(NextArg(argEnum),
                    FileFlags.Directory, out unused);
                UnitTests.UpdateTests(testDataDirPath);
            }
            else
            if (arg == "/Verbose")
            {
                m_verbose = NextArgAsInt(argEnum);
            }
#endif
            else
            if (arg == "/?")
                throw new UsageException();
            else
            {
                // <arg> should be the name of a command file -- process it
                XmlSchema xmlSchema;
                using (XmlReader xmlReader = XmlReader.Create(
                    App.GetPath("CodeDoc.xsd", m_exePath)))
                {
                    xmlSchema = XmlSchema.Read(xmlReader,
                        delegate(object sender, ValidationEventArgs e)
                        {
                            Debug.Fail("Error in CodeDoc.xsd", e.Message);
                        });
                }
                XmlReaderSettings xmlSettings = new XmlReaderSettings();
                xmlSettings.Schemas.Add(xmlSchema);
                xmlSettings.ValidationType = ValidationType.Schema;
                using (XmlReader xmlReader = XmlReader.Create(arg, xmlSettings))
                {
                    try
                    {
                        // initialize the state of <this> for this CodeDoc XML
                        // project file
                        m_projectFilePath = Path.GetFullPath(arg);
                        m_docSet = new DocumentationSet();
                        InitializeDocumentationSet();
                        m_documentedTopicCount = 0;
                        m_overloadLists = new Dictionary<string,int>(50);
                        m_referencesNotFound =
                            new List<ReferenceNotFound>(100);
                        m_externalReferences =
                            DefaultCSharpExternalReferences();
                        new Dictionary<string,bool>(100);
                        m_verbose = 1;
                        m_docBrowserNodes =
                            new Dictionary<string,DocBrowserNode>();
                        m_processedTopics = false;

                        // process the CodeDoc XML project file
                        ProcessProjectFile(xmlReader);

                        // to reduce bugs, clear most of the state that only
                        // makes sense in the context of a given project file
                        m_projectFilePath = null;
                        m_supportFilesPath = null;
                        m_docSet = null;
                        m_overloadLists = null;
                        m_referencesNotFound = null;
                        m_externalReferences = null;
                    }
                    catch (XmlSchemaValidationException ex)
                    {
                        throw new ProjectFileException("{0}({1}): {2}",
                            arg, ((IXmlLineInfo)xmlReader).LineNumber,
                            ex.Message);
                    }
#if false
                    catch (XmlException ex)
                    {
                        throw new ProjectFileException("{0}: {1}", arg, ex.Message);
                    }
#endif
                    catch (ProjectFileException ex)
                    {
                        throw new ProjectFileException("{0}({1}): {2}",
                            arg, ((IXmlLineInfo)xmlReader).LineNumber,
                            ex.Message);
                    }
                    catch (BegunUsingSourcesException ex)
                    {
                        throw new ProjectFileException("{0}({1}): {2}",
                            arg, ((IXmlLineInfo)xmlReader).LineNumber,
                            ex.Message);
                    }
                }
            }
        }

        // provide feedback on warnings
        Console.WriteLine(Resources.Done, m_warningCount);
        if ((m_referencesNotFound != null) &&
            (m_referencesNotFound.Count > 0) && (m_verbose < 1))
            Console.Error.WriteLine(Resources.TipReferencesNotFound);

        // done
        return (m_warningCount == 0);
    }

    
App.InitializeDocumentationSet Method

Initializes m_docSet.

    public void InitializeDocumentationSet()
    {
        // hook up <m_docSet> events
        m_docSet.AddingSourceFile +=
            new AddingSourceFileEventDelegate(m_docSet_AddingSourceFile);
        m_docSet.AddedSourceFile +=
            new AddedSourceFileEventDelegate(m_docSet_AddedSourceFile);
        m_docSet.ParsingError +=
            new ParsingErrorEventDelegate(m_docSet_ParsingError);
        m_docSet.DuplicateTopicTitle +=
            new DuplicateTopicTitleEventDelegate(m_docSet_DuplicateTopicTitle);
        m_docSet.DuplicateCanonicalName +=
            new DuplicateCanonicalNameEventDelegate(
                m_docSet_DuplicateCanonicalName);
        m_docSet.ReferenceNotFound +=
            new ReferenceNotFoundEventDelegate(m_docSet_ReferenceNotFound);
    }

    
App.DefaultCSharpExternalReferences Method

Returns a collection of default external references for the C# languages; for example, predefined types like "int". May be used to initialize m_externalReferences.

    Dictionary<string,bool> DefaultCSharpExternalReferences()
    {
        string[] wordsArray = new string[]
        {
            "bool",  "byte", "char", "decimal", "double", "fixed", "float",
            "int", "long", "object", "sbyte", "short", "string", "uint",
            "ulong", "ushort", "void"
        };
        Dictionary<string, bool> words =
            new Dictionary<string, bool>(wordsArray.Length);
        foreach (string word in wordsArray)
            words.Add(word, true);
        return words;
    }

    
App.ProcessProjectFile Method

Performs the operations specified in a CodeDoc XML project file.

Parameters

xmlReader

An XmlReader which, on entry, is positioned at the beginning of the CodeDoc XML project file.

    void ProcessProjectFile(XmlReader xmlReader)
    {
        // advance to the root "<CodeDoc>" XML element
        if (!xmlReader.ReadToFollowing("CodeDoc"))
            throw new ProjectFileException(Resources.ProjectFileMissingRoot);
        if (xmlReader.IsEmptyElement)
            return; // empty "<CodeDoc/>" --> nothing to do

        // parse attributes on the "<CodeDoc>" element
        string pathFromExe = GetAttribute(xmlReader, "SupportFilesPathFromExe",
            "SupportFiles");
        bool unused;
        m_supportFilesPath = MakePath(GetPath(pathFromExe, m_exePath), FileFlags.Directory,
            out unused);

        // parse XML elements under "<CodeDoc>"
        while (xmlReader.Read())
        {
            if (xmlReader.NodeType == XmlNodeType.Element)
            {
                switch (xmlReader.Name)
                {

                case "ExternalReferences":

                    // populate <m_externalReferences> using lines of text from
                    // <ExternalReferences>...</ExternalReferences>
                    xmlReader.Read();
                    xmlReader.MoveToContent();
                    foreach (string line in xmlReader.Value.Split(
                        new char[] { '\n' }))
                    {
                        string reference = line.Trim();
                        if (reference.Length > 0)                       
                            m_externalReferences[reference] = true;
                    }
                    xmlReader.Read();
                    break;

                case "DefinedSymbols":

                    // call DocumentationSet.DefineSymbol() using lines of
                    // text from <DefinedSymbols>...</DefinedSymbols>
                    m_docSet.ClearSymbols();
                    xmlReader.Read();
                    xmlReader.MoveToContent();
                    foreach (string line in xmlReader.Value.Split(
                        new char[] { '\n' }))
                    {
                        string symbol = line.Trim();
                        if (symbol.Length > 0)                      
                            m_docSet.DefineSymbol(symbol);
                    }
                    xmlReader.Read();
                    break;

                case "AllowXmlElements":

                    // call DocumentationSet.AllowXmlElement() using lines of
                    // text from <AllowXmlElements>...</AllowXmlElements>
                    m_docSet.ClearAllowedXmlElements();
                    xmlReader.Read();
                    xmlReader.MoveToContent();
                    foreach (string line in xmlReader.Value.Split(
                        new char[] { '\n' }))
                    {
                        string elementName = line.Trim();
                        if (elementName.Length > 0)                     
                            m_docSet.AllowXmlElement(elementName);
                    }
                    xmlReader.Read();
                    break;

                case "DeleteFilesInDirectory":

                    DeleteFilesInDirectory(xmlReader);
                    break;

                case "CreateDocBrowser":

                    // begin a DocBrowser
                    CreateDocBrowser(xmlReader);
                    break;

                case "DocBrowserStaticPages":

                    // add static content to a DocBrowser
                    DocBrowserStaticPages(xmlReader);
                    break;

                case "ReadSource":

                    // add source code from the file or directory specified by
                    // the "Path" attribute to the documentation set
                    ReadSource(xmlReader);
                    break;

                case "ClearSources":

                    // remove all source files from the documentation set
                    m_docSet.ClearSourceFiles();
                    break;

                case "WriteDocumentation":

                    // generate documentation from the source files in the
                    // documentation set
                    WriteDocumentation(xmlReader);
                    break;

                case "WriteFormattedCode":

                    // generate code HTML from the source files in the
                    // documentation set
                    WriteFormattedCode(xmlReader);
                    break;

                default:

                    throw new ProjectFileException(
                        Resources.ProjectFileUnknownCommand, xmlReader.Name);
                }
            }
            else
            if (xmlReader.NodeType == XmlNodeType.Text) 
                throw UnexpectedText(xmlReader);
        }

        // finish generating each DocBrowser that was begun with a
        // "<CreateDocBrowser>" element in the CodeDoc XML project file
        foreach (KeyValuePair<string,DocBrowserNode> kvp in m_docBrowserNodes)
        {
            DocBrowserNode docBrowserNode = kvp.Value;
            DocBrowser docBrowser = docBrowserNode as DocBrowser;
            if (docBrowser != null)
                FinishDocBrowser(docBrowser);
        }

        // list references not found, sorted by reference name
        m_referencesNotFound.Sort();
        List<ReferenceNotFound>.Enumerator listEnum =
            m_referencesNotFound.GetEnumerator();
        ReferenceNotFound previous = null;
        int count = 0;
        List<string> topicsNotFound = new List<string>(20);
        while (true)
        {
            bool atEnd = !listEnum.MoveNext();
            if ((previous != null) &&
                (atEnd ||
                 (listEnum.Current.Reference != previous.Reference) ||
                 (listEnum.Current.IsTypeOnly != previous.IsTypeOnly)))
            {
                string message = String.Format(Resources.ReferencesNotFound,
                    previous, count);
                Console.Error.WriteLine(message);
                if (m_verbose >= 1)
                {
                    // at high enough verbosity level, display the context
                    // topics
                    topicsNotFound.Sort();
                    foreach (string location in topicsNotFound)
                    {
                        Console.Error.WriteLine(Resources.ReferenceNotFound,
                            location, previous);
                    }
                }
                count = 1;
                topicsNotFound.Clear();
            }
            else
                count++;
            if (atEnd)
                break;
            previous = listEnum.Current;
            if ((previous.LineNumber > 0) &&
                (previous.ContextTopic.SourceFile != null))
            {
                topicsNotFound.Add(String.Format("{0}({1})",
                    previous.ContextTopic.SourceFile.SourceFileAbsolutePath,
                    previous.LineNumber));
            }
            else
                topicsNotFound.Add(previous.ContextTopic.Location);
        }

        // at a high enough verbosity level, summarize the duplicate overloads
        // (i.e. the results from DuplicateCanonicalName events)
        if (m_verbose >= 3)
        {
            List<KeyValuePair<string, int>> list = new
                List<KeyValuePair<string, int>>(m_overloadLists.Count);
            foreach (KeyValuePair<string, int> kvp in m_overloadLists)
                list.Add(kvp);
            list.Sort(delegate(KeyValuePair<string, int> kvp1,
                KeyValuePair<string, int> kvp2)
            {
                return String.Compare(kvp1.Key, kvp2.Key);
            });
            foreach (KeyValuePair<string, int> kvp in list)
            {
                string message = String.Format(Resources.OverloadsDetected,
                    kvp.Key, kvp.Value);
                Console.Error.WriteLine(message);
            }
        }

        // at a high enough verbosity level, output a list of all documented
        // topics, excluding namespaces and overloads
        if (m_verbose >= 4)
        {
            Console.WriteLine();
            Console.WriteLine(Resources.DocumentedMembers);
            foreach (Topic topic in m_docSet.Topics)
            {
                if (topic.IsDocumented &&
                    (topic.TopicKind != TopicKind.NamespaceTopic) &&
                    (topic.OverloadListTopic == null))
                {
                    Console.WriteLine(topic.QualifiedMemberName);
                }
            }
            Console.WriteLine();
        }
    }

    
App.DeleteFilesInDirectory Method

Executes a "<DeleteFilesInDirectory>" CodeDoc XML project file command: deletes all files in a given directory, if that directory exists. Does not delete subdirectories, or files in subdirectories.

Parameters

xmlReader

An XmlReader which, on entry, is positioned on a "<DeleteFilesInDirectory>" element within the CodeDoc XML project file. On exit, xmlReader must be positioned on the "</DeleteFilesInDirectory>" element (or, equivalently, "<DeleteFilesInDirectory/>").

    void DeleteFilesInDirectory(XmlReader xmlReader)
    {
        // set <directoryPath> to the full path to the directory to delete the
        // files of; do nothing if the directory doesn't exist
        bool directoryExists;
        string directoryPath = MakePath(GetPath(xmlReader, "Path",
            m_projectFilePath), FileFlags.Directory | FileFlags.Nonexistent,
            out directoryExists);
        if (!directoryExists)
            return;

        // delete all files in the directory
        foreach (string filePath in Directory.GetFiles(directoryPath))
        {
            try
            {
                File.Delete(filePath);
            }
            catch (Exception)
            {
                // give the user extra information, i.e. the line number within
                // the CodeDoc XML project file of the
                // "<DeleteFilesInDirectory>" element, before re-throwing the
                // exception
                Console.WriteLine(Resources.DeleteFileError, m_projectFilePath,
                    ((IXmlLineInfo)xmlReader).LineNumber);
                throw;
            }
        }
    }

    
App.CreateDocBrowser Method

Executes a "<CreateDocBrowser>" CodeDoc XML project file command: begins a DocBrowser.

Parameters

xmlReader

An XmlReader which, on entry, is positioned on a "<CreateDocBrowser>" element within the CodeDoc XML project file. On exit, xmlReader must be positioned on the "</CreateDocBrowser>" element (or, equivalently, "<CreateDocBrowser/>").

    void CreateDocBrowser(XmlReader xmlReader)
    {
        // set <rootPath> to the full path to DocBrowser root directory
        // (i.e. the directory into which the frameset files will be written)
        bool unused;
        string rootPath = MakePath(
            GetPath(xmlReader, "Path", m_projectFilePath),
            FileFlags.Directory | FileFlags.Nonexistent, out unused);
        CreateDirectoryIfNeeded(rootPath);

        // parse other attributes on the "<CreateDocBrowser>" element
        string pagesRelativePath = GetAttribute(xmlReader, "PagesRelativePath",
            null);
        string framesetFileName = GetAttribute(xmlReader, "FramesetFileName",
            null);
        string basicFramesetFileName = GetAttribute(xmlReader,
            "BasicFramesetFileName", null);
        string nodeId = GetAttribute(xmlReader, "NodeId");
        string baseName = GetAttribute(xmlReader, "BaseName", "Default");
        string title = GetAttribute(xmlReader, "Title", baseName);
        int leftFrameWidth = GetAttribute<int>(xmlReader, "LeftFrameWidth",
            300);
        bool markOfTheWeb = GetAttribute<bool>(xmlReader, "MarkOfTheWeb",
            true);

        // set <docBrowser> to a new DocBrowser object corresponding to this
        // XML node
        DocBrowser docBrowser = new DocBrowser(nodeId, rootPath,
            pagesRelativePath, framesetFileName, basicFramesetFileName, title,
            baseName, leftFrameWidth, markOfTheWeb);

        // create the pages directory if it doesn't exist already
        CreateDirectoryIfNeeded(docBrowser.PagesAbsolutePath);

        // parse the contents of the "<CreateDocBrowser>" element, if any
        if (!xmlReader.IsEmptyElement)
        {
            int initialDepth = xmlReader.Depth;
            while (xmlReader.Read() && (xmlReader.Depth != initialDepth))
            {
                if (xmlReader.NodeType == XmlNodeType.Element) 
                {
                    if (xmlReader.Name == "CopyFile")
                    {
                        string sourcePath = GetPath(xmlReader, "Path",
                            m_projectFilePath);
                        string destinationFileName = GetAttribute(xmlReader,
                            "NewName", Path.GetFileName(sourcePath));
                        CopyStaticFile(sourcePath, destinationFileName,
                            docBrowser.PagesAbsolutePath, xmlReader);
                    }
                    else
                    {
                        throw new ProjectFileException(
                            Resources.ProjectFileUnexpectedElement,
                            xmlReader.Name);
                    }
                }
                else
                if (xmlReader.NodeType == XmlNodeType.Text) 
                    throw UnexpectedText(xmlReader);
            }
        }

        // create the DocBrowser pages directory
        CreateDirectoryIfNeeded(docBrowser.PagesAbsolutePath);

        // add <docBrowser> to <m_docBrowserNodes>
        m_docBrowserNodes[nodeId] = docBrowser;
    }

    
App.FinishDocBrowser Method

Finishes generating each DocBrowser that was begun with a "<CreateDocBrowser>" element in the CodeDoc XML project file.

Parameters

docBrowser

The DocBrowser to finish.

    void FinishDocBrowser(DocBrowser docBrowser)
    {
        // provide user feedback
        Console.WriteLine(Resources.WritingDocBrowser, docBrowser.NodeId);

        // copy support files into the DocBrowser pages directory
        foreach (string fileName in s_docBrowserSupportFiles)
        {
            string originalFilePath = Path.Combine(m_supportFilesPath,
                fileName);
            string newFilePath = Path.Combine(docBrowser.PagesAbsolutePath,
                fileName);
            try
            {
                CopyFileContents(originalFilePath, newFilePath, false);
            }
            catch (Exception)
            {
                // give the user extra information, i.e. the line number within
                // the CodeDoc XML project file of the "<DocBrowserNode>"
                // element, before re-throwing the exception
                Console.WriteLine(
                    Resources.ErrorCopyingDocBrowserSupportFile,
                    docBrowser.NodeId, fileName);
                throw;
            }
        }

        // write the DocBrowser root frameset HTML file; we actually write two
        // files: the regular DHTML version and a basic (downlevel) version
        string outputFileName, outputPath;
        foreach (bool basic in FalseTrue)
        {
            outputFileName = (basic ? docBrowser.BasicFramesetFileName
                : docBrowser.FramesetFileName);
            if (outputFileName == null)
                continue; // no frameset file requested in project file
            outputPath = Path.Combine(docBrowser.RootPath, outputFileName);
            using (StreamWriter streamWriter = new StreamWriter(outputPath))
            {
                using (HtmlTextWriterHelper htmlWriter =
                        new HtmlTextWriterHelper(streamWriter))
                {
                    WriteDocBrowserFrameset(docBrowser, basic, htmlWriter);
                }
            }
        }

        // write <root-name>_Tabs.htm (implements the tabs) -- not used in the
        // basic version
        outputFileName = docBrowser.TabsHtmlFileName;
        outputPath = Path.Combine(docBrowser.PagesAbsolutePath,
            outputFileName);
        using (StreamWriter streamWriter = new StreamWriter(outputPath))
        {
            // copy DocBrowserTabs.htm to <outputFileName>; prepend the
            /// "Mark of the Web", if specified
            if (docBrowser.MarkOfTheWeb)
            {
                streamWriter.WriteLine(
                    HtmlTextWriterHelper.MarkOfTheWebHtml);
            }
            string path = Path.Combine(m_supportFilesPath,
                "DocBrowserTabs.htm");
            streamWriter.Write(File.ReadAllText(path));
        }

        // write <root-name>_Contents.htm (the "Contents" tab)
        int topicCount = 0;
        foreach (bool basic in FalseTrue)
        {
            outputFileName =
                (basic ? Resources.BasicDocBrowserPrefix : String.Empty) +
                docBrowser.ContentsHtmlFileName;
            outputPath = Path.Combine(docBrowser.PagesAbsolutePath,
                outputFileName);
            using (StreamWriter streamWriter = new StreamWriter(outputPath))
            {
                using (HtmlTextWriterHelper htmlWriter =
                    new HtmlTextWriterHelper(streamWriter))
                {
                    topicCount = WriteDocBrowserContentsHtml(docBrowser, basic,
                        htmlWriter);
                }
            }
        }

        // sort index entries and remove duplicate index entries
        docBrowser.SortIndexEntries();
        docBrowser.RemoveDuplicateIndexEntries();

        // write <root-name>_Index.htm (the "Index" tab)
        foreach (bool basic in FalseTrue)
        {
            outputFileName =
                (basic ? Resources.BasicDocBrowserPrefix : String.Empty) +
                docBrowser.IndexHtmlFileName;
            outputPath = Path.Combine(docBrowser.PagesAbsolutePath,
                outputFileName);
            using (StreamWriter streamWriter = new StreamWriter(outputPath))
            {
                using (HtmlTextWriterHelper htmlWriter =
                    new HtmlTextWriterHelper(streamWriter))
                {
                    WriteDocBrowserIndexHtml(docBrowser, basic, topicCount,
                        htmlWriter);
                }
            }
        }

        // write <root-name>_Index.xml (an XML version of the index)
        outputPath = Path.Combine(docBrowser.PagesAbsolutePath,
            docBrowser.IndexXmlFileName);
        XmlWriterSettings settings = new XmlWriterSettings();
        settings.Indent = true;
        settings.IndentChars = "\t";
        using (XmlWriter xmlWriter = XmlWriter.Create(outputPath))
            WriteDocBrowserIndexXml(docBrowser, xmlWriter);
    }

    
App.DocBrowserStaticPages Method

Executes a "<DocBrowserStaticPages>" CodeDoc XML project file command: adds static (non-generated) content to a DocBrowser.

Parameters

xmlReader

An XmlReader which, on entry, is positioned on a "<DocBrowserStaticPages>" element within the CodeDoc XML project file. On exit, xmlReader must be positioned on the "</DocBrowserStaticPages>" element (or, equivalently, "<DocBrowserStaticPages/>").

    void DocBrowserStaticPages(XmlReader xmlReader)
    {
        // parse attributes on the "<DocBrowserStaticPages>" element
        string nodeId = GetAttribute(xmlReader, "NodeId");

        // set <parentDocBrowserNode> to the DocBrowserNode that child
        // "<DocBrowserNode>" elements will append their content to
        DocBrowserNode parentDocBrowserNode;
        try
        {
            parentDocBrowserNode = m_docBrowserNodes[nodeId];
        }
        catch (KeyNotFoundException)
        {
            throw new ProjectFileException(Resources.UndefinedNodeId, nodeId);
        }

        // set <docBrowser> to the DocBrowser that contains (or is)
        // <parentDocBrowserNode>
        DocBrowserNode docBrowserNode = parentDocBrowserNode;
        while (docBrowserNode.ParentNode != null)
            docBrowserNode = docBrowserNode.ParentNode;
        DocBrowser docBrowser = (DocBrowser) docBrowserNode;

        // parse the contents of the "<DocBrowserStaticPages>" element, if any
        if (!xmlReader.IsEmptyElement)
        {
            int initialDepth = xmlReader.Depth;
            while (xmlReader.Read() && (xmlReader.Depth != initialDepth))
            {
                if (xmlReader.NodeType == XmlNodeType.Element) 
                {
                    if (xmlReader.Name == "DocBrowserNode")
                    {
                        ProcessDocBrowserNode(xmlReader, docBrowser,
                            parentDocBrowserNode);
                    }
                    else
                    {
                        throw new ProjectFileException(
                            Resources.ProjectFileUnexpectedElement,
                            xmlReader.Name);
                    }
                }
                else
                if (xmlReader.NodeType == XmlNodeType.Text) 
                    throw UnexpectedText(xmlReader);
            }
        }
    }

    
App.ProcessDocBrowserNode Method

Adds a node to a DocBrowser tree.

Parameters

xmlReader

An XmlReader which, on entry, is positioned on a "<DocBrowserNode>" or "<SourceFilesNode>" element within the CodeDoc XML project file. On exit, xmlReader must be positioned on the "</DocBrowserNode>" or "</SourceFilesNode>" element. (or, equivalently, "<DocBrowserNode/> or "<SourceFilesNode/>").

docBrowser

The current DocBrowser.

parentDocBrowserNode

The DocBrowserNode that the content specified by this "<DocBrowserNode>" element will be placed under.

Return Value

The newly-created DocBrowserNode.

    DocBrowserNode ProcessDocBrowserNode(XmlReader xmlReader,
        DocBrowser docBrowser, DocBrowserNode parentDocBrowserNode)
    {
        // parse attributes on the "<DocBrowserNode>" or "<SourceFilesNode>"
        // element
        string nodeId = GetAttribute(xmlReader, "NodeId", null);
        bool unused;
        string originalFilePath = MakePath(GetAttribute(xmlReader, "Path"),
            FileFlags.File, out unused);
        string fileName = GetAttribute(xmlReader, "NewFileName",
            Path.GetFileName(originalFilePath));
        string title = GetAttribute(xmlReader, "Title",
            Path.GetFileNameWithoutExtension(fileName));
        bool initiallyExpanded = GetAttribute<bool>(xmlReader,
            "InitiallyExpanded", false);

        // don't allow initially expanding a node if its parent isn't initially
        // expanded
        if (!parentDocBrowserNode.InitiallyExpanded)
            initiallyExpanded = false;

        // copy the original file into the DocBrowser pages directory
        string newFilePath = Path.Combine(docBrowser.PagesAbsolutePath,
            fileName);
        try
        {
            CopyFileContents(originalFilePath, newFilePath, false);
        }
        catch (Exception)
        {
            // give the user extra information, i.e. the line number within
            // the CodeDoc XML project file of the "<DocBrowserNode>" or
            // "<SourceFilesNode>" element, before re-throwing the exception
            Console.WriteLine(Resources.CopyFileError, m_projectFilePath,
                ((IXmlLineInfo)xmlReader).LineNumber);
            throw;
        }

        // set <docBrowserNode> to a new DocBrowserNode object correpsonding
        // to this XML node
        DocBrowserNode docBrowserNode = new DocBrowserNode(nodeId,
            m_docSet.GenerateTopicId(), originalFilePath, fileName, fileName,
            title, initiallyExpanded);

        // add <docBrowserNode> as a child of <parentDocBrowserNode>
        parentDocBrowserNode.AddChild(docBrowserNode);

        // add <docBrowserNode> to <m_docBrowserNodes> if the "NodeId"
        // attribute was specified
        if (nodeId != null)
            m_docBrowserNodes[nodeId] = docBrowserNode;
        
        // parse the contents of the "<DocBrowserNode>" or "<SourceFilesNode>"
        // element, if any
        if (!xmlReader.IsEmptyElement)
        {
            int initialDepth = xmlReader.Depth;
            while (xmlReader.Read() && (xmlReader.Depth != initialDepth))
            {
                if (xmlReader.NodeType == XmlNodeType.Element) 
                {
                    if (xmlReader.Name == "IndexEntry")
                    {
                        // add an index entry
                        ProcessDocBrowserIndexEntry(xmlReader, docBrowserNode,
                            docBrowser);
                    }
                    else
                    if (xmlReader.Name == "DocBrowserNode")
                    {
                        // recursively add DocBrowser nodes
                        ProcessDocBrowserNode(xmlReader, docBrowser,
                            docBrowserNode);
                    }
                    else
                    {
                        throw new ProjectFileException(
                            Resources.ProjectFileUnexpectedElement,
                            xmlReader.Name);
                    }
                }
                else
                if (xmlReader.NodeType == XmlNodeType.Text) 
                    throw UnexpectedText(xmlReader);
            }
        }

        return docBrowserNode;
    }

    
App.ProcessDocBrowserIndexEntry Method

Adds an index entry to a DocBrowser tree, corresponding to a given node in the tree.

Parameters

xmlReader

An XmlReader which, on entry, is positioned on a "<IndexEntry>" element within the CodeDoc XML project file. On exit, xmlReader must be positioned on the "</IndexEntry>" element (or, equivalently, "<IndexEntry/>").

docBrowserNode

The DocBrowser node that contains the index entry.

docBrowser

The DocBrowser that contains the index.

    void ProcessDocBrowserIndexEntry(XmlReader xmlReader,
        DocBrowserNode docBrowserNode, DocBrowser docBrowser)
    {
        // parse attributes on the "<IndexEntry>" element
        string bookmark = GetAttribute(xmlReader, "Bookmark", null);

        // parse the contents of the "<IndexEntry>" element, if any
        if (!xmlReader.IsEmptyElement)
        {
            int initialDepth = xmlReader.Depth;
            while (xmlReader.Read() && (xmlReader.Depth != initialDepth))
            {
                if (xmlReader.NodeType == XmlNodeType.Element) 
                {
                    throw new ProjectFileException(
                        Resources.ProjectFileUnexpectedElement,
                        xmlReader.Name);
                }
                else
                if (xmlReader.NodeType == XmlNodeType.Text)
                {
                    docBrowser.AddIndexEntry(xmlReader.Value, docBrowserNode,
                        bookmark);
                }
            }
        }
    }

    
App.ReadSource Method

Executes an "<AddSource>" CodeDoc XML project file command: adds a given source file, or all source files in a given directory tree, to the documentation set.

Parameters

xmlReader

An XmlReader which, on entry, is positioned on a "<ReadSource>" element within the CodeDoc XML project file. On exit, xmlReader must be positioned on the "</ReadSource>" element (or, equivalently, "<ReadSource/>").

    void ReadSource(XmlReader xmlReader)
    {
        // set <sourcePath> to the full path to the source file or directory
        string sourcePath = GetPath(xmlReader, "Path", m_projectFilePath);

        // set <prefix> to the value of the "Prefix" attribute; this is used
        // in the table of contents as the prefix for the relative file
        // path(s); use null if none
        string prefix = GetAttribute(xmlReader, "Prefix", null);

        // parse the contents of the "<ReadSource>" element, if any; set
        // <excludeFilePaths> to the collection of regular expressions
        // specified in "<ExcludeFilePaths>" elements; set <assemblyName>
        // to the "Name" attribute specified in an "<Assembly>" element, or
        // null if none
        List<Regex> excludeFilePaths = new List<Regex>(10);
        string assemblyName = null;
        if (!xmlReader.IsEmptyElement)
        {
            int initialDepth = xmlReader.Depth;
            while (xmlReader.Read() && (xmlReader.Depth != initialDepth))
            {
                if (xmlReader.NodeType == XmlNodeType.Element) 
                {
                    if (xmlReader.Name == "ExcludeFilePaths")
                    {
                        excludeFilePaths.Add(
                            GetRegexAttribute(xmlReader, "Regex"));
                    }
                    else
                    if (xmlReader.Name == "Assembly")
                    {
                        assemblyName = GetAttribute(xmlReader, "Name");
                    }
                    else
                    {
                        throw new ProjectFileException(
                            Resources.ProjectFileUnexpectedElement,
                            xmlReader.Name);
                    }
                }
                else
                if (xmlReader.NodeType == XmlNodeType.Text) 
                    throw UnexpectedText(xmlReader);
            }
        }

        // add <sourcePath> to the documentation set
        bool isDir;
        string sourceFileOrDirPath = MakePath(sourcePath,
            FileFlags.File | FileFlags.Directory, out isDir);
        if (isDir)
        {
            m_docSet.AddSourceFilesInDirectoryTree(sourceFileOrDirPath,
                (prefix ?? ""), excludeFilePaths, assemblyName);
        }
        else
        {
            if (!MatchRegexList(excludeFilePaths, sourceFileOrDirPath, false))
            {
                try
                {
                    string relativePath =
                        Path.GetFileName(sourceFileOrDirPath);
                    if (prefix != null)
                        relativePath = Path.Combine(prefix, relativePath);
                    m_docSet.AddSourceFile(sourceFileOrDirPath, relativePath,
                        assemblyName);
                }
                catch (ArgumentException ex)
                {
                    // unsupported source file name extension
                    throw new ProjectFileException("{0}", ex.Message);
                }
            }
        }
    }

    
App.WriteDocumentation Method

Executes a "<WriteDocumentation>" CodeDoc XML project file command: generates documentation from the source files in the documentation set.

Parameters

xmlReader

An XmlReader which, on entry, is positioned on a "<WriteDocumentation>" element within the CodeDoc XML project file. On exit, xmlReader must be positioned on the "</WriteDocumentation>" element (or, equivalently, "<WriteDocumentation/>").

    void WriteDocumentation(XmlReader xmlReader)
    {
        // reset the state of <n>m_docSet</n> to be the same as it was before
        // the last call to WriteDocumentation() or WriteFormattedCode().
        ResetDocSet();

        // set <docHtmlDirPath> to the full path to the documentation HTML
        // directory; create it if it doesn't exist
        bool unused;
        string docHtmlDirPath =
            MakePath(GetPath(xmlReader, "Path", m_projectFilePath),
                FileFlags.Directory | FileFlags.Nonexistent, out unused);
        CreateDirectoryIfNeeded(docHtmlDirPath);

        // parse other attributes on the "<WriteDocumentation>" element
        m_docSet.ExternalOnly = GetAttribute<bool>(xmlReader, "ExternalOnly",
            false);
        bool markOfTheWeb = GetAttribute<bool>(xmlReader, "MarkOfTheWeb",
            true);
        string topicFooterHtml = GetAttribute(xmlReader, "TopicFooterHtml",
            null);

        // parse the contents of the "<WriteDocumentation>" element, if any;
        // set <namespaceRegexList> to the collection of regular expressions
        // specified in "<Namespace>" elements -- these regular expressions
        // identify which namespaces to include in the documentation set;
        // if none are specified, all namespaces are included; similarly,
        // <memberRegexList> lists member names to include (unless the list
        // is empty, in which case all members are included); set
        // <hideAttributesRegexList> to the list of regular expressions which
        // match "[attribute]" declarations we don't want to display in the
        // generated documentation
        List<Regex> namespaceRegexList = new List<Regex>(10);
        List<Regex> memberRegexList = new List<Regex>(10);
        List<Regex> hideAttributesRegexList = new List<Regex>(10);
        TopicRenderer topicRenderer = null;
        DocBrowserNode addToDocBrowserNode = null;
        InitiallyExpand initiallyExpand = InitiallyExpand.None;
        string indexLabelSuffix = null;
        List<KeyValuePair<Regex, string>> namespaceTitleReplacements = null;
        if (!xmlReader.IsEmptyElement)
        {
            int initialDepth = xmlReader.Depth;
            while (xmlReader.Read() && (xmlReader.Depth != initialDepth))
            {
                if (xmlReader.NodeType == XmlNodeType.Element) 
                {
                    if (xmlReader.Name == "HideAttributes")
                    {
                        hideAttributesRegexList.Add(
                            GetRegexAttribute(xmlReader, "Regex"));
                    }
                    else
                    if (xmlReader.Name == "IncludeNamespaces")
                    {
                        namespaceRegexList.Add(
                            GetRegexAttribute(xmlReader, "Regex"));
                    }
                    else
                    if (xmlReader.Name == "IncludeMembers")
                    {
                        memberRegexList.Add(
                            GetRegexAttribute(xmlReader, "Regex"));
                    }
                    else
                    if (xmlReader.Name == "CopyFile")
                    {
                        string sourcePath = GetPath(xmlReader, "Path",
                            m_projectFilePath);
                        string destinationFileName = GetAttribute(xmlReader,
                            "NewName", Path.GetFileName(sourcePath));
                        CopyStaticFile(sourcePath, destinationFileName,
                            docHtmlDirPath, xmlReader);
                    }
                    else
                    if (xmlReader.Name == "AddToDocBrowser")
                    {
                        string nodeId = GetAttribute(xmlReader, "NodeId");
                        initiallyExpand = GetInitiallyExpandAttribute(xmlReader,
                            "InitiallyExpand");
                        indexLabelSuffix = GetAttribute(xmlReader,
                            "IndexLabelSuffix", null);
                        try
                        {
                            addToDocBrowserNode = m_docBrowserNodes[nodeId];
                        }
                        catch (KeyNotFoundException)
                        {
                            throw new ProjectFileException(
                                Resources.UndefinedNodeId, nodeId);
                        }
                        namespaceTitleReplacements =
                            ReadChangeTitleElements(xmlReader);
                    }
                    else
                    if (xmlReader.Name == "TopicRenderer")
                    {
                        try
                        {
                            topicRenderer = TopicRenderer.CreateRenderer(
                                GetAttribute(xmlReader, "Assembly"),
                                GetAttribute(xmlReader, "Type"), markOfTheWeb,
                                topicFooterHtml, hideAttributesRegexList);
                        }
                        catch (CreateExternalObjectException ex)
                        {
                            throw new ProjectFileException("{0}", ex.Message);
                        }
                    }
                    else
                    {
                        throw new ProjectFileException(
                            Resources.ProjectFileUnexpectedElement,
                            xmlReader.Name);
                    }
                }
                else
                if (xmlReader.NodeType == XmlNodeType.Text) 
                    throw UnexpectedText(xmlReader);
            }
        }

        // if no "<TopicRenderer>" element was specified, create an instance of
        // the default topic renderer
        if (topicRenderer == null)
        {
            topicRenderer = TopicRenderer.CreateRenderer("CodeDocRenderer",
                "DwellNet.CodeDoc.DefaultTopicRenderer", markOfTheWeb,
                topicFooterHtml, hideAttributesRegexList);
        }

        // receive events from <topicRenderer>
        topicRenderer.WarningInSourceFile +=
            new WarningInSourceFileEventDelegate(
                topicRenderer_WarningInSourceFile);

        // perform initial processing on topics
        topicRenderer.CodeHtml = false;
        ProcessTopics(namespaceRegexList, memberRegexList, topicRenderer);

        // loop once for each topic in the documentation set
        Console.WriteLine(Resources.WritingTopics, m_documentedTopicCount);
        foreach (Topic topic in m_docSet.Topics)
        {
            // skip undocumented topics
            if (!topic.IsDocumented)
                continue;

            // generate a documentation HTML file for each documented topic
            try
            {
                TopicRenderer.GenerateDocHtmlInDirectory(topic,
                    docHtmlDirPath, topicRenderer);
            }
            catch (ParsingException ex)
            {
                Console.Error.WriteLine(ex.WarningMessage);
                m_warningCount++;
            }
        }

        // if specified, add documented topics to a specified DocBrowser
        if (addToDocBrowserNode != null)
        {
            AddTopicsToDocBrowserContents(initiallyExpand,
                namespaceTitleReplacements, indexLabelSuffix,
                addToDocBrowserNode, false);
        }
    }

    
App.WriteFormattedCode Method

Executes a "<WriteFormattedCode>" CodeDoc XML project file command: generates code HTML from the source files in the documentation set.

Parameters

xmlReader

An XmlReader which, on entry, is positioned on a "<WriteFormattedCode>" element within the CodeDoc XML project file. On exit, xmlReader must be positioned on the "</WriteFormattedCode>" element (or, equivalently, "<WriteFormattedCode/>").

    void WriteFormattedCode(XmlReader xmlReader)
    {
        // reset the state of <n>m_docSet</n> to be the same as it was before
        // the last call to WriteDocumentation() or WriteFormattedCode().
        ResetDocSet();

        // set <codeHtmlDirPath> to the full path to the code HTML directory;
        // create it if it doesn't exist
        bool unused;
        string codeHtmlDirPath =
            MakePath(GetPath(xmlReader, "Path", m_projectFilePath),
                FileFlags.Directory | FileFlags.Nonexistent, out unused);
        CreateDirectoryIfNeeded(codeHtmlDirPath);

        // parse other attributes on the "<WriteFormattedCode>" element
        InitiallyExpand initiallyExpand = InitiallyExpand.None;
        bool markOfTheWeb = GetAttribute<bool>(xmlReader, "MarkOfTheWeb",
            true);
        string topicFooterHtml = GetAttribute(xmlReader, "TopicFooterHtml",
            null);

        // parse the contents of the "<WriteFormattedCode>" element, if any
        SourceFileRenderer sourceFileRenderer = null;
        TopicRenderer topicRenderer = null;
        DocBrowserNode addToDocBrowserNode = null;
        DocBrowserNode sourceFilesDocBrowserNode = null;
        string indexLabelSuffix = null;
        List<KeyValuePair<Regex, string>> namespaceTitleReplacements = null;
        if (!xmlReader.IsEmptyElement)
        {
            int initialDepth = xmlReader.Depth;
            while (xmlReader.Read() && (xmlReader.Depth != initialDepth))
            {
                if (xmlReader.NodeType == XmlNodeType.Element) 
                {
                    if (xmlReader.Name == "CopyFile")
                    {
                        string sourcePath = GetPath(xmlReader, "Path",
                            m_projectFilePath);
                        string destinationFileName = GetAttribute(xmlReader,
                            "NewName", Path.GetFileName(sourcePath));
                        CopyStaticFile(sourcePath, destinationFileName,
                            codeHtmlDirPath, xmlReader);
                    }
                    else
                    if (xmlReader.Name == "AddToDocBrowser")
                    {
                        string nodeId = GetAttribute(xmlReader, "NodeId");
                        initiallyExpand = GetInitiallyExpandAttribute(xmlReader,
                            "InitiallyExpand");
                        indexLabelSuffix = GetAttribute(xmlReader,
                            "IndexLabelSuffix", null);
                        try
                        {
                            addToDocBrowserNode = m_docBrowserNodes[nodeId];
                        }
                        catch (KeyNotFoundException)
                        {
                            throw new ProjectFileException(
                                Resources.UndefinedNodeId, nodeId);
                        }
                        namespaceTitleReplacements =
                            ReadChangeTitleElements(xmlReader);
                    }
                    else
                    if (xmlReader.Name == "SourceFilesNode")
                    {
                        if (addToDocBrowserNode == null)
                        {
                            throw new ProjectFileException(
                                Resources.SourceFilesNodeUsesAddToDocBrowser);
                        }
                        sourceFilesDocBrowserNode = ProcessDocBrowserNode(
                            xmlReader, addToDocBrowserNode.DocBrowser,
                            addToDocBrowserNode);
                    }
                    else
                    if (xmlReader.Name == "SourceFileRenderer")
                    {
                        try
                        {
                            sourceFileRenderer =
                                SourceFileRenderer.CreateRenderer(
                                    GetAttribute(xmlReader, "Assembly"),
                                    GetAttribute(xmlReader, "Type"),
                                    markOfTheWeb);
                        }
                        catch (CreateExternalObjectException ex)
                        {
                            throw new ProjectFileException("{0}", ex.Message);
                        }
                    }
                    else
                    if (xmlReader.Name == "TopicRenderer")
                    {
                        try
                        {
                            topicRenderer = TopicRenderer.CreateRenderer(
                                GetAttribute(xmlReader, "Assembly"),
                                GetAttribute(xmlReader, "Type"), markOfTheWeb,
                                topicFooterHtml, null);
                        }
                        catch (CreateExternalObjectException ex)
                        {
                            throw new ProjectFileException("{0}", ex.Message);
                        }
                    }
                    else
                    {
                        throw new ProjectFileException(
                            Resources.ProjectFileUnexpectedElement,
                            xmlReader.Name);
                    }
                }
                else
                if (xmlReader.NodeType == XmlNodeType.Text) 
                    throw UnexpectedText(xmlReader);
            }
        }

        // if no "<SourceFileRenderer>" element was specified, create an
        // instance of the default source file renderer
        if (sourceFileRenderer == null)
        {
            sourceFileRenderer = SourceFileRenderer.CreateRenderer(
                "CodeDocRenderer",
                "DwellNet.CodeDoc.DefaultSourceFileRenderer", markOfTheWeb);
        }

        // if no "<TopicRenderer>" element was specified, create an instance
        // of the default topic renderer
        if (topicRenderer == null)
        {
            topicRenderer = TopicRenderer.CreateRenderer("CodeDocRenderer",
                "DwellNet.CodeDoc.DefaultTopicRenderer", markOfTheWeb,
                topicFooterHtml, null);
        }

        // perform initial processing on topics
        topicRenderer.CodeHtml = true;
        ProcessTopics(null, null, topicRenderer);

        // write formatted source code HTML for each source file in the
        // documentation set
        Console.WriteLine(Resources.WritingFormattedSourceFiles,
            m_docSet.SourceFiles.Count);
        foreach (SourceFile sourceFile in m_docSet.SourceFiles)
        {
            string htmlPath = Path.Combine(codeHtmlDirPath,
                sourceFile.CodeHtmlFileName);
            sourceFileRenderer.SourceFile = sourceFile;
            sourceFileRenderer.GenerateCodeHtml(topicRenderer, htmlPath);
        }

        // generate code namespace files
        foreach (Topic topic in m_docSet.NamespaceTopics)
        {
            try
            {
                string htmlPath = Path.Combine(codeHtmlDirPath,
                    topic.CodeNamespaceHtmlFileName);
                using (StreamWriter streamWriter = new StreamWriter(htmlPath))
                {
                    using (HtmlTextWriterHelper htmlWriter =
                        new HtmlTextWriterHelper(streamWriter))
                    {
                        topicRenderer.Topic = topic;
                        topicRenderer.GenerateCodeNamespaceHtml(htmlWriter);
                    }
                }
            }
            catch (ParsingException ex)
            {
                Console.Error.WriteLine(ex.WarningMessage);
                m_warningCount++;
            }
        }

        // generate code overload topic files
        foreach (Topic topic in m_docSet.OverloadListTopics)
        {
            try
            {
                string htmlPath = Path.Combine(codeHtmlDirPath,
                    topic.CodeOverloadListHtmlFileName);
                using (StreamWriter streamWriter = new StreamWriter(htmlPath))
                {
                    using (HtmlTextWriterHelper htmlWriter =
                        new HtmlTextWriterHelper(streamWriter))
                    {
                        topicRenderer.Topic = topic;
                        topicRenderer.GenerateCodeOverloadListHtml(
                            htmlWriter);
                    }
                }
            }
            catch (ParsingException ex)
            {
                Console.Error.WriteLine(ex.WarningMessage);
                m_warningCount++;
            }
        }

        // if specified (by an "<AddToDocBrowser>" element in the CodeDoc XML
        // project file), add documented topics to the specified DocBrowser
        if (addToDocBrowserNode != null)
        {
            // write DocBrowser Contents nodes corresponding to namespaces,
            // types and members
            AddTopicsToDocBrowserContents(initiallyExpand,
                namespaceTitleReplacements, indexLabelSuffix,
                addToDocBrowserNode, true);

            // if specified (by a "<SourceFilesNode>" element in the CodeDoc
            // XML project file), write DocBrowser Contents nodes corresponding
            // to source files
            if (sourceFilesDocBrowserNode != null)
            {
                DocBrowser docBrowser = sourceFilesDocBrowserNode.DocBrowser;
                foreach (SourceFile sourceFile in m_docSet.SourceFiles)
                {
                    // add a node to the table of contents
                    string url = sourceFile.CodeHtmlFileName;
                    DocBrowserNode node = new DocBrowserNode(null,
                        sourceFile.SourceFileTopicId, null, url, url,
                        sourceFile.SourceFileRelativePath, false);
                    sourceFilesDocBrowserNode.AddChild(node);

                    // add an index entry (e.g. "Foo.cs (Source Code)"); if the
                    // source file relative path includes a directory component
                    // (e.g. "Abc\Def\Foo.cs") include an entry for both the
                    // full path (e.g. "Abc\Def\Foo.cs") and base file name
                    // (e.g.  // "Foo.cs")
                    string fileName1 = sourceFile.SourceFileRelativePath;
                    string fileName2 = Path.GetFileName(fileName1);
                    docBrowser.AddIndexEntry(fileName1 + indexLabelSuffix,
                        node, null);
                    if (fileName1 != fileName2)
                    {
                        docBrowser.AddIndexEntry(fileName2 + indexLabelSuffix,
                            node, null);
                    }
                }
            }
        }
    }

    
App.ReadChangeTitleElements Method

Parses "<ChangeTitle>" elements.

Parameters

xmlReader

An XmlReader pointing to the current location within the CodeDoc XML project file.

Return Value

A list of change-title Regex/replacement-string pairs, or null if no "<ChangeTitle>" elements were present.

    List<KeyValuePair<Regex, string>> ReadChangeTitleElements(
        XmlReader xmlReader)
    {
        List<KeyValuePair<Regex, string>> titleReplacements = null;
        int initialDepth = xmlReader.Depth;
        while (xmlReader.Read() && (xmlReader.Depth != initialDepth))
        {
            if (xmlReader.NodeType == XmlNodeType.Element) 
            {
                if (xmlReader.Name == "ChangeTitle")
                {
                    if (titleReplacements == null)
                    {
                        titleReplacements =
                            new List<KeyValuePair<Regex, string>>();
                    }
                    Regex regex = GetRegexAttribute(xmlReader, "Regex");
                    string replacement = GetAttribute(xmlReader,
                        "Replacement");
                    titleReplacements.Add(new KeyValuePair<Regex, string>
                        (regex, replacement));
                }
                else
                {
                    throw new ProjectFileException(
                        Resources.ProjectFileUnexpectedElement,
                        xmlReader.Name);
                }
            }
            else
            if (xmlReader.NodeType == XmlNodeType.Text) 
                throw UnexpectedText(xmlReader);
        }

        return titleReplacements;
    }

    
App.ResetDocSet Method

Resets the state of m_docSet to be the same as it was before the last call to WriteDocumentation() or WriteFormattedCode().

    void ResetDocSet()
    {
        // if ProcessTopics() was previously called, we need to reset the state
        // of <m_docSet>
        if (m_processedTopics)
        {
            // create a new DocumentationSet
            DocumentationSet newDocSet = new DocumentationSet();

            // copy "core" state from the old DocumentationSet to the new one
            m_docSet.CopyCoreState(newDocSet);

            // switch <m_docSet> over to the new DocumentationSet
            m_docSet = newDocSet;

            // initialize <m_docSet>
            InitializeDocumentationSet();
        }
        else
            m_processedTopics = true;

        // other initialization
        m_documentedTopicCount = 0;
    }

    
App.ProcessTopics Method

Performs initial processing on topics that's common to WriteDocumentation and WriteFormattedCode.

Parameters

namespaceRegexList

The collection of regular expressions specified in "<Namespace>" elements -- these regular expressions identify which namespaces to include in the documentation set; if none are specified, or namespaceRegexList is null, all namespaces are included.

memberRegexList

The collection of regular expressions specified in "<Member>" elements -- these regular expressions identify the member names names to include in the documentation set; if none are specified, or memberRegexList is null, all members are included.

topicRenderer

The TopicRenderer to use to generate HTML corresponding to XML comments.

    void ProcessTopics(List<Regex> namespaceRegexList,
        List<Regex> memberRegexList, TopicRenderer topicRenderer)
    {
        // perform general processing on the list of topics, such as generating
        // unique file names; do this before any topic lists get sorted so that
        // the file name suffix numbering is relatively consistent among runs
        m_docSet.BeginUsingSources();

        // for debugging purposes, list all topics
        if (m_verbose >= 99)
        {
            foreach (Topic topic in m_docSet.Topics)
                Console.WriteLine(topic.QualifiedMemberName);
        }

        // pass 1: sets Topic.IsDocumented to true for each topic we want to
        // generate documentation for; also, update <m_documentedTopicCount> to
        // be the count of documented topics
        Console.WriteLine(Resources.EnumeratingTopicsToDocument);
        foreach (NamespaceTopic namespaceTopic in m_docSet.NamespaceTopics)
        {
            // see if <namespaceRegexList> matches this namespace; if the
            // list is empty, all namespaces are included
            if (!MatchRegexList(namespaceRegexList,
                    namespaceTopic.MemberName, true))
                continue;

            // keep track of the number of topics that will be documented in
            // this namespace, not including the namespace itself
            int documentedTopicsInNamespace = 0;

            // sort topics
            namespaceTopic.SortTypeTopics();
            namespaceTopic.SortChildTopics();

            // loop once per type in this namespace
            foreach (Topic typeTopic in namespaceTopic.TypeTopics)
            {
                // mark <typeTopic> as documentable if <memberRegexList>
                // matches it or is empty
                if (MatchRegexList(memberRegexList,
                    typeTopic.QualifiedMemberName.ToString(), true))
                {
                    if (!m_docSet.ExternalOnly ||
                        typeTopic.IsExternallyAccessible)
                    {
                        typeTopic.IsDocumented = true;
                        documentedTopicsInNamespace++;
                        m_documentedTopicCount++;
                    }
                }

                // loop once per member of type <typeTopic>, but skip nested
                // type topics since we're already enumerating through all type
                // topics
                foreach (Topic childTopic in
                    Topic.EnumerateDescendentNonTypes(typeTopic))
                {
                    // mark <childTopic> as documentable if <memberRegexList>
                    // matches it or is empty; exclude enumeration value topics
                    // since we only document the enumeration topics (not
                    // one topic for each individual value)
                    if (MatchRegexList(memberRegexList,
                            childTopic.QualifiedMemberName.ToString(), true) &&
                        (childTopic.TopicKind != TopicKind.EnumValueTopic) &&
                        (!m_docSet.ExternalOnly ||
                         childTopic.IsExternallyAccessible))
                    {
                        childTopic.IsDocumented = true;
                        documentedTopicsInNamespace++;
                        m_documentedTopicCount++;
                    }
                }
            }

            // mark this namespace topic as documentable, unless it has no
            // child topics
            if (documentedTopicsInNamespace > 0)
            {
                namespaceTopic.IsDocumented = true;
                m_documentedTopicCount++;
            }
        }

        // perform general processing on the list of topics, such as
        // determining the base class of each documented topic, that must occur
        // after the application has set Topic.IsDocumented to true for all
        // topics the applicatin wants documented
        m_docSet.AfterSettingIsDocumented();

        // pass 2: generates Topic.XmlCommentSections
        Console.WriteLine(Resources.ConvertingXmlToHtml);
        foreach (Topic topic in m_docSet.Topics)
        {
            if (topic.CanLinkTo)
            {
                try
                {
                    TopicRenderer.GenerateDocHtmlInDirectory(topic, null,
                        topicRenderer);
                }
                catch (ParsingException ex)
                {
                    Console.Error.WriteLine(ex.WarningMessage);
                    m_warningCount++;
                }
            }
        }
    }

    
App.CopyStaticFile Method

Copies a static file (i.e. a file that's not generated) to the documentation