Пример #1
0
        /// <summary>
        /// This method can be used by plug-ins to merge content from another Sandcastle Help File Builder
        /// project file.
        /// </summary>
        /// <param name="project">The project file from which to merge content</param>
        /// <remarks>Auto-generated content can be added to a temporary SHFB project and then added to the
        /// current project's content at build time using this method.  Such content cannot always be added to
        /// the project being built as it may alter the underlying MSBuild project which is not wanted.</remarks>
        public void MergeContentFrom(SandcastleProject project)
        {
            var otherImageFiles         = new ImageReferenceCollection(project);
            var otherCodeSnippetFiles   = new FileItemCollection(project, BuildAction.CodeSnippets);
            var otherTokenFiles         = new FileItemCollection(project, BuildAction.Tokens);
            var otherContentLayoutFiles = new FileItemCollection(project, BuildAction.ContentLayout);

            foreach (var image in otherImageFiles)
            {
                imageFiles.Add(image);
            }

            foreach (var snippets in otherCodeSnippetFiles)
            {
                codeSnippetFiles.Add(snippets);
            }

            foreach (var tokens in otherTokenFiles)
            {
                tokenFiles.Add(tokens);
            }

            foreach (FileItem file in otherContentLayoutFiles)
            {
                topics.Add(new TopicCollection(file));
            }
        }
Пример #2
0
        //=====================================================================

        /// <summary>
        /// This loads the tree view with token file entries from the project
        /// </summary>
        private void LoadTokenInfo()
        {
            TreeNode        rootNode = null, node;
            TokenCollection tokens;

            tvEntities.ImageList = ilImages;
            tvEntities.Nodes.Clear();

            if (tokenFiles == null)
            {
                tokenFiles = new FileItemCollection(currentProject,
                                                    BuildAction.Tokens);
            }

            foreach (FileItem tokenFile in tokenFiles)
            {
                try
                {
                    if (File.Exists(tokenFile.FullPath))
                    {
                        rootNode = tvEntities.Nodes.Add(Path.GetFileName(
                                                            tokenFile.FullPath));
                        rootNode.ImageIndex = rootNode.SelectedImageIndex =
                            (int)EntityType.CodeEntities;

                        tokens = new TokenCollection(tokenFile);
                        tokens.Load();

                        foreach (Token t in tokens)
                        {
                            node            = rootNode.Nodes.Add(t.TokenName);
                            node.Name       = t.TokenName;
                            node.Tag        = t;
                            node.ImageIndex = node.SelectedImageIndex =
                                (int)EntityType.Tokens;
                        }
                    }

                    rootNode = null;
                }
                catch (Exception ex)
                {
                    if (rootNode == null)
                    {
                        tvEntities.Nodes.Add("Unable to load file '" +
                                             tokenFile.FullPath + "'.  Reason: " + ex.Message);
                    }
                    else
                    {
                        rootNode.Nodes.Add("Unable to load file: " + ex.Message);
                    }
                }
            }

            txtFindName.Enabled = true;
            tvEntities.Enabled  = true;
            tvEntities.ExpandAll();
        }
        //=====================================================================
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="project">The project from which to load the settings</param>
        public ConceptualContentSettings(SandcastleProject project)
        {
            imageFiles = new ImageReferenceCollection(project);
            codeSnippetFiles = new FileItemCollection(project, BuildAction.CodeSnippets);
            tokenFiles = new FileItemCollection(project, BuildAction.Tokens);
            contentLayoutFiles = new FileItemCollection(project, BuildAction.ContentLayout);
            topics = new Collection<TopicCollection>();

            foreach(FileItem file in contentLayoutFiles)
                topics.Add(new TopicCollection(file));
        }
Пример #4
0
        public ImportForm(IDatabaseManager databaseManager)
        {
            this.InitializeComponent();
            this.InitializeListViewEnhancements();
            this.listViewWorker = this.InitializeListViewWorker();
            this.processWorker  = this.InitializeProcessWorker();

            this.files           = new FileItemCollection();
            this.options         = new FileOptions();
            this.databaseManager = databaseManager;

            this.isValidDataTable = false;
        }
Пример #5
0
        //=====================================================================

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="project">The project from which to load the settings</param>
        public ConceptualContentSettings(SandcastleProject project)
        {
            imageFiles         = new ImageReferenceCollection(project);
            codeSnippetFiles   = new FileItemCollection(project, BuildAction.CodeSnippets);
            tokenFiles         = new FileItemCollection(project, BuildAction.Tokens);
            contentLayoutFiles = new FileItemCollection(project, BuildAction.ContentLayout);
            topics             = new Collection <TopicCollection>();

            foreach (FileItem file in contentLayoutFiles)
            {
                topics.Add(new TopicCollection(file));
            }
        }
Пример #6
0
 public PackageClass()
 {
   Groups = new GroupItemCollection();
   Sections = new SectionItemCollection();
   GeneralInfo = new GeneralInfoItem();
   UniqueFileList = new FileItemCollection();
   Version = "2.0";
   ZipProvider = new ZipProviderClass();
   UnInstallInfo = new UnInstallInfoCollection();
   Dependencies = new DependencyItemCollection();
   PluginDependencies = new PluginDependencyItemCollection();
   ProjectSettings = new ProjectSettings();
   Silent = false;
   IsHiden = false;
   Parent = null;
 }
Пример #7
0
        //=====================================================================

        /// <summary>
        /// This is called to copy the additional content files to the help format content folders
        /// </summary>
        private void CopyAdditionalContent()
        {
            FileItemCollection contentItems;
            string             projectPath, source, filename, dirName;

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

            if (!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf))
            {
                // Plug-ins 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))
                {
                    this.ReportProgress("No additional content to copy");
                }
                else
                {
                    // 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));

                        this.EnsureOutputFoldersExist(dirName);

                        foreach (string baseFolder in this.HelpFormatOutputFolders)
                        {
                            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);
                        }
                    }
                }

                this.ExecutePlugIns(ExecutionBehaviors.After);
            }
        }
Пример #8
0
        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            if (!CanEnterState(State.Working))
            {
                return;
            }

            var configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

            configuration.AppSettings.Settings["imageFinder.dir1"].Value = txtDir1.Text.Trim();
            configuration.AppSettings.Settings["imageFinder.dir2"].Value = txtDir2.Text.Trim();
            configuration.AppSettings.Settings["imageFinder.dir3"].Value = txtDir3.Text.Trim();
            configuration.AppSettings.Settings["imageFinder.dir4"].Value = txtDir4.Text.Trim();
            configuration.AppSettings.Settings["imageFinder.dir5"].Value = txtDir5.Text.Trim();
            configuration.Save();

            var dirs = new List <string>();

            AddValidDir(dirs, txtDir1);
            AddValidDir(dirs, txtDir2);
            AddValidDir(dirs, txtDir3);
            AddValidDir(dirs, txtDir4);
            AddValidDir(dirs, txtDir5);
            if (dirs.Count > 0)
            {
                if (_files != null)
                {
                    _files.SelectionChanged -= files_SelectionChanged;
                }

                _files = new FileItemCollection();
                _files.SelectionChanged += files_SelectionChanged;
                fileList.ItemsSource     = _files;
                _task = new FinderTask(this, dirs.ToArray());
                _task.Start();
            }
            else
            {
                MessageBox.Show("请输入目录");
            }
        }
Пример #9
0
        /// <summary>
        /// Refresh the currently displayed entity information
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The event arguments</param>
        private void tsbRefresh_Click(object sender, EventArgs e)
        {
            switch ((EntityType)cboContentType.SelectedIndex)
            {
            case EntityType.Tokens:
                tokenFiles = null;
                break;

            case EntityType.Images:
                images = null;
                break;

            case EntityType.CodeSnippets:
                codeSnippetFiles = null;
                break;

            default:
                codeEntities = null;
                break;
            }

            tvEntities.Nodes.Clear();
            this.cboContentType_SelectedIndexChanged(sender, e);
        }
Пример #10
0
        //=====================================================================

        /// <summary>
        /// This loads the tree view with table of contents file entries from the project
        /// </summary>
        private List <EntityReference> LoadTableOfContentsInfo()
        {
            FileItemCollection      contentLayoutFiles, siteMapFiles;
            List <ITableOfContents> tocFiles;
            TopicCollection         contentLayout;
            TocEntryCollection      siteMap, mergedToc;
            EntityReference         er;
            bool hasSelectedItem = false;

            if (tableOfContents != null)
            {
                return(tableOfContents);
            }

            tableOfContents = new List <EntityReference>();

            // Get content from open file editors
            var args = new FileContentNeededEventArgs(FileContentNeededEvent, this);

            base.RaiseEvent(args);

            try
            {
                // Get the content layout and site map files
                currentProject.EnsureProjectIsCurrent(false);

                contentLayoutFiles = new FileItemCollection(currentProject, BuildAction.ContentLayout);
                siteMapFiles       = new FileItemCollection(currentProject, BuildAction.SiteMap);
                tocFiles           = new List <ITableOfContents>();

                // Add the conceptual content layout files
                foreach (FileItem file in contentLayoutFiles)
                {
                    // If open in an editor, use the edited values
                    if (!args.ContentLayoutFiles.TryGetValue(file.FullPath, out contentLayout))
                    {
                        contentLayout = new TopicCollection(file);
                        contentLayout.Load();
                    }

                    tocFiles.Add(contentLayout);
                }

                // Load all site maps and add them to the list
                foreach (FileItem fileItem in siteMapFiles)
                {
                    // If open in an editor, use the edited values
                    if (!args.SiteMapFiles.TryGetValue(fileItem.FullPath, out siteMap))
                    {
                        siteMap = new TocEntryCollection(fileItem);
                        siteMap.Load();
                    }

                    tocFiles.Add(siteMap);
                }

                // Sort the files
                tocFiles.Sort((x, y) =>
                {
                    FileItem fx = x.ContentLayoutFile, fy = y.ContentLayoutFile;

                    if (fx.SortOrder < fy.SortOrder)
                    {
                        return(-1);
                    }

                    if (fx.SortOrder > fy.SortOrder)
                    {
                        return(1);
                    }

                    return(String.Compare(fx.Name, fy.Name, StringComparison.OrdinalIgnoreCase));
                });

                // Create the merged TOC.  For the purpose of adding links, we'll include everything
                // even topics marked as invisible.
                mergedToc = new TocEntryCollection();

                foreach (ITableOfContents file in tocFiles)
                {
                    file.GenerateTableOfContents(mergedToc, currentProject, true);
                }

                // Convert the TOC info to entity references
                foreach (var t in mergedToc)
                {
                    er = new EntityReference
                    {
                        EntityType = EntityType.TocEntry,
                        Id         = t.Id,
                        Label      = (t.Title ?? t.Id ?? "(No title)"),
                        ToolTip    = String.Format(CultureInfo.CurrentCulture, "ID: {0}\nFile: {1}",
                                                   (t.Id ?? t.Title ?? "(No ID)"), t.SourceFile),
                        Tag        = t,
                        IsExpanded = t.IsExpanded,
                        IsSelected = (t.IsSelected && !hasSelectedItem)
                    };

                    // Only use the first selected item
                    if (er.IsSelected)
                    {
                        hasSelectedItem = true;
                    }

                    tableOfContents.Add(er);

                    if (t.Children.Count != 0)
                    {
                        hasSelectedItem = this.AddChildTocEntries(t, er, hasSelectedItem);
                    }
                }
            }
            catch (Exception ex)
            {
                tableOfContents.Add(new EntityReference
                {
                    EntityType = EntityType.File,
                    Label      = "Unable to load TOC info: " + ex.Message,
                    ToolTip    = "Error"
                });
            }

            if (!hasSelectedItem && tableOfContents.Count != 0)
            {
                tableOfContents[0].IsSelected = true;
            }

            return(tableOfContents);
        }
