/* Function: BuildDataFiles * * Builds the content HTML file for the passed <Context's> <PageLocation> and its supporting <JSONSummary> and * <JSONToolTips>. Returns whether there was any content. It will also return false if it was interrupted by the * <CancelDelegate>. * * If the <CodeDB.Accessor> doesn't have a lock, this function will automatically acquire and release a read-only * lock. This is the preferred way of using this function as the lock will only be held during the data querying stage * and will be released before writing output to disk. * * If it already had a lock it will use it and not release it unless you set releaseExistingLocks. */ public bool BuildDataFiles(Context context, CodeDB.Accessor accessor, CancelDelegate cancelDelegate, bool releaseExistingLocks = false) { this.Context = context; var location = context.Page; List <Engine.Topics.Topic> topics; List <Engine.Links.Link> links; List <Engine.Links.ImageLink> imageLinks; bool releaseDBLock = false; if (accessor.LockHeld == CodeDB.Accessor.LockType.None) { accessor.GetReadOnlyLock(); releaseDBLock = true; } else if (releaseExistingLocks) { releaseDBLock = true; } try { // Get the topics from the database. if (location.IsSourceFile) { topics = accessor.GetTopicsInFile(location.FileID, cancelDelegate); } else if (location.InHierarchy) { topics = accessor.GetTopicsInClass(location.ClassID, cancelDelegate); } else { throw new NotImplementedException(); } if (topics == null || topics.Count == 0 || cancelDelegate()) { return(false); } // Create the class view if appropriate if (location.InHierarchy) { ClassView.Merge(ref topics, EngineInstance); // It's possible for ClassView to reduce the number of topics to zero, so check for that and treat it as if the // class page has no content. if (topics.Count == 0) { return(false); } } // Get the links from the database. if (location.IsSourceFile) { links = accessor.GetLinksInFile(location.FileID, cancelDelegate) ?? new List <Engine.Links.Link>(); imageLinks = accessor.GetImageLinksInFile(location.FileID, cancelDelegate) ?? new List <Engine.Links.ImageLink>(); } else if (location.InHierarchy) { links = accessor.GetLinksInClass(location.ClassID, cancelDelegate) ?? new List <Engine.Links.Link>(); imageLinks = accessor.GetImageLinksInClass(location.ClassID, cancelDelegate) ?? new List <Engine.Links.ImageLink>(); } else { throw new NotImplementedException(); } if (cancelDelegate()) { return(false); } // Find all the classes that are defined in this page, since we have to do additional lookups for class prototypes. IDObjects.NumberSet classIDsDefined = new IDObjects.NumberSet(); foreach (var topic in topics) { if (topic.DefinesClass) { classIDsDefined.Add(topic.ClassID); } } if (cancelDelegate()) { return(false); } // We need the class parent links of all the classes defined on this page so the class prototypes can show the parents. // If this is a class page then we can skip this step since all the links should already be included. However, for any // other type of page this may not be the case. A source file page would return all the links in that file, but the class // may be defined across multiple files and we need the class parent links in all of them. In this case we need to look // up the class parent links separately by class ID. if (location.InHierarchy == false && classIDsDefined.IsEmpty == false) { List <Engine.Links.Link> classParentLinks = accessor.GetClassParentLinksInClasses(classIDsDefined, cancelDelegate); if (classParentLinks != null && classParentLinks.Count > 0) { links.AddRange(classParentLinks); } } if (cancelDelegate()) { return(false); } // Now we need to find the children of all the classes defined on this page. Get the class parent links that resolve to // any of the defined classes, but keep them separate for now. List <Engine.Links.Link> childLinks = null; if (classIDsDefined.IsEmpty == false) { childLinks = accessor.GetClassParentLinksToClasses(classIDsDefined, cancelDelegate); } if (cancelDelegate()) { return(false); } // Get link targets for everything but the children, since they would just resolve to classes already in this file. IDObjects.NumberSet linkTargetIDs = new IDObjects.NumberSet(); foreach (var link in links) { if (link.IsResolved) { linkTargetIDs.Add(link.TargetTopicID); } } List <Engine.Topics.Topic> linkTargets = accessor.GetTopicsByID(linkTargetIDs, cancelDelegate) ?? new List <Engine.Topics.Topic>(); if (cancelDelegate()) { return(false); } // Now get targets for the children. List <Engine.Topics.Topic> childTargets = null; if (childLinks != null && childLinks.Count > 0) { IDObjects.NumberSet childClassIDs = new IDObjects.NumberSet(); foreach (var childLink in childLinks) { childClassIDs.Add(childLink.ClassID); } childTargets = accessor.GetBestClassDefinitionTopics(childClassIDs, cancelDelegate); } if (cancelDelegate()) { return(false); } // We can merge the child links and targets into the main lists now. if (childLinks != null) { links.AddRange(childLinks); } if (childTargets != null) { linkTargets.AddRange(childTargets); } if (cancelDelegate()) { return(false); } // Now we need to find any Natural Docs and image links appearing inside the summaries of link targets. // The tooltips that will be generated for them include their summaries, and even though we don't generate // HTML links inside tooltips, how and if they're resolved affects their appearance. We need to know whether // to include the original text with angle brackets, the text without angle brackets if it's resolved, or only part // of the text if it's a resolved named link. // Links don't store which topic they appear in but they do store the file, so gather the file IDs of the link // targets that have Natural Docs or image links in the summaries and get all the links in those files. // Links also store which class they appear in, so why not do this by class instead of by file? Because a // link could be to something global, and the global scope could potentially have a whole hell of a lot of // content, depending on the project and language. While there can also be some really long files, the // chances of that are smaller so we stick with doing this by file. IDObjects.NumberSet summaryLinkFileIDs = new IDObjects.NumberSet(); foreach (var linkTarget in linkTargets) { if (linkTarget.Summary != null && (linkTarget.Summary.IndexOf("<link type=\"naturaldocs\"") != -1 || linkTarget.Summary.IndexOf("<image ") != -1)) { summaryLinkFileIDs.Add(linkTarget.FileID); } } List <Engine.Links.Link> summaryLinks = null; List <Engine.Links.ImageLink> summaryImageLinks = null; if (!summaryLinkFileIDs.IsEmpty) { summaryLinks = accessor.GetNaturalDocsLinksInFiles(summaryLinkFileIDs, cancelDelegate); summaryImageLinks = accessor.GetImageLinksInFiles(summaryLinkFileIDs, cancelDelegate); } if (cancelDelegate()) { return(false); } // Finally done with the database. if (releaseDBLock) { accessor.ReleaseLock(); releaseDBLock = false; } // Determine the page title string pageTitle; if (context.Page.IsSourceFile) { pageTitle = EngineInstance.Files.FromID(context.Page.FileID).FileName.NameWithoutPath; } else if (context.Page.InHierarchy) { pageTitle = context.Page.ClassString.Symbol.LastSegment; } else { throw new NotImplementedException(); } // Build the HTML for the list of topics StringBuilder html = new StringBuilder("\r\n\r\n"); HTML.Components.Topic topicBuilder = new HTML.Components.Topic(context); HTML.Components.Tooltip tooltipBuilder = new HTML.Components.Tooltip(context); // We don't put embedded topics in the output, so we need to find the last non-embedded one to make // sure that the "last" CSS tag is correctly applied. int lastNonEmbeddedTopic = topics.Count - 1; while (lastNonEmbeddedTopic > 0 && topics[lastNonEmbeddedTopic].IsEmbedded == true) { lastNonEmbeddedTopic--; } for (int i = 0; i <= lastNonEmbeddedTopic; i++) { string extraClass = null; if (i == 0) { extraClass = "first"; } else if (i == lastNonEmbeddedTopic) { extraClass = "last"; } if (topics[i].IsEmbedded == false) { topicBuilder.AppendTopic(topics[i], context, links, linkTargets, imageLinks, html, topics, i + 1, extraClass); html.Append("\r\n\r\n"); } } // Build the full HTML file Build(context.OutputFile, pageTitle, html.ToString(), PageType.Content); // Build summary and tooltips files JSONSummary summaryBuilder = new JSONSummary(context); summaryBuilder.ConvertToJSON(topics, context); summaryBuilder.BuildDataFile(pageTitle); JSONToolTips toolTipsBuilder = new JSONToolTips(context); toolTipsBuilder.ConvertToJSON(topics, links, imageLinks, context); toolTipsBuilder.BuildDataFileForSummary(); toolTipsBuilder.ConvertToJSON(linkTargets, summaryLinks, summaryImageLinks, context); toolTipsBuilder.BuildDataFileForContent(); return(true); } catch (Exception e) { try { e.AddNaturalDocsTask("Building HTML file " + context.OutputFile); } catch { } throw; } finally { if (releaseDBLock) { accessor.ReleaseLock(); } } }
/* Function: Build * * Builds the page and its supporting JSON files. Returns whether there was any content. It will also return false * if it was interrupted by the <CancelDelegate>. * * If the <CodeDB.Accessor> doesn't have a lock, this function will automatically acquire and release a read-only lock. * This is the preferred way of using this function as the lock will only be held during the data querying stage and will be * released before writing output to disk. If it already has a lock it will use it and not release it. */ public bool Build(CodeDB.Accessor accessor, CancelDelegate cancelDelegate) { bool releaseDBLock = false; if (accessor.LockHeld == CodeDB.Accessor.LockType.None) { accessor.GetReadOnlyLock(); releaseDBLock = true; } try { // DEPENDENCY: HTMLTopicPages.Class assumes that this function will call a database function before using any path // properties. List <Topic> topics = GetTopics(accessor, cancelDelegate) ?? new List <Topic>(); if (topics.Count == 0 || cancelDelegate()) { return(false); } List <Link> links = GetLinks(accessor, cancelDelegate) ?? new List <Link>(); if (cancelDelegate()) { return(false); } // Find all the classes that are defined in this page, since we have to do additional lookups for class prototypes. IDObjects.NumberSet classIDsDefined = new IDObjects.NumberSet(); foreach (var topic in topics) { if (topic.DefinesClass) { classIDsDefined.Add(topic.ClassID); } } if (cancelDelegate()) { return(false); } // We need the class parent links of all the classes defined on this page so the class prototypes can show the parents. // If this is a class page then we can skip this step since all the links should already be included. However, for any // other type of page this may not be the case. A source file page would return all the links in that file, but the class // may be defined across multiple files and we need the class parent links in all of them. In this case we need to look // up the class parent links separately by class ID. if ((this is HTMLTopicPages.Class) == false && classIDsDefined.IsEmpty == false) { List <Link> classParentLinks = accessor.GetClassParentLinksInClasses(classIDsDefined, cancelDelegate); if (classParentLinks != null && classParentLinks.Count > 0) { links.AddRange(classParentLinks); } } if (cancelDelegate()) { return(false); } // Now we need to find the children of all the classes defined on this page. Get the class parent links that resolve to // any of the defined classes, but keep them separate for now. List <Link> childLinks = null; if (classIDsDefined.IsEmpty == false) { childLinks = accessor.GetClassParentLinksToClasses(classIDsDefined, cancelDelegate); } if (cancelDelegate()) { return(false); } // Get link targets for everything but the children, since they would just resolve to classes already in this file. IDObjects.NumberSet linkTargetIDs = new IDObjects.NumberSet(); foreach (Link link in links) { if (link.IsResolved) { linkTargetIDs.Add(link.TargetTopicID); } } List <Topic> linkTargets = accessor.GetTopicsByID(linkTargetIDs, cancelDelegate) ?? new List <Topic>(); if (cancelDelegate()) { return(false); } // Now get targets for the children. List <Topic> childTargets = null; if (childLinks != null && childLinks.Count > 0) { IDObjects.NumberSet childClassIDs = new IDObjects.NumberSet(); foreach (var childLink in childLinks) { childClassIDs.Add(childLink.ClassID); } childTargets = accessor.GetBestClassDefinitionTopics(childClassIDs, cancelDelegate); } if (cancelDelegate()) { return(false); } // We can merge the child links and targets into the main lists now. if (childLinks != null) { links.AddRange(childLinks); } if (childTargets != null) { linkTargets.AddRange(childTargets); } if (cancelDelegate()) { return(false); } // Now we need to find any Natural Docs links appearing inside the summaries of link targets. The tooltips // that will be generated for them include their summaries, and even though we don't generate HTML links // inside tooltips, how and if they're resolved affects the appearance of Natural Docs links. We need to know // whether to include the original text with angle brackets, the text without angle brackets if it's resolved, or // only part of the text if it's a resolved named link. // Links don't store which topic they appear in but they do store the file, so gather the file IDs of the link // targets that have Natural Docs links in the summaries and get all the links in those files. // Links also store which class they appear in, so why not do this by class instead of by file? Because a // link could be to something global, and the global scope could potentially have a whole hell of a lot of // content, depending on the project and language. While there can also be some really long files, the // chances of that are smaller so we stick with doing this by file. IDObjects.NumberSet summaryLinkFileIDs = new IDObjects.NumberSet(); foreach (Topic linkTarget in linkTargets) { if (linkTarget.Summary != null && linkTarget.Summary.IndexOf("<link type=\"naturaldocs\"") != -1) { summaryLinkFileIDs.Add(linkTarget.FileID); } } List <Link> summaryLinks = null; if (!summaryLinkFileIDs.IsEmpty) { summaryLinks = accessor.GetNaturalDocsLinksInFiles(summaryLinkFileIDs, cancelDelegate); } if (cancelDelegate()) { return(false); } // Finally done with the database. if (releaseDBLock) { accessor.ReleaseLock(); releaseDBLock = false; } try { // Build the HTML for the list of topics StringBuilder html = new StringBuilder("\r\n\r\n"); HTML.Context context = new HTML.Context(HTMLBuilder, this); HTML.Components.Topic topicBuilder = new HTML.Components.Topic(context); HTML.Components.Tooltip tooltipBuilder = new HTML.Components.Tooltip(context); // We don't put embedded topics in the output, so we need to find the last non-embedded one to make // sure that the "last" CSS tag is correctly applied. int lastNonEmbeddedTopic = topics.Count - 1; while (lastNonEmbeddedTopic > 0 && topics[lastNonEmbeddedTopic].IsEmbedded == true) { lastNonEmbeddedTopic--; } for (int i = 0; i <= lastNonEmbeddedTopic; i++) { string extraClass = null; if (i == 0) { extraClass = "first"; } else if (i == lastNonEmbeddedTopic) { extraClass = "last"; } if (topics[i].IsEmbedded == false) { topicBuilder.AppendTopic(topics[i], context, links, linkTargets, html, topics, i + 1, extraClass); html.Append("\r\n\r\n"); } } // Build the full HTML file htmlBuilder.BuildFile(OutputFile, PageTitle, html.ToString(), Builders.HTML.PageType.Content); // Build the tooltips file using (System.IO.StreamWriter file = htmlBuilder.CreateTextFileAndPath(ToolTipsFile)) { file.Write("NDContentPage.OnToolTipsLoaded({"); if (!EngineInstance.Config.ShrinkFiles) { file.WriteLine(); } for (int i = 0; i < linkTargets.Count; i++) { Topic topic = linkTargets[i]; string toolTipHTML = tooltipBuilder.BuildToolTip(topic, context, summaryLinks); if (toolTipHTML != null) { if (!EngineInstance.Config.ShrinkFiles) { file.Write(" "); } file.Write(topic.TopicID); file.Write(":\""); file.Write(toolTipHTML.StringEscape()); file.Write('"'); if (i != linkTargets.Count - 1) { file.Write(','); } if (!EngineInstance.Config.ShrinkFiles) { file.WriteLine(); } } } if (!EngineInstance.Config.ShrinkFiles) { file.Write(" "); } file.Write("});"); } // Build summary and summary tooltips files JSSummaryData summaryBuilder = new JSSummaryData(this); summaryBuilder.Build(topics, links, PageTitle, OutputFileHashPath, SummaryFile, SummaryToolTipsFile); return(true); } catch (Exception e) { try { e.AddNaturalDocsTask("Building File: " + OutputFile); } catch { } throw; } } finally { if (releaseDBLock) { accessor.ReleaseLock(); } } }