Example #1
0
 /// <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());
        }
Example #3
0
        /// <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());
        }
Example #4
0
        /// <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());
        }
Example #5
0
        /// <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");
                }
            }
        }
Example #6
0
        //=====================================================================
        /// <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();
        }
Example #8
0
        /// <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&lt;$3");
                    content = reEncodeRight.Replace(content, "$1&gt;$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);
                }
            }
        }
Example #10
0
        //=====================================================================

        /// <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;
            }
        }