Пример #11
0
        //=====================================================================

        /// <summary>
        /// This loads the tree view with token file entries from the project
        /// </summary>
        private List <EntityReference> LoadTokenInfo()
        {
            FileItemCollection tokenFiles;
            EntityReference    tokenFileEntity = null;
            TokenCollection    tokenColl;

            if (tokens != null)
            {
                return(tokens);
            }

            tokens = new List <EntityReference>();

            currentProject.EnsureProjectIsCurrent(false);
            tokenFiles = new FileItemCollection(currentProject, BuildAction.Tokens);

            // Get content from open file editors
            var args = new FileContentNeededEventArgs(FileContentNeededEvent, this);

            base.RaiseEvent(args);

            foreach (FileItem tokenFile in tokenFiles)
            {
                try
                {
                    if (File.Exists(tokenFile.FullPath))
                    {
                        tokenFileEntity = new EntityReference
                        {
                            EntityType = EntityType.File,
                            Id         = tokenFile.FullPath,
                            Label      = Path.GetFileName(tokenFile.FullPath),
                            ToolTip    = tokenFile.FullPath
                        };

                        tokens.Add(tokenFileEntity);

                        // If open in an editor, use the edited values
                        if (!args.TokenFiles.TryGetValue(tokenFile.FullPath, out tokenColl))
                        {
                            tokenColl = new TokenCollection(tokenFile.FullPath);
                            tokenColl.Load();
                        }

                        foreach (Token t in tokenColl)
                        {
                            tokenFileEntity.SubEntities.Add(new EntityReference
                            {
                                EntityType = EntityType.Token,
                                Id         = t.TokenName,
                                Label      = t.TokenName,
                                ToolTip    = t.TokenName,
                                Tag        = t
                            });
                        }
                    }

                    tokenFileEntity = null;
                }
                catch (Exception ex)
                {
                    if (tokenFileEntity == null)
                    {
                        tokens.Add(new EntityReference
                        {
                            EntityType = EntityType.File,
                            Label      = "Unable to load file '" + tokenFile.FullPath +
                                         "'.  Reason: " + ex.Message,
                            ToolTip = "Error"
                        });
                    }
                    else
                    {
                        tokens.Add(new EntityReference
                        {
                            EntityType = EntityType.File,
                            Label      = "Unable to load file: " + ex.Message,
                            ToolTip    = "Error"
                        });
                    }
                }
            }

            if (tokens.Count != 0)
            {
                tokens[0].IsSelected = true;

                if (tokens[0].SubEntities.Count != 0)
                {
                    tokens[0].IsExpanded = true;
                }
            }

            return(tokens);
        }
        /// <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 wilcard content items, the folders are
        /// copied recursively.</remarks>
        protected 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 = sharedBuilderContent = styleContent = null;

            this.ExecutePlugIns(ExecutionBehaviors.After);
        }
        /// <summary>
        /// This is used to merge the conceptual content table of contents with
        /// any additional content table of contents information.
        /// </summary>
        /// <remarks>This will also split the table of contents if any entry
        /// has the "split" option.  A split in the conceptual content will
        /// take precedence as additional content is always appended to
        /// the end of the conceptual content.  Likewise, a default topic in
        /// the conceptual content will take precedence over a default topic
        /// in the additional content.</remarks>
        private void MergeConceptualAndAdditionalContentTocInfo()
        {
            FileItemCollection      siteMapFiles;
            List <ITableOfContents> tocFiles;
            TocEntryCollection      siteMap, mergedToc;
            TocEntry tocEntry;

            this.ReportProgress(BuildStep.MergeTablesOfContents,
                                "Merging conceptual and additional tables of contents...");

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

            this.ExecutePlugIns(ExecutionBehaviors.Before);

            // Add the conceptual content layout files
            tocFiles = new List <ITableOfContents>();

            foreach (TopicCollection topics in conceptualContent.Topics)
            {
                tocFiles.Add(topics);
            }

            // Load all site maps and add them to the list
            siteMapFiles = new FileItemCollection(project, BuildAction.SiteMap);

            foreach (FileItem fileItem in siteMapFiles)
            {
                this.ReportProgress("    Loading site map '{0}'", fileItem.FullPath);
                siteMap = new TocEntryCollection(fileItem);
                siteMap.Load();

                // Merge destination file information into the site map
                foreach (TocEntry site in siteMap)
                {
                    this.MergeTocInfo(site);
                }

                tocFiles.Add(siteMap);
            }

            // Sort the files
            tocFiles.Sort((x, y) =>
            {
                FileItem fx = x.ContentLayoutFile, fy = y.ContentLayoutFile;

                if (fx.SortOrder < fy.SortOrder)
                {
                    return(-1);
                }

                if (fx.SortOrder > fy.SortOrder)
                {
                    return(1);
                }

                return(String.Compare(fx.Name, fy.Name, StringComparison.OrdinalIgnoreCase));
            });

            // Create the merged TOC.  Invisible items are excluded.
            mergedToc = new TocEntryCollection();

            foreach (ITableOfContents file in tocFiles)
            {
                file.GenerateTableOfContents(mergedToc, project, false);
            }

            // If there were no site maps, add items copied from the project.
            // Empty container nodes are ignored.
            if (siteMapFiles.Count == 0 && toc != null && toc.Count != 0)
            {
                foreach (TocEntry t in toc)
                {
                    if (t.DestinationFile != null || t.Children.Count != 0)
                    {
                        mergedToc.Add(t);
                    }
                }
            }

            toc = mergedToc;

            if (toc.Count != 0)
            {
                // Look for the default topic
                tocEntry = toc.FindDefaultTopic();

                if (tocEntry != null)
                {
                    defaultTopic = tocEntry.DestinationFile;
                }
            }

            this.ExecutePlugIns(ExecutionBehaviors.After);
        }
        /// <summary>
        /// This is used to merge the conceptual content table of contents with
        /// any additional content table of contents information.
        /// </summary>
        /// <remarks>This will also split the table of contents if any entry
        /// has the "split" option.  A split in the conceptual content will
        /// take precedence as additional content is always appended to
        /// the end of the conceptual content.  Likewise, a default topic in
        /// the conceptual content will take precedence over a default topic
        /// in the additional content.</remarks>
        private void MergeConceptualAndAdditionalContentTocInfo()
        {
            FileItemCollection siteMapFiles;
            List<ITableOfContents> tocFiles;
            TocEntryCollection siteMap, mergedToc;
            TocEntry tocEntry;

            this.ReportProgress(BuildStep.MergeTablesOfContents,
                "Merging conceptual and additional tables of contents...");

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

            this.ExecutePlugIns(ExecutionBehaviors.Before);

            // Add the conceptual content layout files
            tocFiles = new List<ITableOfContents>();

            foreach(TopicCollection topics in conceptualContent.Topics)
                tocFiles.Add(topics);

            // Load all site maps and add them to the list
            siteMapFiles = new FileItemCollection(project, BuildAction.SiteMap);

            foreach(FileItem fileItem in siteMapFiles)
            {
                this.ReportProgress("    Loading site map '{0}'", fileItem.FullPath);
                siteMap = new TocEntryCollection(fileItem);
                siteMap.Load();

                // Merge destination file information into the site map
                foreach(TocEntry site in siteMap)
                    this.MergeTocInfo(site);

                tocFiles.Add(siteMap);
            }

            // Sort the files
            tocFiles.Sort((x, y) =>
            {
                FileItem fx = x.ContentLayoutFile, fy = y.ContentLayoutFile;

                if(fx.SortOrder < fy.SortOrder)
                    return -1;

                if(fx.SortOrder > fy.SortOrder)
                    return 1;

                return String.Compare(fx.Name, fy.Name, StringComparison.OrdinalIgnoreCase);
            });

            // Create the merged TOC
            mergedToc = new TocEntryCollection();

            foreach(ITableOfContents file in tocFiles)
                file.GenerateTableOfContents(mergedToc, project);

            // If there were no site maps, add items copied from the project.
            // Empty container nodes are ignored.
            if(siteMapFiles.Count == 0 && toc != null && toc.Count != 0)
                foreach(TocEntry t in toc)
                    if(t.DestinationFile != null || t.Children.Count != 0)
                        mergedToc.Add(t);

            toc = mergedToc;

            if(toc.Count != 0)
            {
                // Look for the default topic
                tocEntry = toc.FindDefaultTopic();

                if(tocEntry != null)
                    defaultTopic = tocEntry.DestinationFile;
            }

            this.ExecutePlugIns(ExecutionBehaviors.After);
        }
        //=====================================================================
        /// <summary>
        /// This loads the tree view with token file entries from the project
        /// </summary>
        private void LoadTokenInfo()
        {
            TreeNode rootNode = null, node;
            TokenCollection tokens;

            tvEntities.ImageList = ilImages;
            tvEntities.Nodes.Clear();

            if(tokenFiles == null)
                tokenFiles = new FileItemCollection(currentProject,
                    BuildAction.Tokens);

            foreach(FileItem tokenFile in tokenFiles)
                try
                {
                    if(File.Exists(tokenFile.FullPath))
                    {
                        rootNode = tvEntities.Nodes.Add(Path.GetFileName(
                            tokenFile.FullPath));
                        rootNode.ImageIndex = rootNode.SelectedImageIndex =
                            (int)EntityType.CodeEntities;

                        tokens = new TokenCollection(tokenFile);
                        tokens.Load();

                        foreach(Token t in tokens)
                        {
                            node = rootNode.Nodes.Add(t.TokenName);
                            node.Name = t.TokenName;
                            node.Tag = t;
                            node.ImageIndex = node.SelectedImageIndex =
                                (int)EntityType.Tokens;
                        }
                    }

                    rootNode = null;
                }
                catch(Exception ex)
                {
                    if(rootNode == null)
                        tvEntities.Nodes.Add("Unable to load file '" +
                            tokenFile.FullPath + "'.  Reason: " + ex.Message);
                    else
                        rootNode.Nodes.Add("Unable to load file: " + ex.Message);
                }

            txtFindName.Enabled = true;
            tvEntities.Enabled = true;
            tvEntities.ExpandAll();
        }
