public ContentGenerator(BuildProcess builder, string gherkinFeaturesPath, CultureInfo gherkinFeaturesLanguage) { this.builder = builder; this.gherkinFeaturesPath = gherkinFeaturesPath; this.gherkinFeaturesLanguage = gherkinFeaturesLanguage; this.guidsInUse = new HashSet<Guid>(); }
/// <summary> /// Open the specified collection file and return it as an /// <see cref="XmlDocument"/> ready for editing. /// </summary> /// <param name="file">The file to open</param> /// <remarks>The DTD is removed before returning it.</remarks> private static XmlDocument OpenCollectionFile(string file) { XmlDocument doc; Encoding enc = Encoding.Default; string content = BuildProcess.ReadWithEncoding(file, ref enc); // Get rid of the DTD declaration content = reRemoveDTD.Replace(content, "$1$3"); doc = new XmlDocument(); doc.LoadXml(content); return(doc); }
/// <summary> /// This is used to extract table of contents information from a file /// that will appear in the help file's table of contents. /// </summary> /// <param name="filename">The file from which to extract the /// information</param> /// <returns>The table of contents entry</returns> internal static TocEntry GetTocInfo(string filename) { TocEntry tocEntry; Encoding enc = Encoding.Default; string content; content = BuildProcess.ReadWithEncoding(filename, ref enc); tocEntry = new TocEntry(null); tocEntry.IncludePage = !reTocExclude.IsMatch(content); tocEntry.IsDefaultTopic = reIsDefaultTopic.IsMatch(content); if (reSplitToc.IsMatch(content)) { tocEntry.ApiParentMode = ApiParentMode.InsertAfter; } Match m = reSortOrder.Match(content); if (m.Success) { tocEntry.SortOrder = Convert.ToInt32(m.Groups["SortOrder"].Value, CultureInfo.InvariantCulture); } // Get the page title if possible. If not found, use the filename // without the path or extension as the page title. m = rePageTitle.Match(content); if (!m.Success) { tocEntry.Title = Path.GetFileNameWithoutExtension(filename); } else { tocEntry.Title = HttpUtility.HtmlDecode(m.Groups["Title"].Value).Replace( "\r", String.Empty).Replace("\n", String.Empty); } // Since we've got the file loaded, see if there are links // that need to be resolved when the file is copied, if it // contains <pre> blocks that should be colorized, or if it // contains tags or shared content items that need replacing. tocEntry.HasLinks = reResolveLinks.IsMatch(content); tocEntry.HasCodeBlocks = reCodeBlock.IsMatch(content); tocEntry.NeedsColorizing = reColorizeCheck.IsMatch(content); tocEntry.HasProjectTags = (reProjectTags.IsMatch(content) || reSharedContent.IsMatch(content)); return(tocEntry); }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="currentBuild">The current build for which to perform substitution tag replacement</param> public SubstitutionTagReplacement(BuildProcess currentBuild) { this.currentBuild = currentBuild; sandcastleProject = currentBuild.CurrentProject; msbuildProject = sandcastleProject.MSBuildProject; presentationStyle = currentBuild.PresentationStyle; replacementValue = new StringBuilder(10240); fieldMatchEval = new MatchEvaluator(OnFieldMatch); // Get the substitution tag methods so that we can invoke them. The dictionary keys are the method // names and are case-insensitive. Substitution tag methods take no parameters and return a value // that is convertible to a string. methodCache = this.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).Where( m => m.GetCustomAttribute(typeof(SubstitutionTagAttribute)) != null).ToDictionary( m => m.Name, m => m, StringComparer.OrdinalIgnoreCase); }
public ContentGenerator(BuildProcess builder, string sourcePath) { this.builder = builder; this.sourcePath = sourcePath; }
/// <summary> /// View the last build help output /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void miViewBuiltHelpFile_Click(object sender, EventArgs e) { // Make sure we start out in the project's output folder in case the output folder is relative to it Directory.SetCurrentDirectory(Path.GetDirectoryName(Path.GetFullPath(project.Filename))); string outputPath = project.OutputPath; // If the output path contains MSBuild variables, get the evaluated value from the project if(outputPath.IndexOf("$(", StringComparison.Ordinal) != -1) outputPath = project.MSBuildProject.GetProperty("OutputPath").EvaluatedValue; if(String.IsNullOrEmpty(outputPath)) outputPath = Directory.GetCurrentDirectory(); else outputPath = Path.GetFullPath(outputPath); if(sender == miViewHtmlHelp1) outputPath += project.HtmlHelpName + ".chm"; else if(sender == miViewOpenXml) outputPath += project.HtmlHelpName + ".docx"; else if(sender == miViewHelpFile) outputPath += "_Sidebar.md"; else outputPath += "Index.html"; // If there are substitution tags present, have a go at resolving them if(outputPath.IndexOf("{@", StringComparison.Ordinal) != -1) { try { var bp = new BuildProcess(project); outputPath = bp.SubstitutionTags.TransformText(outputPath); } catch { // Ignore errors MessageBox.Show("The help filename appears to contain substitution tags but they could " + "not be resolved to determine the actual file to open for viewing. Building " + "website output and viewing it can be used to work around this issue.", Constants.AppName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return; } } if(!File.Exists(outputPath)) { MessageBox.Show("A copy of the help file does not appear to exist yet. It may need to be built.", Constants.AppName, MessageBoxButtons.OK, MessageBoxIcon.Information); return; } try { System.Diagnostics.Process.Start(outputPath); } catch(Exception ex) { System.Diagnostics.Debug.WriteLine(ex.ToString()); MessageBox.Show(String.Format(CultureInfo.CurrentCulture, "Unable to open help file '{0}'\r\nReason: {1}", outputPath, ex.Message), Constants.AppName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } }
/// <summary> /// Write out the <strong>BuildAssembler</strong> manifest entry /// </summary> /// <param name="writer">The XML writer to which the entry is written</param> /// <param name="builder">The build process</param> /// <remarks>This will recursively write out entries for sub-topics as well</remarks> internal void WriteManifest(XmlWriter writer, BuildProcess builder) { // MS Help Viewer doesn't support empty place holders so we automatically generate a dummy place // holder file for them. if(!noFile || (builder.CurrentProject.HelpFileFormat & HelpFileFormats.MSHelpViewer) != 0) { writer.WriteStartElement("topic"); writer.WriteAttributeString("id", this.Id); writer.WriteAttributeString("type", "MAML"); writer.WriteEndElement(); } foreach(Topic t in subtopics) t.WriteManifest(writer, builder); }
/// <summary> /// This method is used to initialize the plug-in at the start of the build process /// </summary> /// <param name="buildProcess">A reference to the current build process</param> /// <param name="configuration">The configuration data that the plug-in should use to initialize itself</param> /// <exception cref="BuilderException">This is thrown if the plug-in configuration is not valid</exception> public void Initialize(BuildProcess buildProcess, XPathNavigator configuration) { XPathNavigator root, node; string value; builder = buildProcess; attachLogOnSuccess = false; attachLogOnFailure = true; smtpServer = successEMailAddress = failureEMailAddress = xslTransformFile = String.Empty; credentials = new UserCredentials(); smtpPort = 25; var metadata = (HelpFileBuilderPlugInExportAttribute)this.GetType().GetCustomAttributes( typeof(HelpFileBuilderPlugInExportAttribute), false).First(); builder.ReportProgress("{0} Version {1}\r\n{2}", metadata.Id, metadata.Version, metadata.Copyright); root = configuration.SelectSingleNode("configuration"); if(root.IsEmptyElement) throw new BuilderException("CNP0001", "The Completion Notification plug-in has not been " + "configured yet"); node = root.SelectSingleNode("smtpServer"); if(node != null) { smtpServer = node.GetAttribute("host", String.Empty).Trim(); value = node.GetAttribute("port", String.Empty); if(!Int32.TryParse(value, out smtpPort)) smtpPort = 25; } credentials = UserCredentials.FromXPathNavigator(root); node = root.SelectSingleNode("fromEMail"); if(node != null) fromEMailAddress = node.GetAttribute("address", String.Empty).Trim(); node = root.SelectSingleNode("successEMail"); if(node != null) { successEMailAddress = node.GetAttribute("address", String.Empty).Trim(); attachLogOnSuccess = Convert.ToBoolean(node.GetAttribute("attachLog", String.Empty), CultureInfo.InvariantCulture); } node = root.SelectSingleNode("failureEMail"); if(node != null) { failureEMailAddress = node.GetAttribute("address", String.Empty).Trim(); attachLogOnFailure = Convert.ToBoolean(node.GetAttribute("attachLog", String.Empty), CultureInfo.InvariantCulture); } node = root.SelectSingleNode("xslTransform"); if(node != null) xslTransformFile = builder.TransformText(node.GetAttribute("filename", String.Empty).Trim()); if((!credentials.UseDefaultCredentials && (credentials.UserName.Length == 0 || credentials.Password.Length == 0)) || failureEMailAddress.Length == 0) throw new BuilderException("CNP0002", "The Completion Notification plug-in has an invalid " + "configuration"); }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="currentBuild">The current build for which to execute tasks</param> public TaskRunner(BuildProcess currentBuild) { this.currentBuild = currentBuild; }
/// <summary> /// Write out the topic metadata /// </summary> /// <param name="writer">The writer to which the metadata is written</param> /// <param name="builder">The build process</param> /// <remarks>This will recursively write out metadata for sub-topics /// as well.</remarks> internal void WriteMetadata(XmlWriter writer, BuildProcess builder) { // MS Help Viewer doesn't support empty place holders so we automatically // generate a dummy place holder file for them. if(!noFile || (builder.CurrentProject.HelpFileFormat & HelpFileFormat.MSHelpViewer) != 0) { writer.WriteStartElement("topic"); writer.WriteAttributeString("id", this.Id); writer.WriteAttributeString("revisionNumber", this.RevisionNumber.ToString(CultureInfo.InvariantCulture)); // Write out the help file version project property value writer.WriteStartElement("item"); writer.WriteAttributeString("id", "PBM_FileVersion"); writer.WriteValue(builder.TransformText(builder.CurrentProject.HelpFileVersion)); writer.WriteEndElement(); // If no title is specified, use the display title writer.WriteStartElement("title"); if(!String.IsNullOrEmpty(title)) writer.WriteString(title); else writer.WriteString(this.DisplayTitle); writer.WriteEndElement(); // TOC title is optional if(!String.IsNullOrEmpty(tocTitle)) writer.WriteElementString("tableOfContentsTitle", tocTitle); // The running header text ID is set to "runningHeaderText" // so that it pulls in the shared content item by that name. // This will equate to the project's HTML encoded HelpTitle // property value. writer.WriteStartElement("runningHeaderText"); writer.WriteAttributeString("uscid", "runningHeaderText"); writer.WriteEndElement(); // Each topic includes the project-level help attributes foreach(MSHelpAttr attr in builder.CurrentProject.HelpAttributes) { writer.WriteStartElement("attribute"); writer.WriteAttributeString("name", attr.AttributeName); // Replace tags with their project property value writer.WriteValue(builder.TransformText(attr.AttributeValue)); writer.WriteEndElement(); } // Add topic-specific attributes foreach(MSHelpAttr attr in helpAttributes) { writer.WriteStartElement("attribute"); writer.WriteAttributeString("name", attr.AttributeName); // Replace tags with their project property value writer.WriteValue(builder.TransformText(attr.AttributeValue)); writer.WriteEndElement(); } // Add topic-specific index keywords foreach(MSHelpKeyword kw in keywords) { writer.WriteStartElement("keyword"); writer.WriteAttributeString("index", kw.Index); // Replace tags with their project property value writer.WriteValue(builder.TransformText(kw.Term)); writer.WriteEndElement(); } // If this is the default topic and the NamedUrlIndex keywords // for DefaultPage and/or HomePage are not present, add them. if(this.IsDefaultTopic) { if(!keywords.Contains(new MSHelpKeyword("NamedUrlIndex", "DefaultPage"))) { writer.WriteStartElement("keyword"); writer.WriteAttributeString("index", "NamedUrlIndex"); writer.WriteValue("DefaultPage"); writer.WriteEndElement(); } if(!keywords.Contains(new MSHelpKeyword("NamedUrlIndex", "HomePage"))) { writer.WriteStartElement("keyword"); writer.WriteAttributeString("index", "NamedUrlIndex"); writer.WriteValue("HomePage"); writer.WriteEndElement(); } } writer.WriteEndElement(); // </topic> } // Write metadata for sub-topics too foreach(Topic t in subtopics) t.WriteMetadata(writer, builder); }
/// <summary> /// This is called to load an additional content file, resolve links to namespace content and copy it to /// the output folder. /// </summary> /// <param name="sourceFile">The source filename to copy</param> /// <param name="destFile">The destination filename</param> /// <param name="entry">The entry being resolved.</param> internal void ResolveLinksAndCopy(string sourceFile, string destFile, TocEntry entry) { Encoding enc = Encoding.Default; string content, script, syntaxFile; int pos; // For topics, change the extension back to ".topic". It's ".html" in the TOC as that's what it ends // up as after transformation. if (sourceFile.EndsWith(".topic", StringComparison.OrdinalIgnoreCase)) { destFile = Path.ChangeExtension(destFile, ".topic"); } this.ReportProgress("{0} -> {1}", sourceFile, destFile); // When reading the file, use the default encoding but detect the encoding if byte order marks are // present. content = BuildProcess.ReadWithEncoding(sourceFile, ref enc); // Expand <code> tags if necessary if (entry.HasCodeBlocks) { content = reCodeBlock.Replace(content, codeBlockMatchEval); } // Colorize <pre> tags if necessary if (entry.NeedsColorizing || entry.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"); entry.HasProjectTags = true; // Add the links to the colorizer style sheet and script files unless it's going to be // transformed. In which case, the links should be in the XSL style sheet. if (!sourceFile.EndsWith(".topic", StringComparison.OrdinalIgnoreCase) && !sourceFile.EndsWith(".xsl", StringComparison.OrdinalIgnoreCase)) { script = String.Format(CultureInfo.InvariantCulture, "<link type='text/css' rel='stylesheet' href='{0}styles/highlight.css' />" + "<script type='text/javascript' src='{0}scripts/highlight_ac.js'></script>", pathToRoot); pos = content.IndexOf("</head>", StringComparison.Ordinal); // Create a <head> section if one doesn't exist if (pos == -1) { script = "<head>" + script + "</head>"; pos = content.IndexOf("<html>", StringComparison.Ordinal); if (pos != -1) { pos += 6; } else { pos = 0; } } content = content.Insert(pos, script); } // Copy the colorizer files if not already there this.EnsureOutputFoldersExist("icons"); this.EnsureOutputFoldersExist("styles"); this.EnsureOutputFoldersExist("scripts"); foreach (string baseFolder in this.HelpFormatOutputFolders) { if (!File.Exists(baseFolder + @"styles\highlight.css")) { syntaxFile = baseFolder + @"styles\highlight.css"; File.Copy(ComponentUtilities.ToolsFolder + @"PresentationStyles\Colorizer\highlight.css", syntaxFile); File.SetAttributes(syntaxFile, FileAttributes.Normal); syntaxFile = baseFolder + @"scripts\highlight_ac.js"; File.Copy(ComponentUtilities.ToolsFolder + @"PresentationStyles\Colorizer\highlight_ac.js", syntaxFile); File.SetAttributes(syntaxFile, FileAttributes.Normal); // Always copy the image files, they may be different. Also, delete the destination file // first if it exists as the filename casing may be different. syntaxFile = baseFolder + @"icons\CopyCode.gif"; if (File.Exists(syntaxFile)) { File.SetAttributes(syntaxFile, FileAttributes.Normal); File.Delete(syntaxFile); } File.Copy(ComponentUtilities.ToolsFolder + @"PresentationStyles\Colorizer\CopyCode.gif", syntaxFile); File.SetAttributes(syntaxFile, FileAttributes.Normal); syntaxFile = baseFolder + @"icons\CopyCode_h.gif"; if (File.Exists(syntaxFile)) { File.SetAttributes(syntaxFile, FileAttributes.Normal); File.Delete(syntaxFile); } File.Copy(ComponentUtilities.ToolsFolder + @"PresentationStyles\Colorizer\CopyCode_h.gif", syntaxFile); File.SetAttributes(syntaxFile, FileAttributes.Normal); } } } // 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 (entry.HasLinks || entry.HasCodeBlocks) { content = reResolveLinks.Replace(content, linkMatchEval); } // Replace project option tags with project option values if (entry.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); } // Transform .topic files into .html files if (sourceFile.EndsWith(".topic", StringComparison.OrdinalIgnoreCase)) { this.XslTransform(destFile); } }
/// <summary> /// This is used to merge destination file information into the site /// map TOC. /// </summary> /// <param name="site">The site entry to update</param> /// <remarks>In addition, files in the site map that do not exist in /// the TOC built from the defined content will be processed and /// copied to the root folder.</remarks> private void MergeTocInfo(TocEntry site) { TocEntry match; string source, filename; if (site.SourceFile.Path.Length != 0) { match = toc.Find(site.SourceFile); if (match != null) { site.DestinationFile = match.DestinationFile; } else { source = site.SourceFile; site.DestinationFile = Path.GetFileName(source); filename = site.DestinationFile; // .topic files get transformed into .html files if (source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase)) { site.DestinationFile = Path.ChangeExtension(site.DestinationFile, ".html"); } // Check to see if anything needs resolving if (source.EndsWith(".htm", StringComparison.OrdinalIgnoreCase) || source.EndsWith(".html", StringComparison.OrdinalIgnoreCase) || source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase)) { match = BuildProcess.GetTocInfo(source); } foreach (string baseFolder in this.HelpFormatOutputFolders) { // If the file contains items that need to be resolved, it is handled separately if (match != null && (match.HasLinks || match.HasCodeBlocks || match.NeedsColorizing || match.HasProjectTags || source.EndsWith(".topic", StringComparison.OrdinalIgnoreCase))) { // Files are always copied to the root pathToRoot = String.Empty; this.ResolveLinksAndCopy(source, baseFolder + filename, match); } else { this.ReportProgress("{0} -> {1}{2}", source, baseFolder, filename); // All attributes are turned off so that we can delete it later File.Copy(source, baseFolder + filename, true); File.SetAttributes(baseFolder + filename, FileAttributes.Normal); } } } } if (site.Children.Count != 0) { foreach (TocEntry entry in site.Children) { this.MergeTocInfo(entry); } } }
/// <summary> /// This is 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> /// Create a full-text index from web pages found in the specified file path /// </summary> /// <param name="filePath">The path containing the files to index</param> /// <remarks>Words in the exclusion list, those that are less than three characters long, and anything /// starting with a digit will not appear in the index.</remarks> public void CreateFullTextIndex(string filePath) { Dictionary <string, int> wordCounts = new Dictionary <string, int>(); Encoding enc = Encoding.Default; Match m; string content, fileInfo, title; string[] words; int rootPathLength; if (filePath[filePath.Length - 1] == '\\') { rootPathLength = filePath.Length; } else { rootPathLength = filePath.Length + 1; } foreach (string name in Directory.EnumerateFiles(filePath, "*.htm?", SearchOption.AllDirectories)) { content = BuildProcess.ReadWithEncoding(name, ref enc); // Extract the page title m = rePageTitle.Match(content); if (!m.Success) { title = Path.GetFileNameWithoutExtension(name); } else { title = m.Groups["Title"].Value.Trim(); } // Put some space between tags content = content.Replace("><", "> <"); // Remove script, style sheet, and head blocks as they won't contain any usable keywords. Pre // tags contain code which may or may not be useful but we'll leave them alone for now. content = reStripScriptStyleHead.Replace(content, " "); // Remove all HTML tags content = reStripTags.Replace(content, " "); // Decode the text content = HttpUtility.HtmlDecode(content); // Strip apostrophe suffixes content = reStripApos.Replace(content, String.Empty); // Condense all runs of whitespace to a single space content = reCondenseWS.Replace(content, " "); // Convert to lowercase and split text on non-word boundaries words = reSplitWords.Split(content.ToLower(lang)); // We're going to use simple types for the index structure so that we don't have to deploy an // assembly to deserialize it. As such, concatenate the title, filename, and its word count // into a string separated by nulls. Note that file paths are assumed to be relative to the // root folder. fileInfo = String.Join("\x0", new string[] { title, name.Substring(rootPathLength).Replace('\\', '/'), words.Length.ToString(CultureInfo.InvariantCulture) }); wordCounts.Clear(); // Get a list of all unique words and the number of time that they appear in this file. // Exclude words that are less than three characters in length, start with a digit, or // are in the common words exclusion list. foreach (string word in words) { if (word.Length < 3 || Char.IsDigit(word[0]) || exclusionWords.Contains(word)) { continue; } // The number of times it occurs helps determine the ranking of the search results if (wordCounts.ContainsKey(word)) { wordCounts[word] += 1; } else { wordCounts.Add(word, 1); } } // Shouldn't happen but just in case, ignore files with no usable words if (wordCounts.Keys.Count != 0) { fileList.Add(fileInfo); // Add the index information to the word dictionary foreach (string word in wordCounts.Keys) { // For each unique word, we'll track the files in which it occurs and the number // of times it occurs in each file. if (!wordDictionary.ContainsKey(word)) { wordDictionary.Add(word, new List <long>()); } // Store the file index in the upper part of a 64-bit integer and the word count // in the lower 16-bits. More room is given to the file count as some builds // contain a large number of topics. wordDictionary[word].Add(((long)(fileList.Count - 1) << 16) + (long)(wordCounts[word] & 0xFFFF)); } } } }
/// <summary> /// This is used to clean up the MS Help 2 collection files so that /// they are ready for use in registering the collection. /// </summary> private void CleanUpCollectionFiles() { XmlDocument document; XmlNode node; string extension, toc, iniFile; this.ReportProgress("Cleaning up collection files..."); foreach (string file in Directory.GetFiles(outputFolder, project.HtmlHelpName + "*.Hx?")) { extension = Path.GetExtension(file).ToLower( CultureInfo.InvariantCulture); switch (extension) { case ".hxc": document = BuildProcess.OpenCollectionFile(file); // Remove the compiler options node = document.SelectSingleNode( "HelpCollection/CompilerOptions"); if (node != null) { node.ParentNode.RemoveChild(node); } break; case ".hxt": // We don't need the whole TOC so recreate it from // this string. toc = this.TransformText( "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" + "<!DOCTYPE HelpTOC>\r\n" + "<HelpTOC DTDVersion=\"1.0\" LangId=\"{@LangId}\" " + "PluginStyle=\"{@CollectionTocStyle}\" " + "PluginTitle=\"{@HtmlEncHelpTitle}\">\r\n" + " <HelpTOCNode NodeType=\"TOC\" " + "Url=\"{@HTMLEncHelpName}\" />\r\n" + "</HelpTOC>\r\n"); document = new XmlDocument(); document.LoadXml(toc); break; case ".hxk": document = BuildProcess.OpenCollectionFile(file); break; default: // Ignore it (i.e. .HXS) document = null; break; } if (document != null) { document.Save(file); } } this.ReportProgress("Creating H2Reg.ini file..."); iniFile = outputFolder + project.HtmlHelpName + "_H2Reg.ini"; if (File.Exists(iniFile)) { File.Delete(iniFile); } this.TransformTemplate("Help2x_H2Reg.ini", templateFolder, outputFolder); File.Move(outputFolder + "Help2x_H2Reg.ini", iniFile); }
//===================================================================== /// <summary> /// This performs the requested task /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void actionThread_DoWork(object sender, DoWorkEventArgs e) { ThreadAction action = (ThreadAction)e.Argument; string arguments, contentSetupFile; int errorCode; runningThread = Thread.CurrentThread; try { HelpLibraryManager hlm = new HelpLibraryManager(viewerVersion); // Remove old content. We'll remove it if installing to be sure that the latest copy is // installed. if(action == ThreadAction.Install || action == ThreadAction.Remove) { if(action == ThreadAction.Install) actionThread.ReportProgress(0, (int)ThreadState.RemovingOldContent); else actionThread.ReportProgress(0, (int)ThreadState.RemovingContent); if(viewerVersion.Major == 1) arguments = String.Format(CultureInfo.InvariantCulture, "/product \"{0}\" /version \"{1}\" /locale {2} /uninstall /silent /vendor " + "\"{3}\" /productName \"{4}\" /mediaBookList \"{5}\"", project.CatalogProductId, project.CatalogVersion, project.Language.Name, !String.IsNullOrEmpty(project.VendorName) ? project.VendorName : "Vendor Name", !String.IsNullOrEmpty(project.ProductTitle) ? project.ProductTitle : project.HelpTitle, project.HelpTitle); else arguments = String.Format(CultureInfo.InvariantCulture, "/catalogName \"{0}\" /locale {1} /wait 0 /operation uninstall /vendor \"{2}\" " + "/productName \"{3}\" /bookList \"{4}\" ", catalogName, project.Language.Name, !String.IsNullOrEmpty(project.VendorName) ? project.VendorName : "Vendor Name", !String.IsNullOrEmpty(project.ProductTitle) ? project.ProductTitle : project.HelpTitle, project.HelpTitle); // If there are substitution tags present, have a go at resolving them if(arguments.IndexOf("{@", StringComparison.Ordinal) != -1) { try { var bp = new BuildProcess(project); arguments = bp.SubstitutionTags.TransformText(arguments); } catch(Exception ex) { throw new InvalidOperationException("Unable to transform substitution tags: " + ex.Message, ex); } } // This doesn't have to run as an administrator errorCode = hlm.RunAsNormalUser(arguments, ProcessWindowStyle.Minimized); // Ignore it if not found and we are installing if(errorCode != HelpLibraryManagerException.Success && (errorCode != HelpLibraryManagerException.NoBooksToInstall || action == ThreadAction.Remove)) throw new HelpLibraryManagerException(viewerVersion, errorCode); } if(action == ThreadAction.Install) { // Install the new content actionThread.ReportProgress(0, (int)ThreadState.InstallingContent); // Copy the MSHA file to the required name contentSetupFile = Path.Combine(Path.GetDirectoryName(setupFile), "HelpContentSetup.msha"); File.Copy(setupFile, contentSetupFile, true); if(viewerVersion.Major == 1) arguments = String.Format(CultureInfo.InvariantCulture, "/product \"{0}\" " + "/version \"{1}\" /locale {2} /sourceMedia \"{3}\"", project.CatalogProductId, project.CatalogVersion, project.Language.Name, contentSetupFile); else arguments = String.Format(CultureInfo.InvariantCulture, "/catalogName \"{0}\" " + "/locale {1} /wait 0 /operation install /sourceUri \"{2}\"", catalogName, project.Language.Name, contentSetupFile); // Always interactive and must run as administrator. We can't run silently as we don't have // a signed cabinet file. errorCode = hlm.RunAsAdministrator(arguments, ProcessWindowStyle.Normal); if(errorCode != HelpLibraryManagerException.Success) throw new HelpLibraryManagerException(viewerVersion, errorCode); // Open it if installed successfully action = ThreadAction.OpenCurrent; } if(action == ThreadAction.OpenCurrent) { arguments = null; if(msHelpViewer == null) { msHelpViewer = hlm.HelpViewerPath; if(msHelpViewer == null) msHelpViewer = "ms-xhelp:///?method=page&id=-1"; else if(viewerVersion.Major == 2) arguments = "/catalogname \"" + catalogName + "\""; } actionThread.ReportProgress(0, (int)ThreadState.OpeningContent); System.Diagnostics.Process.Start(msHelpViewer, arguments); } if(action == ThreadAction.OpenContentManager) { actionThread.ReportProgress(0, (int)ThreadState.OpeningContentManager); // Can't do anything if the Help Library Manager is not installed if(hlm.HelpLibraryManagerPath == null) throw new HelpLibraryManagerException(viewerVersion, HelpLibraryManagerException.HelpLibraryManagerNotFound); if(viewerVersion.Major == 1) hlm.LaunchInteractive(String.Format(CultureInfo.InvariantCulture, "/product \"{0}\" /version \"{1}\" /locale {2}", project.CatalogProductId, project.CatalogVersion, project.Language.Name)); else hlm.LaunchInteractive(String.Format(CultureInfo.InvariantCulture, "/catalogName \"{0}\" /locale {1} /manage", catalogName, project.Language.Name)); } } catch(ThreadAbortException) { // Ignore thread abort exceptions } }
/// <summary> /// This is used to create the companion file used by the build /// component that resolves conceptual links. /// </summary> /// <param name="folder">The folder in which to place the file</param> /// <param name="builder">The build process</param> /// <remarks>The file will be named using the ID and a ".xml" /// extension.</remarks> internal void WriteCompanionFile(string folder, BuildProcess builder) { string linkElement = String.Empty; // MS Help Viewer doesn't support empty place holders so we automatically // generate a dummy place holder file for them. if(!noFile || (builder.CurrentProject.HelpFileFormat & HelpFileFormat.MSHelpViewer) != 0) { // Link text is optional if(!String.IsNullOrEmpty(linkText)) linkElement = String.Format(CultureInfo.InvariantCulture, " <linkText>{0}</linkText>\r\n", HttpUtility.HtmlEncode(linkText)); // It's small enough that we'll just write it out as a string // rather than using an XML writer. using(StreamWriter sw = new StreamWriter(Path.Combine(folder, this.Id + ".cmp.xml"), false, Encoding.UTF8)) { sw.WriteLine( "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" + "<metadata>\r\n" + " <topic id=\"{0}\">\r\n" + " <title>{1}</title>\r\n" + "{2}" + " </topic>\r\n" + "</metadata>\r\n", this.Id, HttpUtility.HtmlEncode(this.DisplayTitle), linkElement); } } foreach(Topic t in subtopics) t.WriteCompanionFile(folder, builder); }
/// <summary> /// This is used to transform a *.topic file into a *.html file using an XSLT transformation based on the /// presentation style. /// </summary> /// <param name="sourceFile">The source topic filename</param> private void XslTransform(string sourceFile) { TocEntry tocInfo; XmlReader reader = null; XmlWriter writer = null; XsltSettings settings; XmlReaderSettings readerSettings; XmlWriterSettings writerSettings; Encoding enc = Encoding.Default; FileItemCollection transforms; string content; string sourceStylesheet, destFile = Path.ChangeExtension(sourceFile, ".html"); try { readerSettings = new XmlReaderSettings(); readerSettings.CloseInput = true; readerSettings.DtdProcessing = DtdProcessing.Parse; // Create the transform on first use if (xslTransform == null) { transforms = new FileItemCollection(project, BuildAction.TopicTransform); if (transforms.Count != 0) { if (transforms.Count > 1) { this.ReportWarning("BE0011", "Multiple topic transformations found. Using '{0}'", transforms[0].FullPath); } sourceStylesheet = transforms[0].FullPath; } else { sourceStylesheet = templateFolder + project.PresentationStyle + ".xsl"; } xslStylesheet = workingFolder + Path.GetFileName(sourceStylesheet); tocInfo = BuildProcess.GetTocInfo(sourceStylesheet); // The style sheet may contain shared content items so we must resolve it this way rather // than using TransformTemplate. this.ResolveLinksAndCopy(sourceStylesheet, xslStylesheet, tocInfo); xslTransform = new XslCompiledTransform(); settings = new XsltSettings(true, true); xslArguments = new XsltArgumentList(); xslTransform.Load(XmlReader.Create(xslStylesheet, readerSettings), settings, new XmlUrlResolver()); } this.ReportProgress("Applying XSL transformation '{0}' to '{1}'.", xslStylesheet, sourceFile); reader = XmlReader.Create(sourceFile, readerSettings); writerSettings = xslTransform.OutputSettings.Clone(); writerSettings.CloseOutput = true; writerSettings.Indent = false; writer = XmlWriter.Create(destFile, writerSettings); xslArguments.Clear(); xslArguments.AddParam("pathToRoot", String.Empty, pathToRoot); xslTransform.Transform(reader, xslArguments, writer); } catch (Exception ex) { throw new BuilderException("BE0017", String.Format(CultureInfo.CurrentCulture, "Unexpected error using '{0}' to transform additional content file '{1}' to '{2}'. The " + "error is: {3}\r\n{4}", xslStylesheet, sourceFile, destFile, ex.Message, (ex.InnerException == null) ? String.Empty : ex.InnerException.Message)); } finally { if (reader != null) { reader.Close(); } if (writer != null) { writer.Flush(); writer.Close(); } } // The source topic file is deleted as the transformed file takes its place File.Delete(sourceFile); // <span> and <script> tags cannot be self-closing if empty. The template may contain them correctly // but when written out as XML, they get converted to self-closing tags which breaks them. To fix // them, convert them to full start and close tags. content = BuildProcess.ReadWithEncoding(destFile, ref enc); content = reSpanScript.Replace(content, "<$1$2></$1>"); // An XSL transform might have added tags and include items that need replacing so run it through // those options if needed. tocInfo = BuildProcess.GetTocInfo(destFile); // Expand <code> tags if necessary if (tocInfo.HasCodeBlocks) { content = reCodeBlock.Replace(content, codeBlockMatchEval); } // Colorize <pre> tags if necessary if (tocInfo.NeedsColorizing || tocInfo.HasCodeBlocks) { // Initialize code colorizer on first use if (codeColorizer == null) { codeColorizer = new CodeColorizer(ComponentUtilities.ToolsFolder + @"PresentationStyles\Colorizer\highlight.xml", ComponentUtilities.ToolsFolder + @"PresentationStyles\Colorizer\highlight.xsl"); } // Set the path the "Copy" image codeColorizer.CopyImageUrl = pathToRoot + "icons/CopyCode.gif"; // Colorize it and replace the "Copy" literal text with the shared content include item so that // it gets localized. content = codeColorizer.ProcessAndHighlightText(content); content = content.Replace(codeColorizer.CopyText + "</span", "<include item=\"copyCode\"/></span"); tocInfo.HasProjectTags = true; } // Use a regular expression to find and replace all tags with cref attributes with a link to the help // file content. This needs to happen after the code block processing as they may contain <see> tags // that need to be resolved. if (tocInfo.HasLinks || tocInfo.HasCodeBlocks) { content = reResolveLinks.Replace(content, linkMatchEval); } // Replace project option tags with project option values if (tocInfo.HasProjectTags) { // Project tags can be nested while (reProjectTags.IsMatch(content)) { content = reProjectTags.Replace(content, fieldMatchEval); } // Shared content items can be nested while (reSharedContent.IsMatch(content)) { content = reSharedContent.Replace(content, contentMatchEval); } } // Write the file back out with the appropriate encoding using (StreamWriter sw = new StreamWriter(destFile, false, enc)) { sw.Write(content); } }
/// <summary> /// This method is used to initialize the plug-in at the start of the /// build process. /// </summary> /// <param name="buildProcess">A reference to the current build /// process.</param> /// <param name="configuration">The configuration data that the plug-in /// should use to initialize itself.</param> public void Initialize(BuildProcess buildProcess, XPathNavigator configuration) { builder = buildProcess; builder.ReportProgress("{0} Version {1}\r\n{2}", this.Name, this.Version, this.Copyright); exclusionList = new List<string>(); }
//===================================================================== /// <summary> /// This is used to start the background build process from which /// we will get the information to load the tree view. /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void ApiFilterEditorDlg_Load(object sender, EventArgs e) { string tempPath; tvApiList.Enabled = splitContainer.Panel2.Enabled = btnReset.Enabled = false; try { // Clone the project for the build and adjust its properties // for our needs. tempProject = new SandcastleProject(apiFilter.Project, true); // The temporary project resides in the same folder as the // current project (by filename only, it isn't saved) to // maintain relative paths. However, build output is stored // in a temporary folder and it keeps the intermediate files. tempProject.CleanIntermediates = false; tempPath = Path.GetTempFileName(); File.Delete(tempPath); tempPath = Path.Combine(Path.GetDirectoryName(tempPath), "SHFBPartialBuild"); if(!Directory.Exists(tempPath)) Directory.CreateDirectory(tempPath); tempProject.OutputPath = tempPath; buildProcess = new BuildProcess(tempProject, true); // We must suppress the current API filter for this build buildProcess.SuppressApiFilter = true; buildProcess.BuildStepChanged += new EventHandler<BuildProgressEventArgs>( buildProcess_BuildStepChanged); buildProcess.BuildProgress += new EventHandler<BuildProgressEventArgs>( buildProcess_BuildProgress); buildThread = new Thread(new ThreadStart(buildProcess.Build)); buildThread.Name = "API fitler partial build thread"; buildThread.IsBackground = true; buildThread.Start(); } catch(Exception ex) { System.Diagnostics.Debug.WriteLine(ex.ToString()); MessageBox.Show("Unable to build project to obtain " + "API information. Error: " + ex.Message, Constants.AppName, MessageBoxButtons.OK, MessageBoxIcon.Error); } }
/// <inheritdoc/> public void Initialize(BuildProcess buildProcess, XPathNavigator configuration) { builder = buildProcess; var metadata = (HelpFileBuilderPlugInExportAttribute)this.GetType().GetCustomAttributes( typeof(HelpFileBuilderPlugInExportAttribute), false).First(); builder.ReportProgress("{0} Version {1}\r\n{2}", metadata.Id, metadata.Version, metadata.Copyright); }
/// <summary> /// This method is used to initialize the plug-in at the start of the build process /// </summary> /// <param name="buildProcess">A reference to the current build process</param> /// <param name="configuration">The configuration data that the plug-in should use to initialize itself</param> public void Initialize(BuildProcess buildProcess, XPathNavigator configuration) { XPathNavigator root, node; builder = buildProcess; var metadata = (HelpFileBuilderPlugInExportAttribute)this.GetType().GetCustomAttributes( typeof(HelpFileBuilderPlugInExportAttribute), false).First(); builder.ReportProgress("{0} Version {1}\r\n{2}", metadata.Id, metadata.Version, metadata.Copyright); root = configuration.SelectSingleNode("configuration"); if(root.IsEmptyElement) throw new BuilderException("DFP0001", "The DBCS Fix plug-in has not been configured yet"); node = root.SelectSingleNode("sbAppLocale"); if(node != null) sbAppLocalePath = node.GetAttribute("path", String.Empty).Trim(); if(String.IsNullOrWhiteSpace(sbAppLocalePath)) { builder.ReportWarning("DFP0002", "A path to the Steel Bytes App Locale tool was not specified " + "and it will not be used for this build."); } else { // If relative, the path is relative to the project folder sbAppLocalePath = FilePath.RelativeToAbsolutePath(builder.ProjectFolder, builder.TransformText(sbAppLocalePath)); if(!File.Exists(sbAppLocalePath)) throw new BuilderException("DFP0003", "Unable to locate SBAppLocale tool at " + sbAppLocalePath); } // If not building HTML Help 1, there's nothing to do if((builder.CurrentProject.HelpFileFormat & HelpFileFormats.HtmlHelp1) == 0) { executionPoints.Clear(); builder.ReportWarning("DFP0007", "An HTML Help 1 file is not being built. This plug-in will " + "not be ran."); } }
/// <summary> /// This method is used to initialize the plug-in at the start of the build process /// </summary> /// <param name="buildProcess">A reference to the current build process</param> /// <param name="configuration">The configuration data that the plug-in should use to initialize itself</param> /// <exception cref="BuilderException">This is thrown if the plug-in configuration is not valid</exception> public void Initialize(BuildProcess buildProcess, XPathNavigator configuration) { XPathNavigator root, msHelpViewer; string value; builder = buildProcess; var metadata = (HelpFileBuilderPlugInExportAttribute)this.GetType().GetCustomAttributes( typeof(HelpFileBuilderPlugInExportAttribute), false).First(); builder.ReportProgress("{0} Version {1}\r\n{2}", metadata.Id, metadata.Version, metadata.Copyright); root = configuration.SelectSingleNode("configuration"); value = root.GetAttribute("deleteAfterDeploy", String.Empty); if(!String.IsNullOrEmpty(value)) deleteAfterDeploy = Convert.ToBoolean(value, CultureInfo.InvariantCulture); if(root.IsEmptyElement) throw new BuilderException("ODP0001", "The Output Deployment plug-in has not been " + "configured yet"); deployHelp1 = DeploymentLocation.FromXPathNavigator(root, "help1x"); deployHelp2 = DeploymentLocation.FromXPathNavigator(root, "help2x"); deployHelpViewer = DeploymentLocation.FromXPathNavigator(root, "helpViewer"); deployWebsite = DeploymentLocation.FromXPathNavigator(root, "website"); deployOpenXml = DeploymentLocation.FromXPathNavigator(root, "openXml"); msHelpViewer = root.SelectSingleNode("deploymentLocation[@id='helpViewer']"); if(msHelpViewer == null || !Boolean.TryParse(msHelpViewer.GetAttribute("renameMSHA", String.Empty).Trim(), out renameMSHA)) renameMSHA = false; // At least one deployment location must be defined if(deployHelp1.Location == null && deployHelp2.Location == null && deployHelpViewer.Location == null && deployWebsite.Location == null && deployOpenXml.Location == null) throw new BuilderException("ODP0002", "The output deployment plug-in must have at least " + "one configured deployment location"); // Issue a warning if the deployment location is null and the associated help file format is active if(deployHelp1.Location == null && (builder.CurrentProject.HelpFileFormat & HelpFileFormats.HtmlHelp1) != 0) builder.ReportWarning("ODP0003", "HTML Help 1 will be generated but not deployed due to " + "missing deployment location information"); if(deployHelp2.Location == null && (builder.CurrentProject.HelpFileFormat & HelpFileFormats.MSHelp2) != 0) builder.ReportWarning("ODP0003", "MS Help 2 will be generated but not deployed due to " + "missing deployment location information"); if(deployHelpViewer.Location == null && (builder.CurrentProject.HelpFileFormat & HelpFileFormats.MSHelpViewer) != 0) builder.ReportWarning("ODP0003", "MS Help Viewer will be generated but not deployed due " + "to missing deployment location information"); if(deployWebsite.Location == null && (builder.CurrentProject.HelpFileFormat & HelpFileFormats.Website) != 0) builder.ReportWarning("ODP0003", "Website will be generated but not deployed due to " + "missing deployment location information"); if(deployOpenXml.Location == null && (builder.CurrentProject.HelpFileFormat & HelpFileFormats.OpenXml) != 0) builder.ReportWarning("ODP0003", "Open XML will be generated but not deployed due to " + "missing deployment location information"); }
//===================================================================== /// <summary> /// This is used to copy the additional content token, image, and topic files to the build folder /// </summary> /// <param name="builder">The build process</param> /// <remarks>This will copy the code snippet file if specified, save token information to a shared /// content file called <strong>_Tokens_.xml</strong> in the build process's working folder, copy the /// image files to the <strong>.\media</strong> folder in the build process's working folder, save the /// media map to a file called <strong>_MediaContent_.xml</strong> in the build process's working folder, /// and save the topic files to the <strong>.\ddueXml</strong> folder in the build process's working /// folder. The topic files will have their content wrapped in a <c><topic></c> tag if needed and /// will be named using their <see cref="Topic.Id" /> value.</remarks> public void CopyContentFiles(BuildProcess builder) { string folder; bool missingFile = false; builder.ReportProgress("Copying standard token shared content file..."); builder.SubstitutionTags.TransformTemplate("HelpFileBuilderTokens.tokens", builder.TemplateFolder, builder.WorkingFolder); builder.ReportProgress("Checking for other token files..."); foreach(var tokenFile in this.tokenFiles) if(!File.Exists(tokenFile.FullPath)) { missingFile = true; builder.ReportProgress(" Missing token file: {0}", tokenFile.FullPath); } else { builder.ReportProgress(" {0} -> {1}", tokenFile.FullPath, Path.Combine(builder.WorkingFolder, Path.GetFileName(tokenFile.FullPath))); builder.SubstitutionTags.TransformTemplate(Path.GetFileName(tokenFile.FullPath), Path.GetDirectoryName(tokenFile.FullPath), builder.WorkingFolder); } if(missingFile) throw new BuilderException("BE0052", "One or more token files could not be found"); builder.ReportProgress("Checking for code snippets files..."); foreach(var snippetsFile in this.codeSnippetFiles) if(!File.Exists(snippetsFile.FullPath)) { missingFile = true; builder.ReportProgress(" Missing code snippets file: {0}", snippetsFile.FullPath); } else builder.ReportProgress(" Found {0}", snippetsFile.FullPath); if(missingFile) throw new BuilderException("BE0053", "One or more code snippets files could not be found"); // Save the image info to a shared content file and copy the image files to the working folder folder = builder.WorkingFolder + "media"; if(!Directory.Exists(folder)) Directory.CreateDirectory(folder); // Create the build process's help format output folders too if needed builder.EnsureOutputFoldersExist("media"); builder.ReportProgress("Copying images and creating the media map file..."); // Copy all image project items and create the content file this.SaveImageSharedContent(builder.WorkingFolder + "_MediaContent_.xml", folder, builder); // Copy the topic files folder = builder.WorkingFolder + "ddueXml"; if(!Directory.Exists(folder)) Directory.CreateDirectory(folder); builder.ReportProgress("Generating conceptual topic files"); // Get the list of valid framework namespaces for the referenced namespace search in each topic HashSet<string> validNamespaces = new HashSet<string>(Directory.EnumerateFiles(builder.FrameworkReflectionDataFolder, "*.xml", SearchOption.AllDirectories).Select(f => Path.GetFileNameWithoutExtension(f))); // Create topic files foreach(TopicCollection tc in topics) { tc.Load(); tc.GenerateConceptualTopics(folder, builder, validNamespaces); } }
/// <summary> /// Write out the topic metadata /// </summary> /// <param name="writer">The writer to which the metadata is written</param> /// <param name="builder">The build process</param> /// <remarks>This will recursively write out metadata for sub-topics /// as well.</remarks> internal void WriteMetadata(XmlWriter writer, BuildProcess builder) { // MS Help Viewer doesn't support empty place holders so we automatically generate a dummy place // holder file for them. if(!noFile || (builder.CurrentProject.HelpFileFormat & HelpFileFormats.MSHelpViewer) != 0) { writer.WriteStartElement("topic"); writer.WriteAttributeString("id", this.Id); writer.WriteAttributeString("revisionNumber", this.RevisionNumber.ToString(CultureInfo.InvariantCulture)); // Write out the help file version project property value writer.WriteStartElement("item"); writer.WriteAttributeString("id", "PBM_FileVersion"); writer.WriteValue(builder.SubstitutionTags.TransformText(builder.CurrentProject.HelpFileVersion)); writer.WriteEndElement(); // If no title is specified, use the display title writer.WriteStartElement("title"); if(!String.IsNullOrEmpty(title)) writer.WriteString(title); else writer.WriteString(this.DisplayTitle); writer.WriteEndElement(); // TOC title is optional if(!String.IsNullOrEmpty(tocTitle)) writer.WriteElementString("tableOfContentsTitle", tocTitle); // Add topic-specific index keywords foreach(MSHelpKeyword kw in keywords) { writer.WriteStartElement("keyword"); writer.WriteAttributeString("index", kw.Index); // Replace tags with their project property value writer.WriteValue(builder.SubstitutionTags.TransformText(kw.Term)); writer.WriteEndElement(); } writer.WriteEndElement(); // </topic> } // Write metadata for sub-topics too foreach(Topic t in subtopics) t.WriteMetadata(writer, builder); }
/// <summary> /// Write the image reference collection to a map file ready for use by <strong>BuildAssembler</strong> /// </summary> /// <param name="filename">The file to which the image reference collection is saved</param> /// <param name="imagePath">The path to which the image files should be copied</param> /// <param name="builder">The build process</param> /// <remarks>Images with their <see cref="ImageReference.CopyToMedia" /> property set to true are copied /// to the media folder immediately.</remarks> public void SaveImageSharedContent(string filename, string imagePath, BuildProcess builder) { XmlWriterSettings settings = new XmlWriterSettings(); XmlWriter writer = null; string destFile; builder.EnsureOutputFoldersExist("media"); try { settings.Indent = true; settings.CloseOutput = true; writer = XmlWriter.Create(filename, settings); writer.WriteStartDocument(); // There are normally some attributes on this element but they aren't used by Sandcastle so we'll // ignore them. writer.WriteStartElement("stockSharedContentDefinitions"); foreach(var ir in imageFiles) { writer.WriteStartElement("item"); writer.WriteAttributeString("id", ir.Id); writer.WriteStartElement("image"); // The art build component assumes everything is in a single, common folder. The build // process will ensure that happens. As such, we only need the filename here. writer.WriteAttributeString("file", ir.Filename); if(!String.IsNullOrEmpty(ir.AlternateText)) { writer.WriteStartElement("altText"); writer.WriteValue(ir.AlternateText); writer.WriteEndElement(); } writer.WriteEndElement(); // </image> writer.WriteEndElement(); // </item> destFile = Path.Combine(imagePath, ir.Filename); if(File.Exists(destFile)) builder.ReportWarning("BE0010", "Image file '{0}' already exists. It will be replaced " + "by '{1}'.", destFile, ir.FullPath); builder.ReportProgress(" {0} -> {1}", ir.FullPath, destFile); // The attributes are set to Normal so that it can be deleted after the build File.Copy(ir.FullPath, destFile, true); File.SetAttributes(destFile, FileAttributes.Normal); // Copy it to the output media folders if CopyToMedia is true if(ir.CopyToMedia) foreach(string baseFolder in builder.HelpFormatOutputFolders) { destFile = Path.Combine(baseFolder + "media", ir.Filename); builder.ReportProgress(" {0} -> {1} (Always copied)", ir.FullPath, destFile); File.Copy(ir.FullPath, destFile, true); File.SetAttributes(destFile, FileAttributes.Normal); } } writer.WriteEndElement(); // </stockSharedContentDefinitions> writer.WriteEndDocument(); } finally { if(writer != null) writer.Close(); } }
/// <summary> /// Build the help file using the current project settings /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private async void miBuildProject_Click(object sender, EventArgs e) { if(project == null || !this.SaveBeforeBuild()) return; miClearOutput_Click(miBuildProject, e); this.SetUIEnabledState(false); Application.DoEvents(); StatusBarTextProvider.InitializeProgressBar(0, (int)BuildStep.Completed, "Building help file"); try { cancellationTokenSource = new CancellationTokenSource(); buildProcess = new BuildProcess(project) { ProgressReportProvider = new Progress<BuildProgressEventArgs>(buildProcess_ReportProgress), CancellationToken = cancellationTokenSource.Token, }; await Task.Run(() => buildProcess.Build(), cancellationTokenSource.Token); } finally { if(cancellationTokenSource != null) { cancellationTokenSource.Dispose(); cancellationTokenSource = null; } StatusBarTextProvider.ResetProgressBar(); this.SetUIEnabledState(true); outputWindow.LogFile = buildProcess.LogFilename; if(buildProcess.CurrentBuildStep == BuildStep.Completed && Settings.Default.OpenHelpAfterBuild) miViewHelpFile.PerformClick(); buildProcess = null; } }
/// <summary> /// This is used to create the conceptual content build configuration files /// </summary> /// <param name="builder">The build process</param> /// <remarks>This will create the companion files used to resolve conceptual links and the /// <strong>_ContentMetadata_.xml</strong> configuration file. It will also merge the conceptual topics /// into the BuildAssembler manifest file.</remarks> public void CreateConfigurationFiles(BuildProcess builder) { this.CreateCompanionFiles(builder); this.CreateContentMetadata(builder); this.MergeConceptualManifest(builder); }
/// <summary> /// This method is used to initialize the plug-in at the start of the build process /// </summary> /// <param name="buildProcess">A reference to the current build process</param> /// <param name="configuration">The configuration data that the plug-in should use to initialize itself</param> public void Initialize(BuildProcess buildProcess, XPathNavigator configuration) { builder = buildProcess; var metadata = (HelpFileBuilderPlugInExportAttribute)this.GetType().GetCustomAttributes( typeof(HelpFileBuilderPlugInExportAttribute), false).First(); builder.ReportProgress("{0} Version {1}\r\n{2}\r\n This build will only include additional " + "content items.", metadata.Id, metadata.Version, metadata.Copyright); if(!builder.CurrentProject.HasItems(BuildAction.ContentLayout) && !builder.CurrentProject.HasItems(BuildAction.SiteMap) && !builder.CurrentProject.HasItems(BuildAction.Content)) throw new BuilderException("ACP0001", "The Additional Content Only plug-in requires a " + "conceptual content layout file, a site map file, or content items in the project."); }
/// <summary> /// This is used to create the companion files used to resolve conceptual links /// </summary> /// <param name="builder">The build process</param> private void CreateCompanionFiles(BuildProcess builder) { string destFolder = builder.WorkingFolder + "xmlComp\\"; builder.ReportProgress(" Companion files"); if(!Directory.Exists(destFolder)) Directory.CreateDirectory(destFolder); foreach(TopicCollection tc in topics) foreach(Topic t in tc) t.WriteCompanionFile(destFolder, builder); }
/// <summary> /// This method is used to initialize the plug-in at the start of the /// build process. /// </summary> /// <param name="buildProcess">A reference to the current build /// process.</param> /// <param name="configuration">The configuration data that the plug-in /// should use to initialize itself.</param> public void Initialize(BuildProcess buildProcess, XPathNavigator configuration) { builder = buildProcess; builder.ReportProgress("{0} Version {1}\r\n{2}\r\n", this.Name, this.Version, this.Copyright); expressions = new List<string>(); foreach(XPathNavigator nav in configuration.Select( "configuration/expressions/expression")) expressions.Add(nav.InnerXml); if(expressions.Count == 0) throw new BuilderException("XRF0001", "No queries have been " + "defined for the XPath Reflection File Filter plug-in"); }
/// <summary> /// Create the content metadata file /// </summary> /// <param name="builder">The build process</param> /// <remarks>The content metadata file contains metadata information for each topic such as its title, /// table of contents title, help attributes, and index keywords. Help attributes are a combination /// of the project-level help attributes and any parsed from the topic file. Any replacement tags in /// the token values will be replaced with the appropriate project values. /// /// <p/>A true MAML version of this file contains several extra attributes. Since Sandcastle doesn't use /// them, I'm not going to waste time adding them. The only stuff written is what is required by /// Sandcastle. In addition, I'm putting the <c>title</c> and <c>PBM_FileVersion</c> item elements in /// here rather than use the separate companion files. They all end up in the metadata section of the /// topic being built so this saves having two extra components in the configuration that do the same /// thing with different files.</remarks> private void CreateContentMetadata(BuildProcess builder) { XmlWriterSettings settings = new XmlWriterSettings(); XmlWriter writer = null; builder.ReportProgress(" _ContentMetadata_.xml"); try { settings.Indent = true; settings.CloseOutput = true; writer = XmlWriter.Create(builder.WorkingFolder + "_ContentMetadata_.xml", settings); writer.WriteStartDocument(); writer.WriteStartElement("metadata"); // Write out each topic and all of its sub-topics foreach(TopicCollection tc in topics) foreach(Topic t in tc) t.WriteMetadata(writer, builder); writer.WriteEndElement(); // </metadata> writer.WriteEndDocument(); } finally { if(writer != null) writer.Close(); } }
/// <summary> /// This is used to determine the state of the help content and set the form options when a help viewer /// version is selected. /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void cboHelpViewerVersion_SelectedIndexChanged(object sender, EventArgs e) { txtInfo.Text = null; grpOptions.Enabled = rbInstall.Enabled = true; lastVersionSelected = cboHelpViewerVersion.SelectedIndex; // If there are substitution tags present, have a go at resolving them if(helpFilePath.IndexOf("{@", StringComparison.Ordinal) != -1) { try { var bp = new SandcastleBuilder.Utils.BuildEngine.BuildProcess(project); helpFilePath = bp.SubstitutionTags.TransformText(helpFilePath); setupFile = Path.ChangeExtension(helpFilePath, ".msha"); } catch { // Ignore errors txtInfo.AppendText("The help filename appears to contain substitution tags but they could " + "not be resolved to determine the actual file to use for installation. Building " + "website output and viewing it can be used to work around this issue.\r\n\r\n"); rbInstall.Enabled = false; } } if(rbInstall.Enabled && (!File.Exists(helpFilePath) || !File.Exists(setupFile))) { txtInfo.AppendText("A copy of the help file does not appear to exist yet. It may need to be built.\r\n\r\n"); rbInstall.Enabled = false; } try { viewerVersion = new Version((string)cboHelpViewerVersion.SelectedItem); HelpLibraryManager hlm = new HelpLibraryManager(viewerVersion); // Can't do anything if the Help Library Manager is not installed if(hlm.HelpLibraryManagerPath == null) throw new HelpLibraryManagerException(viewerVersion, HelpLibraryManagerException.HelpLibraryManagerNotFound); // Can't do anything if the Help Library Manager is already running if(Process.GetProcessesByName(Path.GetFileNameWithoutExtension(hlm.HelpLibraryManagerPath)).Length > 0) throw new HelpLibraryManagerException(viewerVersion, HelpLibraryManagerException.HelpLibraryManagerAlreadyRunning); // Can't do anything if the local store is not initialized if(!hlm.LocalStoreInitialized) throw new HelpLibraryManagerException(viewerVersion, HelpLibraryManagerException.LocalStoreNotInitialized); if(hlm.HelpContentFileInstalled(helpFilePath)) rbOpenCurrent.Enabled = rbRemove.Enabled = true; else { txtInfo.AppendText("The help file does not appear to be installed yet.\r\n"); rbOpenCurrent.Enabled = rbRemove.Enabled = false; } } catch(Exception ex) { txtInfo.AppendText("Problem: " + ex.Message + "\r\n"); rbOpenCurrent.Enabled = rbRemove.Enabled = false; } if(rbOpenCurrent.Enabled) rbOpenCurrent.Checked = true; else if(rbInstall.Enabled) rbInstall.Checked = true; else rbLaunchContentManager.Checked = true; if(!rbOpenCurrent.Enabled && !rbInstall.Enabled && !rbRemove.Enabled) txtInfo.AppendText("\r\nNo action can be taken with the help content."); // Determine the catalog name here as it's used in a lot of places and varies by version if not // defined in the project. catalogName = !String.IsNullOrWhiteSpace(project.CatalogName) ? project.CatalogName : HelpLibraryManager.DefaultCatalogName(viewerVersion); // If it looks like a default value, warn the user if it doesn't match. It may need to be cleared. if(!String.IsNullOrWhiteSpace(project.CatalogName) && project.CatalogName.StartsWith("VisualStudio", StringComparison.Ordinal) && project.CatalogName != HelpLibraryManager.DefaultCatalogName(viewerVersion)) txtInfo.AppendText("\r\n\r\nWARNING: The project's catalog name property is set to '" + project.CatalogName + "' which does not match the default catalog name for the selected " + "version of the help viewer. If necessary, clear the catalog name property value."); }
/// <summary> /// Merge the conceptual topic IDs into the BuildAssembler manifest file. /// </summary> /// <param name="builder">The build process</param> private void MergeConceptualManifest(BuildProcess builder) { XmlWriterSettings settings = new XmlWriterSettings(); XmlWriter writer = null; string conceptualManifest = builder.WorkingFolder + "ConceptualManifest.xml", referenceManifest = builder.WorkingFolder + "manifest.xml"; builder.ReportProgress(" Merging topic IDs into manifest.xml"); try { settings.Indent = true; settings.CloseOutput = true; writer = XmlWriter.Create(conceptualManifest, settings); writer.WriteStartDocument(); writer.WriteStartElement("topics"); foreach(TopicCollection tc in topics) foreach(Topic t in tc) t.WriteManifest(writer, builder); if(File.Exists(referenceManifest)) foreach(var topic in ComponentUtilities.XmlStreamAxis(referenceManifest, "topic")) { writer.WriteStartElement("topic"); foreach(var attr in topic.Attributes()) writer.WriteAttributeString(attr.Name.LocalName, attr.Value); writer.WriteEndElement(); } writer.WriteEndElement(); // </topics> writer.WriteEndDocument(); } finally { if(writer != null) writer.Close(); if(File.Exists(referenceManifest)) File.Copy(referenceManifest, Path.ChangeExtension(referenceManifest, "old"), true); File.Copy(conceptualManifest, referenceManifest, true); File.Delete(conceptualManifest); } }
/// <summary> /// Write out the <b>BuildAssembler</b> manifest entry /// </summary> /// <param name="writer">The XML writer to which the entry is written</param> /// <param name="builder">The build process</param> /// <remarks>This will recursively write out entries for sub-topics /// as well.</remarks> internal void WriteManifest(XmlWriter writer, BuildProcess builder) { // MS Help Viewer doesn't support empty place holders so we automatically // generate a dummy place holder file for them. Don't add an entry for // raw HTML files. if((!noFile && topicFile.DocumentType != DocumentType.Html) || (noFile && (builder.CurrentProject.HelpFileFormat & HelpFileFormat.MSHelpViewer) != 0)) { writer.WriteStartElement("topic"); writer.WriteAttributeString("id", this.Id); writer.WriteEndElement(); } foreach(Topic t in subtopics) t.WriteManifest(writer, builder); }
/// <summary> /// This method is used to initialize the plug-in at the start of the /// build process. /// </summary> /// <param name="buildProcess">A reference to the current build /// process.</param> /// <param name="configuration">The configuration data that the plug-in /// should use to initialize itself.</param> public void Initialize(BuildProcess buildProcess, XPathNavigator configuration) { this.builder = buildProcess; XPathNavigator configRoot = configuration.SelectSingleNode("configuration"); if (configRoot != null) { XPathNavigator node = configRoot.SelectSingleNode("targetLanguage"); if (node != null) { targetLanguage = node.InnerXml.Trim(); } } }
//===================================================================== /// <summary> /// This is used to start the background build process from which we will get the information to load the /// tree view. /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private async void ApiFilterEditorDlg_Load(object sender, EventArgs e) { string tempPath; tvApiList.Enabled = splitContainer.Panel2.Enabled = btnReset.Enabled = false; try { // Clone the project for the build and adjust its properties for our needs tempProject = new SandcastleProject(apiFilter.Project.MSBuildProject); // Build output is stored in a temporary folder and it keeps the intermediate files tempProject.CleanIntermediates = false; tempPath = Path.GetTempFileName(); File.Delete(tempPath); tempPath = Path.Combine(Path.GetDirectoryName(tempPath), "SHFBPartialBuild"); if(!Directory.Exists(tempPath)) Directory.CreateDirectory(tempPath); tempProject.OutputPath = tempPath; cancellationTokenSource = new CancellationTokenSource(); buildProcess = new BuildProcess(tempProject, PartialBuildType.GenerateReflectionInfo) { ProgressReportProvider = new Progress<BuildProgressEventArgs>(buildProcess_ReportProgress), CancellationToken = cancellationTokenSource.Token, SuppressApiFilter = true // We must suppress the current API filter for this build }; await Task.Run(() => buildProcess.Build(), cancellationTokenSource.Token); if(!cancellationTokenSource.IsCancellationRequested) { // Restore the current project's base path Directory.SetCurrentDirectory(Path.GetDirectoryName(apiFilter.Project.Filename)); // If successful, load the namespace nodes, and enable the UI if(buildProcess.CurrentBuildStep == BuildStep.Completed) { reflectionFile = buildProcess.ReflectionInfoFilename; // Convert the build API filter to a dictionary to make it easier to find entries buildFilterEntries = new Dictionary<string, ApiFilter>(); this.ConvertApiFilter(buildProcess.CurrentProject.ApiFilter); this.LoadNamespaces(); tvApiList.Enabled = splitContainer.Panel2.Enabled = btnReset.Enabled = true; } else MessageBox.Show("Unable to build project to obtain API information. Please perform a " + "normal build to identify and correct the problem.", Constants.AppName, MessageBoxButtons.OK, MessageBoxIcon.Error); pbWait.Visible = lblLoading.Visible = false; } else { this.DialogResult = DialogResult.Cancel; this.Close(); } buildProcess = null; } catch(Exception ex) { System.Diagnostics.Debug.WriteLine(ex.ToString()); MessageBox.Show("Unable to build project to obtain API information. Error: " + ex.Message, Constants.AppName, MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { if(cancellationTokenSource != null) { cancellationTokenSource.Dispose(); cancellationTokenSource = null; } } }
/// <summary> /// This method is used to initialize the plug-in at the start of the build process /// </summary> /// <param name="buildProcess">A reference to the current build process</param> /// <param name="configuration">The configuration data that the plug-in should use to initialize itself</param> public void Initialize(BuildProcess buildProcess, XPathNavigator configuration) { builder = buildProcess; var metadata = (HelpFileBuilderPlugInExportAttribute)this.GetType().GetCustomAttributes( typeof(HelpFileBuilderPlugInExportAttribute), false).First(); builder.ReportProgress("{0} Version {1}\r\n{2}", metadata.Id, metadata.Version, metadata.Copyright); expressions = new List<MemberIdMatchExpression>(); foreach(XPathNavigator nav in configuration.Select("configuration/expressions/expression")) expressions.Add(new MemberIdMatchExpression { MatchExpression = nav.GetAttribute("matchExpression", String.Empty), ReplacementValue = nav.GetAttribute("replacementValue", String.Empty), MatchAsRegEx = Convert.ToBoolean(nav.GetAttribute("matchAsRegEx", String.Empty)) }); if(expressions.Count == 0) throw new BuilderException("MNF0001", "No fix-up expressions have been defined for the Member " + "Name Fix-Up plug-in"); }
/// <summary> /// This method is used to initialize the plug-in at the start of the /// build process. /// </summary> /// <param name="buildProcess">A reference to the current build /// process.</param> /// <param name="configuration">The configuration data that the plug-in /// should use to initialize itself.</param> /// <exception cref="BuilderException">This is thrown if the plug-in /// configuration is not valid.</exception> public void Initialize(BuildProcess buildProcess, XPathNavigator configuration) { XPathNavigator root, node; builder = buildProcess; ajaxDocUrl = projectName = String.Empty; userCreds = new UserCredentials(); proxyCreds = new ProxyCredentials(); builder.ReportProgress("{0} Version {1}\r\n{2}", this.Name, this.Version, this.Copyright); root = configuration.SelectSingleNode("configuration"); if(root.IsEmptyElement) throw new BuilderException("ADP0001", "The AjaxDoc plug-in " + "has not been configured yet"); node = root.SelectSingleNode("ajaxDoc"); if(node != null) { ajaxDocUrl = node.GetAttribute("url", String.Empty).Trim(); projectName = node.GetAttribute("project", String.Empty).Trim(); regenerateFiles = Convert.ToBoolean(node.GetAttribute( "regenerate", String.Empty), CultureInfo.InvariantCulture); } userCreds = UserCredentials.FromXPathNavigator(root); proxyCreds = ProxyCredentials.FromXPathNavigator(root); if(ajaxDocUrl.Length == 0 || projectName.Length == 0 || (!userCreds.UseDefaultCredentials && (userCreds.UserName.Length == 0 || userCreds.Password.Length == 0)) || (proxyCreds.UseProxyServer && (proxyCreds.ProxyServer == null || (!proxyCreds.Credentials.UseDefaultCredentials && (proxyCreds.Credentials.UserName.Length == 0 || proxyCreds.Credentials.Password.Length == 0))))) throw new BuilderException("ADP0002", "The AjaxDoc plug-in " + "has an invalid configuration"); }
/// <summary> /// This is used to determine the state of the help content and set the form options when a help viewer /// version is selected. /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void cboHelpViewerVersion_SelectedIndexChanged(object sender, EventArgs e) { txtInfo.Text = null; grpOptions.Enabled = rbInstall.Enabled = true; lastVersionSelected = cboHelpViewerVersion.SelectedIndex; // If there are substitution tags present, have a go at resolving them if (helpFilePath.IndexOf("{@", StringComparison.Ordinal) != -1) { try { var bp = new SandcastleBuilder.Utils.BuildEngine.BuildProcess(project); helpFilePath = bp.SubstitutionTags.TransformText(helpFilePath); setupFile = Path.ChangeExtension(helpFilePath, ".msha"); } catch { // Ignore errors txtInfo.AppendText("The help filename appears to contain substitution tags but they could " + "not be resolved to determine the actual file to use for installation. Building " + "website output and viewing it can be used to work around this issue.\r\n\r\n"); rbInstall.Enabled = false; } } if (rbInstall.Enabled && (!File.Exists(helpFilePath) || !File.Exists(setupFile))) { txtInfo.AppendText("A copy of the help file does not appear to exist yet. It may need to be built.\r\n\r\n"); rbInstall.Enabled = false; } try { viewerVersion = new Version((string)cboHelpViewerVersion.SelectedItem); HelpLibraryManager hlm = new HelpLibraryManager(viewerVersion); // Can't do anything if the Help Library Manager is not installed if (hlm.HelpLibraryManagerPath == null) { throw new HelpLibraryManagerException(viewerVersion, HelpLibraryManagerException.HelpLibraryManagerNotFound); } // Can't do anything if the Help Library Manager is already running if (Process.GetProcessesByName(Path.GetFileNameWithoutExtension(hlm.HelpLibraryManagerPath)).Length > 0) { throw new HelpLibraryManagerException(viewerVersion, HelpLibraryManagerException.HelpLibraryManagerAlreadyRunning); } // Can't do anything if the local store is not initialized if (!hlm.LocalStoreInitialized) { throw new HelpLibraryManagerException(viewerVersion, HelpLibraryManagerException.LocalStoreNotInitialized); } if (hlm.HelpContentFileInstalled(helpFilePath)) { rbOpenCurrent.Enabled = rbRemove.Enabled = true; } else { txtInfo.AppendText("The help file does not appear to be installed yet.\r\n"); rbOpenCurrent.Enabled = rbRemove.Enabled = false; } } catch (Exception ex) { txtInfo.AppendText("Problem: " + ex.Message + "\r\n"); rbOpenCurrent.Enabled = rbRemove.Enabled = false; } if (rbOpenCurrent.Enabled) { rbOpenCurrent.Checked = true; } else if (rbInstall.Enabled) { rbInstall.Checked = true; } else { rbLaunchContentManager.Checked = true; } if (!rbOpenCurrent.Enabled && !rbInstall.Enabled && !rbRemove.Enabled) { txtInfo.AppendText("\r\nNo action can be taken with the help content."); } // Determine the catalog name here as it's used in a lot of places and varies by version if not // defined in the project. catalogName = !String.IsNullOrWhiteSpace(project.CatalogName) ? project.CatalogName : HelpLibraryManager.DefaultCatalogName(viewerVersion); // If it looks like a default value, warn the user if it doesn't match. It may need to be cleared. if (!String.IsNullOrWhiteSpace(project.CatalogName) && project.CatalogName.StartsWith("VisualStudio", StringComparison.Ordinal) && project.CatalogName != HelpLibraryManager.DefaultCatalogName(viewerVersion)) { txtInfo.AppendText("\r\n\r\nWARNING: The project's catalog name property is set to '" + project.CatalogName + "' which does not match the default catalog name for the selected " + "version of the help viewer. If necessary, clear the catalog name property value."); } }