/// <summary> /// This is used to convert the collection to a string and append it /// to the specified string builder. /// </summary> /// <param name="format">The help file format to use</param> /// <param name="sb">The string builder to which the information is /// appended.</param> internal void ConvertToString(HelpFileFormat format, StringBuilder sb) { foreach (TocEntry te in this) { te.ConvertToString(format, sb); } }
/// <summary> /// This returns a complete list of files for inclusion in the /// compiled help file. /// </summary> /// <param name="folder">The folder to expand</param> /// <param name="format">The HTML help file format</param> /// <returns>The full list of all files for the help project</returns> /// <remarks>The help file list is expanded to ensure that we get /// all additional content including all nested sub-folders. The /// <b>format</b> parameter determines the format of the returned /// file list. For HTML 1.x, it returns a list of the filenames. /// For HTML 2.x, it returns the list formatted with the necessary /// XML markup.</remarks> protected static string HelpProjectFileList(string folder, HelpFileFormat format) { StringBuilder sb = new StringBuilder(10240); string itemFormat; if (folder == null) { throw new ArgumentNullException("folder"); } string[] files = Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories); if (!folder.EndsWith(@"\")) { folder += @"\"; } if ((format & HelpFileFormat.HtmlHelp1x) != 0) { itemFormat = "{0}\r\n"; } else { itemFormat = " <File Url=\"{0}\" />\r\n"; } foreach (string name in files) { sb.AppendFormat(itemFormat, name.Replace(folder, String.Empty)); } return(sb.ToString()); }
/// <summary> /// Convert the table of contents entry and its children to a string /// in the specified help file format. /// </summary> /// <param name="format">The help file format to use</param> /// <returns>The entries in the specified help format</returns> public string ToString(HelpFileFormat format) { StringBuilder sb = new StringBuilder(1024); this.ConvertToString(format, sb); return(sb.ToString()); }
/// <summary> /// Convert the table of content entry and its children to a string /// in the specified help file format. /// </summary> /// <param name="format">The help file format to use</param> /// <returns>The entries in specified help format</returns> /// <exception cref="ArgumentException">This is thrown if the /// format is not <b>HtmlHelp1x</b> or <b>HtmlHelp2x</b>.</exception> public string ToString(HelpFileFormat format) { if (format != HelpFileFormat.HtmlHelp1x && format != HelpFileFormat.HtmlHelp2x) { throw new ArgumentException("The format specified must be " + "HtmlHelp1x or HtmlHelp2x only", "format"); } StringBuilder sb = new StringBuilder(1024); this.ConvertToString(format, sb); return(sb.ToString()); }
/// <summary> /// This is used to convert the collection to a string and append it /// to the specified string builder. /// </summary> /// <param name="format">The help file format to use</param> /// <param name="sb">The string builder to which the information is /// appended.</param> internal void ConvertToString(HelpFileFormat format, StringBuilder sb) { if (format == HelpFileFormat.HtmlHelp1x) { if (children.Count == 0) { sb.AppendFormat("<LI><OBJECT type=\"text/sitemap\">" + "\r\n<param name=\"Name\" value=\"{0}\">\r\n" + "<param name=\"Local\" value=\"{1}\">\r\n" + "</OBJECT></LI>\r\n", HttpUtility.HtmlEncode(title), destFile); } else { if (String.IsNullOrEmpty(destFile)) { sb.AppendFormat("<LI><OBJECT type=\"text/sitemap\">" + "\r\n<param name=\"Name\" value=\"{0}\">\r\n" + "</OBJECT></LI>\r\n", HttpUtility.HtmlEncode(title)); } else { sb.AppendFormat("<LI><OBJECT type=\"text/sitemap\">" + "\r\n<param name=\"Name\" value=\"{0}\">\r\n" + "<param name=\"Local\" value=\"{1}\">\r\n" + "</OBJECT></LI>\r\n", HttpUtility.HtmlEncode(title), destFile); } sb.Append("<UL>\r\n"); children.ConvertToString(format, sb); sb.Append("</UL>\r\n"); } } else { // Use a GUID to uniquely identify the entries string guid = Guid.NewGuid().ToString(); if (children.Count == 0) { sb.AppendFormat("<HelpTOCNode Id=\"{0}\" Url=\"{1}\" />\r\n", guid, destFile); } else { // If there is no file for the root node, define the title // property instead. if (String.IsNullOrEmpty(destFile)) { sb.AppendFormat("<HelpTOCNode Id=\"{0}\" Title=\"{1}\">\r\n", guid, HttpUtility.HtmlEncode(title)); } else { sb.AppendFormat("<HelpTOCNode Id=\"{0}\" Url=\"{1}\">\r\n", guid, destFile); } children.ConvertToString(format, sb); sb.Append("</HelpTOCNode>\r\n"); } } }
//===================================================================== /// <summary> /// Call this method to perform the build on the project. /// </summary> /// <event cref="BuildStepChanged">This event fires when the /// current build step changes.</event> /// <event cref="BuildProgress">This event fires to report progress /// information.</event> public void Build() { Project msBuildProject; BuildItem buildItem; Version v; string helpFile, languageFile, scriptFile, message = null; int waitCount; System.Diagnostics.Debug.WriteLine("Build process starting\r\n"); try { Assembly asm = Assembly.GetExecutingAssembly(); FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(asm.Location); this.ReportProgress(BuildStep.Initializing, "[{0}, version {1}]", fvi.ProductName, fvi.ProductVersion); buildStart = stepStart = DateTime.Now; msBuildExePath = Path.Combine(Engine.GlobalEngine.Toolsets[ project.MSBuildProject.ToolsVersion].ToolsPath, "MSBuild.exe"); // Base folder for SHFB shfbFolder = Path.GetDirectoryName(asm.Location) + @"\"; // Get the location of the template files templateFolder = shfbFolder + @"Templates\"; // Get the location of the web files webFolder = shfbFolder + @"\Web\"; // Make sure we start out in the project's output folder // in case the output folder is relative to it. projectFolder = Path.GetDirectoryName(originalProjectName); if(projectFolder.Length == 0) projectFolder = Directory.GetCurrentDirectory(); projectFolder += @"\"; Directory.SetCurrentDirectory(projectFolder); this.ReportProgress("Creating output and working folders..."); outputFolder = project.OutputPath; if(String.IsNullOrEmpty(outputFolder)) outputFolder = Directory.GetCurrentDirectory(); else outputFolder = Path.GetFullPath(outputFolder); if(!Directory.Exists(outputFolder)) Directory.CreateDirectory(outputFolder); if(outputFolder[outputFolder.Length - 1] != '\\') outputFolder += @"\"; // Create the log file. The log may be in a folder other than // the output so make sure it exists too. if(!Directory.Exists(Path.GetDirectoryName(this.LogFilename))) Directory.CreateDirectory(Path.GetDirectoryName(this.LogFilename)); swLog = new StreamWriter(this.LogFilename); swLog.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "\r\n<shfbBuild product=\"{0}\" version=\"{1}\" " + "projectFile=\"{2}\" started=\"{3}\">\r\n" + "<buildStep step=\"{4}\">", fvi.ProductName, fvi.ProductVersion, originalProjectName, DateTime.Now, BuildStep.Initializing); if(project.WorkingPath.Path.Length == 0) workingFolder = outputFolder + @"Working\"; else workingFolder = project.WorkingPath; if((project.HelpFileFormat & HelpFileFormat.Website) != 0) BuildProcess.VerifySafePath("OutputPath", outputFolder, projectFolder); // The output folder and the working folder cannot be the same if(workingFolder == outputFolder) throw new BuilderException("BE0030", "The OutputPath and " + "WorkingPath properties cannot be set to the same path"); // For MS Help 2, the HTML Help Name cannot contain spaces if((project.HelpFileFormat & HelpFileFormat.MSHelp2) != 0 && project.HtmlHelpName.IndexOf(' ') != -1) throw new BuilderException("BE0031", "For MS Help 2 " + "builds, the HtmlHelpName property cannot contain " + "spaces as they are not valid in the collection name."); // Check for the SHFBROOT environment variable. It may not be // present yet if a reboot hasn't occurred after installation. // In such cases, set it to the proper folder for this process // so that projects can be loaded and built. if(Environment.GetEnvironmentVariable("SHFBROOT") == null) { // We won't issue a warning since it may not be defined // in some build environments such as on a build server. // In such cases, it is passed in as a command line // option to MSBuild. Storing it in the environment here // lets the SHFB build projects work as expected. this.ReportProgress("The SHFBROOT system environment variable was " + "not found. This variable is usually created during installation " + "and may require a reboot. It has been defined temporarily " + "for this process as: SHFBROOT={0}", shfbFolder); Environment.SetEnvironmentVariable("SHFBROOT", shfbFolder); } if(project.PlugInConfigurations.Count != 0) this.LoadPlugIns(); this.ExecutePlugIns(ExecutionBehaviors.After); try { if(Directory.Exists(workingFolder)) { // Clear any data from a prior run this.ReportProgress(BuildStep.ClearWorkFolder, "Clearing working folder..."); BuildProcess.VerifySafePath("WorkingPath", workingFolder, projectFolder); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); try { Directory.Delete(workingFolder, true); } catch(IOException ioEx) { this.ReportProgress(" Not all prior output was removed from '{0}': {1}", workingFolder, ioEx.Message); } catch(UnauthorizedAccessException uaEx) { this.ReportProgress(" Not all prior output was removed from '{0}': {1}", workingFolder, uaEx.Message); } this.ExecutePlugIns(ExecutionBehaviors.After); } } // If the help file is open, it will fail to build so try // to get rid of it now before we get too far into it. helpFile = outputFolder + project.HtmlHelpName + ".chm"; if((project.HelpFileFormat & HelpFileFormat.HtmlHelp1) != 0 && File.Exists(helpFile)) File.Delete(helpFile); helpFile = Path.ChangeExtension(helpFile, ".hxs"); if((project.HelpFileFormat & HelpFileFormat.MSHelp2) != 0 && File.Exists(helpFile)) File.Delete(helpFile); helpFile = Path.ChangeExtension(helpFile, ".mshc"); if((project.HelpFileFormat & HelpFileFormat.MSHelpViewer) != 0 && File.Exists(helpFile)) File.Delete(helpFile); if((project.HelpFileFormat & HelpFileFormat.Website) != 0) { helpFile = outputFolder + "Index.aspx"; if(File.Exists(helpFile)) File.Delete(helpFile); helpFile = Path.ChangeExtension(helpFile, ".html"); if(File.Exists(helpFile)) File.Delete(helpFile); } } catch(IOException ex) { throw new BuilderException("BE0025", "Unable to remove prior build output: " + ex.Message); } catch { throw; } Directory.CreateDirectory(workingFolder); // Make sure we can find the tools this.FindTools(); if(!Directory.Exists(sandcastleFolder + @"Data\Reflection")) throw new BuilderException("BE0032", "Reflection data files do not exist yet"); // Make sure the HelpFileVersion property is in the form of // a real version number. try { if(project.HelpFileVersion.IndexOf('{') == -1) v = new Version(project.HelpFileVersion); else v = new Version(this.TransformText(project.HelpFileVersion)); if(v.Build == -1 || v.Revision == -1) throw new FormatException("The version number must specify all four parts. " + "Specify zero for unused parts."); } catch(Exception ex) { throw new BuilderException("BE0066", "The HelpFileVersion property value '" + project.HelpFileVersion + "' is not in the correct format (#.#.#.#)", ex); } this.GarbageCollect(); // Validate the documentation source information, gather // assembly and reference info, and copy XML comments files // to the working folder. this.ValidateDocumentationSources(); // Transform the shared builder content files language = project.Language; languageFile = "SharedBuilderContent_" + language.Name + ".xml"; this.ReportProgress(BuildStep.GenerateSharedContent, "Generating shared content files ({0}, {1})...", language.Name, language.DisplayName); // First we need to figure out which style is in effect. // Base it on whether the presentation style folder contains // "v2005", "hana", or "prototype". presentationParam = project.PresentationStyle.ToLower(CultureInfo.InvariantCulture); if(presentationParam.IndexOf("vs2005", StringComparison.Ordinal) != -1) presentationParam = "vs2005"; else if(presentationParam.IndexOf("hana", StringComparison.Ordinal) != -1) presentationParam = "hana"; else { if(presentationParam.IndexOf("prototype", StringComparison.Ordinal) == -1) this.ReportWarning("BE0001", "Unable to determine presentation style from folder " + "'{0}'. Assuming Prototype style.", project.PresentationStyle); presentationParam = "prototype"; } if(!File.Exists(templateFolder + @"..\SharedContent\" + languageFile)) { languageFile = "SharedBuilderContent_en-US.xml"; // Warn the user about the default being used this.ReportWarning("BE0002", "Shared builder content " + "for the '{0}, {1}' language could not be found. " + "Using 'en-US, English (US)' defaults.", language.Name, language.DisplayName); } // See if the user has translated the Sandcastle resources. // If not found, default to US English. languageFolder = presentationFolder + @"\Content\" + language.Name; if(Directory.Exists(languageFolder)) languageFolder = language.Name + @"\"; else { // Warn the user about the default being used. The // language will still be used for the help file though. if(language.Name != "en-US") this.ReportWarning("BE0003", "Sandcastle shared " + "content for the '{0}, {1}' language could not " + "be found. Using 'en-US, English (US)' " + "defaults.", language.Name, language.DisplayName); languageFolder = String.Empty; } if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); this.TransformTemplate(languageFile, templateFolder + @"..\SharedContent\", workingFolder); File.Move(workingFolder + languageFile, workingFolder + "SharedBuilderContent.xml"); // Presentation-style specific shared content languageFile = languageFile.Replace("Shared", presentationParam); this.TransformTemplate(languageFile, templateFolder + @"..\SharedContent\", workingFolder); File.Move(workingFolder + languageFile, workingFolder + "PresentationStyleBuilderContent.xml"); // Copy the stop word list languageFile = Path.ChangeExtension(languageFile.Replace(presentationParam + "BuilderContent", "StopWordList"), ".txt"); File.Copy(templateFolder + @"..\SharedContent\" + languageFile, workingFolder + "StopWordList.txt"); File.SetAttributes(workingFolder + "StopWordList.txt", FileAttributes.Normal); this.ExecutePlugIns(ExecutionBehaviors.After); } // Generate the API filter used by MRefBuilder this.GenerateApiFilter(); // Generate the reflection information this.ReportProgress(BuildStep.GenerateReflectionInfo, "Generating reflection information..."); reflectionFile = workingFolder + "reflection.org"; if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.TransformTemplate("MRefBuilder.config", templateFolder, workingFolder); scriptFile = this.TransformTemplate("GenerateRefInfo.proj", templateFolder, workingFolder); msBuildProject = new Project(Engine.GlobalEngine); msBuildProject.Load(scriptFile); // Add the references foreach(BuildItem item in referenceDictionary.Values) { buildItem = msBuildProject.AddNewItem(item.Name, item.Include); item.CopyCustomMetadataTo(buildItem); } // Add the assemblies to document foreach(string assemblyName in assembliesList) buildItem = msBuildProject.AddNewItem("Assembly", assemblyName); msBuildProject.Save(scriptFile); this.ExecutePlugIns(ExecutionBehaviors.Before); this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m GenerateRefInfo.proj"); this.ExecutePlugIns(ExecutionBehaviors.After); } reflectionInfo = new XmlDocument(); reflectionInfo.Load(reflectionFile); apisNode = reflectionInfo.SelectSingleNode("reflection/apis"); // Generate namespace summary information this.GenerateNamespaceSummaries(); // Remove unwanted items from the reflection information file this.ApplyVisibilityProperties(); // If there is nothing to document, stop the build if(apisNode.ChildNodes.Count == 0) throw new BuilderException("BE0033", "No APIs found to " + "document. See error topic in help file for details."); reflectionInfo = null; apisNode = null; // If this was a partial build used to obtain API information, // stop now. if(isPartialBuild) { commentsFiles.Save(); goto AllDone; // Yeah, I know it's evil but it's quick } // Expand <inheritdoc /> tags? if(commentsFiles.ContainsInheritedDocumentation) { commentsFiles.Save(); // Transform the reflection output. this.ReportProgress(BuildStep.GenerateInheritedDocumentation, "Generating inherited documentation..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.TransformTemplate("GenerateInheritedDocs.config", templateFolder, workingFolder); scriptFile = this.TransformTemplate("GenerateInheritedDocs.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m GenerateInheritedDocs.proj"); this.ExecutePlugIns(ExecutionBehaviors.After); } // This should always be last so that it overrides comments in the project XML comments files commentsFiles.Add(new XmlCommentsFile(workingFolder + "_InheritedDocs_.xml")); } this.GarbageCollect(); // Transform the reflection output. this.ReportProgress(BuildStep.TransformReflectionInfo, "Transforming reflection output..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = this.TransformTemplate("TransformManifest.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m TransformManifest.proj"); this.ExecutePlugIns(ExecutionBehaviors.After); } // Load the transformed file reflectionFile = reflectionFile.Replace(".org", ".xml"); reflectionInfo = new XmlDocument(); reflectionInfo.Load(reflectionFile); apisNode = reflectionInfo.SelectSingleNode("reflection/apis"); // Alter the help topic filenames if necessary this.ModifyHelpTopicFilenames(); // Backup the original reflection file for reference and save the changed file File.Copy(reflectionFile, Path.ChangeExtension(reflectionFile, ".bak"), true); reflectionInfo.Save(reflectionFile); commentsFiles.Save(); // Copy the standard help file content this.CopyStandardHelpContent(); // Copy conceptual content files if there are topics conceptualContent = new ConceptualContentSettings(project); if(conceptualContent.ContentLayoutFiles.Count != 0) { this.ReportProgress(BuildStep.CopyConceptualContent, "Copying conceptual content..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); conceptualContent.CopyContentFiles(this); this.ExecutePlugIns(ExecutionBehaviors.After); } this.ReportProgress(BuildStep.CreateConceptualTopicConfigs, "Creating conceptual topic configuration files..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); conceptualContent.CreateConfigurationFiles(this); this.ExecutePlugIns(ExecutionBehaviors.After); } } // Copy the additional content this.CopyAdditionalContent(); // Merge the conceptual and additional content TOC info this.MergeConceptualAndAdditionalContentTocInfo(); // Generate the intermediate table of contents file. This // must occur prior to running BuildAssembler as the MS Help // Viewer build component is dependent on the toc.xml file. this.ReportProgress(BuildStep.GenerateIntermediateTableOfContents, "Generating intermediate table of contents file..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = this.TransformTemplate("GenerateIntermediateTOC.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m GenerateIntermediateTOC.proj"); // Determine the API content placement this.DetermineApiContentPlacement(); // If there is conceptual content, generate the conceptual intermediate TOC if(toc != null) { this.ReportProgress("Generating conceptual content intermediate TOC file..."); toc.SaveToIntermediateTocFile((project.HelpFileFormat & HelpFileFormat.MSHelpViewer) != 0 ? this.RootContentContainerId : null, project.TocOrder, workingFolder + "_ConceptualTOC_.xml"); } this.ExecutePlugIns(ExecutionBehaviors.After); } // The June 2007 CTP removed the root namespace container from the TOC so we'll get // the default project page filename from the refelection information file. XmlNode defTopic = apisNode.SelectSingleNode("api[@id='R:Project']/file/@name"); if(defTopic != null) { namespacesTopic = defTopic.Value; // Use it as the default topic if one wasn't specified explicitly in the additional content if(defaultTopic == null) defaultTopic = @"html\" + namespacesTopic + ".htm"; } // Create the Sandcastle configuration file this.ReportProgress(BuildStep.CreateBuildAssemblerConfigs, "Creating Sandcastle configuration files..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); this.ReportProgress(" sandcastle.config"); // The configuration varies based on the style. However, // we'll use a common name (sandcastle.config). this.TransformTemplate(presentationParam + ".config", templateFolder, workingFolder); File.Move(workingFolder + presentationParam + ".config", workingFolder + "sandcastle.config"); // The conceptual content configuration file is common to // all styles. It is only created if needed. if(conceptualContent.ContentLayoutFiles.Count != 0) { this.ReportProgress(" conceptual.config"); this.TransformTemplate("conceptual.config", templateFolder, workingFolder); } this.ExecutePlugIns(ExecutionBehaviors.After); } // Merge the build component custom configurations this.MergeComponentConfigurations(); reflectionInfo = null; commentsFiles = null; apisNode = null; this.GarbageCollect(); // Build the conceptual help topics if(conceptualContent.ContentLayoutFiles.Count != 0) { this.ReportProgress(BuildStep.BuildConceptualTopics, "Building conceptual help topics..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = this.TransformTemplate("BuildConceptualTopics.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m BuildConceptualTopics.proj"); this.ExecutePlugIns(ExecutionBehaviors.After); } this.GarbageCollect(); } // Build the reference help topics this.ReportProgress(BuildStep.BuildReferenceTopics, "Building reference help topics..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = this.TransformTemplate("BuildReferenceTopics.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m BuildReferenceTopics.proj"); this.ExecutePlugIns(ExecutionBehaviors.After); } // Combine the conceptual and API intermediate TOC files into one this.CombineIntermediateTocFiles(); // The last part differs based on the help file format if((project.HelpFileFormat & HelpFileFormat.Website) != 0) { this.ReportProgress("\r\nClearing any prior web output"); // Purge all files and folders from the output path except for the working folder // and the build log. This is done first as the CHM and HxS files end up in the // same output folder. However, the website output is built last so that unnecessary // files are not compiled into the CHM and HxS files. Read-only and/or hidden files // and folders are ignored as they are assumed to be under source control. foreach(string file in Directory.GetFiles(outputFolder)) if(!file.EndsWith(Path.GetFileName(this.LogFilename), StringComparison.Ordinal)) if((File.GetAttributes(file) & (FileAttributes.ReadOnly | FileAttributes.Hidden)) == 0) File.Delete(file); else this.ReportProgress(" Ignoring read-only/hidden file {0}", file); foreach(string folder in Directory.GetDirectories(outputFolder)) try { // Ignore the working folder if(!folder.EndsWith("Working", StringComparison.Ordinal)) if((File.GetAttributes(folder) & (FileAttributes.ReadOnly | FileAttributes.Hidden)) == 0) Directory.Delete(folder, true); else this.ReportProgress(" Ignoring read-only/hidden folder {0}", folder); } catch(IOException ioEx) { this.ReportProgress(" Ignoring folder '{0}': {1}", folder, ioEx.Message); } catch(UnauthorizedAccessException uaEx) { this.ReportProgress(" Ignoring folder '{0}': {1}", folder, uaEx.Message); } } if((project.HelpFileFormat & (HelpFileFormat.HtmlHelp1 | HelpFileFormat.Website)) != 0) { this.ReportProgress(BuildStep.ExtractingHtmlInfo, "Extracting HTML info for HTML Help 1 and/or website..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = this.TransformTemplate("ExtractHtmlInfo.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m ExtractHtmlInfo.proj"); this.ExecutePlugIns(ExecutionBehaviors.After); } } if((project.HelpFileFormat & HelpFileFormat.HtmlHelp1) != 0) { // Generate the table of contents and set the default topic this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents, "Generating HTML Help 1 table of contents file..."); currentFormat = HelpFileFormat.HtmlHelp1; if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); // It got created in the ExtractingHtmlInfo step above // so there is actually nothing to do here. this.ExecutePlugIns(ExecutionBehaviors.After); } // Generate the help file index this.ReportProgress(BuildStep.GenerateHelpFileIndex, "Generating HTML Help 1 index file..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); // It got created in the ExtractingHtmlInfo step above // so there is actually nothing to do here. this.ExecutePlugIns(ExecutionBehaviors.After); } // Generate the help project file this.ReportProgress(BuildStep.GenerateHelpProject, "Generating HTML Help 1 project file..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); this.TransformTemplate("Help1x.hhp", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.After); } // Build the HTML Help 1 help file this.ReportProgress(BuildStep.CompilingHelpFile, "Compiling HTML Help 1 file..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = this.TransformTemplate("Build1xHelpFile.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m Build1xHelpFile.proj"); this.GatherBuildOutputFilenames(); this.ExecutePlugIns(ExecutionBehaviors.After); } } if((project.HelpFileFormat & HelpFileFormat.MSHelp2) != 0) { // Generate the table of contents and set the default topic this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents, "Generating MS Help 2 table of contents file..."); currentFormat = HelpFileFormat.MSHelp2; if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = this.TransformTemplate("Generate2xTOC.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m Generate2xTOC.proj"); this.ExecutePlugIns(ExecutionBehaviors.After); } // Generate the help project files this.ReportProgress(BuildStep.GenerateHelpProject, "Generating MS Help 2 project files..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); string[] help2xFiles = Directory.GetFiles(templateFolder, "Help2x*.*"); foreach(string projectFile in help2xFiles) this.TransformTemplate(Path.GetFileName(projectFile), templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.After); } // Build the MS Help 2 help file this.ReportProgress(BuildStep.CompilingHelpFile, "Compiling MS Help 2 file..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = this.TransformTemplate("Build2xHelpFile.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m Build2xHelpFile.proj"); // Clean up the collection files this.CleanUpCollectionFiles(); this.GatherBuildOutputFilenames(); this.ExecutePlugIns(ExecutionBehaviors.After); } } if((project.HelpFileFormat & HelpFileFormat.MSHelpViewer) != 0) { // The following build steps are executed to allow plug-ins to handle any necessary processing // but nothing actually happens here: // // BuildStep.GenerateHelpFormatTableOfContents // BuildStep.GenerateHelpProject // // For the MS Help Viewer format, there is no project file to compile and the TOC layout is // generated when the help file is ultimately installed using metadata within each topic file. // All of the necessary TOC info is stored in the intermediate TOC file generated prior to // building the topics. The BuildAssembler MSHCComponent inserts the TOC info into each topic // as it is built. this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents, "Executing informational Generate Table of Contents " + "build step for plug-ins (not used for MS Help Viewer)"); currentFormat = HelpFileFormat.MSHelpViewer; if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); this.ExecutePlugIns(ExecutionBehaviors.After); } this.ReportProgress(BuildStep.GenerateHelpProject, "Executing informational Generate Help Project " + "build step for plug-ins (not used for MS Help Viewer)"); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); this.ExecutePlugIns(ExecutionBehaviors.After); } // Build the MS Help Viewer help file this.ReportProgress(BuildStep.CompilingHelpFile, "Generating MS Help Viewer file..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.TransformTemplate("HelpContentSetup.msha", templateFolder, workingFolder); // Rename the content setup file to use the help filename to keep them related and // so that multiple output files can be sent to the same output folder. File.Move(workingFolder + "HelpContentSetup.msha", workingFolder + project.HtmlHelpName + ".msha"); // Generate the example install and remove scripts this.TransformTemplate("InstallMSHC.bat", templateFolder, workingFolder); File.Move(workingFolder + "InstallMSHC.bat", workingFolder + "Install_" + project.HtmlHelpName + ".bat"); this.TransformTemplate("RemoveMSHC.bat", templateFolder, workingFolder); File.Move(workingFolder + "RemoveMSHC.bat", workingFolder + "Remove_" + project.HtmlHelpName + ".bat"); // Copy the launcher utility File.Copy(shfbFolder + "HelpLibraryManagerLauncher.exe", workingFolder + "HelpLibraryManagerLauncher.exe"); File.SetAttributes(workingFolder + "HelpLibraryManagerLauncher.exe", FileAttributes.Normal); scriptFile = this.TransformTemplate("BuildHelpViewerFile.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); this.RunProcess(msBuildExePath, "/nologo /clp:NoSummary /v:m BuildHelpViewerFile.proj"); this.GatherBuildOutputFilenames(); this.ExecutePlugIns(ExecutionBehaviors.After); } } if((project.HelpFileFormat & HelpFileFormat.Website) != 0) { // Generate the table of contents and set the default topic this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents, "Generating website table of contents file..."); currentFormat = HelpFileFormat.Website; if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); // It got created in the ExtractingHtmlInfo step above // so there is actually nothing to do here. this.ExecutePlugIns(ExecutionBehaviors.After); } this.GenerateWebsite(); } // All done if(project.CleanIntermediates) { this.ReportProgress(BuildStep.CleanIntermediates, "Removing intermediate files..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); try { Directory.Delete(workingFolder, true); } catch(IOException ioEx) { this.ReportProgress(" Not all build output was removed from '{0}': {1}", workingFolder, ioEx.Message); } catch(UnauthorizedAccessException uaEx) { this.ReportProgress(" Not all build output was removed from '{0}': {1}", workingFolder, uaEx.Message); } this.ExecutePlugIns(ExecutionBehaviors.After); } } AllDone: progressArgs.HasCompleted = true; TimeSpan runtime = DateTime.Now - buildStart; this.ReportProgress(BuildStep.Completed, "\r\nBuild completed successfully at {0:MM/dd/yyyy hh:mm tt}. " + "Total time: {1:00}:{2:00}:{3:00.0000}\r\n", DateTime.Now, Math.Floor(runtime.TotalSeconds / 3600), Math.Floor((runtime.TotalSeconds % 3600) / 60), (runtime.TotalSeconds % 60)); System.Diagnostics.Debug.WriteLine("Build process finished successfully\r\n"); } catch(ThreadAbortException ) { // Kill off the process, known child processes and the STDOUT // and STDERR threads too if necessary. if(currentProcess != null && !currentProcess.HasExited) { currentProcess.Kill(); foreach(Process p in Process.GetProcesses()) if(reKillProcess.IsMatch(p.ProcessName)) { System.Diagnostics.Debug.WriteLine("Killing " + p.ProcessName); p.Kill(); } } if(stdOutThread != null && stdOutThread.IsAlive) { stdOutThread.Abort(); waitCount = 0; while(waitCount < 5 && !stdOutThread.Join(1000)) waitCount++; } if(stdErrThread != null && stdErrThread.IsAlive) { stdErrThread.Abort(); waitCount = 0; while(waitCount < 5 && !stdErrThread.Join(1000)) waitCount++; } progressArgs.HasCompleted = true; this.ReportError(BuildStep.Canceled, "BE0064", "BUILD CANCELLED BY USER"); System.Diagnostics.Debug.WriteLine("Build process aborted\r\n"); } catch(Exception ex) { BuilderException bex = ex as BuilderException; System.Diagnostics.Debug.WriteLine(ex); progressArgs.HasCompleted = true; do { if(message != null) message += "\r\n"; message += ex.Message; ex = ex.InnerException; } while(ex != null); // NOTE: Message may contain format markers so pass it as a // format argument. if(bex != null) this.ReportError(BuildStep.Failed, bex.ErrorCode, "{0}", message); else this.ReportError(BuildStep.Failed, "BE0065", "BUILD FAILED: {0}", message); System.Diagnostics.Debug.WriteLine("Build process failed\r\n"); } finally { if(currentProcess != null) currentProcess.Dispose(); try { this.ExecutePlugIns(ExecutionBehaviors.Before); } catch(Exception ex) { // Not much we can do at this point... this.ReportProgress(ex.ToString()); } try { this.ExecutePlugIns(ExecutionBehaviors.After); if(loadedPlugIns != null) foreach(IPlugIn plugIn in loadedPlugIns.Values) plugIn.Dispose(); } catch(Exception ex) { // Not much we can do at this point... this.ReportProgress(ex.ToString()); } finally { this.GarbageCollect(); if(swLog != null) { swLog.WriteLine("</buildStep>\r\n</shfbBuild>"); swLog.Close(); swLog = null; } if(progressArgs.BuildStep == BuildStep.Completed && !project.KeepLogFile) File.Delete(this.LogFilename); } } }
//===================================================================== /// <summary> /// This returns a complete list of files for inclusion in the /// compiled help file. /// </summary> /// <param name="folder">The folder to expand</param> /// <param name="format">The HTML help file format</param> /// <returns>The full list of all files for the help project</returns> /// <remarks>The help file list is expanded to ensure that we get /// all additional content including all nested subfolders. The /// <b>format</b> parameter determines the format of the returned /// file list. For HTML Help 1, it returns a list of the filenames. /// For MS Help 2, it returns the list formatted with the necessary /// XML markup.</remarks> protected string HelpProjectFileList(string folder, HelpFileFormat format) { StringBuilder sb = new StringBuilder(10240); string itemFormat, filename, checkName; bool encode; if(folder == null) throw new ArgumentNullException("folder"); string[] files = Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories); if(folder.Length != 0 && folder[folder.Length - 1] != '\\') folder += @"\"; if((format & HelpFileFormat.HtmlHelp1) != 0) { if(folder.IndexOf(',') != -1 || folder.IndexOf(".h", StringComparison.OrdinalIgnoreCase) != -1) this.ReportWarning("BE0060", "The file path '{0}' " + "contains a comma or '.h' which may cause the Help 1 " + "compiler to fail.", folder); if(project.HtmlHelpName.IndexOf(',') != -1 || project.HtmlHelpName.IndexOf(".h", StringComparison.OrdinalIgnoreCase) != -1) this.ReportWarning("BE0060", "The HTMLHelpName property " + "value '{0}' contains a comma or '.h' which may " + "cause the Help 1 compiler to fail.", project.HtmlHelpName); itemFormat = "{0}\r\n"; encode = false; } else { itemFormat = " <File Url=\"{0}\" />\r\n"; encode = true; } foreach(string name in files) if(!encode) { filename = checkName = name.Replace(folder, String.Empty); if(checkName.EndsWith(".htm", StringComparison.OrdinalIgnoreCase) || checkName.EndsWith(".html", StringComparison.OrdinalIgnoreCase)) checkName = checkName.Substring(0, checkName.LastIndexOf(".htm", StringComparison.OrdinalIgnoreCase)); if(checkName.IndexOf(',') != -1 || checkName.IndexOf(".h", StringComparison.OrdinalIgnoreCase) != -1) this.ReportWarning("BE0060", "The filename '{0}' " + "contains a comma or '.h' which may cause the " + "Help 1 compiler to fail.", filename); sb.AppendFormat(itemFormat, filename); } else sb.AppendFormat(itemFormat, HttpUtility.HtmlEncode(name.Replace(folder, String.Empty))); return sb.ToString(); }
/// <summary> /// This is used to convert the collection to a string and append it /// to the specified string builder. /// </summary> /// <param name="format">The help file format to use</param> /// <param name="sb">The string builder to which the information is /// appended.</param> internal void ConvertToString(HelpFileFormat format, StringBuilder sb) { string guid, url, orderAttr, titleAttr; switch (format) { case HelpFileFormat.HtmlHelp1: if (children.Count == 0) { sb.AppendFormat("<LI><OBJECT type=\"text/sitemap\">\r\n" + "<param name=\"Name\" value=\"{0}\">\r\n" + "<param name=\"Local\" value=\"{1}\">\r\n" + "</OBJECT></LI>\r\n", HttpUtility.HtmlEncode(this.Title), HttpUtility.HtmlEncode(this.DestinationFile)); } else { if (String.IsNullOrEmpty(this.DestinationFile)) { sb.AppendFormat("<LI><OBJECT type=\"text/sitemap\">\r\n" + "<param name=\"Name\" value=\"{0}\">\r\n" + "</OBJECT></LI>\r\n", HttpUtility.HtmlEncode(this.Title)); } else { sb.AppendFormat("<LI><OBJECT type=\"text/sitemap\">\r\n" + "<param name=\"Name\" value=\"{0}\">\r\n" + "<param name=\"Local\" value=\"{1}\">\r\n" + "</OBJECT></LI>\r\n", HttpUtility.HtmlEncode(this.Title), HttpUtility.HtmlEncode(this.DestinationFile)); } sb.Append("<UL>\r\n"); children.ConvertToString(format, sb); sb.Append("</UL>\r\n"); } break; case HelpFileFormat.MSHelp2: case HelpFileFormat.Website: if (!String.IsNullOrEmpty(this.DestinationFile) && format == HelpFileFormat.Website) { url = this.DestinationFile.Replace('\\', '/'); } else { url = this.DestinationFile; } if (children.Count == 0) { sb.AppendFormat("<HelpTOCNode Url=\"{0}\" Title=\"{1}\" />\r\n", HttpUtility.HtmlEncode(url), HttpUtility.HtmlEncode(this.Title)); } else { // Use a GUID to uniquely identify the entries with // children. This allows the ASP.NET web page to find // them to load the child nodes dynamically. guid = Guid.NewGuid().ToString(); // If there is no file for the root node, define the title // property instead. if (String.IsNullOrEmpty(url)) { sb.AppendFormat("<HelpTOCNode Id=\"{0}\" Title=\"{1}\">\r\n", guid, HttpUtility.HtmlEncode(this.Title)); } else { sb.AppendFormat("<HelpTOCNode Id=\"{0}\" Url=\"{1}\" Title=\"{2}\">\r\n", guid, url, HttpUtility.HtmlEncode(this.Title)); } children.ConvertToString(format, sb); sb.Append("</HelpTOCNode>\r\n"); } break; case HelpFileFormat.MSHelpViewer: if (String.IsNullOrEmpty(this.DestinationFile)) { url = this.Id; titleAttr = String.Format(CultureInfo.InvariantCulture, " title=\"{0}\"", this.Title); } else { url = Path.GetFileNameWithoutExtension(this.DestinationFile); titleAttr = String.Empty; } if (this.SortOrder != -1) { orderAttr = String.Format(CultureInfo.InvariantCulture, " sortOrder=\"{0}\"", this.SortOrder); } else { orderAttr = String.Empty; } if (children.Count == 0) { sb.AppendFormat("<topic id=\"{0}\" file=\"{0}\"{1}{2} />\r\n", url, orderAttr, titleAttr); } else { sb.AppendFormat("<topic id=\"{0}\" file=\"{0}\"{1}{2}>\r\n", url, orderAttr, titleAttr); children.ConvertToString(format, sb); sb.Append("</topic>\r\n"); } break; default: throw new InvalidOperationException("Unknown TOC help format: " + format.ToString()); } }
/// <summary> /// This is called to determine the default topic for the help file /// and insert any additional table of content entries for the /// additional content files. /// </summary> /// <param name="format">The format of the table of content /// (HtmlHelp1x, HtmlHelp2x, or Website).</param> /// <remarks>In the absence of an additional content item with a /// default topic indicator, the default page is determined by /// extracting the first entry from the generated table of contents /// file. If an additional content item with a default topic indicator /// has been specified, it will be the used instead. The default /// topic is not used by HTML Help 2.x files.</remarks> /// <exception cref="ArgumentException">This is thrown if the /// format is not <b>HtmlHelp1x</b>, <b>HtmlHelp2x</b>, or /// <b>Website</b>.</exception> protected void UpdateTableOfContent(HelpFileFormat format) { Encoding enc = Encoding.Default; string tocFile, content; bool tocChanged = false; int endTagPos; if (format != HelpFileFormat.HtmlHelp1x && format != HelpFileFormat.HtmlHelp2x && format != HelpFileFormat.Website) { throw new ArgumentException("The format specified must be " + "a single format, not a combination", "format"); } // HTML 2.x or HTML 1.x/website if (format != HelpFileFormat.HtmlHelp2x) { tocFile = workingFolder + project.HtmlHelpName + ".hhc"; } else { tocFile = workingFolder + project.HtmlHelpName + ".hxt"; } // When reading the file, use the default encoding but detect the // encoding if byte order marks are present. content = BuildProcess.ReadWithEncoding(tocFile, ref enc); // We only need the default page for HTML Help 1.x files and // websites. if (format != HelpFileFormat.HtmlHelp2x) { // Don't bother if an explicit default has been specified if (defaultTopic == null) { Match m = reExtractDefTopic.Match(content); if (!m.Success) { throw new BuilderException("Unable to determine " + "default page in " + tocFile); } defaultTopic = m.Groups["Filename"].Value; } // Remove the root namespace container if not wanted and it // is there. if (!project.RootNamespaceContainer && re1xRootEntry.IsMatch(content)) { tocChanged = true; content = re1xRootEntry.Replace(content, String.Empty, 1); endTagPos = content.LastIndexOf("</UL>"); if (endTagPos != -1) { content = content.Remove(endTagPos, 5); } } // Reset the encoding as HTML 1.x does not appear to // support UTF-8 encoding in the table of content. enc = Encoding.Default; tocChanged = true; } else { // Remove the root namespace container if not wanted and // it is there. if (!project.RootNamespaceContainer && content.LastIndexOf("</HelpTOCNode>") != -1) { tocChanged = true; content = re2xRootEntry.Replace(content, String.Empty, 1); endTagPos = content.LastIndexOf("</HelpTOCNode>"); content = content.Remove(endTagPos, 14); } } // Update and save table of content with additional items if (tocChanged || (toc != null && toc.Count != 0)) { // The additional entries can go in ahead of the namespace // documentation entries or after them. if (toc != null && toc.Count != 0) { if (format != HelpFileFormat.HtmlHelp2x) { if (project.ContentPlacement == ContentPlacement.AboveNamespaces) { content = content.Insert(content.IndexOf( "<UL>") + 6, toc.ToString()); } else { content = content.Insert(content.LastIndexOf( "</UL>"), toc.ToString()); } } else { if (project.ContentPlacement == ContentPlacement.AboveNamespaces) { content = content.Insert(content.IndexOf( "\"1.0\">") + 8, toc.ToString(HelpFileFormat.HtmlHelp2x)); } else { content = content.Insert(content.IndexOf( "</HelpTOC>"), toc.ToString(HelpFileFormat.HtmlHelp2x)); } } } // We'll parse the HTML TOC as an XML file later on to // generate the tree view TOC for the website. This // requires some fix-ups to the <param> tags and the // removal of the DOCTYPE tag. if (format == HelpFileFormat.Website) { content = reParamFixUp.Replace(content, "<param$1/>"); content = reEncodeLeft.Replace(content, "$1<$3"); content = reEncodeRight.Replace(content, "$1>$3"); content = content.Remove(0, content.IndexOf("<HTML>")); } // Write the file back out with the appropriate encoding using (StreamWriter sw = new StreamWriter(tocFile, false, enc)) { sw.Write(content); } } }
//===================================================================== /// <summary> /// This returns a complete list of files for inclusion in the /// compiled help file. /// </summary> /// <param name="folder">The folder to expand</param> /// <param name="format">The HTML help file format</param> /// <returns>The full list of all files for the help project</returns> /// <remarks>The help file list is expanded to ensure that we get /// all additional content including all nested subfolders. The /// <b>format</b> parameter determines the format of the returned /// file list. For HTML Help 1, it returns a list of the filenames. /// For MS Help 2, it returns the list formatted with the necessary /// XML markup.</remarks> protected string HelpProjectFileList(string folder, HelpFileFormat format) { StringBuilder sb = new StringBuilder(10240); string itemFormat, filename, checkName; bool encode; if (folder == null) { throw new ArgumentNullException("folder"); } string[] files = Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories); if (folder.Length != 0 && folder[folder.Length - 1] != '\\') { folder += @"\"; } if ((format & HelpFileFormat.HtmlHelp1) != 0) { if (folder.IndexOf(',') != -1 || folder.IndexOf(".h", StringComparison.OrdinalIgnoreCase) != -1) { this.ReportWarning("BE0060", "The file path '{0}' " + "contains a comma or '.h' which may cause the Help 1 " + "compiler to fail.", folder); } if (project.HtmlHelpName.IndexOf(',') != -1 || project.HtmlHelpName.IndexOf(".h", StringComparison.OrdinalIgnoreCase) != -1) { this.ReportWarning("BE0060", "The HTMLHelpName property " + "value '{0}' contains a comma or '.h' which may " + "cause the Help 1 compiler to fail.", project.HtmlHelpName); } itemFormat = "{0}\r\n"; encode = false; } else { itemFormat = " <File Url=\"{0}\" />\r\n"; encode = true; } foreach (string name in files) { if (!encode) { filename = checkName = name.Replace(folder, String.Empty); if (checkName.EndsWith(".htm", StringComparison.OrdinalIgnoreCase) || checkName.EndsWith(".html", StringComparison.OrdinalIgnoreCase)) { checkName = checkName.Substring(0, checkName.LastIndexOf(".htm", StringComparison.OrdinalIgnoreCase)); } if (checkName.IndexOf(',') != -1 || checkName.IndexOf(".h", StringComparison.OrdinalIgnoreCase) != -1) { this.ReportWarning("BE0060", "The filename '{0}' " + "contains a comma or '.h' which may cause the " + "Help 1 compiler to fail.", filename); } sb.AppendFormat(itemFormat, filename); } else { sb.AppendFormat(itemFormat, HttpUtility.HtmlEncode(name.Replace(folder, String.Empty))); } } return(sb.ToString()); }
/// <summary> /// This is used to load the properties from the project file /// </summary> private void LoadProperties() { BuildProperty property; Version schemaVersion; string helpFileFormat; Dictionary<string, string> translateFormat = new Dictionary<string, string> { { "HTMLHELP1X", "HtmlHelp1" }, { "HTMLHELP2X", "MSHelp2" }, { "HELP1XANDHELP2X", "HtmlHelp1, MSHelp2" }, { "HELP1XANDWEBSITE", "HtmlHelp1, Website" }, { "HELP2XANDWEBSITE", "MSHelp2, Website" }, { "HELP1XAND2XANDWEBSITE", "HtmlHelp1, MSHelp2, Website" } }; try { // Ensure that we use the correct build engine for the project if(msBuildProject.ToolsVersion != "3.5") msBuildProject.DefaultToolsVersion = "3.5"; loadingProperties = true; projectCache = msBuildProject.EvaluatedProperties; property = projectCache["SHFBSchemaVersion"]; if(property == null || String.IsNullOrEmpty(property.Value)) throw new BuilderException("PRJ0001", "Invalid or missing SHFBSchemaVersion"); schemaVersion = new Version(property.Value); if(schemaVersion > SandcastleProject.SchemaVersion) throw new BuilderException("PRJ0002", "The selected " + "file is for a more recent version of the help file " + "builder. Please upgrade your copy to load the file."); // Note that many properties don't use the final value as they // don't contain variables that need replacing. foreach(BuildProperty prop in projectCache) switch(prop.Name.ToUpper(CultureInfo.InvariantCulture)) { case "CONFIGURATION": // These are ignored case "PLATFORM": break; case "APIFILTER": apiFilter.FromXml(prop.Value); break; case "COMPONENTCONFIGURATIONS": componentConfigs.FromXml(prop.Value); break; case "DOCUMENTATIONSOURCES": // The paths in the elements may contain variable // references so use final values if requested. if(!usingFinalValues) docSources.FromXml(prop.Value); else docSources.FromXml(prop.FinalValue); break; case "HELPATTRIBUTES": helpAttributes.FromXml(prop.Value); break; case "NAMESPACESUMMARIES": namespaceSummaries.FromXml(prop.Value); break; case "PLUGINCONFIGURATIONS": plugInConfigs.FromXml(prop.Value); break; case "HELPFILEFORMAT": // The enum value names changed in v1.8.0.3 if(schemaVersion.Major == 1 && schemaVersion.Minor == 8 && schemaVersion.Build == 0 && schemaVersion.Revision < 3) { helpFileFormat = prop.Value.ToUpper(CultureInfo.InvariantCulture); foreach(string key in translateFormat.Keys) helpFileFormat = helpFileFormat.Replace(key, translateFormat[key]); this.SetLocalProperty(prop.Name, helpFileFormat); msBuildProject.SetProperty("HelpFileFormat", this.HelpFileFormat.ToString(), null); } else this.SetLocalProperty(prop.Name, prop.Value); break; default: // These may or may not contain variable references // so use the final value if requested. if(!usingFinalValues) this.SetLocalProperty(prop.Name, prop.Value); else this.SetLocalProperty(prop.Name, prop.FinalValue); break; } // Note: Project data stored in item groups are loaded on // demand when first used (i.e. references, additional and // conceptual content, etc.) } catch(Exception ex) { throw new BuilderException("PRJ0003", String.Format( CultureInfo.CurrentCulture, "Error reading project " + "from '{0}':\r\n{1}", msBuildProject.FullFileName, ex.Message), ex); } finally { loadingProperties = false; this.OnDocumentationSourcesChanged(EventArgs.Empty); } }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <overloads>There are five overloads for the constructor</overloads> protected SandcastleProject() { characterMatchEval = new MatchEvaluator(this.OnCharacterMatch); buildVarMatchEval = new MatchEvaluator(this.OnBuildVarMatch); docSources = new DocumentationSourceCollection(this); docSources.ListChanged += new ListChangedEventHandler(docSources_ListChanged); namespaceSummaries = new NamespaceSummaryItemCollection(this); namespaceSummaries.ListChanged += new ListChangedEventHandler(ItemList_ListChanged); references = new ReferenceItemCollection(this); references.ListChanged += new ListChangedEventHandler(ItemList_ListChanged); componentConfigs = new ComponentConfigurationDictionary(this); componentConfigs.DictionaryChanged += new ListChangedEventHandler(ItemList_ListChanged); plugInConfigs = new PlugInConfigurationDictionary(this); plugInConfigs.DictionaryChanged += new ListChangedEventHandler(ItemList_ListChanged); apiFilter = new ApiFilterCollection(this); apiFilter.ListChanged += new ListChangedEventHandler(ItemList_ListChanged); helpAttributes = new MSHelpAttrCollection(this); helpAttributes.ListChanged += new ListChangedEventHandler(ItemList_ListChanged); try { loadingProperties = true; contentPlacement = ContentPlacement.AboveNamespaces; cleanIntermediates = keepLogFile = binaryTOC = includeStopWordList = selfBranded = true; this.BuildLogFile = null; missingTags = MissingTags.Summary | MissingTags.Parameter | MissingTags.TypeParameter | MissingTags.Returns | MissingTags.AutoDocumentCtors | MissingTags.Namespace | MissingTags.AutoDocumentDispose; visibleItems = VisibleItems.InheritedFrameworkMembers | VisibleItems.InheritedMembers | VisibleItems.Protected | VisibleItems.SealedProtected; helpFileFormat = HelpFileFormat.HtmlHelp1; htmlSdkLinkType = websiteSdkLinkType = HtmlSdkLinkType.Msdn; help2SdkLinkType = MSHelp2SdkLinkType.Msdn; helpViewerSdkLinkType = MSHelpViewerSdkLinkType.Msdn; sdkLinkTarget = SdkLinkTarget.Blank; presentationStyle = PresentationStyleTypeConverter.DefaultStyle; namingMethod = NamingMethod.Guid; syntaxFilters = BuildComponentManager.DefaultSyntaxFilter; collectionTocStyle = CollectionTocStyle.Hierarchical; helpFileVersion = "1.0.0.0"; tocOrder = -1; this.OutputPath = null; this.HtmlHelp1xCompilerPath = this.HtmlHelp2xCompilerPath = this.SandcastlePath = this.WorkingPath = null; this.HelpTitle = this.HtmlHelpName = this.CopyrightHref = this.CopyrightText = this.FeedbackEMailAddress = this.FeedbackEMailLinkText = this.HeaderText = this.FooterText = this.ProjectSummary = this.RootNamespaceTitle = this.PlugInNamespaces = this.TopicVersion = this.TocParentId = this.TocParentVersion = this.CatalogProductId = this.CatalogVersion = null; language = new CultureInfo("en-US"); frameworkVersion = FrameworkVersionTypeConverter.LatestMatching("3.5"); } finally { loadingProperties = false; } }