Пример #16
0
        //=====================================================================

        /// <summary>
        /// This loads the tree view with code snippet file entries from the project
        /// </summary>
        private List <EntityReference> LoadCodeSnippetInfo()
        {
            FileItemCollection codeSnippetFiles;
            EntityReference    snippetFileEntity = null;
            XPathDocument      snippets;
            XPathNavigator     navSnippets;
            CodeReference      cr;

            if (codeSnippets != null)
            {
                return(codeSnippets);
            }

            codeSnippets = new List <EntityReference>();

            currentProject.EnsureProjectIsCurrent(false);
            codeSnippetFiles = new FileItemCollection(currentProject, BuildAction.CodeSnippets);

            foreach (FileItem snippetFile in codeSnippetFiles)
            {
                try
                {
                    if (File.Exists(snippetFile.FullPath))
                    {
                        snippetFileEntity = new EntityReference
                        {
                            EntityType = EntityType.File,
                            Id         = snippetFile.FullPath,
                            Label      = Path.GetFileName(snippetFile.FullPath),
                            ToolTip    = snippetFile.FullPath
                        };

                        codeSnippets.Add(snippetFileEntity);

                        snippets    = new XPathDocument(snippetFile.FullPath);
                        navSnippets = snippets.CreateNavigator();

                        foreach (XPathNavigator nav in navSnippets.Select("examples/item/@id"))
                        {
                            cr = new CodeReference(nav.Value);

                            snippetFileEntity.SubEntities.Add(new EntityReference
                            {
                                EntityType = EntityType.CodeSnippet,
                                Id         = cr.Id,
                                Label      = cr.Id,
                                ToolTip    = cr.Id,
                                Tag        = cr
                            });
                        }
                    }

                    snippetFileEntity = null;
                }
                catch (Exception ex)
                {
                    if (snippetFileEntity == null)
                    {
                        codeSnippets.Add(new EntityReference
                        {
                            EntityType = EntityType.File,
                            Label      = "Unable to load file '" + snippetFile.FullPath +
                                         "'.  Reason: " + ex.Message,
                            ToolTip = "Error"
                        });
                    }
                    else
                    {
                        codeSnippets.Add(new EntityReference
                        {
                            EntityType = EntityType.File,
                            Label      = "Unable to load file: " + ex.Message,
                            ToolTip    = "Error"
                        });
                    }
                }
            }

            if (codeSnippets.Count != 0)
            {
                codeSnippets[0].IsSelected = true;

                if (codeSnippets[0].SubEntities.Count != 0)
                {
                    codeSnippets[0].IsExpanded = true;
                }
            }

            return(codeSnippets);
        }
        //=====================================================================

        /// <summary>
        /// This loads the tree view with code snippet file entries from the project
        /// </summary>
        private List<EntityReference> LoadCodeSnippetInfo()
        {
            FileItemCollection codeSnippetFiles;
            EntityReference snippetFileEntity = null;
            XPathDocument snippets;
            XPathNavigator navSnippets;
            CodeReference cr;

            if(codeSnippets != null)
                return codeSnippets;

            codeSnippets = new List<EntityReference>();

            currentProject.EnsureProjectIsCurrent(false);
            codeSnippetFiles = new FileItemCollection(currentProject, BuildAction.CodeSnippets);

            foreach(FileItem snippetFile in codeSnippetFiles)
                try
                {
                    if(File.Exists(snippetFile.FullPath))
                    {
                        snippetFileEntity = new EntityReference
                        {
                            EntityType = EntityType.File,
                            Id = snippetFile.FullPath,
                            Label = Path.GetFileName(snippetFile.FullPath),
                            ToolTip = snippetFile.FullPath
                        };

                        codeSnippets.Add(snippetFileEntity);

                        snippets = new XPathDocument(snippetFile.FullPath);
                        navSnippets = snippets.CreateNavigator();

                        foreach(XPathNavigator nav in navSnippets.Select("examples/item/@id"))
                        {
                            cr = new CodeReference(nav.Value);

                            snippetFileEntity.SubEntities.Add(new EntityReference
                            {
                                EntityType = EntityType.CodeSnippet,
                                Id = cr.Id,
                                Label = cr.Id,
                                ToolTip = cr.Id,
                                Tag = cr
                            });
                        }
                    }

                    snippetFileEntity = null;
                }
                catch(Exception ex)
                {
                    if(snippetFileEntity == null)
                        codeSnippets.Add(new EntityReference
                        {
                            EntityType = EntityType.File,
                            Label = "Unable to load file '" + snippetFile.FullPath +
                                "'.  Reason: " + ex.Message,
                            ToolTip = "Error"
                        });
                    else
                        codeSnippets.Add(new EntityReference
                        {
                            EntityType = EntityType.File,
                            Label = "Unable to load file: " + ex.Message,
                            ToolTip = "Error"
                        });
                }

            if(codeSnippets.Count != 0)
            {
                codeSnippets[0].IsSelected = true;

                if(codeSnippets[0].SubEntities.Count != 0)
                    codeSnippets[0].IsExpanded = true;
            }

            return codeSnippets;
        }
        //=====================================================================

        /// <summary>
        /// This loads the tree view with table of contents file entries from the project
        /// </summary>
        private List<EntityReference> LoadTableOfContentsInfo()
        {
            FileItemCollection contentLayoutFiles, siteMapFiles;
            List<ITableOfContents> tocFiles;
            TopicCollection contentLayout;
            TocEntryCollection siteMap, mergedToc;
            EntityReference er;
            bool hasSelectedItem = false;

            if(tableOfContents != null)
                return tableOfContents;

            tableOfContents = new List<EntityReference>();

            // Get content from open file editors
            var args = new FileContentNeededEventArgs(FileContentNeededEvent, this);
            base.RaiseEvent(args);

            try
            {
                // Get the content layout and site map files
                currentProject.EnsureProjectIsCurrent(false);

                contentLayoutFiles = new FileItemCollection(currentProject, BuildAction.ContentLayout);
                siteMapFiles = new FileItemCollection(currentProject, BuildAction.SiteMap);
                tocFiles = new List<ITableOfContents>();

                // Add the conceptual content layout files
                foreach(FileItem file in contentLayoutFiles)
                {
                    // If open in an editor, use the edited values
                    if(!args.ContentLayoutFiles.TryGetValue(file.FullPath, out contentLayout))
                    {
                        contentLayout = new TopicCollection(file);
                        contentLayout.Load();
                    }

                    tocFiles.Add(contentLayout);
                }

                // Load all site maps and add them to the list
                foreach(FileItem fileItem in siteMapFiles)
                {
                    // If open in an editor, use the edited values
                    if(!args.SiteMapFiles.TryGetValue(fileItem.FullPath, out siteMap))
                    {
                        siteMap = new TocEntryCollection(fileItem);
                        siteMap.Load();
                    }

                    tocFiles.Add(siteMap);
                }

                // Sort the files
                tocFiles.Sort((x, y) =>
                {
                    FileItem fx = x.ContentLayoutFile, fy = y.ContentLayoutFile;

                    if(fx.SortOrder < fy.SortOrder)
                        return -1;

                    if(fx.SortOrder > fy.SortOrder)
                        return 1;

                    return String.Compare(fx.Name, fy.Name, StringComparison.OrdinalIgnoreCase);
                });

                // Create the merged TOC.  For the purpose of adding links, we'll include everything
                // even topics marked as invisible.
                mergedToc = new TocEntryCollection();

                foreach(ITableOfContents file in tocFiles)
                    file.GenerateTableOfContents(mergedToc, currentProject, true);

                // Convert the TOC info to entity references
                foreach(var t in mergedToc)
                {
                    er = new EntityReference
                    {
                        EntityType = EntityType.TocEntry,
                        Id = t.Id,
                        Label = (t.Title ?? t.Id ?? "(No title)"),
                        ToolTip = String.Format(CultureInfo.CurrentCulture, "ID: {0}\nFile: {1}",
                            (t.Id ?? t.Title ?? "(No ID)"), t.SourceFile),
                        Tag = t,
                        IsExpanded = t.IsExpanded,
                        IsSelected = (t.IsSelected && !hasSelectedItem)
                    };

                    // Only use the first selected item
                    if(er.IsSelected)
                        hasSelectedItem = true;

                    tableOfContents.Add(er);

                    if(t.Children.Count != 0)
                        hasSelectedItem = this.AddChildTocEntries(t, er, hasSelectedItem);
                }
            }
            catch(Exception ex)
            {
                tableOfContents.Add(new EntityReference
                {
                    EntityType = EntityType.File,
                    Label = "Unable to load TOC info: " + ex.Message,
                    ToolTip = "Error"
                });
            }

            if(!hasSelectedItem && tableOfContents.Count != 0)
                tableOfContents[0].IsSelected = true;

            return tableOfContents;
        }
        //=====================================================================

        /// <summary>
        /// This loads the tree view with token file entries from the project
        /// </summary>
        private List<EntityReference> LoadTokenInfo()
        {
            FileItemCollection tokenFiles;
            EntityReference tokenFileEntity = null;
            TokenCollection tokenColl;

            if(tokens != null)
                return tokens;

            tokens = new List<EntityReference>();

            currentProject.EnsureProjectIsCurrent(false);
            tokenFiles = new FileItemCollection(currentProject, BuildAction.Tokens);

            // Get content from open file editors
            var args = new FileContentNeededEventArgs(FileContentNeededEvent, this);
            base.RaiseEvent(args);

            foreach(FileItem tokenFile in tokenFiles)
                try
                {
                    if(File.Exists(tokenFile.FullPath))
                    {
                        tokenFileEntity = new EntityReference
                        {
                            EntityType = EntityType.File,
                            Id = tokenFile.FullPath,
                            Label = Path.GetFileName(tokenFile.FullPath),
                            ToolTip = tokenFile.FullPath
                        };

                        tokens.Add(tokenFileEntity);

                        // If open in an editor, use the edited values
                        if(!args.TokenFiles.TryGetValue(tokenFile.FullPath, out tokenColl))
                        {
                            tokenColl = new TokenCollection(tokenFile.FullPath);
                            tokenColl.Load();
                        }

                        foreach(Token t in tokenColl)
                            tokenFileEntity.SubEntities.Add(new EntityReference
                            {
                                EntityType = EntityType.Token,
                                Id = t.TokenName,
                                Label = t.TokenName,
                                ToolTip = t.TokenName,
                                Tag = t
                            });
                    }

                    tokenFileEntity = null;
                }
                catch(Exception ex)
                {
                    if(tokenFileEntity == null)
                        tokens.Add(new EntityReference
                        {
                            EntityType = EntityType.File,
                            Label = "Unable to load file '" + tokenFile.FullPath +
                                "'.  Reason: " + ex.Message,
                            ToolTip = "Error"
                        });
                    else
                        tokens.Add(new EntityReference
                        {
                            EntityType = EntityType.File,
                            Label = "Unable to load file: " + ex.Message,
                            ToolTip = "Error"
                        });
                }

            if(tokens.Count != 0)
            {
                tokens[0].IsSelected = true;

                if(tokens[0].SubEntities.Count != 0)
                    tokens[0].IsExpanded = true;
            }

            return tokens;
        }
