//===================================================================== /// <summary> /// Load a site map file for editing /// </summary> /// <param name="siteMapFile">The site map file item to load</param> public void LoadSiteMapFile(FileItem siteMapFile) { if (siteMapFile == null) { throw new ArgumentNullException("siteMapFile", "A site map file item must be specified"); } topics = new TocEntryCollection(siteMapFile.ToContentFile()); topics.Load(); // This works around a legacy support issue related to object equality foreach (var t in topics.All()) { t.UniqueId = Guid.NewGuid(); } topics.ListChanged += new ListChangedEventHandler(topics_ListChanged); if (topics.Count != 0 && topics.Find(t => t.IsSelected, false).Count() == 0) { topics[0].IsSelected = true; } tvContent.ItemsSource = topics; this.topics_ListChanged(this, new ListChangedEventArgs(ListChangedType.Reset, -1)); }
/// <summary> /// Load the tree view with the table of content nodes /// </summary> private void LoadTree() { TreeNode node; TocEntryCollection toc = bp.TableOfContent; tvTOC.Nodes.Clear(); defaultNode = null; if (toc != null && toc.Count != 0) { btnDefaultTopic.Enabled = btnMoveUp.Enabled = btnMoveDown.Enabled = btnSave.Enabled = true; foreach (TocEntry te in bp.TableOfContent) { node = tvTOC.Nodes.Add(te.Title); node.Tag = te; if (te.IsDefaultTopic) { defaultNode = node; } if (te.Children.Count != 0) { this.AddChildren(te.Children, node); } } if (defaultNode != null) { defaultNode.ToolTipText = "Default topic"; defaultNode.ImageIndex = defaultNode.SelectedImageIndex = 1; } } else { btnDefaultTopic.Enabled = btnMoveUp.Enabled = btnMoveDown.Enabled = btnSave.Enabled = false; } if (tvTOC.Nodes.Count != 0) { tvTOC.ExpandAll(); } }
/// <summary> /// Add a root node and its children to the tree view recursively /// </summary> /// <param name="children">The collection of entries to add</param> /// <param name="root">The root to which they are added</param> private void AddChildren(TocEntryCollection children, TreeNode root) { TreeNode node; foreach (TocEntry te in children) { node = root.Nodes.Add(te.Title); node.Tag = te; if (te.IsDefaultTopic) { defaultNode = node; } if (te.Children.Count != 0) { this.AddChildren(te.Children, node); } } }
//===================================================================== /// <summary> /// This loads the tree view with table of contents file entries from the project /// </summary> private List <EntityReference> LoadTableOfContentsInfo() { 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 { tocFiles = new List <ITableOfContents>(); // Load all content layout files and add them to the list 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); } // Load all site maps and add them to the list foreach (var contentFile in currentProject.ContentFiles(BuildAction.SiteMap)) { // If open in an editor, use the edited values if (!args.SiteMapFiles.TryGetValue(contentFile.FullPath, out siteMap)) { siteMap = new TocEntryCollection(contentFile); siteMap.Load(); } tocFiles.Add(siteMap); } 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. mergedToc = new TocEntryCollection(); foreach (ITableOfContents file in tocFiles) { file.GenerateTableOfContents(mergedToc, 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> /// Add new topics from all files in a selected folder /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void cmdAddAllFromFolder_Executed(object sender, ExecutedRoutedEventArgs e) { FileNode thisNode = this.FileNode; TocEntryCollection parent, newTopics = new TocEntryCollection(null); TocEntry selectedTopic = base.UIControl.CurrentTopic; string projectPath = Path.GetDirectoryName(siteMapFile.ProjectElement.Project.Filename); int idx; using (WinFormsFolderBrowserDialog dlg = new WinFormsFolderBrowserDialog()) { dlg.Description = "Select a folder to add all of its content"; dlg.SelectedPath = (selectedTopic != null && selectedTopic.SourceFile.Path.Length != 0) ? Path.GetDirectoryName(selectedTopic.SourceFile) : projectPath; if (dlg.ShowDialog() == WinFormsDialogResult.OK) { Utility.GetServiceFromPackage <IVsUIShell, SVsUIShell>(true).SetWaitCursor(); newTopics.AddTopicsFromFolder(dlg.SelectedPath, dlg.SelectedPath, siteMapFile.ProjectElement.Project); if (thisNode != null) { thisNode.ProjectManager.RefreshProject(); } } if (newTopics.Count != 0) { if (e.Parameter == null || selectedTopic == null) { // Insert as siblings if (selectedTopic == null) { parent = base.UIControl.Topics; idx = 0; } else { parent = selectedTopic.Parent; idx = parent.IndexOf(selectedTopic) + 1; } foreach (TocEntry t in newTopics) { parent.Insert(idx++, t); } } else { // Insert as children parent = selectedTopic.Children; foreach (TocEntry t in newTopics) { parent.Add(t); } selectedTopic.IsExpanded = 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() { 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> private void MergeConceptualAndAdditionalContentTocInfo() { List <ITableOfContents> tocFiles; TocEntryCollection siteMap; 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 this.ConceptualContent.Topics) { tocFiles.Add(topics); } // Load all site maps and add them to the list foreach (var contentFile in project.ContentFiles(BuildAction.SiteMap)) { this.ReportProgress(" Loading site map '{0}'", contentFile.FullPath); siteMap = new TocEntryCollection(contentFile); siteMap.Load(); // Copy site map files to the help format folders foreach (TocEntry site in siteMap) { this.CopySiteMapFiles(site); } tocFiles.Add(siteMap); } // Sort the files 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. Invisible items are excluded. toc = new TocEntryCollection(); foreach (ITableOfContents file in tocFiles) { file.GenerateTableOfContents(toc, false); } 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> /// Load a site map file for editing /// </summary> /// <param name="siteMapFile">The site map file item to load</param> public void LoadSiteMapFile(FileItem siteMapFile) { if(siteMapFile == null) throw new ArgumentNullException("siteMapFile", "A site map file item must be specified"); topics = new TocEntryCollection(siteMapFile.ToContentFile()); topics.Load(); // This works around a legacy support issue related to object equality foreach(var t in topics.All()) t.UniqueId = Guid.NewGuid(); topics.ListChanged += new ListChangedEventHandler(topics_ListChanged); if(topics.Count != 0 && topics.Find(t => t.IsSelected, false).Count() == 0) topics[0].IsSelected = true; tvContent.ItemsSource = topics; this.topics_ListChanged(this, new ListChangedEventArgs(ListChangedType.Reset, -1)); }
/// <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> /// Constructor /// </summary> /// <param name="fileItem">The project file item to edit</param> public SiteMapEditorWindow(FileItem fileItem) { EventHandler onClick = new EventHandler(templateFile_OnClick); ToolStripMenuItem miTemplate; Image itemImage; string name; InitializeComponent(); sbStatusBarText.InstanceStatusBar = MainForm.Host.StatusBarTextLabel; // Look for custom templates in the local application data folder name = Path.Combine(Environment.GetFolderPath( Environment.SpecialFolder.LocalApplicationData), Constants.ItemTemplates); if(!Directory.Exists(name)) miCustomSibling.Enabled = miCustomChild.Enabled = false; else { string[] files = Directory.GetFiles(name, "*.htm?"); if(files.Length == 0) miCustomSibling.Enabled = miCustomChild.Enabled = false; else foreach(string file in files) { name = Path.GetFileNameWithoutExtension(file); itemImage = null; miTemplate = new ToolStripMenuItem(name, null, onClick); miTemplate.Image = itemImage; miTemplate.Tag = file; sbStatusBarText.SetStatusBarText(miTemplate, "Add new '" + name + "' topic"); miCustomSibling.DropDownItems.Add(miTemplate); miTemplate = new ToolStripMenuItem(name, null, onClick); miTemplate.Image = itemImage; miTemplate.Tag = file; sbStatusBarText.SetStatusBarText(miTemplate, "Add new '" + name + "' topic"); miCustomChild.DropDownItems.Add(miTemplate); } } topics = new TocEntryCollection(fileItem); topics.Load(); topics.ListChanged += new ListChangedEventHandler(topics_ListChanged); this.Text = Path.GetFileName(fileItem.FullPath); this.ToolTipText = fileItem.FullPath; this.LoadTopics(null); }
/// <summary> /// Add child nodes to the tree view recursively /// </summary> /// <param name="children">The collection of entries to add</param> /// <param name="root">The root to which they are added</param> private void AddChildren(TocEntryCollection children, TreeNode root) { TreeNode node; foreach(TocEntry t in children) { node = root.Nodes.Add(t.Title); node.Name = t.SourceFile; node.Tag = t; if(t.IsDefaultTopic) defaultNode = node; if(t == firstSelection) firstNode = node; if(t.Children.Count != 0) this.AddChildren(t.Children, node); } }
/// <summary> /// Add all topic files found in the selected folder /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void AddAllTopicsInFolder_Click(object sender, EventArgs e) { ToolStripItem tiAdd = (ToolStripItem)sender; TocEntryCollection parent, newTopics = new TocEntryCollection(null); TocEntry selectedTopic; int idx; using(FolderBrowserDialog dlg = new FolderBrowserDialog()) { dlg.Description = "Select a folder to add all of its content"; dlg.SelectedPath = Directory.GetCurrentDirectory(); if(dlg.ShowDialog() == DialogResult.OK) { try { Cursor.Current = Cursors.WaitCursor; newTopics.AddTopicsFromFolder(dlg.SelectedPath, dlg.SelectedPath, topics.FileItem.ProjectElement.Project); MainForm.Host.ProjectExplorer.RefreshProject(); } finally { Cursor.Current = Cursors.Default; } } if(newTopics.Count != 0) { if(tiAdd.Owner != cmsNewChildTopic) { // Insert as siblings if(tvContent.SelectedNode == null) { parent = topics; idx = 0; } else { selectedTopic = (TocEntry)tvContent.SelectedNode.Tag; parent = selectedTopic.Parent; idx = parent.IndexOf(selectedTopic) + 1; } foreach(TocEntry t in newTopics) { parent.Insert(idx, t); idx++; } } else // Insert as children { parent = ((TocEntry)tvContent.SelectedNode.Tag).Children; foreach(TocEntry t in newTopics) parent.Add(t); } // Take the easy way out and reload the tree this.LoadTopics(newTopics[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() { 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> /// Add new topics from all files in a selected folder /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void cmdAddAllFromFolder_Executed(object sender, ExecutedRoutedEventArgs e) { TocEntryCollection parent, newTopics = new TocEntryCollection(null); TocEntry selectedTopic = ucSiteMapEditor.CurrentTopic; string projectPath = Path.GetDirectoryName(siteMapFile.Project.Filename); int idx; using(FolderBrowserDialog dlg = new FolderBrowserDialog()) { dlg.Description = "Select a folder to add all of its content"; dlg.SelectedPath = (selectedTopic != null && selectedTopic.SourceFile.Path.Length != 0) ? Path.GetDirectoryName(selectedTopic.SourceFile) : projectPath; if(dlg.ShowDialog() == DialogResult.OK) { try { MouseCursor.Current = MouseCursors.WaitCursor; newTopics.AddTopicsFromFolder(dlg.SelectedPath, dlg.SelectedPath, siteMapFile.Project); MainForm.Host.ProjectExplorer.RefreshProject(); } finally { MouseCursor.Current = MouseCursors.Default; } } if(newTopics.Count != 0) if(e.Parameter == null || selectedTopic == null) { // Insert as siblings if(selectedTopic == null) { parent = ucSiteMapEditor.Topics; idx = 0; } else { parent = selectedTopic.Parent; idx = parent.IndexOf(selectedTopic) + 1; } foreach(TocEntry t in newTopics) parent.Insert(idx++, t); } else { // Insert as children parent = selectedTopic.Children; foreach(TocEntry t in newTopics) parent.Add(t); selectedTopic.IsExpanded = true; } } }
/// <summary> /// This is used to merge the conceptual content table of contents with any additional content table of /// contents information. /// </summary> private void MergeConceptualAndAdditionalContentTocInfo() { List<ITableOfContents> tocFiles; TocEntryCollection siteMap; 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 this.ConceptualContent.Topics) tocFiles.Add(topics); // Load all site maps and add them to the list foreach(var contentFile in project.ContentFiles(BuildAction.SiteMap)) { this.ReportProgress(" Loading site map '{0}'", contentFile.FullPath); siteMap = new TocEntryCollection(contentFile); siteMap.Load(); // Copy site map files to the help format folders foreach(TocEntry site in siteMap) this.CopySiteMapFiles(site); tocFiles.Add(siteMap); } // Sort the files 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. Invisible items are excluded. toc = new TocEntryCollection(); foreach(ITableOfContents file in tocFiles) file.GenerateTableOfContents(toc, false); if(toc.Count != 0) { // Look for the default topic tocEntry = toc.FindDefaultTopic(); if(tocEntry != null) defaultTopic = tocEntry.DestinationFile; } this.ExecutePlugIns(ExecutionBehaviors.After); }
/// <summary> /// Add new topics from all files in a selected folder /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void cmdAddAllFromFolder_Executed(object sender, ExecutedRoutedEventArgs e) { TocEntryCollection parent, newTopics = new TocEntryCollection(null); TocEntry selectedTopic = ucSiteMapEditor.CurrentTopic; string projectPath = Path.GetDirectoryName(siteMapFile.Project.Filename); int idx; using (FolderBrowserDialog dlg = new FolderBrowserDialog()) { dlg.Description = "Select a folder to add all of its content"; dlg.SelectedPath = (selectedTopic != null && selectedTopic.SourceFile.Path.Length != 0) ? Path.GetDirectoryName(selectedTopic.SourceFile) : projectPath; if (dlg.ShowDialog() == DialogResult.OK) { try { MouseCursor.Current = MouseCursors.WaitCursor; newTopics.AddTopicsFromFolder(dlg.SelectedPath, dlg.SelectedPath, siteMapFile.Project); MainForm.Host.ProjectExplorer.RefreshProject(); } finally { MouseCursor.Current = MouseCursors.Default; } } if (newTopics.Count != 0) { if (e.Parameter == null || selectedTopic == null) { // Insert as siblings if (selectedTopic == null) { parent = ucSiteMapEditor.Topics; idx = 0; } else { parent = selectedTopic.Parent; idx = parent.IndexOf(selectedTopic) + 1; } foreach (TocEntry t in newTopics) { parent.Insert(idx++, t); } } else { // Insert as children parent = selectedTopic.Children; foreach (TocEntry t in newTopics) { parent.Add(t); } selectedTopic.IsExpanded = true; } } } }
//===================================================================== /// <summary> /// This loads the tree view with table of contents file entries from the project /// </summary> private List<EntityReference> LoadTableOfContentsInfo() { 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 { tocFiles = new List<ITableOfContents>(); // Load all content layout files and add them to the list 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); } // Load all site maps and add them to the list foreach(var contentFile in currentProject.ContentFiles(BuildAction.SiteMap)) { // If open in an editor, use the edited values if(!args.SiteMapFiles.TryGetValue(contentFile.FullPath, out siteMap)) { siteMap = new TocEntryCollection(contentFile); siteMap.Load(); } tocFiles.Add(siteMap); } 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. mergedToc = new TocEntryCollection(); foreach(ITableOfContents file in tocFiles) file.GenerateTableOfContents(mergedToc, 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 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); }