/// <summary> /// Constructor /// </summary> /// <param name="helpFileFormats">The help file formats to which the files apply</param> /// <param name="basePath">An alternate base path or null to use the presentation style base path</param> /// <param name="sourcePath">The source path</param> /// <param name="destination">The destination path</param> /// <param name="templateFileExtensions">An enumerable list of file extensions to treat as template files</param> public ContentFiles(HelpFileFormats helpFileFormats, string basePath, string sourcePath, string destination, IEnumerable <string> templateFileExtensions) { if (Path.IsPathRooted(sourcePath)) { throw new InvalidOperationException("Content source path must be relative"); } if (!String.IsNullOrEmpty(destination)) { if (Path.IsPathRooted(destination)) { throw new InvalidOperationException("Content destination path must be relative"); } if (destination.IndexOfAny(new[] { '*', '?' }) != -1) { throw new InvalidOperationException("Content destination must be a path only"); } } this.HelpFileFormats = helpFileFormats; this.BasePath = basePath; this.SourcePathWildcard = sourcePath; this.DestinationFolder = destination; this.TemplateFileExtensions = templateFileExtensions.ToList(); }
/// <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(HelpFileFormats format, StringBuilder sb) { foreach (TocEntry te in this) { te.ConvertToString(format, sb); } }
/// <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(HelpFileFormats format) { StringBuilder sb = new StringBuilder(1024); this.ConvertToString(format, sb); return(sb.ToString()); }
static void editHtmlFolder(BuildProcess builder, string path, HelpFileFormats format) { string[] htmlFilesPaths = Directory.GetFiles(path, "*.htm", SearchOption.TopDirectoryOnly); // Then we classify the Files into different types List <Tuple <string, FileType> > filesWithType = new List <Tuple <string, FileType> >(htmlFilesPaths.Length); foreach (string htmlFilePath in htmlFilesPaths) { string htmlFileName = htmlFilePath.Remove(0, path.Length + 1); // +1 comes from backslash character in the path ending. htmlFileName = htmlFileName.Remove(htmlFileName.Length - 4); // remove the .htm at the end of the string. FileType htmlType = findFileType(htmlFileName); //if (htmlType != FileType.other) //{ // Console.WriteLine(htmlFileName + " | " + htmlType); //} Tuple <string, FileType> fileWithType = new Tuple <string, FileType>(htmlFilePath, htmlType); filesWithType.Add(fileWithType); } //edit each html page int counter = 0; foreach (Tuple <string, FileType> fileWithType in filesWithType) { string fileName = fileWithType.Item1; FileType filetype = fileWithType.Item2; // edit the page itself editPage(fileName, filetype); // edit all the typeNames on the page editForTypeNames(fileWithType.Item1); if (format == HelpFileFormats.MSHelpViewer) { editFileForMSHV(fileName, filetype); } else if (format == HelpFileFormats.Website) { editWebsiteToc(fileName); } counter++; if (counter % 500 == 0) { builder.ReportProgress(" Adjusted {0} pages", counter); } } builder.ReportProgress(" Finished adjusting {0} pages", counter); return; }
static void editHtmlFolder(BuildProcess builder, string path, HelpFileFormats format) { string[] htmlFilesPaths = Directory.GetFiles(path, "*.htm", SearchOption.TopDirectoryOnly); // Then we classify the Files into different types var filesWithType = new List <Tuple <string, FileType, string> >(htmlFilesPaths.Length); foreach (string htmlFilePath in htmlFilesPaths) { string htmlFileName = htmlFilePath.Remove(0, path.Length + 1); // +1 comes from backslash character in the path ending. htmlFileName = htmlFileName.Remove(htmlFileName.Length - 4); // remove the .htm at the end of the string. FileType htmlType = findFileType(htmlFileName); string strType = ""; if (htmlType == FileType.singleField) { var contents = File.ReadAllText(htmlFilePath); strType = determineFieldType(contents); } var fileWithType = new Tuple <string, FileType, string>(htmlFilePath, htmlType, strType); filesWithType.Add(fileWithType); } //edit each html page int counter = 0; foreach (var fileWithType in filesWithType) { string fileName = fileWithType.Item1; FileType filetype = fileWithType.Item2; // edit the page itself editPage(fileName, filetype); // edit all the typeNames on the page editForTypeNames(fileWithType.Item1); if (format == HelpFileFormats.MSHelpViewer) { editFileForMSHV(fileName, filetype, fileWithType.Item3); } counter++; if (counter % 500 == 0) { builder.ReportProgress(" Adjusted {0} pages", counter); } } builder.ReportProgress(" Finished adjusting {0} pages", counter); return; }
/// <summary> /// Update the info provider text and available help file formats when the presentation style changes /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void cboPresentationStyle_SelectionChanged(object sender, SelectionChangedEventArgs e) { IPresentationStyleMetadata pss; if (presentationStyles == null) { return; } pss = (IPresentationStyleMetadata)cboPresentationStyle.SelectedItem; imgPresentationStyleInfo.ToolTip = String.Format(CultureInfo.InvariantCulture, "{0}\r\n\r\nVersion {1}\r\n{2}", pss.Description, pss.Version, pss.Copyright); // Filter the help file formats based on what is supported by the presentation style HelpFileFormats supportedFormats = HelpFileFormats.HtmlHelp1; var style = componentCache.ComponentContainer.GetExports <PresentationStyleSettings, IPresentationStyleMetadata>().FirstOrDefault(s => s.Metadata.Id.Equals(pss.Id, StringComparison.OrdinalIgnoreCase)); if (style != null) { supportedFormats = style.Value.SupportedFormats; } allHelpFileFormats.ForEach(f => f.IsActive = false); var styleFormats = allHelpFileFormats.Where(f => (supportedFormats & f.Format) != 0).ToList(); if (styleFormats.Count != 0 && !styleFormats.Any(f => f.IsSelected)) { styleFormats[0].IsSelected = true; } styleFormats.ForEach(f => f.IsActive = true); lbHelpFileFormat.ItemsSource = styleFormats; if (!isBinding) { this.PropertyChanged?.Invoke(this, e); } }
/// <summary> /// This is used to copy the presentation style help file content to the given destination folder /// </summary> /// <param name="format">The help file format for which to copy files</param> /// <param name="destinationBasePath">The destination base path to which the files are copied</param> /// <param name="progressReporter">An optional action delegate used to report progress</param> /// <param name="transformTemplate">A action delegate used to transform a template file (file, source /// folder, destination folder)</param> public void CopyHelpContent(HelpFileFormats format, string destinationBasePath, Action <string, object[]> progressReporter, Action <string, string, string> transformTemplate) { string sourcePath, destPath; if (transformTemplate == null) { throw new ArgumentNullException(nameof(transformTemplate)); } foreach (var content in contentFiles) { if ((content.HelpFileFormats & format) != 0) { if (content.BasePath == null) { sourcePath = this.ResolvePath(content.SourcePathWildcard); } else { sourcePath = this.ResolvePath(Path.Combine(content.BasePath, content.SourcePathWildcard)); } if (content.DestinationFolder == null) { destPath = Path.Combine(destinationBasePath, Path.GetFileName(Path.GetDirectoryName(content.SourcePathWildcard))); } else if (content.DestinationFolder.Length == 0) { destPath = destinationBasePath; } else { destPath = Path.Combine(destinationBasePath, content.DestinationFolder); } RecursiveCopy(sourcePath, destPath, progressReporter, content.TemplateFileExtensions, transformTemplate); } } }
/// <summary> /// This is used to copy the presentation style help file content to the given destination folder /// </summary> /// <param name="format">The help file format for which to copy files</param> /// <param name="destinationBasePath">The destination base path to which the files are copied</param> /// <param name="progressReporter">An optional action delegate used to report progress</param> /// <param name="transformTemplate">A action delegate used to transform a template file (file, source /// folder, destination folder)</param> public void CopyHelpContent(HelpFileFormats format, string destinationBasePath, Action<string, object[]> progressReporter, Action<string, string, string> transformTemplate) { string sourcePath, destPath; foreach(var content in contentFiles) { if((content.HelpFileFormats & format) != 0) { if(content.BasePath == null) sourcePath = this.ResolvePath(content.SourcePathWildcard); else sourcePath = this.ResolvePath(Path.Combine(content.BasePath, content.SourcePathWildcard)); if(content.DestinationFolder == null) destPath = Path.Combine(destinationBasePath, Path.GetFileName(Path.GetDirectoryName(content.SourcePathWildcard))); else if(content.DestinationFolder.Length == 0) destPath = destinationBasePath; else destPath = Path.Combine(destinationBasePath, content.DestinationFolder); RecursiveCopy(sourcePath, destPath, progressReporter, content.TemplateFileExtensions, transformTemplate); } } }
//===================================================================== /// <summary> /// Call this method to perform the build on the project. /// </summary> public void Build() { Project msBuildProject = null; ProjectItem projectItem; string resolvedPath, helpFile, languageFile, scriptFile, hintPath, message = null; SandcastleProject originalProject = null; System.Diagnostics.Debug.WriteLine("Build process starting\r\n"); try { taskRunner = new TaskRunner(this); // If the project isn't using final values suitable for the build, create a copy of the // project that is using final values. if(!project.UsingFinalValues) { originalProject = project; project = new SandcastleProject(originalProject.MSBuildProject); } 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; // The version of MSBuild to use is based on the tools version set in the project msBuildExePath = Path.Combine(ProjectCollection.GlobalProjectCollection.Toolsets.First( t => t.ToolsVersion == project.MSBuildProject.ToolsVersion).ToolsPath, "MSBuild.exe"); // Get the location of the template files templateFolder = ComponentUtilities.ToolsFolder + @"Templates\"; // 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 & HelpFileFormats.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"); // Make sure we can find the tools this.FindTools(); // 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}", ComponentUtilities.ToolsFolder); Environment.SetEnvironmentVariable("SHFBROOT", ComponentUtilities.ToolsFolder); } this.ReportProgress("Locating components in the following folder(s):"); if(!String.IsNullOrEmpty(project.ComponentPath)) this.ReportProgress(" {0}", project.ComponentPath); this.ReportProgress(" {0}", Path.GetDirectoryName(project.Filename)); this.ReportProgress(" {0}", ComponentUtilities.ComponentsFolder); this.ReportProgress(" {0}", ComponentUtilities.ToolsFolder); // Get the framework reflection data settings to use for the build reflectionDataDictionary = new ReflectionDataSetDictionary(new[] { project.ComponentPath, Path.GetDirectoryName(project.Filename) }); frameworkReflectionData = reflectionDataDictionary.CoreFrameworkByTitle(project.FrameworkVersion, true); if(frameworkReflectionData == null) throw new BuilderException("BE0071", String.Format(CultureInfo.CurrentCulture, "Unable to locate information for the project framework version '{0}' or a suitable " + "redirected version on this system. See error number help topic for details.", project.FrameworkVersion)); this.ReportProgress("Framework reflection data location: {0}", this.FrameworkReflectionDataFolder); if(!Directory.EnumerateFiles(this.FrameworkReflectionDataFolder, "*.xml").Any()) throw new BuilderException("BE0032", "Reflection data files for the selected framework " + "do not exist yet (" + frameworkReflectionData.Title + "). See help file for " + "details about this error number."); // Warn if a different framework is being used for the build if(frameworkReflectionData.Title != project.FrameworkVersion) this.ReportWarning("BE0072", "Project framework version '{0}' not found. It has been " + "redirected and will use '{1}' instead.", project.FrameworkVersion, frameworkReflectionData.Title); // Get the composition container used to find build components in the rest of the build process componentContainer = ComponentUtilities.CreateComponentContainer(new[] { project.ComponentPath, Path.GetDirectoryName(project.Filename) }, this.CancellationToken); syntaxGenerators = componentContainer.GetExports<ISyntaxGeneratorFactory, ISyntaxGeneratorMetadata>().Select(sf => sf.Metadata).ToList(); buildComponents = componentContainer.GetExports<BuildComponentFactory, IBuildComponentMetadata>().GroupBy(c => c.Metadata.Id).Select(g => g.First()).ToDictionary( key => key.Metadata.Id, value => value.Value); // Figure out which presentation style to use var style = componentContainer.GetExports<PresentationStyleSettings, IPresentationStyleMetadata>().FirstOrDefault(s => s.Metadata.Id.Equals( project.PresentationStyle, StringComparison.OrdinalIgnoreCase)); if(style == null) throw new BuilderException("BE0001", "The PresentationStyle property value of '" + project.PresentationStyle + "' is not recognized as a valid presentation style definition"); presentationStyle = style.Value; this.ReportProgress("Using presentation style '{0}' located in '{1}'", style.Metadata.Id, Path.Combine(presentationStyle.Location, presentationStyle.BasePath ?? String.Empty)); var psErrors = presentationStyle.CheckForErrors(); if(psErrors.Any()) throw new BuilderException("BE0004", String.Format(CultureInfo.CurrentCulture, "The selected presentation style ({0}) is not valid. Reason(s):\r\n{1}", style.Metadata.Id, String.Join("\r\n", psErrors))); // If the presentation style does not support one or more of the selected help file formats, // stop now. if((project.HelpFileFormat & ~presentationStyle.SupportedFormats) != 0) throw new BuilderException("BE0074", String.Format(CultureInfo.CurrentCulture, "The selected presentation style ({0}) does not support one or more of the selected " + "help file formats. Supported formats: {1}", style.Metadata.Id, presentationStyle.SupportedFormats)); // Create the substitution tag replacement handler now as we have everything it needs substitutionTags = new SubstitutionTagReplacement(this); // Load the plug-ins if necessary if(project.PlugInConfigurations.Count != 0 || presentationStyle.PlugInDependencies.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); } } // For MS Help Viewer, the HTML Help Name cannot contain periods, ampersands, or pound signs if((project.HelpFileFormat & HelpFileFormats.MSHelpViewer) != 0 && this.ResolvedHtmlHelpName.IndexOfAny(new[] { '.', '#', '&' }) != -1) throw new BuilderException("BE0075", "For MS Help Viewer builds, the HtmlHelpName property " + "cannot contain periods, ampersands, or pound signs as they are not valid in the " + "help file name."); // 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 + this.ResolvedHtmlHelpName + ".chm"; if((project.HelpFileFormat & HelpFileFormats.HtmlHelp1) != 0 && File.Exists(helpFile)) File.Delete(helpFile); helpFile = Path.ChangeExtension(helpFile, ".mshc"); if((project.HelpFileFormat & HelpFileFormats.MSHelpViewer) != 0 && File.Exists(helpFile)) File.Delete(helpFile); if((project.HelpFileFormat & HelpFileFormats.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); } helpFile = outputFolder + this.ResolvedHtmlHelpName + ".docx"; if((project.HelpFileFormat & HelpFileFormats.OpenXml) != 0 && File.Exists(helpFile)) File.Delete(helpFile); } catch(IOException ex) { throw new BuilderException("BE0025", "Unable to remove prior build output: " + ex.Message); } catch { throw; } if((project.HelpFileFormat & (HelpFileFormats.Website | HelpFileFormats.Markdown)) != 0) { this.ReportProgress("-------------------------------"); this.ReportProgress("Clearing any prior web/markdown output..."); // Purge all files and folders from the output path except for the working folder and the // build log. Read-only and/or hidden files and folders are ignored as they are assumed to // be under source control. foreach(string file in Directory.EnumerateFiles(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.EnumerateDirectories(outputFolder)) try { // Ignore the working folder in case it wasn't removed above if(!folder.Equals(workingFolder.Substring(0, workingFolder.Length - 1), StringComparison.OrdinalIgnoreCase)) { // Some source control providers have a mix of read-only/hidden files within a // folder that isn't read-only/hidden (i.e. Subversion). In such cases, leave // the folder alone. if(Directory.EnumerateFileSystemEntries(folder, "*", SearchOption.AllDirectories).Any( f => (File.GetAttributes(f) & (FileAttributes.ReadOnly | FileAttributes.Hidden)) != 0)) { this.ReportProgress(" Did not delete folder '{0}' as it contains " + "read-only or hidden folders/files", folder); } else 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); } } Directory.CreateDirectory(workingFolder); // 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 = Path.Combine(presentationStyle.ResolvePath(presentationStyle.ToolResourceItemsPath), language.Name + ".xml"); this.ReportProgress(BuildStep.GenerateSharedContent, "Generating shared content files ({0}, {1})...", language.Name, language.DisplayName); if(!File.Exists(languageFile)) { languageFile = Path.Combine(presentationStyle.ResolvePath(presentationStyle.ToolResourceItemsPath), "en-US.xml"); // Warn the user about the default being used this.ReportWarning("BE0002", "Help file 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 = Path.Combine(presentationStyle.ResolvePath(presentationStyle.ResourceItemsPath), 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); substitutionTags.TransformTemplate(Path.GetFileName(languageFile), Path.GetDirectoryName(languageFile), workingFolder); File.Move(workingFolder + Path.GetFileName(languageFile), workingFolder + "SHFBContent.xml"); // Copy the stop word list languageFile = Path.Combine(ComponentUtilities.ToolsFolder, @"PresentationStyles\Shared\" + @"StopWordList\" + Path.GetFileNameWithoutExtension(languageFile) +".txt"); File.Copy(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)) { substitutionTags.TransformTemplate("MRefBuilder.config", templateFolder, workingFolder); scriptFile = substitutionTags.TransformTemplate("GenerateRefInfo.proj", templateFolder, workingFolder); try { msBuildProject = new Project(scriptFile); // Add the references foreach(var r in referenceDictionary.Values) { projectItem = msBuildProject.AddItem(r.Item1, r.Item2, r.Item3)[0]; // Make sure hint paths are correct by adding the project folder to any relative // paths. Skip any containing MSBuild variable references. if(projectItem.HasMetadata(BuildItemMetadata.HintPath)) { hintPath = projectItem.GetMetadataValue(BuildItemMetadata.HintPath); if(!Path.IsPathRooted(hintPath) && hintPath.IndexOf("$(", StringComparison.Ordinal) == -1) { hintPath = FilePath.GetFullPath(Path.Combine(projectFolder, hintPath)); // If the full path length would exceed the system maximums, make it relative // to keep it under the maximum lengths. if(hintPath.Length > 259 || Path.GetDirectoryName(hintPath).Length > 247) hintPath = FolderPath.AbsoluteToRelativePath(workingFolder, hintPath); projectItem.SetMetadataValue(BuildItemMetadata.HintPath, hintPath); } } } // Add the assemblies to document foreach(string assemblyName in assembliesList) msBuildProject.AddItem("Assembly", assemblyName); msBuildProject.Save(scriptFile); } finally { // If we loaded it, we must unload it. If not, it is cached and may cause problems later. if(msBuildProject != null) { ProjectCollection.GlobalProjectCollection.UnloadProject(msBuildProject); ProjectCollection.GlobalProjectCollection.UnloadProject(msBuildProject.Xml); } } this.ExecutePlugIns(ExecutionBehaviors.Before); // Silverlight build targets are only available for 32-bit builds regardless of the framework // version and require the 32-bit version of MSBuild in order to load the target file correctly. if(project.FrameworkVersion.StartsWith("Silverlight", StringComparison.OrdinalIgnoreCase)) taskRunner.Run32BitProject("GenerateRefInfo.proj", false); else taskRunner.RunProject("GenerateRefInfo.proj", false); this.ExecutePlugIns(ExecutionBehaviors.After); } // If this was a partial build used to obtain API information, stop now if(this.PartialBuildType == PartialBuildType.GenerateReflectionInfo) { commentsFiles.Save(); goto AllDone; // Yeah, I know it's evil but it's quick } // Transform the reflection output based on the document model and create the topic manifest this.ReportProgress(BuildStep.TransformReflectionInfo, "Transforming reflection output..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = substitutionTags.TransformTemplate("TransformManifest.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("TransformManifest.proj", false); // Change the reflection file extension before running the ExecutionBehaviors.After plug-ins // so that the plug-ins (if any) get the correct filename. reflectionFile = Path.ChangeExtension(reflectionFile, ".xml"); this.ExecutePlugIns(ExecutionBehaviors.After); } else reflectionFile = Path.ChangeExtension(reflectionFile, ".xml"); // If this was a partial build used to obtain information for namespace and namespace group // comments, stop now. if(this.PartialBuildType == PartialBuildType.TransformReflectionInfo) { commentsFiles.Save(); goto AllDone; // Yeah, I know it's evil but it's quick } // Load the transformed reflection information file reflectionFile = workingFolder + "reflection.xml"; // If there is nothing to document, stop the build if(!ComponentUtilities.XmlStreamAxis(reflectionFile, "api").Any()) throw new BuilderException("BE0033", "No APIs found to document. See error topic in " + "help file for details."); // Generate namespace summary information this.GenerateNamespaceSummaries(); // Expand <inheritdoc /> tags? if(commentsFiles.ContainsInheritedDocumentation) { commentsFiles.Save(); // Transform the reflection output. this.ReportProgress(BuildStep.GenerateInheritedDocumentation, "Generating inherited documentation..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { substitutionTags.TransformTemplate("GenerateInheritedDocs.config", templateFolder, workingFolder); scriptFile = substitutionTags.TransformTemplate("GenerateInheritedDocs.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("GenerateInheritedDocs.proj", true); 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")); } commentsFiles.Save(); this.EnsureOutputFoldersExist(null); // Copy conceptual content files if there are topics or tokens. Tokens can be replaced in // XML comments files so we check for them too. if(this.ConceptualContent.ContentLayoutFiles.Count != 0 || this.ConceptualContent.TokenFiles.Count != 0) { this.ReportProgress(BuildStep.CopyConceptualContent, "Copying conceptual content..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); this.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); this.ConceptualContent.CreateConfigurationFiles(this); this.ExecutePlugIns(ExecutionBehaviors.After); } } else // Create an empty xmlComp folder required by the build configuration Directory.CreateDirectory(Path.Combine(workingFolder, "xmlComp")); // 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 = substitutionTags.TransformTemplate("GenerateIntermediateTOC.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("GenerateIntermediateTOC.proj", false); // 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 & HelpFileFormats.MSHelpViewer) != 0 ? this.RootContentContainerId : null, project.TocOrder, workingFolder + "_ConceptualTOC_.xml"); } this.ExecutePlugIns(ExecutionBehaviors.After); } // Create the Sandcastle configuration file this.ReportProgress(BuildStep.CreateBuildAssemblerConfigs, "Creating Sandcastle configuration files..."); // Add referenced namespaces to the hash set. These are used to ensure just the needed set of // reflection target files are loaded by BuildAssembler and nothing more to save some time and // memory. var rn = this.ReferencedNamespaces; // These are all of the valid namespaces we are interested in. This prevents the methods below // from returning nested types as potential namespaces since they can't tell the difference. HashSet<string> validNamespaces = new HashSet<string>(Directory.EnumerateFiles( this.FrameworkReflectionDataFolder, "*.xml", SearchOption.AllDirectories).Select( f => Path.GetFileNameWithoutExtension(f))); // Get namespaces referenced in the XML comments of the documentation sources foreach(var n in commentsFiles.GetReferencedNamespaces(validNamespaces)) rn.Add(n); // Get namespaces referenced in the reflection data (plug-ins are responsible for adding // additional namespaces if they add other reflection data files). foreach(string n in GetReferencedNamespaces(reflectionFile, validNamespaces)) rn.Add(n); // Get namespaces from the Framework comments files of the referenced namespaces. This adds // references for stuff like designer and support classes not directly referenced anywhere else. foreach(string n in frameworkReflectionData.GetReferencedNamespaces(language, rn, validNamespaces).ToList()) rn.Add(n); // If F# syntax is being generated, add some of the F# namespaces as the syntax sections generate // references to types that may not be there in non-F# projects. if(ComponentUtilities.SyntaxFiltersFrom(syntaxGenerators, project.SyntaxFilters).Any( f => f.Id == "F#")) { rn.Add("Microsoft.FSharp.Core"); rn.Add("Microsoft.FSharp.Control"); } // If there are no referenced namespaces, add System as a default to prevent the build components // from loading the entire set. if(rn.Count == 0) rn.Add("System"); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); this.ReportProgress(" sandcastle.config"); // The configuration varies based on the style. We'll use a common name (sandcastle.config). resolvedPath = presentationStyle.ResolvePath(presentationStyle.ReferenceBuildConfiguration); substitutionTags.TransformTemplate(Path.GetFileName(resolvedPath), Path.GetDirectoryName(resolvedPath), workingFolder); if(!Path.GetFileName(resolvedPath).Equals("sandcastle.config", StringComparison.OrdinalIgnoreCase)) File.Move(workingFolder + Path.GetFileName(resolvedPath), workingFolder + "sandcastle.config"); // The conceptual content configuration file is only created if needed. if(this.ConceptualContent.ContentLayoutFiles.Count != 0) { this.ReportProgress(" conceptual.config"); resolvedPath = presentationStyle.ResolvePath(presentationStyle.ConceptualBuildConfiguration); substitutionTags.TransformTemplate(Path.GetFileName(resolvedPath), Path.GetDirectoryName(resolvedPath), workingFolder); if(!Path.GetFileName(resolvedPath).Equals("conceptual.config", StringComparison.OrdinalIgnoreCase)) File.Move(workingFolder + Path.GetFileName(resolvedPath), workingFolder + "conceptual.config"); } this.ExecutePlugIns(ExecutionBehaviors.After); } // Merge the build component custom configurations this.MergeComponentConfigurations(); commentsFiles = null; // Build the conceptual help topics if(this.ConceptualContent.ContentLayoutFiles.Count != 0) { this.ReportProgress(BuildStep.BuildConceptualTopics, "Building conceptual help topics..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = substitutionTags.TransformTemplate("BuildConceptualTopics.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("BuildConceptualTopics.proj", false); this.ExecutePlugIns(ExecutionBehaviors.After); } } // Build the reference help topics this.ReportProgress(BuildStep.BuildReferenceTopics, "Building reference help topics..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = substitutionTags.TransformTemplate("BuildReferenceTopics.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("BuildReferenceTopics.proj", false); 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 & (HelpFileFormats.HtmlHelp1 | HelpFileFormats.Website)) != 0) { this.ReportProgress(BuildStep.ExtractingHtmlInfo, "Extracting HTML info for HTML Help 1 and/or website..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = substitutionTags.TransformTemplate("ExtractHtmlInfo.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("ExtractHtmlInfo.proj", true); this.ExecutePlugIns(ExecutionBehaviors.After); } } // Copy the standard help file content. This is done just before compiling the help so that // template files from the presentation style can take advantage of tag substitution. By this // point, we should have everything we could possibly need. this.CopyStandardHelpContent(); if((project.HelpFileFormat & HelpFileFormats.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 = HelpFileFormats.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); substitutionTags.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 = substitutionTags.TransformTemplate("Build1xHelpFile.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("Build1xHelpFile.proj", true); this.GatherBuildOutputFilenames(); this.ExecutePlugIns(ExecutionBehaviors.After); } } if((project.HelpFileFormat & HelpFileFormats.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 = HelpFileFormats.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)) { substitutionTags.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 + this.ResolvedHtmlHelpName + ".msha"); // Generate the example install and remove scripts substitutionTags.TransformTemplate("InstallMSHC.bat", templateFolder, workingFolder); File.Move(workingFolder + "InstallMSHC.bat", workingFolder + "Install_" + this.ResolvedHtmlHelpName + ".bat"); substitutionTags.TransformTemplate("RemoveMSHC.bat", templateFolder, workingFolder); File.Move(workingFolder + "RemoveMSHC.bat", workingFolder + "Remove_" + this.ResolvedHtmlHelpName + ".bat"); // Copy the launcher utility File.Copy(ComponentUtilities.ToolsFolder + "HelpLibraryManagerLauncher.exe", workingFolder + "HelpLibraryManagerLauncher.exe"); File.SetAttributes(workingFolder + "HelpLibraryManagerLauncher.exe", FileAttributes.Normal); scriptFile = substitutionTags.TransformTemplate("BuildHelpViewerFile.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("BuildHelpViewerFile.proj", true); this.GatherBuildOutputFilenames(); this.ExecutePlugIns(ExecutionBehaviors.After); } } if((project.HelpFileFormat & HelpFileFormats.Website) != 0) { // Generate the table of contents and set the default topic this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents, "Generating website table of contents file..."); currentFormat = HelpFileFormats.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(); } if((project.HelpFileFormat & HelpFileFormats.OpenXml) != 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 Open XML format, there is no project file to compile and the TOC layout is // generated when the document is opened. All of the necessary TOC info is stored in the // intermediate TOC file generated prior to building the topics. The process used to merge // the topics into a single document uses it to define the order in which the topics are // combined. this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents, "Executing informational " + "Generate Table of Contents build step for plug-ins (not used for Open XML)"); currentFormat = HelpFileFormats.OpenXml; 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 Open XML)"); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); this.ExecutePlugIns(ExecutionBehaviors.After); } // Build the Open XML document this.ReportProgress(BuildStep.CompilingHelpFile, "Generating Open XML document file..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = substitutionTags.TransformTemplate("BuildOpenXmlFile.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("BuildOpenXmlFile.proj", true); this.GatherBuildOutputFilenames(); this.ExecutePlugIns(ExecutionBehaviors.After); } } if((project.HelpFileFormat & HelpFileFormats.Markdown) != 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 Markdown format, there is no project file to compile and the TOC layout is // generated by the build task. All of the necessary TOC info is stored in the intermediate // TOC file generated prior to building the topics. The build task uses it to find the // topics to finalize and generate the sidebar TOC file. this.ReportProgress(BuildStep.GenerateHelpFormatTableOfContents, "Executing informational " + "Generate Table of Contents build step for plug-ins (not used for Markdown)"); currentFormat = HelpFileFormats.Markdown; 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 Markdown)"); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { this.ExecutePlugIns(ExecutionBehaviors.Before); this.ExecutePlugIns(ExecutionBehaviors.After); } // Generate the markdown content this.ReportProgress(BuildStep.CompilingHelpFile, "Generating markdown content..."); if(!this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { scriptFile = substitutionTags.TransformTemplate("GenerateMarkdownContent.proj", templateFolder, workingFolder); this.ExecutePlugIns(ExecutionBehaviors.Before); taskRunner.RunProject("GenerateMarkdownContent.proj", true); this.GatherBuildOutputFilenames(); this.ExecutePlugIns(ExecutionBehaviors.After); } } // 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: TimeSpan runtime = DateTime.Now - buildStart; this.ReportProgress(BuildStep.Completed, "\r\nBuild completed successfully at {0}. " + "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(OperationCanceledException ) { buildCancelling = true; this.ReportError(BuildStep.Canceled, "BE0064", "BUILD CANCELLED BY USER"); System.Diagnostics.Debug.WriteLine("Build process aborted\r\n"); } catch(Exception ex) { System.Diagnostics.Debug.WriteLine(ex); var agEx = ex as AggregateException; if(agEx != null) foreach(var inEx in agEx.InnerExceptions) { if(message != null) message += "\r\n\r\n"; message += inEx.Message + "\r\n" + inEx.StackTrace; } var bex = ex as BuilderException; do { if(message != null) message += "\r\n\r\n"; message += ex.Message + "\r\n" + ex.StackTrace; 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 { 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(componentContainer != null) componentContainer.Dispose(); } catch(Exception ex) { // Not much we can do at this point... this.ReportProgress(ex.ToString()); } finally { if(swLog != null) { swLog.WriteLine("</buildStep>\r\n</shfbBuild>"); swLog.Close(); swLog = null; } // If we created a copy of the project, dispose of it and return to the original if(originalProject != null) { project.Dispose(); project = originalProject; } if(this.CurrentBuildStep == BuildStep.Completed && !project.KeepLogFile) File.Delete(this.LogFilename); } } }
static internal void Convert(BuildProcess builder, HelpFileFormats format) { // Check which modes all need to be compiled string basePath = builder.WorkingFolder; string htmlHelp1Path = @"Output\HtmlHelp1"; string MsHelpViewerPath = @"Output\MsHelpViewer"; string websitePath = @"Output\Website"; string htmlPath = "html"; if (format == HelpFileFormats.HtmlHelp1) { builder.ReportProgress("Editing htmlHelp1 files..."); string htmlFolderPath = Path.Combine(basePath, htmlHelp1Path, htmlPath); string[] hhcPaths = Directory.GetFiles(basePath, "*.hhc", SearchOption.TopDirectoryOnly); string[] hhkPaths = Directory.GetFiles(basePath, "*.hhk", SearchOption.TopDirectoryOnly); if (Directory.Exists(htmlFolderPath)) { builder.ReportProgress(" Editing html topic pages..."); XSharpDocChanger.editHtmlFolder(builder, htmlFolderPath, format); } else { builder.ReportProgress(" Could not find html folder!"); } if (hhcPaths.Length > 0) { builder.ReportProgress(" Editing {0} TOC's for HtmlHelp1...", hhcPaths.Length); foreach (string hhc in hhcPaths) { XSharpDocChanger.editHhc(hhc); XSharpDocChanger.editForTypeNames(hhc); } } else { builder.ReportProgress(" Found no TOC for HtmlHelp1"); } if (hhkPaths.Length > 0) { builder.ReportProgress(" Editing {0} index file(s) for HtmlHelp1...", hhkPaths.Length); foreach (string hhk in hhkPaths) { XSharpDocChanger.editHhk(hhk); XSharpDocChanger.editForTypeNames(hhk); } } else { builder.ReportProgress(" Found no index file for HtmlHelp1"); } } if (format == HelpFileFormats.MSHelpViewer) { builder.ReportProgress("Editing MsHelpViewer files..."); string htmlFolderPath = Path.Combine(basePath, MsHelpViewerPath, htmlPath); if (Directory.Exists(htmlFolderPath)) { builder.ReportProgress(" Editing html topic pages..."); XSharpDocChanger.editHtmlFolder(builder, htmlFolderPath, format); // builder.ReportProgress(" Editing TOC and index"); //XSharpDocChanger.editHtmlFolderForMSHV(builder, htmlFolderPath); } else { builder.ReportProgress(" Could not find html folder!"); } } if (format == HelpFileFormats.Website) { builder.ReportProgress("Editing Website files..."); string htmlFolderPath = Path.Combine(basePath, websitePath, htmlPath); if (Directory.Exists(htmlFolderPath)) { builder.ReportProgress(" Editing html topic pages..."); XSharpDocChanger.editHtmlFolder(builder, htmlFolderPath, format); //XSharpDocChanger.editForWebsiteFolder(builder,htmlFolderPath); } } }
//===================================================================== /// <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 <paramref name="format"/> 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> private string HelpProjectFileList(string folder, HelpFileFormats format) { StringBuilder sb = new StringBuilder(10240); string itemFormat, filename, checkName, sourceFolder = folder; bool encode; if (folder == null) { throw new ArgumentNullException("folder"); } if (folder.Length != 0 && folder[folder.Length - 1] != '\\') { folder += @"\"; } if ((format & HelpFileFormats.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 (this.ResolvedHtmlHelpName.IndexOf(',') != -1 || this.ResolvedHtmlHelpName.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.", this.ResolvedHtmlHelpName); } itemFormat = "{0}\r\n"; encode = false; } else { itemFormat = " <File Url=\"{0}\" />\r\n"; encode = true; } foreach (string name in Directory.EnumerateFiles(sourceFolder, "*.*", SearchOption.AllDirectories)) { 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(HelpFileFormats format, StringBuilder sb) { string guid, url, orderAttr, titleAttr; switch (format) { case HelpFileFormats.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", WebUtility.HtmlEncode(this.Title), WebUtility.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", WebUtility.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", WebUtility.HtmlEncode(this.Title), WebUtility.HtmlEncode(this.DestinationFile)); } sb.Append("<UL>\r\n"); children.ConvertToString(format, sb); sb.Append("</UL>\r\n"); } break; case HelpFileFormats.MSHelp2: case HelpFileFormats.Website: case HelpFileFormats.OpenXml: if (!String.IsNullOrEmpty(this.DestinationFile) && format == HelpFileFormats.Website) { url = this.DestinationFile.Replace('\\', '/'); } else { url = this.DestinationFile; } if (children.Count == 0) { sb.AppendFormat("<HelpTOCNode Url=\"{0}\" Title=\"{1}\" />\r\n", WebUtility.HtmlEncode(url), WebUtility.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, WebUtility.HtmlEncode(this.Title)); } else { sb.AppendFormat("<HelpTOCNode Id=\"{0}\" Url=\"{1}\" Title=\"{2}\">\r\n", guid, url, WebUtility.HtmlEncode(this.Title)); } children.ConvertToString(format, sb); sb.Append("</HelpTOCNode>\r\n"); } break; case HelpFileFormats.MSHelpViewer: if (String.IsNullOrEmpty(this.DestinationFile)) { url = WebUtility.HtmlEncode(this.Id); titleAttr = String.Format(CultureInfo.InvariantCulture, " title=\"{0}\"", WebUtility.HtmlEncode(this.Title)); } else { url = WebUtility.HtmlEncode(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> /// Constructor /// </summary> /// <param name="helpFileFormats">The help file formats to which the files apply</param> /// <param name="sourcePath">The source path</param> /// <param name="destination">The destination path to use in the build output</param> public ContentFiles(HelpFileFormats helpFileFormats, string sourcePath, string destination) : this(helpFileFormats, null, sourcePath, destination, Enumerable.Empty<string>()) { }
/// <summary> /// Constructor /// </summary> /// <param name="helpFileFormats">The help file formats to which the files apply</param> /// <param name="basePath">An alternate base path or null to use the presentation style base path</param> /// <param name="sourcePath">The source path</param> /// <param name="destination">The destination path</param> /// <param name="templateFileExtensions">An enumerable list of file extensions to treat as template files</param> public ContentFiles(HelpFileFormats helpFileFormats, string basePath, string sourcePath, string destination, IEnumerable<string> templateFileExtensions) { if(Path.IsPathRooted(sourcePath)) throw new InvalidOperationException("Content source path must be relative"); if(!String.IsNullOrEmpty(destination)) { if(Path.IsPathRooted(destination)) throw new InvalidOperationException("Content destination path must be relative"); if(destination.IndexOfAny(new[] { '*', '?' }) != -1) throw new InvalidOperationException("Content destination must be a path only"); } this.HelpFileFormats = helpFileFormats; this.BasePath = basePath; this.SourcePathWildcard = sourcePath; this.DestinationFolder = destination; this.TemplateFileExtensions = templateFileExtensions.ToList(); }
/// <summary> /// Constructor /// </summary> /// <param name="helpFileFormats">The help file formats to which the files apply</param> /// <param name="sourcePath">The source path</param> /// <param name="destination">The destination path to use in the build output</param> public ContentFiles(HelpFileFormats helpFileFormats, string sourcePath, string destination) : this(helpFileFormats, null, sourcePath, destination, Enumerable.Empty <string>()) { }
/// <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 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 <paramref name="format"/> parameter determines the format of the returned /// file list. For HTML Help 1, it returns a list of the filenames. For all others, it returns the list /// formatted with the necessary XML markup.</remarks> private string HelpProjectFileList(string folder, HelpFileFormats format) { string itemFormat, filename, checkName, sourceFolder = folder; bool encode; if(folder == null) throw new ArgumentNullException("folder"); if(folder.Length != 0 && folder[folder.Length - 1] != '\\') folder += @"\"; if((format & HelpFileFormats.HtmlHelp1) != 0) { if(folder.IndexOf(',') != -1 || folder.IndexOf(".h", StringComparison.OrdinalIgnoreCase) != -1) currentBuild.ReportWarning("BE0060", "The file path '{0}' contains a comma or '.h' which may " + "cause the Help 1 compiler to fail.", folder); if(currentBuild.ResolvedHtmlHelpName.IndexOf(',') != -1 || currentBuild.ResolvedHtmlHelpName.IndexOf(".h", StringComparison.OrdinalIgnoreCase) != -1) currentBuild.ReportWarning("BE0060", "The HtmlHelpName property value '{0}' contains a comma " + "or '.h' which may cause the Help 1 compiler to fail.", currentBuild.ResolvedHtmlHelpName); itemFormat = "{0}\r\n"; encode = false; } else { itemFormat = " <File Url=\"{0}\" />\r\n"; encode = true; } replacementValue.Clear(); foreach(string name in Directory.EnumerateFiles(sourceFolder, "*.*", SearchOption.AllDirectories)) 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) currentBuild.ReportWarning("BE0060", "The filename '{0}' contains a comma or '.h' " + "which may cause the Help 1 compiler to fail.", filename); replacementValue.AppendFormat(itemFormat, filename); } else replacementValue.AppendFormat(itemFormat, WebUtility.HtmlEncode(name.Replace(folder, String.Empty))); return replacementValue.ToString(); }
//===================================================================== /// <summary> /// This is used to set the state of a menu command on the View Help menu /// </summary> /// <param name="command">The command object</param> /// <param name="format">The help file format</param> private static void SetViewHelpCommandState(OleMenuCommand command, HelpFileFormats? format) { Array activeProjects = null; DTE dte = Utility.GetServiceFromPackage<DTE, DTE>(false); bool visible = false, enabled = false; if(dte != null) { Solution s = dte.Solution; // Hide the menu option if a SHFB project is not loaded if(s != null) { visible = s.Projects.Cast<Project>().Any( p => p.UniqueName.EndsWith(".shfbproj", StringComparison.OrdinalIgnoreCase)); // Check the active project for the specified help format if visible if(visible) { try { activeProjects = dte.ActiveSolutionProjects as Array; } catch { // The above can throw an exception while the project is loading which // we should ignore. } if(activeProjects != null && activeProjects.Length > 0) { Project p = activeProjects.GetValue(0) as Project; if(p != null && p.Object != null && p.UniqueName.EndsWith(".shfbproj", StringComparison.OrdinalIgnoreCase)) { SandcastleBuilderProjectNode pn = (SandcastleBuilderProjectNode)p.Object; string projectHelpFormat = (pn.GetProjectProperty("HelpFileFormat") ?? HelpFileFormats.HtmlHelp1.ToString()); enabled = (!pn.BuildInProgress && (format == null || projectHelpFormat.IndexOf( format.ToString(), StringComparison.OrdinalIgnoreCase) != -1)); } } } } } command.Visible = visible; command.Enabled = enabled; }
//===================================================================== /// <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 += docSources_ListChanged; namespaceSummaries = new NamespaceSummaryItemCollection(this); namespaceSummaries.ListChanged += ItemList_ListChanged; references = new ReferenceItemCollection(this); references.ListChanged += ItemList_ListChanged; componentConfigs = new ComponentConfigurationDictionary(this); plugInConfigs = new PlugInConfigurationDictionary(this); apiFilter = new ApiFilterCollection(this); apiFilter.ListChanged += ItemList_ListChanged; helpAttributes = new MSHelpAttrCollection(this); helpAttributes.ListChanged += ItemList_ListChanged; try { loadingProperties = removeProjectWhenDisposed = true; contentPlacement = ContentPlacement.AboveNamespaces; cleanIntermediates = keepLogFile = binaryTOC = includeStopWordList = 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.ProtectedInternalAsProtected; buildAssemblerVerbosity = BuildAssemblerVerbosity.OnlyWarningsAndErrors; helpFileFormat = HelpFileFormats.HtmlHelp1; htmlSdkLinkType = websiteSdkLinkType = HtmlSdkLinkType.Msdn; help2SdkLinkType = MSHelp2SdkLinkType.Msdn; helpViewerSdkLinkType = MSHelpViewerSdkLinkType.Msdn; sdkLinkTarget = SdkLinkTarget.Blank; presentationStyle = Constants.DefaultPresentationStyle; namingMethod = NamingMethod.Guid; syntaxFilters = ComponentUtilities.DefaultSyntaxFilter; collectionTocStyle = CollectionTocStyle.Hierarchical; helpFileVersion = "1.0.0.0"; tocOrder = -1; maximumGroupParts = 2; this.OutputPath = null; this.HtmlHelp1xCompilerPath = this.HtmlHelp2xCompilerPath = this.WorkingPath = this.ComponentPath = 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 = this.CatalogName = null; this.FrameworkVersion = null; language = new CultureInfo("en-US"); } finally { loadingProperties = false; } }