/// <summary>
        /// This is used to merge destination file information into the site
        /// map TOC.
        /// </summary>
        /// <param name="site">The site entry to update</param>
        /// <remarks>In addition, files in the site map that do not exist in
        /// the TOC built from the defined content will be processed and
        /// copied to the root folder.</remarks>
        private void MergeTocInfo(TocEntry site)
        {
            TocEntry match;
            string   source, filename;

            if (site.SourceFile.Path.Length != 0)
            {
                match = toc.Find(site.SourceFile);

                if (match != null)
                {
                    site.DestinationFile = match.DestinationFile;
                }
                else
                {
                    source = site.SourceFile;
                    site.DestinationFile = Path.GetFileName(source);
                    filename             = site.DestinationFile;

                    // .topic files get transformed into .html files
                    if (source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase))
                    {
                        site.DestinationFile = Path.ChangeExtension(site.DestinationFile, ".html");
                    }

                    // Check to see if anything needs resolving
                    if (source.EndsWith(".htm", StringComparison.OrdinalIgnoreCase) ||
                        source.EndsWith(".html", StringComparison.OrdinalIgnoreCase) ||
                        source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase))
                    {
                        match = BuildProcess.GetTocInfo(source);
                    }

                    foreach (string baseFolder in this.HelpFormatOutputFolders)
                    {
                        // If the file contains items that need to be resolved, it is handled separately
                        if (match != null && (match.HasLinks || match.HasCodeBlocks ||
                                              match.NeedsColorizing || match.HasProjectTags ||
                                              source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase)))
                        {
                            // Files are always copied to the root
                            pathToRoot = String.Empty;

                            this.ResolveLinksAndCopy(source, baseFolder + filename, match);
                        }
                        else
                        {
                            this.ReportProgress("{0} -> {1}{2}", source, baseFolder, filename);

                            // All attributes are turned off so that we can delete it later
                            File.Copy(source, baseFolder + filename, true);
                            File.SetAttributes(baseFolder + filename, FileAttributes.Normal);
                        }
                    }
                }
            }

            if (site.Children.Count != 0)
            {
                foreach (TocEntry entry in site.Children)
                {
                    this.MergeTocInfo(entry);
                }
            }
        }
        /// <summary>
        /// This is used to transform a *.topic file into a *.html file using an XSLT transformation based on the
        /// presentation style.
        /// </summary>
        /// <param name="sourceFile">The source topic filename</param>
        private void XslTransform(string sourceFile)
        {
            TocEntry           tocInfo;
            XmlReader          reader = null;
            XmlWriter          writer = null;
            XsltSettings       settings;
            XmlReaderSettings  readerSettings;
            XmlWriterSettings  writerSettings;
            Encoding           enc = Encoding.Default;
            FileItemCollection transforms;
            string             content;

            string sourceStylesheet, destFile = Path.ChangeExtension(sourceFile, ".html");

            try
            {
                readerSettings               = new XmlReaderSettings();
                readerSettings.CloseInput    = true;
                readerSettings.DtdProcessing = DtdProcessing.Parse;

                // Create the transform on first use
                if (xslTransform == null)
                {
                    transforms = new FileItemCollection(project, BuildAction.TopicTransform);

                    if (transforms.Count != 0)
                    {
                        if (transforms.Count > 1)
                        {
                            this.ReportWarning("BE0011", "Multiple topic transformations found.  Using '{0}'",
                                               transforms[0].FullPath);
                        }

                        sourceStylesheet = transforms[0].FullPath;
                    }
                    else
                    {
                        sourceStylesheet = templateFolder + project.PresentationStyle + ".xsl";
                    }

                    xslStylesheet = workingFolder + Path.GetFileName(sourceStylesheet);
                    tocInfo       = BuildProcess.GetTocInfo(sourceStylesheet);

                    // The style sheet may contain shared content items so we must resolve it this way rather
                    // than using TransformTemplate.
                    this.ResolveLinksAndCopy(sourceStylesheet, xslStylesheet, tocInfo);

                    xslTransform = new XslCompiledTransform();
                    settings     = new XsltSettings(true, true);
                    xslArguments = new XsltArgumentList();

                    xslTransform.Load(XmlReader.Create(xslStylesheet, readerSettings), settings,
                                      new XmlUrlResolver());
                }

                this.ReportProgress("Applying XSL transformation '{0}' to '{1}'.", xslStylesheet, sourceFile);

                reader                     = XmlReader.Create(sourceFile, readerSettings);
                writerSettings             = xslTransform.OutputSettings.Clone();
                writerSettings.CloseOutput = true;
                writerSettings.Indent      = false;

                writer = XmlWriter.Create(destFile, writerSettings);

                xslArguments.Clear();
                xslArguments.AddParam("pathToRoot", String.Empty, pathToRoot);
                xslTransform.Transform(reader, xslArguments, writer);
            }
            catch (Exception ex)
            {
                throw new BuilderException("BE0017", String.Format(CultureInfo.CurrentCulture,
                                                                   "Unexpected error using '{0}' to transform additional content file '{1}' to '{2}'.  The " +
                                                                   "error is: {3}\r\n{4}", xslStylesheet, sourceFile, destFile, ex.Message,
                                                                   (ex.InnerException == null) ? String.Empty : ex.InnerException.Message));
            }
            finally
            {
                if (reader != null)
                {
                    reader.Close();
                }

                if (writer != null)
                {
                    writer.Flush();
                    writer.Close();
                }
            }

            // The source topic file is deleted as the transformed file takes its place
            File.Delete(sourceFile);

            // <span> and <script> tags cannot be self-closing if empty.  The template may contain them correctly
            // but when written out as XML, they get converted to self-closing tags which breaks them.  To fix
            // them, convert them to full start and close tags.
            content = BuildProcess.ReadWithEncoding(destFile, ref enc);
            content = reSpanScript.Replace(content, "<$1$2></$1>");

            // An XSL transform might have added tags and include items that need replacing so run it through
            // those options if needed.
            tocInfo = BuildProcess.GetTocInfo(destFile);

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

            // Colorize <pre> tags if necessary
            if (tocInfo.NeedsColorizing || tocInfo.HasCodeBlocks)
            {
                // Initialize code colorizer on first use
                if (codeColorizer == null)
                {
                    codeColorizer = new CodeColorizer(ComponentUtilities.ToolsFolder +
                                                      @"PresentationStyles\Colorizer\highlight.xml", ComponentUtilities.ToolsFolder +
                                                      @"PresentationStyles\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");
                tocInfo.HasProjectTags = true;
            }

            // Use a regular expression to find and replace all tags with cref attributes with a link to the help
            // file content.  This needs to happen after the code block processing as they may contain <see> tags
            // that need to be resolved.
            if (tocInfo.HasLinks || tocInfo.HasCodeBlocks)
            {
                content = reResolveLinks.Replace(content, linkMatchEval);
            }

            // Replace project option tags with project option values
            if (tocInfo.HasProjectTags)
            {
                // Project tags can be nested
                while (reProjectTags.IsMatch(content))
                {
                    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 is called to copy the additional content files and build a list of them for the help file
        /// project.
        /// </summary>
        /// <remarks>Note that for wildcard content items, the folders are copied recursively.</remarks>
        private void CopyAdditionalContent()
        {
            Dictionary <string, TocEntryCollection> tocItems = new Dictionary <string, TocEntryCollection>();
            TocEntryCollection parentToc;
            TocEntry           tocEntry, tocFolder;
            FileItemCollection contentItems;
            string             projectPath, source, filename, dirName;

            string[] parts;
            int      part;

            this.ReportProgress(BuildStep.CopyAdditionalContent, "Copying additional content files...");

            if (this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
            {
                return;
            }

            // A plug-in might add or remove additional content so call
            // them before checking to see if there is anything to copy.
            this.ExecutePlugIns(ExecutionBehaviors.Before);

            if (!project.HasItems(BuildAction.Content) && !project.HasItems(BuildAction.SiteMap))
            {
                this.ReportProgress("No additional content to copy");
                this.ExecutePlugIns(ExecutionBehaviors.After);
                return;
            }

            toc = new TocEntryCollection();
            tocItems.Add(String.Empty, toc);

            // Now copy the content files
            contentItems = new FileItemCollection(project, BuildAction.Content);
            projectPath  = FolderPath.TerminatePath(Path.GetDirectoryName(originalProjectName));

            foreach (FileItem fileItem in contentItems)
            {
                source   = fileItem.Include;
                dirName  = Path.GetDirectoryName(fileItem.Link.ToString().Substring(projectPath.Length));
                filename = Path.Combine(dirName, Path.GetFileName(source));

                if (source.EndsWith(".htm", StringComparison.OrdinalIgnoreCase) ||
                    source.EndsWith(".html", StringComparison.OrdinalIgnoreCase) ||
                    source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase))
                {
                    tocEntry = BuildProcess.GetTocInfo(source);

                    // Exclude the page if so indicated via the item metadata
                    if (fileItem.ExcludeFromToc)
                    {
                        tocEntry.IncludePage = false;
                    }

                    // .topic files get transformed into .html files
                    if (source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase))
                    {
                        filename = Path.ChangeExtension(filename, ".html");
                    }

                    tocEntry.SourceFile      = new FilePath(source, project);
                    tocEntry.DestinationFile = filename;

                    // Figure out where to add the entry
                    parts      = tocEntry.DestinationFile.Split('\\');
                    pathToRoot = String.Empty;
                    parentToc  = toc;

                    for (part = 0; part < parts.Length - 1; part++)
                    {
                        pathToRoot += parts[part] + @"\";

                        // Create place holders if necessary
                        if (!tocItems.TryGetValue(pathToRoot, out parentToc))
                        {
                            tocFolder       = new TocEntry(project);
                            tocFolder.Title = parts[part];

                            if (part == 0)
                            {
                                toc.Add(tocFolder);
                            }
                            else
                            {
                                tocItems[String.Join(@"\", parts, 0, part) + @"\"].Add(tocFolder);
                            }

                            parentToc = tocFolder.Children;
                            tocItems.Add(pathToRoot, parentToc);
                        }
                    }

                    parentToc.Add(tocEntry);

                    if (tocEntry.IncludePage && tocEntry.IsDefaultTopic)
                    {
                        defaultTopic = tocEntry.DestinationFile;
                    }
                }
                else
                {
                    tocEntry = null;
                }

                this.EnsureOutputFoldersExist(dirName);

                foreach (string baseFolder in this.HelpFormatOutputFolders)
                {
                    // If the file contains items that need to be resolved,
                    // it is handled separately.
                    if (tocEntry != null &&
                        (tocEntry.HasLinks || tocEntry.HasCodeBlocks ||
                         tocEntry.NeedsColorizing || tocEntry.HasProjectTags ||
                         source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase)))
                    {
                        // Figure out the path to the root if needed
                        parts      = tocEntry.DestinationFile.Split('\\');
                        pathToRoot = String.Empty;

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

                        this.ResolveLinksAndCopy(source, baseFolder + filename, tocEntry);
                    }
                    else
                    {
                        this.ReportProgress("{0} -> {1}{2}", source, baseFolder, filename);

                        // All attributes are turned off so that we can delete it later
                        File.Copy(source, baseFolder + filename, true);
                        File.SetAttributes(baseFolder + filename, FileAttributes.Normal);
                    }
                }
            }

            // Remove excluded nodes, merge folder item info into the root
            // nodes, and sort the items.  If a site map isn't defined, this
            // will define the layout of the items.
            toc.RemoveExcludedNodes(null);
            toc.Sort();

            codeColorizer = null;
            sharedContent = null;

            this.ExecutePlugIns(ExecutionBehaviors.After);
        }