/// <summary>
        /// This is used to extract table of content information from a file
        /// that will appear in the help file's table of content.
        /// </summary>
        /// <param name="filename">The file from which to extract the
        /// information</param>
        /// <returns>The table of content entry</returns>
        private static TocEntry GetTocInfo(string filename)
        {
            TocEntry tocEntry;
            Encoding enc = Encoding.Default;
            string   content;

            content = BuildProcess.ReadWithEncoding(filename, ref enc);

            tocEntry                = new TocEntry();
            tocEntry.IncludePage    = !reTocExclude.IsMatch(content);
            tocEntry.IsDefaultTopic = reIsDefaultTopic.IsMatch(content);

            Match m = reSortOrder.Match(content);

            if (m.Success)
            {
                tocEntry.SortOrder = Convert.ToInt32(
                    m.Groups["SortOrder"].Value,
                    CultureInfo.InvariantCulture);
            }

            // Get the page title if possible.  If not found, use the filename
            // without the path or extension as the page title.
            m = rePageTitle.Match(content);
            if (!m.Success)
            {
                tocEntry.Title = Path.GetFileNameWithoutExtension(filename);
            }
            else
            {
                tocEntry.Title = HttpUtility.HtmlDecode(
                    m.Groups["Title"].Value).Replace("\r", "").Replace("\n", "");
            }

            // Since we've got the file loaded, see if there are links
            // that need to be resolved when the file is copied, if it
            // contains <pre> blocks that should be colorized, or if it
            // contains tags or shared content items that need replacing.
            tocEntry.HasLinks        = reResolveLinks.IsMatch(content);
            tocEntry.HasCodeBlocks   = reCodeBlock.IsMatch(content);
            tocEntry.NeedsColorizing = reColorizeCheck.IsMatch(content);
            tocEntry.HasProjectTags  = (reProjectTags.IsMatch(content) ||
                                        reSharedContent.IsMatch(content));

            return(tocEntry);
        }
        /// <summary>
        /// This is called to load an additional content file, resolve links
        /// to namespace content and copy it to the output folder.
        /// </summary>
        /// <param name="sourceFile">The source filename to copy</param>
        /// <param name="destFile">The destination filename</param>
        /// <param name="entry">The entry being resolved.</param>
        private void ResolveLinksAndCopy(string sourceFile, string destFile,
                                         TocEntry entry)
        {
            Encoding enc = Encoding.Default;
            string   content, script, syntaxFile;
            int      pos;

            this.ReportProgress("{0} -> {1}", sourceFile, destFile);

            // When reading the file, use the default encoding but detect the
            // encoding if byte order marks are present.
            content = BuildProcess.ReadWithEncoding(sourceFile, ref enc);

            // Use a regular expression to find and replace all <see>
            // tags with link to the help file content.
            if (entry.HasLinks)
            {
                content = reResolveLinks.Replace(content, linkMatchEval);
            }

            // Expand <code> tags if necessary
            if (entry.HasCodeBlocks)
            {
                content = reCodeBlock.Replace(content, codeBlockMatchEval);
            }

            // Colorize <pre> tags if necessary
            if (entry.NeedsColorizing || entry.HasCodeBlocks)
            {
                // Initialize code colorizer on first use
                if (codeColorizer == null)
                {
                    codeColorizer = new CodeColorizer(
                        shfbFolder + @"Colorizer\highlight.xml",
                        shfbFolder + @"Colorizer\highlight.xsl");
                }

                // Set the path the "Copy" image
                codeColorizer.CopyImageUrl = pathToRoot + "icons/CopyCode.gif";

                // Colorize it and replace the "Copy" literal text with the
                // shared content include item so that it gets localized.
                content = codeColorizer.ProcessAndHighlightText(content);
                content = content.Replace(codeColorizer.CopyText + "</span",
                                          "<include item=\"copyCode\"/></span");
                entry.HasProjectTags = true;

                // Add the links to the colorizer stylesheet and script files
                script = String.Format(CultureInfo.InvariantCulture,
                                       "<link type='text/css' rel='stylesheet' " +
                                       "href='{0}html/highlight.css' />" +
                                       "<script type='text/javascript' " +
                                       "src='{0}html/highlight.js'></script>", pathToRoot);

                pos = content.IndexOf("</head>");

                // Create a <head> section if one doesn't exist
                if (pos == -1)
                {
                    script = "<head>" + script + "</head>";
                    pos    = content.IndexOf("<html>");

                    if (pos != -1)
                    {
                        pos += 6;
                    }
                    else
                    {
                        pos = 0;
                    }
                }

                content = content.Insert(pos, script);

                // Copy the colorizer files if not already there
                if (!File.Exists(workingFolder + @"Output\html\highlight.css"))
                {
                    syntaxFile = shfbFolder + @"Colorizer\highlight.css";
                    File.Copy(syntaxFile, workingFolder +
                              @"Output\html\highlight.css");
                    File.SetAttributes(syntaxFile, FileAttributes.Normal);

                    syntaxFile = shfbFolder + @"Colorizer\highlight.js";
                    File.Copy(syntaxFile, workingFolder +
                              @"Output\html\highlight.js");
                    File.SetAttributes(syntaxFile, FileAttributes.Normal);

                    // This one may exist as the default presentation styles
                    // contain an image by this name.
                    syntaxFile = shfbFolder + @"Colorizer\CopyCode.gif";
                    File.Copy(syntaxFile, workingFolder +
                              @"Output\icons\CopyCode.gif", true);
                    File.SetAttributes(syntaxFile, FileAttributes.Normal);
                }
            }

            // Replace project option tags with project option values
            if (entry.HasProjectTags)
            {
                content = reProjectTags.Replace(content, fieldMatchEval);

                // Shared content items can be nested
                while (reSharedContent.IsMatch(content))
                {
                    content = reSharedContent.Replace(content, contentMatchEval);
                }
            }

            // Write the file back out with the appropriate encoding
            using (StreamWriter sw = new StreamWriter(destFile, false, enc))
            {
                sw.Write(content);
            }
        }
        /// <summary>
        /// This copies additional content files from the specified source
        /// folder to the specified destination folder and builds table of
        /// content entries for them.  If any subfolders are found below the
        /// source folder and the wildcard is "*.*", the subfolders are also
        /// copied recursively.
        /// </summary>
        /// <param name="previewing">Pass true to generate the table of content
        /// collection for previewing without actually copying anything.</param>
        /// <param name="sourcePath">The source path from which to copy.</param>
        /// <param name="destPath">The destination path to which to copy.</param>
        /// <returns>The table of content entry for the folder and all
        /// of its children or null if there is nothing to show in the table
        /// of content for this item.</returns>
        protected TocEntry RecursiveContentCopy(bool previewing,
                                                string sourcePath, string destPath)
        {
            TocEntry tocFolder, tocEntry;

            string[] files;
            string   filename, nameLower, rootPath = workingFolder + @"Output\";
            bool     hasContent = false;

            if (sourcePath == null)
            {
                throw new ArgumentNullException("sourcePath");
            }

            if (destPath == null)
            {
                throw new ArgumentNullException("destPath");
            }

            if (toc == null)
            {
                throw new ArgumentNullException("toc");
            }

            int idx = sourcePath.LastIndexOf('\\');

            string dirName  = sourcePath.Substring(0, idx),
                   fileSpec = sourcePath.Substring(idx + 1);

            tocFolder            = new TocEntry();
            tocFolder.SourceFile = dirName + @"\";
            tocFolder.Title      = dirName.Substring(dirName.LastIndexOf("\\") + 1);

            // Copy all the files in the folder
            files = Directory.GetFiles(dirName, fileSpec);

            foreach (string name in files)
            {
                if (exclusionList.ContainsKey(name))
                {
                    continue;
                }

                filename  = destPath + Path.GetFileName(name);
                nameLower = name.ToLower(CultureInfo.InvariantCulture);

                if (nameLower.EndsWith(".htm") || nameLower.EndsWith(".html"))
                {
                    tocEntry = BuildProcess.GetTocInfo(name);
                }
                else
                {
                    tocEntry = null;
                }

                // If the filename matches the folder name and it's an HTML
                // file, extract the TOC info from it for the root node.
                // If there is no info or its excluded, the folder entry won't
                // have an associated page.  Even if excluded, it will still use
                // the title, default topic, and sort order properties though.
                if (tocEntry != null)
                {
                    if (Path.GetFileNameWithoutExtension(name) == tocFolder.Title)
                    {
                        tocFolder.Title          = tocEntry.Title;
                        tocFolder.IsDefaultTopic = tocEntry.IsDefaultTopic;
                        tocFolder.SortOrder      = tocEntry.SortOrder;
                        tocFolder.SourceFile     = name;
                        hasContent = true;  // In case it's this page only

                        if (tocEntry.IncludePage)
                        {
                            tocFolder.DestinationFile = filename.Remove(0,
                                                                        rootPath.Length);

                            if (tocFolder.IsDefaultTopic)
                            {
                                defaultTopic = tocFolder.DestinationFile;
                            }
                        }
                        else
                        {
                            tocFolder.DestinationFile = null;
                        }
                    }
                    else
                    {
                        if (tocEntry.IncludePage)
                        {
                            tocEntry.SourceFile      = name;
                            tocEntry.DestinationFile = filename.Remove(0,
                                                                       rootPath.Length);
                            tocFolder.Children.Add(tocEntry);
                        }

                        if (tocEntry.IsDefaultTopic)
                        {
                            defaultTopic = tocEntry.DestinationFile;
                        }
                    }
                }

                if (!previewing && !Directory.Exists(destPath))
                {
                    Directory.CreateDirectory(destPath);
                }

                // If the file contains links that need to be resolved,
                // it is handled separately.
                if (!previewing && tocEntry != null &&
                    (tocEntry.HasLinks || tocEntry.HasCodeBlocks ||
                     tocEntry.NeedsColorizing || tocEntry.HasProjectTags))
                {
                    // Figure out the path to the root if needed
                    string[] parts = destPath.Remove(0, rootPath.Length).Split('\\');
                    pathToRoot = String.Empty;

                    for (int part = 0; part < parts.Length - 1; part++)
                    {
                        pathToRoot += "../";
                    }

                    this.ResolveLinksAndCopy(name, filename, tocEntry);
                }
                else
                {
                    this.ReportProgress("{0} -> {1}", name, filename);

                    // All attributes are turned off so that we can delete
                    // it later.
                    if (!previewing)
                    {
                        File.Copy(name, filename, true);
                        File.SetAttributes(filename, FileAttributes.Normal);
                    }
                }
            }

            // For "*.*", copy subfolders too
            if (fileSpec == "*.*")
            {
                string[] subFolders = Directory.GetDirectories(dirName);

                foreach (string folder in subFolders)
                {
                    tocEntry = this.RecursiveContentCopy(previewing,
                                                         folder + @"\*.*", destPath +
                                                         folder.Substring(dirName.Length + 1) + @"\");

                    if (tocEntry != null)
                    {
                        tocFolder.Children.Add(tocEntry);
                    }
                }
            }

            return((hasContent || tocFolder.Children.Count != 0) ?
                   tocFolder : null);
        }