Пример #20
0
        /// <summary>
        /// Replace a field tag with a value from the project
        /// </summary>
        /// <param name="match">The match that was found</param>
        /// <returns>The string to use as the replacement</returns>
        private string OnFieldMatch(Match match)
        {
            ProjectProperty buildProp;
            FileItemCollection fileItems;
            StringBuilder sb;
            string replaceWith, fieldName;
            string[] parts;

            fieldName = match.Groups["Field"].Value.ToLowerInvariant();

            switch(fieldName)
            {
                case "appdatafolder":
                    // This folder should exist if used
                    replaceWith = FolderPath.TerminatePath(Path.Combine(
                        Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
                        Constants.ProgramDataFolder));
                    break;

                case "localdatafolder":
                    // This folder may not exist and we may need to create it
                    replaceWith = FolderPath.TerminatePath(Path.Combine(
                        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                        Constants.ProgramDataFolder));

                    if(!Directory.Exists(replaceWith))
                        Directory.CreateDirectory(replaceWith);
                    break;

                case "shfbfolder":
                    replaceWith = ComponentUtilities.ToolsFolder;
                    break;

                case "componentsfolder":
                    replaceWith = ComponentUtilities.ComponentsFolder;
                    break;

                case "projectfolder":
                    replaceWith = Path.GetDirectoryName(originalProjectName);

                    if(replaceWith.Length == 0)
                        replaceWith = Directory.GetCurrentDirectory();

                    replaceWith += @"\";
                    break;

                case "htmlencprojectfolder":
                    replaceWith = HttpUtility.HtmlEncode(Path.GetDirectoryName(originalProjectName));

                    if(replaceWith.Length == 0)
                        replaceWith = HttpUtility.HtmlEncode(Directory.GetCurrentDirectory());

                    replaceWith += @"\";
                    break;

                case "outputfolder":
                    replaceWith = outputFolder;
                    break;

                case "htmlencoutputfolder":
                    replaceWith = HttpUtility.HtmlEncode(outputFolder);
                    break;

                case "workingfolder":
                    replaceWith = workingFolder;
                    break;

                case "htmlencworkingfolder":
                    replaceWith = HttpUtility.HtmlEncode(workingFolder);
                    break;

                case "presentationpath":
                    replaceWith = FolderPath.TerminatePath(this.PresentationStyleFolder);
                    break;

                case "presentationstyle":
                    replaceWith = project.PresentationStyle;
                    break;

                case "docmodeltransformation":
                    replaceWith = presentationStyle.ResolvePath(
                        presentationStyle.DocumentModelTransformation.TransformationFilename);
                    break;

                case "docmodeltransformationparameters":
                    replaceWith = String.Join(";", presentationStyle.DocumentModelTransformation.Select(p =>
                        String.Format(CultureInfo.InvariantCulture, "{0}={1}", p.Key, p.Value)));
                    break;

                case "namingmethod":
                    replaceWith = project.NamingMethod.ToString();
                    break;

                case "toctransformation":
                    replaceWith = presentationStyle.ResolvePath(
                        presentationStyle.IntermediateTocTransformation.TransformationFilename);
                    break;

                case "toctransformparameters":
                    replaceWith = String.Join(";", presentationStyle.IntermediateTocTransformation.Select(p =>
                        String.Format(CultureInfo.InvariantCulture, "{0}={1}", p.Key, p.Value)));
                    break;

                case "hhcpath":
                    replaceWith = hhcFolder;
                    break;

                case "hxcomppath":
                    replaceWith = hxcompFolder;
                    break;

                case "disablecodeblockcomponent":
                    replaceWith = project.DisableCodeBlockComponent.ToString().ToLowerInvariant();
                    break;

                case "htmlhelpname":
                    replaceWith = project.HtmlHelpName;
                    break;

                case "htmlenchelpname":
                    replaceWith = HttpUtility.HtmlEncode(project.HtmlHelpName);
                    break;

                case "helpviewersetupname":
                    // Help viewer setup names cannot contain periods so we'll replace them with underscores
                    replaceWith = HttpUtility.HtmlEncode(project.HtmlHelpName.Replace('.', '_'));
                    break;

                case "frameworkcommentlist":
                case "importframeworkcommentlist":
                    replaceWith = this.FrameworkCommentList(fieldName);
                    break;

                case "commentfilelist":
                    replaceWith = commentsFiles.CommentFileList(workingFolder, false);
                    break;

                case "inheritedcommentfilelist":
                    replaceWith = commentsFiles.CommentFileList(workingFolder, true);
                    break;

                case "helptitle":
                    replaceWith = project.HelpTitle;
                    break;

                case "htmlenchelptitle":
                    replaceWith = HttpUtility.HtmlEncode(project.HelpTitle);
                    break;

                case "scripthelptitle":
                    // This is used when the title is passed as a parameter
                    // to a JavaScript function.
                    replaceWith = HttpUtility.HtmlEncode(project.HelpTitle).Replace("'", @"\'");
                    break;

                case "urlenchelptitle": // Just replace &, <, >, and " for  now
                    replaceWith = project.HelpTitle.Replace("&", "%26").Replace(
                        "<", "%3C").Replace(">", "%3E").Replace("\"", "%22");
                    break;

                case "rootnamespacetitle":
                    replaceWith = project.RootNamespaceTitle;

                    if(replaceWith.Length == 0)
                        replaceWith = "<include item=\"rootTopicTitleLocalized\"/>";
                    break;

                case "namespacegrouping":
                    if(project.NamespaceGrouping && presentationStyle.SupportsNamespaceGrouping)
                        replaceWith = "true";
                    else
                    {
                        replaceWith = "false";

                        if(project.NamespaceGrouping)
                            this.ReportWarning("BE0027", "Namespace grouping was requested but the selected " +
                                "presentation style does not support it.  Option ignored.");
                    }
                    break;

                case "codesnippetgrouping":
                    replaceWith = presentationStyle.SupportsCodeSnippetGrouping.ToString().ToLowerInvariant();
                    break;

                case "maximumgroupparts":
                    replaceWith = project.MaximumGroupParts.ToString(CultureInfo.InvariantCulture);
                    break;

                case "binarytoc":
                    replaceWith = project.BinaryTOC ? "Yes" : "No";
                    break;

                case "windowoptions":
                    // Currently, we use a default set of options and only
                    // allow showing or hiding the Favorites tab.
                    replaceWith = (project.IncludeFavorites) ? "0x63520" : "0x62520";
                    break;

                case "langid":
                    replaceWith = language.LCID.ToString(CultureInfo.InvariantCulture);
                    break;

                case "language":
                    replaceWith = String.Format(CultureInfo.InvariantCulture, "0x{0:X} {1}", language.LCID,
                        language.NativeName);
                    break;

                case "resourceitemsfolder":
                    replaceWith = FolderPath.TerminatePath(Path.Combine(
                        presentationStyle.ResolvePath(presentationStyle.ResourceItemsPath), languageFolder));
                    break;

                case "locale":
                    replaceWith = language.Name.ToLowerInvariant();
                    break;

                case "localemixedcase":
                    replaceWith = language.Name;
                    break;

                case "copyright":
                    // Include copyright info if there is a copyright HREF or copyright text
                    if(project.CopyrightHref.Length != 0 || project.CopyrightText.Length != 0)
                        replaceWith = "<include item=\"copyright\"/>";
                    else
                        replaceWith = String.Empty;
                    break;

                case "copyrightinfo":
                    if(project.CopyrightHref.Length == 0 && project.CopyrightText.Length == 0)
                        replaceWith = String.Empty;
                    else
                        if(project.CopyrightHref.Length == 0)
                            replaceWith = project.DecodedCopyrightText;
                        else
                            if(project.CopyrightText.Length == 0)
                                replaceWith = project.CopyrightHref;
                            else
                                replaceWith = String.Format(CultureInfo.CurrentCulture, "{0} ({1})",
                                    project.DecodedCopyrightText, project.CopyrightHref);
                    break;

                case "htmlenccopyrightinfo":
                    if(project.CopyrightHref.Length == 0 && project.CopyrightText.Length == 0)
                        replaceWith = String.Empty;
                    else
                        if(project.CopyrightHref.Length == 0)
                            replaceWith = "<p>" + HttpUtility.HtmlEncode(project.DecodedCopyrightText) + "</p>";
                        else
                            if(project.CopyrightText.Length == 0)
                                replaceWith = String.Format(CultureInfo.CurrentCulture,
                                    "<p><a href='{0}' target='_blank'>{0}</a></p>",
                                    HttpUtility.HtmlEncode(project.CopyrightHref));
                            else
                                replaceWith = String.Format(CultureInfo.CurrentCulture,
                                    "<p><a href='{0}' target='_blank'>{1}</a></p>",
                                    HttpUtility.HtmlEncode(project.CopyrightHref),
                                    HttpUtility.HtmlEncode(project.DecodedCopyrightText));
                    break;

                case "copyrighthref":
                    replaceWith = project.CopyrightHref;
                    break;

                case "htmlenccopyrighthref":
                    if(project.CopyrightHref.Length == 0)
                        replaceWith = String.Empty;
                    else
                        replaceWith = String.Format(CultureInfo.CurrentCulture,
                            "<a href='{0}' target='_blank'>{0}</a>",
                            HttpUtility.HtmlEncode(project.CopyrightHref));
                    break;

                case "copyrighttext":
                    if(project.CopyrightText.Length == 0)
                        replaceWith = String.Empty;
                    else
                        replaceWith = project.DecodedCopyrightText;
                    break;

                case "htmlenccopyrighttext":
                    if(project.CopyrightText.Length == 0)
                        replaceWith = String.Empty;
                    else
                        replaceWith = HttpUtility.HtmlEncode(project.DecodedCopyrightText);
                    break;

                case "comments":
                    // Include "send comments" line if feedback e-mail address
                    // is specified.
                    if(project.FeedbackEMailAddress.Length != 0)
                        replaceWith = "<include item=\"comments\"/>";
                    else
                        replaceWith = String.Empty;
                    break;

                case "feedbackemailaddress":
                    replaceWith = project.FeedbackEMailAddress;
                    break;

                case "feedbackemaillinktext":
                    replaceWith = project.FeedbackEMailLinkText;
                    break;

                case "urlencfeedbackemailaddress":
                    if(project.FeedbackEMailAddress.Length == 0)
                        replaceWith = String.Empty;
                    else
                        replaceWith = HttpUtility.UrlEncode(project.FeedbackEMailAddress);
                    break;

                case "htmlencfeedbackemailaddress":
                    // If link text is specified, it will be used instead
                    if(project.FeedbackEMailAddress.Length == 0)
                        replaceWith = String.Empty;
                    else
                        if(project.FeedbackEMailLinkText.Length == 0)
                            replaceWith = HttpUtility.HtmlEncode(project.FeedbackEMailAddress);
                        else
                            replaceWith = HttpUtility.HtmlEncode(project.FeedbackEMailLinkText);
                    break;

                case "headertext":
                    replaceWith = project.HeaderText;
                    break;

                case "footertext":
                    replaceWith = project.FooterText;
                    break;

                case "indenthtml":
                    replaceWith = project.IndentHtml.ToString().ToLowerInvariant();
                    break;

                case "preliminary":
                    // Include the "preliminary" warning in the header text if wanted
                    if(project.Preliminary)
                        replaceWith = "<include item=\"preliminary\"/>";
                    else
                        replaceWith = String.Empty;
                    break;

                case "defaulttopic":
                    if(defaultTopic.EndsWith(".topic", StringComparison.OrdinalIgnoreCase))
                        replaceWith = Path.ChangeExtension(defaultTopic, ".html");
                    else
                        replaceWith = defaultTopic;
                    break;

                case "webdefaulttopic":
                    if(defaultTopic.EndsWith(".topic", StringComparison.OrdinalIgnoreCase))
                        replaceWith = Path.ChangeExtension(defaultTopic, ".html").Replace('\\', '/');
                    else
                        replaceWith = defaultTopic.Replace('\\', '/');
                    break;

                case "targetframeworkidentifier":
                    replaceWith = frameworkSettings.Platform;
                    break;

                case "frameworkversion":
                    replaceWith = frameworkSettings.Version.ToString();
                    break;

                case "frameworkversionshort":
                    replaceWith = frameworkSettings.Version.ToString(2);
                    break;

                case "platformversion":     // MRefBuilder legacy platform element support
                    // This specifies the framework version for use by CCI in MRefBuilder.  It is related to
                    // the .NET Framework and is currently capped at 2.0.  Using a higher value causes
                    // MRefBuilder to throw an exception.
                    if(frameworkSettings.Platform != PlatformType.DotNetFramework ||
                      frameworkSettings.Version.Major > 2)
                        replaceWith = "2.0";
                    else
                        replaceWith = frameworkSettings.Version.ToString(2);

                    break;

                case "coreframeworkpath":   // MRefBuilder legacy platform element support
                    replaceWith = frameworkSettings.AssemblyLocations.First(l => l.IsCoreLocation).Path;
                    break;

                case "help1xprojectfiles":
                    replaceWith = this.HelpProjectFileList(String.Format(CultureInfo.InvariantCulture,
                        @"{0}Output\{1}", workingFolder, HelpFileFormats.HtmlHelp1), HelpFileFormats.HtmlHelp1);
                    break;

                case "help2xprojectfiles":
                    replaceWith = this.HelpProjectFileList(String.Format(CultureInfo.InvariantCulture,
                        @"{0}Output\{1}", workingFolder, HelpFileFormats.MSHelp2), HelpFileFormats.MSHelp2);
                    break;

                case "htmlsdklinktype":
                    replaceWith = project.HtmlSdkLinkType.ToString().ToLowerInvariant();
                    break;

                case "mshelp2sdklinktype":
                    replaceWith = project.MSHelp2SdkLinkType.ToString().ToLowerInvariant();
                    break;

                case "mshelpviewersdklinktype":
                    replaceWith = project.MSHelpViewerSdkLinkType.ToString().ToLowerInvariant();
                    break;

                case "websitesdklinktype":
                    replaceWith = project.WebsiteSdkLinkType.ToString().ToLowerInvariant();
                    break;

                case "sdklinktarget":
                    replaceWith = "_" + project.SdkLinkTarget.ToString().ToLowerInvariant();
                    break;

                case "htmltoc":
                    replaceWith = this.GenerateHtmlToc();
                    break;

                case "syntaxfilters":
                    replaceWith = ComponentUtilities.SyntaxFilterGeneratorsFrom(syntaxGenerators,
                        project.SyntaxFilters);
                    break;

                case "syntaxfiltersdropdown":
                    // Note that we can't remove the dropdown box if only a single language is selected as
                    // script still depends on it.
                    replaceWith = ComponentUtilities.SyntaxFilterLanguagesFrom(syntaxGenerators,
                        project.SyntaxFilters);
                    break;

                case "autodocumentconstructors":
                    replaceWith = project.AutoDocumentConstructors.ToString().ToLowerInvariant();
                    break;

                case "autodocumentdisposemethods":
                    replaceWith = project.AutoDocumentDisposeMethods.ToString().ToLowerInvariant();
                    break;

                case "showmissingparams":
                    replaceWith = project.ShowMissingParams.ToString().ToLowerInvariant();
                    break;

                case "showmissingremarks":
                    replaceWith = project.ShowMissingRemarks.ToString().ToLowerInvariant();
                    break;

                case "showmissingreturns":
                    replaceWith = project.ShowMissingReturns.ToString().ToLowerInvariant();
                    break;

                case "showmissingsummaries":
                    replaceWith = project.ShowMissingSummaries.ToString().ToLowerInvariant();
                    break;

                case "showmissingtypeparams":
                    replaceWith = project.ShowMissingTypeParams.ToString().ToLowerInvariant();
                    break;

                case "showmissingvalues":
                    replaceWith = project.ShowMissingValues.ToString().ToLowerInvariant();
                    break;

                case "showmissingnamespaces":
                    replaceWith = project.ShowMissingNamespaces.ToString().ToLowerInvariant();
                    break;

                case "showmissingincludetargets":
                    replaceWith = project.ShowMissingIncludeTargets.ToString().ToLowerInvariant();
                    break;

                case "documentattributes":
                    replaceWith = project.DocumentAttributes.ToString().ToLowerInvariant();
                    break;

                case "documentexplicitinterfaceimplementations":
                    replaceWith = project.DocumentExplicitInterfaceImplementations.ToString().ToLowerInvariant();
                    break;

                case "documentinheritedmembers":
                    replaceWith = project.DocumentInheritedMembers.ToString().ToLowerInvariant();
                    break;

                case "documentinheritedframeworkmembers":
                    replaceWith = project.DocumentInheritedFrameworkMembers.ToString().ToLowerInvariant();
                    break;

                case "documentinheritedframeworkinternalmembers":
                    replaceWith = project.DocumentInheritedFrameworkInternalMembers.ToString().ToLowerInvariant();
                    break;

                case "documentinheritedframeworkprivatemembers":
                    replaceWith = project.DocumentInheritedFrameworkPrivateMembers.ToString().ToLowerInvariant();
                    break;

                case "documentinternals":
                    replaceWith = project.DocumentInternals.ToString().ToLowerInvariant();
                    break;

                case "documentprivates":
                    replaceWith = project.DocumentPrivates.ToString().ToLowerInvariant();
                    break;

                case "documentprivatefields":
                    replaceWith = project.DocumentPrivateFields.ToString().ToLowerInvariant();
                    break;

                case "documentprotected":
                    replaceWith = project.DocumentProtected.ToString().ToLowerInvariant();
                    break;

                case "documentsealedprotected":
                    replaceWith = project.DocumentSealedProtected.ToString().ToLowerInvariant();
                    break;

                case "documentprotectedinternalasprotected":
                    replaceWith = project.DocumentProtectedInternalAsProtected.ToString().ToLowerInvariant();
                    break;

                case "documentnopiatypes":
                    replaceWith = project.DocumentNoPIATypes.ToString().ToLowerInvariant();
                    break;

                case "apifilter":
                    // In a partial build used to get API info for the API filter designer, we won't apply the
                    // filter.
                    if(!this.SuppressApiFilter)
                        replaceWith = apiFilter.ToString();
                    else
                        replaceWith = String.Empty;
                    break;

                case "builddate":
                    // Apply a format specifier?
                    if(match.Groups["Format"].Value.Length != 0)
                        replaceWith = String.Format(CultureInfo.CurrentCulture,
                            "{0:" + match.Groups["Format"].Value + "}", DateTime.Now);
                    else
                        replaceWith = DateTime.Now.ToString(CultureInfo.CurrentCulture);
                    break;

                case "projectnodename":
                    replaceWith = "R:Project_" + project.HtmlHelpName.Replace(" ", "_");
                    break;

                case "rootnamespacecontainer":
                    replaceWith = project.RootNamespaceContainer.ToString().ToLowerInvariant();
                    break;

                case "projectnodeidoptional":
                    if(project.RootNamespaceContainer)
                        replaceWith = "Project_" + project.HtmlHelpName.Replace(" ", "_").Replace("&", "_");
                    else
                        replaceWith = String.Empty;
                    break;

                case "projectnodeidrequired":
                    replaceWith = "Project_" + project.HtmlHelpName.Replace(" ", "_").Replace("&", "_");
                    break;

                case "help1folder":
                    if((project.HelpFileFormat & HelpFileFormats.HtmlHelp1) != 0)
                        replaceWith = @"Output\" + HelpFileFormats.HtmlHelp1.ToString();
                    else
                        replaceWith = String.Empty;
                    break;

                case "websitefolder":
                    if((project.HelpFileFormat & HelpFileFormats.Website) != 0)
                        replaceWith = @"Output\" + HelpFileFormats.Website.ToString();
                    else
                        replaceWith = String.Empty;
                    break;

                case "stopwordfile":
                    if(project.IncludeStopWordList)
                        replaceWith = "StopWordFile=\"StopWordList.txt\"";
                    else
                        replaceWith = String.Empty;
                    break;

                case "stopwordlistfilename":
                    if(project.IncludeStopWordList)
                        replaceWith = "StopWordList.txt";
                    else
                        replaceWith = String.Empty;
                    break;

                case "collectiontocstyle":
                    replaceWith = project.CollectionTocStyle.ToString();
                    break;

                case "helpfileversion":
                    replaceWith = project.HelpFileVersion;
                    break;

                case "h2regpluginentries":
                    parts = project.PlugInNamespaces.Split(',');
                    sb = new StringBuilder(1024);

                    foreach(string ns in parts)
                    {
                        replaceWith = ns.Trim();

                        if(replaceWith.Length != 0)
                            sb.AppendFormat("{0}|_DEFAULT|{1}|_DEFAULT\r\n", replaceWith, project.HtmlHelpName);
                    }

                    replaceWith = sb.ToString();
                    break;

                case "h2regmergenamespaces":
                    parts = project.PlugInNamespaces.Split(',');
                    sb = new StringBuilder(1024);

                    foreach(string ns in parts)
                    {
                        replaceWith = ns.Trim();

                        if(replaceWith.Length != 0)
                            sb.AppendFormat("{0}|AUTO\r\n", replaceWith);
                    }

                    replaceWith = sb.ToString();
                    break;

                case "helpattributes":
                    replaceWith = project.HelpAttributes.ToConfigurationString();
                    break;

                case "tokenfiles":
                    sb = new StringBuilder(1024);

                    if(conceptualContent != null)
                        fileItems = conceptualContent.TokenFiles;
                    else
                        fileItems = new FileItemCollection(project, BuildAction.Tokens);

                    foreach(FileItem file in fileItems)
                        sb.AppendFormat("<content file=\"{0}\" />\r\n", Path.GetFileName(file.FullPath));

                    replaceWith = sb.ToString();
                    break;

                case "codesnippetsfiles":
                    sb = new StringBuilder(1024);

                    if(conceptualContent != null)
                        fileItems = conceptualContent.CodeSnippetFiles;
                    else
                        fileItems = new FileItemCollection(project, BuildAction.CodeSnippets);

                    foreach(FileItem file in fileItems)
                        sb.AppendFormat("<examples file=\"{0}\" />\r\n", file.FullPath);

                    replaceWith = sb.ToString();
                    break;

                case "resourceitemfiles":
                    sb = new StringBuilder(1024);

                    fileItems = new FileItemCollection(project, BuildAction.ResourceItems);

                    // Add syntax generator resource item files.  All languages are included regardless of the
                    // project filter settings since code examples can be in any language.  Files are copied and
                    // transformed as they may contain substitution tags
                    foreach(string itemFile in ComponentUtilities.SyntaxGeneratorResourceItemFiles(
                      componentContainer, project.Language))
                    {
                        sb.AppendFormat("<content file=\"{0}\" />\r\n", Path.GetFileName(itemFile));

                        this.TransformTemplate(Path.GetFileName(itemFile), Path.GetDirectoryName(itemFile),
                            workingFolder);
                    }

                    // Add project resource item files last so that they override all other files
                    foreach(FileItem file in fileItems)
                    {
                        sb.AppendFormat("<content file=\"{0}\" />\r\n", Path.GetFileName(file.FullPath));

                        this.TransformTemplate(Path.GetFileName(file.FullPath),
                            Path.GetDirectoryName(file.FullPath), workingFolder);
                    }

                    replaceWith = sb.ToString();
                    break;

                case "xamlconfigfiles":
                    sb = new StringBuilder(1024);

                    fileItems = new FileItemCollection(project, BuildAction.XamlConfiguration);

                    foreach(FileItem file in fileItems)
                        sb.AppendFormat("<filter files=\"{0}\" />\r\n", file.FullPath);

                    replaceWith = sb.ToString();
                    break;

                case "buildassemblerverbosity":
                    replaceWith = (project.BuildAssemblerVerbosity == BuildAssemblerVerbosity.AllMessages) ?
                        "Info" :
                        (project.BuildAssemblerVerbosity == BuildAssemblerVerbosity.OnlyWarningsAndErrors) ?
                        "Warn" : "Error";
                    break;

                case "componentlocations":
                    if(String.IsNullOrWhiteSpace(project.ComponentPath))
                        replaceWith = String.Empty;
                    else
                        replaceWith = String.Format(CultureInfo.InvariantCulture, "<location folder=\"{0}\" />\r\n",
                            HttpUtility.HtmlEncode(project.ComponentPath));

                    replaceWith += String.Format(CultureInfo.InvariantCulture, "<location folder=\"{0}\" />",
                        HttpUtility.HtmlEncode(Path.GetDirectoryName(project.Filename)));
                    break;

                case "helpfileformat":
                    replaceWith = project.HelpFileFormat.ToString();
                    break;

                case "helpformatoutputpaths":
                    sb = new StringBuilder(1024);

                    // Add one entry for each help file format being generated
                    foreach(string baseFolder in this.HelpFormatOutputFolders)
                        sb.AppendFormat("<path value=\"{0}\" />", baseFolder.Substring(workingFolder.Length));

                    replaceWith = sb.ToString();
                    break;

                case "catalogname":
                    replaceWith = project.CatalogName;
                    break;

                case "catalogproductid":
                    replaceWith = project.CatalogProductId;
                    break;

                case "catalogversion":
                    replaceWith = project.CatalogVersion;
                    break;

                case "vendorname":
                    replaceWith = !String.IsNullOrEmpty(project.VendorName) ? project.VendorName : "Vendor Name";
                    break;

                case "htmlencvendorname":
                    replaceWith = !String.IsNullOrEmpty(project.VendorName) ?
                        HttpUtility.HtmlEncode(project.VendorName) : "Vendor Name";
                    break;

                case "producttitle":
                    replaceWith = !String.IsNullOrEmpty(project.ProductTitle) ?
                        project.ProductTitle : project.HelpTitle;
                    break;

                case "htmlencproducttitle":
                    replaceWith = !String.IsNullOrEmpty(project.ProductTitle) ?
                        HttpUtility.HtmlEncode(project.ProductTitle) : HttpUtility.HtmlEncode(project.HelpTitle);
                    break;

                case "topicversion":
                    replaceWith = HttpUtility.HtmlEncode(project.TopicVersion);
                    break;

                case "tocparentid":
                    replaceWith = HttpUtility.HtmlEncode(project.TocParentId);
                    break;

                case "tocparentversion":
                    replaceWith = HttpUtility.HtmlEncode(project.TocParentVersion);
                    break;

                case "apitocparentid":
                    // If null, empty or it starts with '*', it's parented to the root node
                    if(!String.IsNullOrEmpty(this.ApiTocParentId) && this.ApiTocParentId[0] != '*')
                    {
                        // Ensure that the ID is valid and visible in the TOC
                        if(!conceptualContent.Topics.Any(t => t[this.ApiTocParentId] != null &&
                          t[this.ApiTocParentId].Visible))
                            throw new BuilderException("BE0022", String.Format(CultureInfo.CurrentCulture,
                                "The project's ApiTocParent property value '{0}' must be associated with a topic in " +
                                "your project's conceptual content and must have its Visible property set to True in " +
                                "the content layout file.", this.ApiTocParentId));

                        replaceWith = HttpUtility.HtmlEncode(this.ApiTocParentId);
                    }
                    else
                        if(!String.IsNullOrEmpty(this.RootContentContainerId))
                            replaceWith = HttpUtility.HtmlEncode(this.RootContentContainerId);
                        else
                            replaceWith = HttpUtility.HtmlEncode(project.TocParentId);
                    break;

                case "addxamlsyntaxdata":
                    // If the XAML syntax generator is present, add XAML syntax data to the reflection file
                    if(ComponentUtilities.SyntaxFiltersFrom(syntaxGenerators, project.SyntaxFilters).Any(
                      s => s.Id == "XAML Usage"))
                        replaceWith = @";~\ProductionTransforms\AddXamlSyntaxData.xsl";
                    else
                        replaceWith = String.Empty;
                    break;

                case "transformcomponentarguments":
                    sb = new StringBuilder(1024);

                    foreach(var arg in project.TransformComponentArguments)
                        if(arg.Value != null)
                            sb.AppendFormat("<argument key=\"{0}\" value=\"{1}\" />\r\n", arg.Key, arg.Value);
                        else
                            sb.AppendFormat("<argument key=\"{0}\">{1}</argument>\r\n", arg.Key, arg.Content);

                    replaceWith = sb.ToString();
                    break;

                case "referencelinknamespacefiles":
                    sb = new StringBuilder(1024);

                    foreach(string s in this.ReferencedNamespaces)
                        sb.AppendFormat("<namespace file=\"{0}.xml\" />\r\n", s);

                    replaceWith = sb.ToString();
                    break;

                case "uniqueid":
                    // Get a unique ID for the project and current user
                    replaceWith = Environment.GetEnvironmentVariable("USERNAME");

                    if(String.IsNullOrWhiteSpace(replaceWith))
                        replaceWith = "DefaultUser";

                    replaceWith = (project.Filename + "_" + replaceWith).GetHashCode().ToString("X",
                        CultureInfo.InvariantCulture);
                    break;

                case "sandcastlepath":
                    // This is obsolete but will still appear in the older component and plug-in configurations.
                    // Throw an exception that describes what to do to fix it.
                    throw new BuilderException("BE0065", "One or more component or plug-in configurations in " +
                        "this project contains an obsolete path setting.  Please remove the custom components " +
                        "and plug-ins and add them again so that their configurations are updated.  See the " +
                        "version release notes for information on breaking changes that require this update.");

                default:
                    // Try for a custom project property.  Use the last one since the original may be
                    // in a parent project file or it may have been overridden from the command line.
                    buildProp = project.MSBuildProject.AllEvaluatedProperties.LastOrDefault(
                        p => p.Name.Equals(fieldName, StringComparison.OrdinalIgnoreCase));

                    if(buildProp != null)
                        replaceWith = buildProp.EvaluatedValue;
                    else
                    {
                        // If not there, try the global properties.  If still not found, give up.
                        string key = project.MSBuildProject.GlobalProperties.Keys.FirstOrDefault(
                            k => k.Equals(fieldName, StringComparison.OrdinalIgnoreCase));

                        if(key == null || !project.MSBuildProject.GlobalProperties.TryGetValue(key, out replaceWith))
                            switch(fieldName)
                            {
                                case "referencepath":       // Ignore these and use an empty string
                                case "outdir":
                                    replaceWith = String.Empty;
                                    break;

                                default:
                                    throw new BuilderException("BE0020", String.Format(CultureInfo.CurrentCulture,
                                        "Unknown field tag: '{0}'", match.Groups["Field"].Value));
                            }
                    }
                    break;
            }

            return replaceWith;
        }
        /// <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.ProhibitDtd = false;
                readerSettings.CloseInput = true;

                // 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 + presentationParam + ".xsl";

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

                    // The stylesheet 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.InvariantCulture, "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(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");
                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);
        }
        /// <summary>
        /// This method can be used by plug-ins to merge content from another Sandcastle Help File Builder
        /// project file.
        /// </summary>
        /// <param name="project">The project file from which to merge content</param>
        /// <remarks>Auto-generated content can be added to a temporary SHFB project and then added to the
        /// current project's content at build time using this method.  Such content cannot always be added to
        /// the project being built as it may alter the underlying MSBuild project which is not wanted.</remarks>
        public void MergeContentFrom(SandcastleProject project)
        {
            var otherImageFiles = new ImageReferenceCollection(project);
            var otherCodeSnippetFiles = new FileItemCollection(project, BuildAction.CodeSnippets);
            var otherTokenFiles = new FileItemCollection(project, BuildAction.Tokens);
            var otherContentLayoutFiles = new FileItemCollection(project, BuildAction.ContentLayout);

            foreach(var image in otherImageFiles)
                imageFiles.Add(image);

            foreach(var snippets in otherCodeSnippetFiles)
                codeSnippetFiles.Add(snippets);

            foreach(var tokens in otherTokenFiles)
                tokenFiles.Add(tokens);

            foreach(FileItem file in otherContentLayoutFiles)
                topics.Add(new TopicCollection(file));
        }
        /// <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);
            }
        }
