//===================================================================== /// <summary> /// Add an empty container topic to the collection that is not associated with any file /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void cmdAddItem_Executed(object sender, ExecutedRoutedEventArgs e) { TocEntry currentTopic = this.CurrentTopic, newTopic = new TocEntry(topics.ContentLayoutFile.BasePathProvider) { Title = "Table of Contents Container", UniqueId = Guid.NewGuid() }; // If the command parameter is null, add it as a sibling. If not, add it as a child. if (e.Parameter == null || currentTopic == null) { if (currentTopic == null || topics.Count == 0) { topics.Add(newTopic); } else { currentTopic.Parent.Insert(currentTopic.Parent.IndexOf(currentTopic) + 1, newTopic); } } else { currentTopic.Children.Add(newTopic); currentTopic.IsExpanded = true; } newTopic.IsSelected = true; }
/// <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() { 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); 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. foreach(var file in currentProject.ImagesReferences) converter.MediaFiles[file.Id] = 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. foreach(var file in currentProject.ContentFiles(BuildAction.Tokens).OrderBy(f => f.LinkPath)) { // 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(); // Load the content layout files. Site maps are ignored as we don't support rendering them. tocFiles = new List<ITableOfContents>(); foreach(var contentFile in currentProject.ContentFiles(BuildAction.ContentLayout)) { // If open in an editor, use the edited values if(!args.ContentLayoutFiles.TryGetValue(contentFile.FullPath, out contentLayout)) { contentLayout = new TopicCollection(contentFile); contentLayout.Load(); } tocFiles.Add(contentLayout); } tocFiles.Sort((x, y) => { ContentFile fx = x.ContentLayoutFile, fy = y.ContentLayoutFile; if(fx.SortOrder < fy.SortOrder) return -1; if(fx.SortOrder > fy.SortOrder) return 1; return String.Compare(fx.Filename, fy.Filename, 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, 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; }
/// <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; }
/// <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 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 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 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); }