Пример #25
0
    public void GenerateUniqueFileList()
    {
      FileItemCollection copy = new FileItemCollection();
      foreach (FileItem item in UniqueFileList.Items)
        copy.Add(item);
      UniqueFileList.Items.Clear();
      foreach (GroupItem groupItem in Groups.Items)
      {
        foreach (FileItem fileItem in groupItem.Files.Items)
        {
          if (!UniqueFileList.ExistLocalFileName(fileItem))
            UniqueFileList.Add(new FileItem(fileItem));
        }
      }

      foreach (SectionItem sectionItem in Sections.Items)
      {
        foreach (SectionParam sectionParam in sectionItem.Params.Items)
        {
          if (sectionParam.ValueType == ValueTypeEnum.File && !string.IsNullOrEmpty(sectionParam.Value))
          {
            if (!UniqueFileList.ExistLocalFileName(sectionParam.Value))
            {
              FileItem existingItem = copy.GetByLocalFileName(sectionParam.Value);
              if (existingItem != null)
                UniqueFileList.Add(existingItem);
              else
                UniqueFileList.Add(new FileItem(sectionParam.Value, true));
            }
            else
              UniqueFileList.GetByLocalFileName(sectionParam.Value).SystemFile = true;
          }
        }
        foreach (ActionItem actionItem in sectionItem.Actions.Items)
        {
          foreach (SectionParam sectionParam in actionItem.Params.Items)
          {
            if (sectionParam.ValueType == ValueTypeEnum.File && !string.IsNullOrEmpty(sectionParam.Value))
            {
              if (!UniqueFileList.ExistLocalFileName(sectionParam.Value))
              {
                FileItem existingItem = copy.GetByLocalFileName(sectionParam.Value);
                if (existingItem != null)
                  UniqueFileList.Add(existingItem);
                else
                  UniqueFileList.Add(new FileItem(sectionParam.Value, true));
              }
              else
                UniqueFileList.GetByLocalFileName(sectionParam.Value).SystemFile = true;
            }
          }
        }
      }

      foreach (SectionParam sectionParam in GeneralInfo.Params.Items)
      {
        if (sectionParam.ValueType == ValueTypeEnum.File && !string.IsNullOrEmpty(sectionParam.Value))
        {
          if (!UniqueFileList.ExistLocalFileName(sectionParam.Value))
          {
            FileItem existingItem = copy.GetByLocalFileName(sectionParam.Value);
            if (existingItem != null)
              UniqueFileList.Add(existingItem);
            else
              UniqueFileList.Add(new FileItem(sectionParam.Value, true));
          }
          else
            UniqueFileList.GetByLocalFileName(sectionParam.Value).SystemFile = true;
        }
      }
    }
Пример #26
0
        /// <summary>
        /// This loads the tree view with table of contents file entries from the project
        /// </summary>
        /// <remarks>Token information is also loaded here and passed on to the converter.</remarks>
        private void LoadTableOfContentsInfo()
        {
            FileItemCollection      imageFiles, tokenFiles, contentLayoutFiles;
            List <ITableOfContents> tocFiles;
            TopicCollection         contentLayout;
            TokenCollection         tokens;

            tvContent.ItemsSource  = null;
            tableOfContents        = null;
            lblCurrentProject.Text = null;
            browserHistory.Clear();
            historyLocation = -1;

            if (currentProject == null)
            {
                lblCurrentProject.Text = "None - Select a help file builder project in the Solution Explorer";
                return;
            }

            // Make sure the base path is set for imported code blocks
            this.SetImportedCodeBasePath();

            // Get content from open file editors
            var args = new FileContentNeededEventArgs(FileContentNeededEvent, this);

            base.RaiseEvent(args);

            currentProject.EnsureProjectIsCurrent(false);
            lblCurrentProject.Text = currentProject.Filename;
            browserHistory.Clear();
            historyLocation = -1;
            tableOfContents = new TocEntryCollection();

            try
            {
                converter.MediaFiles.Clear();

                // Get the image files.  This information is used to resolve media link elements in the
                // topic files.
                imageFiles = new FileItemCollection(currentProject, BuildAction.Image);

                foreach (FileItem file in imageFiles)
                {
                    if (!String.IsNullOrEmpty(file.ImageId))
                    {
                        converter.MediaFiles[file.ImageId] = new KeyValuePair <string, string>(file.FullPath,
                                                                                               file.AlternateText);
                    }
                }
            }
            catch (Exception ex)
            {
                tableOfContents.Add(new TocEntry(currentProject)
                {
                    Title = "ERROR: Unable to load media info: " + ex.Message
                });
            }

            try
            {
                converter.Tokens.Clear();

                // Get the token files.  This information is used to resolve token elements in the
                // topic files.
                tokenFiles = new FileItemCollection(currentProject, BuildAction.Tokens);

                foreach (FileItem file in tokenFiles)
                {
                    // If open in an editor, use the edited values
                    if (!args.TokenFiles.TryGetValue(file.FullPath, out tokens))
                    {
                        tokens = new TokenCollection(file.FullPath);
                        tokens.Load();
                    }

                    // Store the tokens as XElements so that they can be parsed inline with the topic
                    foreach (var t in tokens)
                    {
                        converter.Tokens.Add(t.TokenName, XElement.Parse("<token>" + t.TokenValue +
                                                                         "</token>"));
                    }
                }
            }
            catch (Exception ex)
            {
                tableOfContents.Add(new TocEntry(currentProject)
                {
                    Title = "ERROR: Unable to load token info: " + ex.Message
                });
            }

            try
            {
                converter.TopicTitles.Clear();

                // Get the content layout files.  Site maps are ignored.  We don't support rendering them.
                contentLayoutFiles = new FileItemCollection(currentProject, BuildAction.ContentLayout);
                tocFiles           = new List <ITableOfContents>();

                // Add the conceptual content layout files
                foreach (FileItem file in contentLayoutFiles)
                {
                    // If open in an editor, use the edited values
                    if (!args.ContentLayoutFiles.TryGetValue(file.FullPath, out contentLayout))
                    {
                        contentLayout = new TopicCollection(file);
                        contentLayout.Load();
                    }

                    tocFiles.Add(contentLayout);
                }

                // Sort the files
                tocFiles.Sort((x, y) =>
                {
                    FileItem fx = x.ContentLayoutFile, fy = y.ContentLayoutFile;

                    if (fx.SortOrder < fy.SortOrder)
                    {
                        return(-1);
                    }

                    if (fx.SortOrder > fy.SortOrder)
                    {
                        return(1);
                    }

                    return(String.Compare(fx.Name, fy.Name, StringComparison.OrdinalIgnoreCase));
                });

                // Create the merged TOC.  For the purpose of adding links, we'll include everything
                // even topics marked as invisible.
                foreach (ITableOfContents file in tocFiles)
                {
                    file.GenerateTableOfContents(tableOfContents, currentProject, true);
                }

                // Pass the topic IDs and titles on to the converter for use in hyperlinks
                foreach (var t in tableOfContents.All())
                {
                    if (!String.IsNullOrEmpty(t.Id))
                    {
                        converter.TopicTitles[t.Id] = t.LinkText;
                    }
                }
            }
            catch (Exception ex)
            {
                tableOfContents.Add(new TocEntry(currentProject)
                {
                    Title = "ERROR: Unable to load TOC info: " + ex.Message
                });
            }

            if (tableOfContents.Count != 0)
            {
                foreach (var t in tableOfContents.All())
                {
                    t.IsSelected = false;
                }

                tableOfContents[0].IsSelected = true;
            }

            tvContent.ItemsSource = tableOfContents;
        }
        //=====================================================================
        private void LoadCodeSnippetInfo()
        {
            TreeNode rootNode = null, node;
            XPathDocument snippets;
            XPathNavigator navSnippets;
            CodeReference cr;

            tvEntities.ImageList = ilImages;
            tvEntities.Nodes.Clear();

            if(codeSnippetFiles == null)
                codeSnippetFiles = new FileItemCollection(currentProject,
                    BuildAction.CodeSnippets);

            foreach(FileItem snippetFile in codeSnippetFiles)
                try
                {
                    if(File.Exists(snippetFile.FullPath))
                    {
                        rootNode = tvEntities.Nodes.Add(Path.GetFileName(
                            snippetFile.FullPath));
                        rootNode.ImageIndex = rootNode.SelectedImageIndex =
                            (int)EntityType.CodeEntities;

                        snippets = new XPathDocument(snippetFile.FullPath);
                        navSnippets = snippets.CreateNavigator();

                        foreach(XPathNavigator nav in navSnippets.Select(
                            "examples/item/@id"))
                        {
                            cr = new CodeReference(nav.Value);
                            node = rootNode.Nodes.Add(cr.Id);
                            node.Name = cr.Id;
                            node.Tag = cr;
                            node.ImageIndex = node.SelectedImageIndex =
                                (int)EntityType.CodeSnippets;
                        }
                    }

                    rootNode = null;
                }
                catch(Exception ex)
                {
                    if(rootNode == null)
                        tvEntities.Nodes.Add("Unable to load file '" +
                            snippetFile.FullPath + "'.  Reason: " + ex.Message);
                    else
                        rootNode.Nodes.Add("Unable to load file: " + ex.Message);
                }

            txtFindName.Enabled = true;
            tvEntities.Enabled = true;
            tvEntities.ExpandAll();
        }
Пример #28
0
        //=====================================================================

        private void LoadCodeSnippetInfo()
        {
            TreeNode       rootNode = null, node;
            XPathDocument  snippets;
            XPathNavigator navSnippets;
            CodeReference  cr;

            tvEntities.ImageList = ilImages;
            tvEntities.Nodes.Clear();

            if (codeSnippetFiles == null)
            {
                codeSnippetFiles = new FileItemCollection(currentProject,
                                                          BuildAction.CodeSnippets);
            }

            foreach (FileItem snippetFile in codeSnippetFiles)
            {
                try
                {
                    if (File.Exists(snippetFile.FullPath))
                    {
                        rootNode = tvEntities.Nodes.Add(Path.GetFileName(
                                                            snippetFile.FullPath));
                        rootNode.ImageIndex = rootNode.SelectedImageIndex =
                            (int)EntityType.CodeEntities;

                        snippets    = new XPathDocument(snippetFile.FullPath);
                        navSnippets = snippets.CreateNavigator();

                        foreach (XPathNavigator nav in navSnippets.Select(
                                     "examples/item/@id"))
                        {
                            cr              = new CodeReference(nav.Value);
                            node            = rootNode.Nodes.Add(cr.Id);
                            node.Name       = cr.Id;
                            node.Tag        = cr;
                            node.ImageIndex = node.SelectedImageIndex =
                                (int)EntityType.CodeSnippets;
                        }
                    }

                    rootNode = null;
                }
                catch (Exception ex)
                {
                    if (rootNode == null)
                    {
                        tvEntities.Nodes.Add("Unable to load file '" +
                                             snippetFile.FullPath + "'.  Reason: " + ex.Message);
                    }
                    else
                    {
                        rootNode.Nodes.Add("Unable to load file: " + ex.Message);
                    }
                }
            }

            txtFindName.Enabled = true;
            tvEntities.Enabled  = true;
            tvEntities.ExpandAll();
        }
        /// <summary>
        /// Refresh the currently displayed entity information
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The event arguments</param>
        private void tsbRefresh_Click(object sender, EventArgs e)
        {
            switch((EntityType)cboContentType.SelectedIndex)
            {
                case EntityType.Tokens:
                    tokenFiles = null;
                    break;

                case EntityType.Images:
                    images = null;
                    break;

                case EntityType.CodeSnippets:
                    codeSnippetFiles = null;
                    break;

                default:
                    codeEntities = null;
                    break;
            }

            tvEntities.Nodes.Clear();
            this.cboContentType_SelectedIndexChanged(sender, e);
        }
        /// <summary>
        /// Replace a field tag with a value from the project
        /// </summary>
        /// <param name="match">The match that was found</param>
        /// <returns>The string to use as the replacement</returns>
        private string OnFieldMatch(Match match)
        {
            BuildProperty buildProp;
            FileItemCollection fileItems;
            StringBuilder sb;
            string replaceWith, fieldName;
            string[] parts;

            fieldName = match.Groups["Field"].Value.ToLower(CultureInfo.InvariantCulture);

            switch(fieldName)
            {
                case "appdatafolder":
                    // This folder should exist if used
                    replaceWith = FolderPath.TerminatePath(Path.Combine(
                        Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
                        Constants.ProgramDataFolder));
                    break;

                case "localdatafolder":
                    // This folder may not exist and we may need to create it
                    replaceWith = FolderPath.TerminatePath(Path.Combine(
                        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                        Constants.ProgramDataFolder));

                    if(!Directory.Exists(replaceWith))
                        Directory.CreateDirectory(replaceWith);
                    break;

                case "shfbfolder":
                    replaceWith = shfbFolder;
                    break;

                case "componentsfolder":
                    replaceWith = BuildComponentManager.BuildComponentsFolder;
                    break;

                case "projectfolder":
                    replaceWith = Path.GetDirectoryName(originalProjectName);

                    if(replaceWith.Length == 0)
                        replaceWith = Directory.GetCurrentDirectory();

                    replaceWith += @"\";
                    break;

                case "htmlencprojectfolder":
                    replaceWith = HttpUtility.HtmlEncode(Path.GetDirectoryName(originalProjectName));

                    if(replaceWith.Length == 0)
                        replaceWith = HttpUtility.HtmlEncode(
                            Directory.GetCurrentDirectory());

                    replaceWith += @"\";
                    break;

                case "outputfolder":
                    replaceWith = outputFolder;
                    break;

                case "htmlencoutputfolder":
                    replaceWith = HttpUtility.HtmlEncode(outputFolder);
                    break;

                case "workingfolder":
                    replaceWith = workingFolder;
                    break;

                case "htmlencworkingfolder":
                    replaceWith = HttpUtility.HtmlEncode(workingFolder);
                    break;

                case "sandcastlepath":
                    replaceWith = sandcastleFolder;
                    break;

                case "presentationpath":
                    replaceWith = presentationFolder;
                    break;

                case "presentationstyle":
                    replaceWith = project.PresentationStyle;
                    break;

                case "presentationparam":
                    replaceWith = presentationParam;
                    break;

                case "hhcpath":
                    replaceWith = hhcFolder;
                    break;

                case "hxcomppath":
                    replaceWith = hxcompFolder;
                    break;

                case "docinternals":
                    if(project.DocumentInternals || project.DocumentPrivates)
                        replaceWith = "true";
                    else
                        replaceWith = "false";
                    break;

                case "htmlhelpname":
                    replaceWith = project.HtmlHelpName;
                    break;

                case "htmlenchelpname":
                    replaceWith = HttpUtility.HtmlEncode(project.HtmlHelpName);
                    break;

                case "helpviewersetupname":
                    // Help viewer setup names cannot contain periods so we'll replace them with underscores
                    replaceWith = HttpUtility.HtmlEncode(project.HtmlHelpName.Replace('.', '_'));
                    break;

                case "frameworkcommentlist":
                case "cachedframeworkcommentlist":
                case "importframeworkcommentlist":
                    replaceWith = this.FrameworkCommentList(match.Groups["Field"].Value.ToLower(
                        CultureInfo.InvariantCulture));
                    break;

                case "commentfilelist":
                    replaceWith = commentsFiles.CommentFileList(workingFolder, false);
                    break;

                case "inheritedcommentfilelist":
                    replaceWith = commentsFiles.CommentFileList(workingFolder, true);
                    break;

                case "helptitle":
                    replaceWith = project.HelpTitle;
                    break;

                case "htmlenchelptitle":
                    replaceWith = HttpUtility.HtmlEncode(project.HelpTitle);
                    break;

                case "scripthelptitle":
                    // This is used when the title is passed as a parameter
                    // to a JavaScript function.
                    replaceWith = HttpUtility.HtmlEncode(project.HelpTitle).Replace("'", @"\'");
                    break;

                case "urlenchelptitle": // Just replace &, <, >, and " for  now
                    replaceWith = project.HelpTitle.Replace("&", "%26").Replace(
                        "<", "%3C").Replace(">", "%3E").Replace("\"", "%22");
                    break;

                case "rootnamespacetitle":
                    replaceWith = project.RootNamespaceTitle;

                    if(replaceWith.Length == 0)
                        replaceWith = "<include item=\"rootTopicTitleLocalized\"/>";
                    break;

                case "binarytoc":
                    replaceWith = project.BinaryTOC ? "Yes" : "No";
                    break;

                case "windowoptions":
                    // Currently, we use a default set of options and only
                    // allow showing or hiding the Favorites tab.
                    replaceWith = (project.IncludeFavorites) ? "0x63520" : "0x62520";
                    break;

                case "langid":
                    replaceWith = language.LCID.ToString(CultureInfo.InvariantCulture);
                    break;

                case "language":
                    replaceWith = String.Format(CultureInfo.InvariantCulture,
                        "0x{0:X} {1}", language.LCID, language.NativeName);
                    break;

                case "languagefolder":
                    replaceWith = languageFolder;
                    break;

                case "locale":
                    replaceWith = language.Name.ToLower(CultureInfo.InvariantCulture);
                    break;

                case "copyright":
                    // Include copyright info if there is a copyright HREF or
                    // copyright text.
                    if(project.CopyrightHref.Length != 0 || project.CopyrightText.Length != 0)
                        replaceWith = "<include item=\"copyright\"/>";
                    else
                        replaceWith = String.Empty;
                    break;

                case "copyrightinfo":
                    if(project.CopyrightHref.Length == 0 && project.CopyrightText.Length == 0)
                        replaceWith = String.Empty;
                    else
                        if(project.CopyrightHref.Length == 0)
                            replaceWith = project.DecodedCopyrightText;
                        else
                            if(project.CopyrightText.Length == 0)
                                replaceWith = project.CopyrightHref;
                            else
                                replaceWith = String.Format(CultureInfo.CurrentCulture, "{0} ({1})",
                                    project.DecodedCopyrightText, project.CopyrightHref);
                    break;

                case "htmlenccopyrightinfo":
                    if(project.CopyrightHref.Length == 0 && project.CopyrightText.Length == 0)
                        replaceWith = String.Empty;
                    else
                        if(project.CopyrightHref.Length == 0)
                            replaceWith = "<p/>" + HttpUtility.HtmlEncode(project.DecodedCopyrightText);
                        else
                            if(project.CopyrightText.Length == 0)
                                replaceWith = String.Format(CultureInfo.CurrentCulture,
                                    "<p/><a href='{0}' target='_blank'>{0}</a>",
                                    HttpUtility.HtmlEncode(project.CopyrightHref));
                            else
                                replaceWith = String.Format(CultureInfo.CurrentCulture,
                                    "<p/><a href='{0}' target='_blank'>{1}</a>",
                                    HttpUtility.HtmlEncode(project.CopyrightHref),
                                    HttpUtility.HtmlEncode(project.DecodedCopyrightText));
                    break;

                case "copyrighthref":
                    replaceWith = project.CopyrightHref;
                    break;

                case "htmlenccopyrighthref":
                    if(project.CopyrightHref.Length == 0)
                        replaceWith = String.Empty;
                    else
                        replaceWith = String.Format(CultureInfo.CurrentCulture,
                            "<a href='{0}' target='_blank'>{0}</a>",
                            HttpUtility.HtmlEncode(project.CopyrightHref));
                    break;

                case "copyrighttext":
                    if(project.CopyrightText.Length == 0)
                        replaceWith = String.Empty;
                    else
                        replaceWith = project.DecodedCopyrightText;
                    break;

                case "htmlenccopyrighttext":
                    if(project.CopyrightText.Length == 0)
                        replaceWith = String.Empty;
                    else
                        replaceWith = HttpUtility.HtmlEncode(project.DecodedCopyrightText);
                    break;

                case "comments":
                    // Include "send comments" line if feedback e-mail address
                    // is specified.
                    if(project.FeedbackEMailAddress.Length != 0)
                        replaceWith = "<include item=\"comments\"/>";
                    else
                        replaceWith = String.Empty;
                    break;

                case "feedbackemailaddress":
                    replaceWith = project.FeedbackEMailAddress;
                    break;

                case "feedbackemaillinktext":
                    replaceWith = project.FeedbackEMailLinkText;
                    break;

                case "urlencfeedbackemailaddress":
                    if(project.FeedbackEMailAddress.Length == 0)
                        replaceWith = String.Empty;
                    else
                        replaceWith = HttpUtility.UrlEncode(project.FeedbackEMailAddress);
                    break;

                case "htmlencfeedbackemailaddress":
                    // If link text is specified, it will be used instead
                    if(project.FeedbackEMailAddress.Length == 0)
                        replaceWith = String.Empty;
                    else
                        if(project.FeedbackEMailLinkText.Length == 0)
                            replaceWith = HttpUtility.HtmlEncode(project.FeedbackEMailAddress);
                        else
                            replaceWith = HttpUtility.HtmlEncode(project.FeedbackEMailLinkText);
                    break;

                case "headertext":
                    replaceWith = project.HeaderText;
                    break;

                case "footertext":
                    replaceWith = project.FooterText;
                    break;

                case "indenthtml":
                    replaceWith = project.IndentHtml.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "preliminary":
                    // Include the "preliminary" warning in the header text if wanted
                    if(project.Preliminary)
                        replaceWith = "<include item=\"preliminary\"/>";
                    else
                        replaceWith = String.Empty;
                    break;

                case "defaulttopic":
                    if(defaultTopic.EndsWith(".topic", StringComparison.OrdinalIgnoreCase))
                        replaceWith = Path.ChangeExtension(defaultTopic, ".html");
                    else
                        replaceWith = defaultTopic;
                    break;

                case "webdefaulttopic":
                    if(defaultTopic.EndsWith(".topic", StringComparison.OrdinalIgnoreCase))
                        replaceWith = Path.ChangeExtension(defaultTopic, ".html").Replace('\\', '/');
                    else
                        replaceWith = defaultTopic.Replace('\\', '/');
                    break;

                case "frameworkversion":
                    replaceWith = project.FrameworkVersion;
                    break;

                case "frameworkversionshort":
                    replaceWith = project.FrameworkVersion.Substring(0, 3);
                    break;

                case "mrefframeworkversion":
                    replaceWith = project.FrameworkVersion;

                    // For .NET 3.0 or higher, Microsoft says to use the .NET 2.0
                    // framework files.
                    if(replaceWith[0] >= '3')
                        replaceWith = FrameworkVersionTypeConverter.LatestMatching("2.0");
                    break;

                case "mrefframeworkversionshort":
                    replaceWith = project.FrameworkVersion.Substring(0, 3);

                    // For .NET 3.0 or higher, Microsoft says to use the .NET 2.0
                    // framework files.
                    if(replaceWith[0] >= '3')
                        replaceWith = "2.0";
                    break;

                case "help1xprojectfiles":
                    replaceWith = this.HelpProjectFileList(String.Format(CultureInfo.InvariantCulture,
                        @"{0}Output\{1}", workingFolder, HelpFileFormat.HtmlHelp1), HelpFileFormat.HtmlHelp1);
                    break;

                case "help2xprojectfiles":
                    replaceWith = this.HelpProjectFileList(String.Format(CultureInfo.InvariantCulture,
                        @"{0}Output\{1}", workingFolder, HelpFileFormat.MSHelp2), HelpFileFormat.MSHelp2);
                    break;

                case "htmlsdklinktype":
                    replaceWith = project.HtmlSdkLinkType.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "mshelp2sdklinktype":
                    replaceWith = project.MSHelp2SdkLinkType.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "mshelpviewersdklinktype":
                    replaceWith = project.MSHelpViewerSdkLinkType.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "websitesdklinktype":
                    replaceWith = project.WebsiteSdkLinkType.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "sdklinktarget":
                    replaceWith = "_" + project.SdkLinkTarget.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "htmltoc":
                    replaceWith = this.GenerateHtmlToc();
                    break;

                case "syntaxfilters":
                    replaceWith = BuildComponentManager.SyntaxFilterGeneratorsFrom(project.SyntaxFilters);
                    break;

                case "syntaxfiltersdropdown":
                    // Note that we can't remove the dropdown box if only a single
                    // language is selected as script still depends on it.
                    replaceWith = BuildComponentManager.SyntaxFilterLanguagesFrom(project.SyntaxFilters);
                    break;

                case "autodocumentconstructors":
                    replaceWith = project.AutoDocumentConstructors.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "autodocumentdisposemethods":
                    replaceWith = project.AutoDocumentDisposeMethods.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "showmissingparams":
                    replaceWith = project.ShowMissingParams.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "showmissingremarks":
                    replaceWith = project.ShowMissingRemarks.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "showmissingreturns":
                    replaceWith = project.ShowMissingReturns.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "showmissingsummaries":
                    replaceWith = project.ShowMissingSummaries.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "showmissingtypeparams":
                    replaceWith = project.ShowMissingTypeParams.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "showmissingvalues":
                    replaceWith = project.ShowMissingValues.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "showmissingnamespaces":
                    replaceWith = project.ShowMissingNamespaces.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "showmissingincludetargets":
                    replaceWith = project.ShowMissingIncludeTargets.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "apifilter":
                    // In a partial build used to get API info for the API
                    // filter designer, we won't apply the filter.
                    if(!suppressApiFilter)
                        replaceWith = apiFilter.ToString();
                    else
                        replaceWith = String.Empty;
                    break;

                case "builddate":
                    // Apply a format specifier?
                    if(match.Groups["Format"].Value.Length != 0)
                        replaceWith = String.Format(CultureInfo.CurrentCulture,
                            "{0:" + match.Groups["Format"].Value + "}", DateTime.Now);
                    else
                        replaceWith = DateTime.Now.ToString(CultureInfo.CurrentCulture);
                    break;

                case "includeprojectnode":
                    if(project.RootNamespaceContainer)
                        replaceWith = "project=Project";
                    else
                        replaceWith = String.Empty;
                    break;

                case "help1folder":
                    if((project.HelpFileFormat & HelpFileFormat.HtmlHelp1) != 0)
                        replaceWith = @"Output\" + HelpFileFormat.HtmlHelp1.ToString();
                    else
                        replaceWith = String.Empty;
                    break;

                case "websitefolder":
                    if((project.HelpFileFormat & HelpFileFormat.Website) != 0)
                        replaceWith = @"Output\" + HelpFileFormat.Website.ToString();
                    else
                        replaceWith = String.Empty;
                    break;

                case "stopwordfile":
                    if(project.IncludeStopWordList)
                        replaceWith = "StopWordFile=\"StopWordList.txt\"";
                    else
                        replaceWith = String.Empty;
                    break;

                case "stopwordlistfilename":
                    if(project.IncludeStopWordList)
                        replaceWith = "StopWordList.txt";
                    else
                        replaceWith = String.Empty;
                    break;

                case "collectiontocstyle":
                    replaceWith = project.CollectionTocStyle.ToString();
                    break;

                case "helpfileversion":
                    replaceWith = project.HelpFileVersion;
                    break;

                case "h2regpluginentries":
                    parts = project.PlugInNamespaces.Split(',');
                    sb = new StringBuilder(1024);

                    foreach(string ns in parts)
                    {
                        replaceWith = ns.Trim();

                        if(replaceWith.Length != 0)
                            sb.AppendFormat("{0}|_DEFAULT|{1}|_DEFAULT\r\n", replaceWith, project.HtmlHelpName);
                    }

                    replaceWith = sb.ToString();
                    break;

                case "h2regmergenamespaces":
                    parts = project.PlugInNamespaces.Split(',');
                    sb = new StringBuilder(1024);

                    foreach(string ns in parts)
                    {
                        replaceWith = ns.Trim();

                        if(replaceWith.Length != 0)
                            sb.AppendFormat("{0}|AUTO\r\n", replaceWith);
                    }

                    replaceWith = sb.ToString();
                    break;

                case "helpattributes":
                    replaceWith = project.HelpAttributes.ToConfigurationString();
                    break;

                case "tokenfiles":
                    sb = new StringBuilder(1024);

                    if(conceptualContent != null)
                        fileItems = conceptualContent.TokenFiles;
                    else
                        fileItems = new FileItemCollection(project, BuildAction.Tokens);

                    foreach(FileItem file in fileItems)
                        sb.AppendFormat("<content file=\"{0}\" />\r\n", Path.GetFileName(file.FullPath));

                    replaceWith = sb.ToString();
                    break;

                case "codesnippetsfiles":
                    sb = new StringBuilder(1024);

                    if(conceptualContent != null)
                        fileItems = conceptualContent.CodeSnippetFiles;
                    else
                        fileItems = new FileItemCollection(project, BuildAction.CodeSnippets);

                    foreach(FileItem file in fileItems)
                        sb.AppendFormat("<examples file=\"{0}\" />\r\n", file.FullPath);

                    replaceWith = sb.ToString();
                    break;

                case "resourceitemfiles":
                    sb = new StringBuilder(1024);

                    fileItems = new FileItemCollection(project, BuildAction.ResourceItems);

                    // Files are copied and transformed as they may contain
                    // substitution tags.
                    foreach(FileItem file in fileItems)
                    {
                        sb.AppendFormat("<content file=\"{0}\" />\r\n", Path.GetFileName(file.FullPath));

                        this.TransformTemplate(Path.GetFileName(file.FullPath),
                            Path.GetDirectoryName(file.FullPath), workingFolder);
                    }

                    replaceWith = sb.ToString();
                    break;

                case "helpfileformat":
                    replaceWith = project.HelpFileFormat.ToString();
                    break;

                case "helpformatoutputpaths":
                    sb = new StringBuilder(1024);

                    // Add one entry for each help file format being generated
                    foreach(string baseFolder in this.HelpFormatOutputFolders)
                        sb.AppendFormat("<path value=\"{0}\" />", baseFolder.Substring(workingFolder.Length));

                    replaceWith = sb.ToString();
                    break;

                case "catalogproductid":
                    replaceWith = project.CatalogProductId;
                    break;

                case "catalogversion":
                    replaceWith = project.CatalogVersion;
                    break;

                case "vendorname":
                    replaceWith = !String.IsNullOrEmpty(project.VendorName) ? project.VendorName : "Vendor Name";
                    break;

                case "htmlencvendorname":
                    replaceWith = !String.IsNullOrEmpty(project.VendorName) ?
                        HttpUtility.HtmlEncode(project.VendorName) : "Vendor Name";
                    break;

                case "producttitle":
                    replaceWith = !String.IsNullOrEmpty(project.ProductTitle) ?
                        project.ProductTitle : project.HelpTitle;
                    break;

                case "htmlencproducttitle":
                    replaceWith = !String.IsNullOrEmpty(project.ProductTitle) ?
                        HttpUtility.HtmlEncode(project.ProductTitle) : HttpUtility.HtmlEncode(project.HelpTitle);
                    break;

                case "selfbranded":
                    replaceWith = project.SelfBranded.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;

                case "topicversion":
                    replaceWith = HttpUtility.HtmlEncode(project.TopicVersion);
                    break;

                case "tocparentid":
                    replaceWith = HttpUtility.HtmlEncode(project.TocParentId);
                    break;

                case "tocparentversion":
                    replaceWith = HttpUtility.HtmlEncode(project.TocParentVersion);
                    break;

                case "apitocparentid":
                    // If null, empty or it starts with '*', it's parented to the root node
                    if(!String.IsNullOrEmpty(this.ApiTocParentId) && this.ApiTocParentId[0] != '*')
                    {
                        // Ensure that the ID is valid and visible in the TOC
                        if(!conceptualContent.Topics.Any(t => t[this.ApiTocParentId] != null &&
                          t[this.ApiTocParentId].Visible))
                            throw new BuilderException("BE0022", String.Format(CultureInfo.CurrentCulture,
                                "The project's ApiTocParent property value '{0}' must be associated with a topic in " +
                                "your project's conceptual content and must have its Visible property set to True in " +
                                "the content layout file.", this.ApiTocParentId));

                        replaceWith = HttpUtility.HtmlEncode(this.ApiTocParentId);
                    }
                    else
                        if(!String.IsNullOrEmpty(this.RootContentContainerId))
                            replaceWith = HttpUtility.HtmlEncode(this.RootContentContainerId);
                        else
                            replaceWith = HttpUtility.HtmlEncode(project.TocParentId);
                    break;

                default:
                    // Try for a custom project property
                    buildProp = project.MSBuildProject.EvaluatedProperties[fieldName];

                    // If not there, try the global properties
                    if(buildProp == null)
                        buildProp = project.MSBuildProject.GlobalProperties[fieldName];

                    // If not there, give up
                    if(buildProp == null)
                        throw new BuilderException("BE0020", String.Format(CultureInfo.CurrentCulture,
                            "Unknown field tag: '{0}'", match.Groups["Field"].Value));

                    replaceWith = buildProp.FinalValue;
                    break;
            }

            return replaceWith;
        }
        /// <summary>
        /// This loads the tree view with table of contents file entries from the project
        /// </summary>
        /// <remarks>Token information is also loaded here and passed on to the converter.</remarks>
        private void LoadTableOfContentsInfo()
        {
            FileItemCollection imageFiles, tokenFiles, contentLayoutFiles;
            List<ITableOfContents> tocFiles;
            TopicCollection contentLayout;
            TokenCollection tokens;

            tvContent.ItemsSource = null;
            tableOfContents = null;
            lblCurrentProject.Text = null;
            browserHistory.Clear();
            historyLocation = -1;

            if(currentProject == null)
            {
                lblCurrentProject.Text = "None - Select a help file builder project in the Solution Explorer";
                return;
            }

            // Make sure the base path is set for imported code blocks
            this.SetImportedCodeBasePath();

            // Get content from open file editors
            var args = new FileContentNeededEventArgs(FileContentNeededEvent, this);
            base.RaiseEvent(args);

            currentProject.EnsureProjectIsCurrent(false);
            lblCurrentProject.Text = currentProject.Filename;
            browserHistory.Clear();
            historyLocation = -1;
            tableOfContents = new TocEntryCollection();

            try
            {
                converter.MediaFiles.Clear();

                // Get the image files.  This information is used to resolve media link elements in the
                // topic files.
                imageFiles = new FileItemCollection(currentProject, BuildAction.Image);

                foreach(FileItem file in imageFiles)
                    if(!String.IsNullOrEmpty(file.ImageId))
                        converter.MediaFiles[file.ImageId] = new KeyValuePair<string, string>(file.FullPath,
                            file.AlternateText);
            }
            catch(Exception ex)
            {
                tableOfContents.Add(new TocEntry(currentProject)
                {
                    Title = "ERROR: Unable to load media info: " + ex.Message
                });
            }

            try
            {
                converter.Tokens.Clear();

                // Get the token files.  This information is used to resolve token elements in the
                // topic files.
                tokenFiles = new FileItemCollection(currentProject, BuildAction.Tokens);

                foreach(FileItem file in tokenFiles)
                {
                    // If open in an editor, use the edited values
                    if(!args.TokenFiles.TryGetValue(file.FullPath, out tokens))
                    {
                        tokens = new TokenCollection(file.FullPath);
                        tokens.Load();
                    }

                    // Store the tokens as XElements so that they can be parsed inline with the topic
                    foreach(var t in tokens)
                        converter.Tokens.Add(t.TokenName, XElement.Parse("<token>" + t.TokenValue +
                            "</token>"));
                }
            }
            catch(Exception ex)
            {
                tableOfContents.Add(new TocEntry(currentProject)
                {
                    Title = "ERROR: Unable to load token info: " + ex.Message
                });
            }

            try
            {
                converter.TopicTitles.Clear();

                // Get the content layout files.  Site maps are ignored.  We don't support rendering them.
                contentLayoutFiles = new FileItemCollection(currentProject, BuildAction.ContentLayout);
                tocFiles = new List<ITableOfContents>();

                // Add the conceptual content layout files
                foreach(FileItem file in contentLayoutFiles)
                {
                    // If open in an editor, use the edited values
                    if(!args.ContentLayoutFiles.TryGetValue(file.FullPath, out contentLayout))
                    {
                        contentLayout = new TopicCollection(file);
                        contentLayout.Load();
                    }

                    tocFiles.Add(contentLayout);
                }

                // Sort the files
                tocFiles.Sort((x, y) =>
                {
                    FileItem fx = x.ContentLayoutFile, fy = y.ContentLayoutFile;

                    if(fx.SortOrder < fy.SortOrder)
                        return -1;

                    if(fx.SortOrder > fy.SortOrder)
                        return 1;

                    return String.Compare(fx.Name, fy.Name, StringComparison.OrdinalIgnoreCase);
                });

                // Create the merged TOC.  For the purpose of adding links, we'll include everything
                // even topics marked as invisible.
                foreach(ITableOfContents file in tocFiles)
                    file.GenerateTableOfContents(tableOfContents, currentProject, true);

                // Pass the topic IDs and titles on to the converter for use in hyperlinks
                foreach(var t in tableOfContents.All())
                    if(!String.IsNullOrEmpty(t.Id))
                        converter.TopicTitles[t.Id] = t.LinkText;
            }
            catch(Exception ex)
            {
                tableOfContents.Add(new TocEntry(currentProject)
                {
                    Title = "ERROR: Unable to load TOC info: " + ex.Message
                });
            }

            if(tableOfContents.Count != 0)
            {
                foreach(var t in tableOfContents.All())
                    t.IsSelected = false;

                tableOfContents[0].IsSelected = true;
            }

            tvContent.ItemsSource = tableOfContents;
        }