diff --git a/Deployment/InstallerConfiguration.xml b/Deployment/InstallerConfiguration.xml index 2ba78242..f30ef704 100644 --- a/Deployment/InstallerConfiguration.xml +++ b/Deployment/InstallerConfiguration.xml @@ -1,7 +1,7 @@  - + @@ -13,7 +13,7 @@ - diff --git a/Documentation/SandcastleBuilder/CommonTokens.tokens b/Documentation/SandcastleBuilder/CommonTokens.tokens index 98c11cc6..2bb809b8 100644 --- a/Documentation/SandcastleBuilder/CommonTokens.tokens +++ b/Documentation/SandcastleBuilder/CommonTokens.tokens @@ -6,6 +6,6 @@ https://GitHub.com/EWSoftware/SHFB _blank - v2021.11.7.0 + v2022.1.22.0 Visual Studio 2017 \ No newline at end of file diff --git a/Documentation/SandcastleBuilder/Content/GettingStarted/Installation.aml b/Documentation/SandcastleBuilder/Content/GettingStarted/Installation.aml index 1d7dfb71..ae7f2bd9 100644 --- a/Documentation/SandcastleBuilder/Content/GettingStarted/Installation.aml +++ b/Documentation/SandcastleBuilder/Content/GettingStarted/Installation.aml @@ -117,7 +117,7 @@ files. These are installed as part of Visual Studio. Visual Studio Spell Checker https://marketplace.visualstudio.com/items?itemName=EWoodruff.VisualStudioSpellCheckerVS2017andLater _blank - extension can be downloaded and installed from the Visual Studio Gallery. It is an + extension can be downloaded and installed from the Visual Studio Gallery. It is an editor extension that checks the spelling of comments, strings, and plain text as you type or interactively with a tool window. Support is included for spell checking source code, XML files, and MAML topic files. diff --git a/Documentation/SandcastleBuilder/Content/License.aml b/Documentation/SandcastleBuilder/Content/License.aml index df901022..71c25dde 100644 --- a/Documentation/SandcastleBuilder/Content/License.aml +++ b/Documentation/SandcastleBuilder/Content/License.aml @@ -107,50 +107,50 @@ implied warranties of merchantability, fitness for a particular purpose and non- ICSharpCode.TextEditor https://github.com/icsharpcode _blank - is Copyright © 2000-2021, IC#Code, All Rights Reserved. + is Copyright © 2000-2022, IC#Code, All Rights Reserved. The Weifen Luo DockPanel Suite https://sourceforge.net/projects/dockpanelsuite/ _blank - is Copyright © 2007-2021, Weifen Luo, All Rights Reserved. + is Copyright © 2007-2022, Weifen Luo, All Rights Reserved. The Managed ESENT Library https://github.com/microsoft/managedesent _blank - is Copyright © 2008-2021, Microsoft Corporation, All Rights Reserved. + is Copyright © 2008-2022, Microsoft Corporation, All Rights Reserved. AjaxDoc https://github.com/bleroy/ajaxdoc _blank - is Copyright © 2006-2021, Bertrand Le Roy, All Rights Reserved. + is Copyright © 2006-2022, Bertrand Le Roy, All Rights Reserved. - NHunSpell is Copyright © 2009-2021 Maierhofer Software, All Rights Reserved + NHunSpell is Copyright © 2009-2022 Maierhofer Software, All Rights Reserved Script# https://github.com/NikhilK/scriptsharp _blank - is Copyright © 2007-2021, Nikhil Kothari, All Rights Reserved. + is Copyright © 2007-2022, Nikhil Kothari, All Rights Reserved. SBAppLocale http://www.SteelBytes.com/?mid=45 _blank - is Copyright © 2005-2021, Steel Bytes, All Rights Reserved. + is Copyright © 2005-2022, Steel Bytes, All Rights Reserved. The code colorizer library https://www.codeproject.com/Articles/3767/Multiple-Language-Syntax-Highlighting-Part-2-C-Con _blank - is Copyright © 2003-2021, Jonathan de Halleux, All Rights Reserved. + is Copyright © 2003-2022, Jonathan de Halleux, All Rights Reserved. - All other code is Copyright © 2006-2021, + All other code is Copyright © 2006-2022, Eric Woodruff https://github.com/EWSoftware/SHFB _blank diff --git a/Documentation/SandcastleBuilder/Content/VersionHistory/VersionHistory.aml b/Documentation/SandcastleBuilder/Content/VersionHistory/VersionHistory.aml index cb9992b0..7f4a56da 100644 --- a/Documentation/SandcastleBuilder/Content/VersionHistory/VersionHistory.aml +++ b/Documentation/SandcastleBuilder/Content/VersionHistory/VersionHistory.aml @@ -20,6 +20,11 @@ updating third-party components, plug-ins, presentation styles, and syntax gener version of the help file builder. + + + + + diff --git a/Documentation/SandcastleBuilder/Content/VersionHistory/v2022.1.22.0.aml b/Documentation/SandcastleBuilder/Content/VersionHistory/v2022.1.22.0.aml new file mode 100644 index 00000000..ea33c69d --- /dev/null +++ b/Documentation/SandcastleBuilder/Content/VersionHistory/v2022.1.22.0.aml @@ -0,0 +1,55 @@ + + + + + Release notes for version 2022.1.22.0. + + +
+ Changes in This Release + + + + + Potential breaking changes: + + + T:Sandcastle.Tools.BuildComponents.FileCreatedEventArgs +was moved to the Sandcastle.Core assembly so that it is available to other build components. + + + TransformingTopicEventArgs and TransformedTopicEventArgs +where renamed to T:Sandcastle.Core.BuildAssembler.BuildComponent.ApplyingChangesEventArgs +and T:Sandcastle.Core.BuildAssembler.BuildComponent.AppliedChangesEventArgs +and were moved to the Sandcastle.Core assembly so that they are available to other build +components and can be used for other tasks besides topic transformation. + + + + + + Fixed an incorrect path issue on default empty place holder topics in help viewer output. + + + + Fixed a problem with the GID0009 circular reference warning for inherited documentation. + + + + Added the Pre-transform Document Dump Component. This is a presentation style development aid. +It saves the pre-transformed content of each document to a file in a .\RawDocs subfolder in +the project's working folder. These files can be used for testing presentation style transformations without +having to do a full project build. This is more for use with upcoming features and its functionality may change +as those plans are revised and implemented. + + + + +
+ + + + + +
+
diff --git a/Documentation/SandcastleBuilder/SandcastleBuilder.content b/Documentation/SandcastleBuilder/SandcastleBuilder.content index 6aa5043f..63cb6793 100644 --- a/Documentation/SandcastleBuilder/SandcastleBuilder.content +++ b/Documentation/SandcastleBuilder/SandcastleBuilder.content @@ -1396,11 +1396,16 @@ - + + + + + + diff --git a/Documentation/SandcastleBuilder/SandcastleBuilder.shfbproj b/Documentation/SandcastleBuilder/SandcastleBuilder.shfbproj index 562732b4..ba531fb9 100644 --- a/Documentation/SandcastleBuilder/SandcastleBuilder.shfbproj +++ b/Documentation/SandcastleBuilder/SandcastleBuilder.shfbproj @@ -29,11 +29,11 @@ .NET Core/.NET Standard/.NET 5.0+ Sandcastle Help File Builder Documentation https://GitHub.com/EWSoftware/SHFB - [v{%40HelpFileVersion}] Copyright \xA9 2006-2021, Eric Woodruff, All rights reserved + [v{%40HelpFileVersion}] Copyright \xA9 2006-2022, Eric Woodruff, All rights reserved Eric%40EWoodruff.us VS2013 Standard - 2021.11.7.0 + 2022.1.22.0 @@ -389,6 +389,7 @@ + diff --git a/Documentation/SandcastleMAMLGuide/Content/BlockElements/table.aml b/Documentation/SandcastleMAMLGuide/Content/BlockElements/table.aml index 6e910226..e067f5d3 100644 --- a/Documentation/SandcastleMAMLGuide/Content/BlockElements/table.aml +++ b/Documentation/SandcastleMAMLGuide/Content/BlockElements/table.aml @@ -66,69 +66,69 @@ which can be used to achieve a similar effect.
<para>This link takes you to <link xlink:href="#Row3Cell1">Row 3, Cell 1</link> in the first table.</para> - + <table> <title>A Simple Table with Title and Headers</title> <tableHeader> - <row> - <entry><para>Header 1</para></entry> - <entry><para>Header 2</para></entry> - <entry><para>Header 3</para></entry> - </row> + <row> + <entry><para>Header 1</para></entry> + <entry><para>Header 2</para></entry> + <entry><para>Header 3</para></entry> + </row> </tableHeader> <row> - <entry><para>Row 1, Cell 1</para></entry> - <entry><para>Row 1, Cell 2</para></entry> - <entry><para>Row 1, Cell 3</para></entry> + <entry><para>Row 1, Cell 1</para></entry> + <entry><para>Row 1, Cell 2</para></entry> + <entry><para>Row 1, Cell 3</para></entry> </row> <row> - <entry><para>Row 2, Cell 1</para></entry> - <entry><para>Row 2, Cell 2</para></entry> - <entry><para>Row 2, Cell 3</para></entry> + <entry><para>Row 2, Cell 1</para></entry> + <entry><para>Row 2, Cell 2</para></entry> + <entry><para>Row 2, Cell 3</para></entry> </row> <row> - <entry address="Row3Cell1"><para>This entry has an <codeInline>address</codeInline> + <entry address="Row3Cell1"><para>This entry has an <codeInline>address</codeInline> attribute that can be used as a link target.</para></entry> - <entry><para>Row 3, Cell 2</para></entry> - <entry><para>Row 3, Cell 3</para></entry> + <entry><para>Row 3, Cell 2</para></entry> + <entry><para>Row 3, Cell 3</para></entry> </row> </table> <table> <tableHeader> - <row> - <entry><para>&#160;</para></entry> - <entry><para>A Nested Table Example</para></entry> - </row> + <row> + <entry><para>&#160;</para></entry> + <entry><para>A Nested Table Example</para></entry> + </row> </tableHeader> <row> <entry><mediaLink><image xlink:href="98a8a8b7-c374-40c7-902a-91c947bf107c"/> - </mediaLink></entry> + </mediaLink> + </entry> <entry> - <table> - <row> - <entry><para>Cell 1</para></entry> - <entry><para>Cell 2</para></entry> - <entry><para>Cell 3</para></entry> - <entry><para>Cell 4</para></entry> - </row> - <row> - <entry><para>Cell 5</para></entry> - <entry><para>Cell 6</para></entry> - <entry><para>Cell 7</para></entry> - <entry><para>Cell 8</para></entry> - </row> - <row> - <entry><para>Cell 9</para></entry> - <entry><para>Cell 10</para></entry> - <entry><para>Cell 11</para></entry> - <entry><para>Cell 12</para></entry> - </row> - </table> - - <para>The table above doesn't have a -<codeInline>tableHeader</codeInline>.</para> + <table> + <row> + <entry><para>Cell 1</para></entry> + <entry><para>Cell 2</para></entry> + <entry><para>Cell 3</para></entry> + <entry><para>Cell 4</para></entry> + </row> + <row> + <entry><para>Cell 5</para></entry> + <entry><para>Cell 6</para></entry> + <entry><para>Cell 7</para></entry> + <entry><para>Cell 8</para></entry> + </row> + <row> + <entry><para>Cell 9</para></entry> + <entry><para>Cell 10</para></entry> + <entry><para>Cell 11</para></entry> + <entry><para>Cell 12</para></entry> + </row> + </table> + + <para>The table above doesn't have a <codeInline>tableHeader</codeInline>.</para> </entry> </row> </table> @@ -141,7 +141,7 @@ attribute that can be used as a link target.</para></entry> This link takes you to Row 3, Cell 1 in the first table. - + A Simple Table with Title and Headers @@ -202,8 +202,7 @@ attribute that can be used as a link target.
- The table above doesn't have a -tableHeader. + The table above doesn't have a tableHeader. diff --git a/LICENSE b/LICENSE index 96b932e3..9e896ec7 100644 --- a/LICENSE +++ b/LICENSE @@ -63,28 +63,28 @@ purpose and non-infringement. Copyright Notices ----------------- -The ICSharpCode.TextEditor is Copyright (c) 2000-2021 IC#Code, All Rights +The ICSharpCode.TextEditor is Copyright (c) 2000-2022 IC#Code, All Rights Reserved. -The Weifen Luo DockPanel Suite is Copyright (c) 2007-2021 Weifen Luo and other +The Weifen Luo DockPanel Suite is Copyright (c) 2007-2022 Weifen Luo and other contributors, All Rights Reserved. -The NHunspell library is Copyright (c) 2009-2021 Maierhofer Software, All +The NHunspell library is Copyright (c) 2009-2022 Maierhofer Software, All Rights Reserved. -The Managed ESENT library is Copyright (c) 2008-2021 Microsoft Corporation, All +The Managed ESENT library is Copyright (c) 2008-2022 Microsoft Corporation, All Rights Reserved. -AjaxDoc is Copyright (c) 2006-2021 Bertrand Le Roy, All Rights Reserved. +AjaxDoc is Copyright (c) 2006-2022 Bertrand Le Roy, All Rights Reserved. -Script# is Copyright (c) 2007-2021 Nikhil Kothari, All Rights Reserved. +Script# is Copyright (c) 2007-2022 Nikhil Kothari, All Rights Reserved. -SBAppLocale is Copyright 2005-2021 Steel Bytes, All Rights Reserved. +SBAppLocale is Copyright 2005-2022 Steel Bytes, All Rights Reserved. -The code colorizer library is Copyright (c) 2003-2021, Jonathan de Halleux, +The code colorizer library is Copyright (c) 2003-2022, Jonathan de Halleux, All Rights Reserved. -All other code is Copyright (c) 2006-2021, Eric Woodruff, All Rights Reserved. +All other code is Copyright (c) 2006-2022, Eric Woodruff, All Rights Reserved. The English US dictionary is based on a subset of the original English wordlist created by Kevin Atkinson for Pspell and Aspell and thus is covered by his diff --git a/NuGet/SHFB.nuspec b/NuGet/SHFB.nuspec index 1015630b..cf894902 100644 --- a/NuGet/SHFB.nuspec +++ b/NuGet/SHFB.nuspec @@ -2,7 +2,7 @@ EWSoftware.SHFB - 2021.11.7.0 + 2022.1.22.0 Sandcastle Help File Builder Eric Woodruff Eric Woodruff diff --git a/SHFB/Source/.editorconfig b/SHFB/Source/.editorconfig index ede92a46..46c6b0fa 100644 --- a/SHFB/Source/.editorconfig +++ b/SHFB/Source/.editorconfig @@ -131,3 +131,9 @@ dotnet_diagnostic.IDE0057.severity = none # IDE0063: Use simple 'using' statement dotnet_diagnostic.IDE0063.severity = none + +# CA1845: Use span-based 'string.Concat' +dotnet_diagnostic.CA1845.severity = none + +# CA1846: Prefer 'AsSpan' over 'Substring' +dotnet_diagnostic.CA1846.severity = none diff --git a/SHFB/Source/BuildAssembler/BuildComponents/CodeBlockComponent.cs b/SHFB/Source/BuildAssembler/BuildComponents/CodeBlockComponent.cs index d366e513..e2732f83 100644 --- a/SHFB/Source/BuildAssembler/BuildComponents/CodeBlockComponent.cs +++ b/SHFB/Source/BuildAssembler/BuildComponents/CodeBlockComponent.cs @@ -2,8 +2,8 @@ // System : Sandcastle Help File Builder Components // File : CodeBlockComponent.cs // Author : Eric Woodruff (Eric@EWoodruff.us) -// Updated : 04/24/2021 -// Note : Copyright 2006-2021, Eric Woodruff, All rights reserved +// Updated : 01/18/2022 +// Note : Copyright 2006-2022, Eric Woodruff, All rights reserved // // This file contains a build component that is used to search for XML comment tags and colorize the code // within them. It can also include code from an external file or a region within the file. @@ -738,7 +738,8 @@ protected override void Dispose(bool disposing) } // Raise an event to indicate that a file was created - OnComponentEvent(new FileCreatedEventArgs(destStylesheet, true)); + OnComponentEvent(new FileCreatedEventArgs(this.GroupId, "Code Block Component", null, + destStylesheet, true)); if(!File.Exists(destScriptFile)) { @@ -746,7 +747,8 @@ protected override void Dispose(bool disposing) File.SetAttributes(destScriptFile, FileAttributes.Normal); } - OnComponentEvent(new FileCreatedEventArgs(destScriptFile, true)); + OnComponentEvent(new FileCreatedEventArgs(this.GroupId, "Code Block Component", null, + destScriptFile, true)); } } @@ -923,13 +925,14 @@ private void TransformComponent_TopicTransformed(object sender, EventArgs e) XmlAttribute attr; // Don't bother if not a transform event, not in our group, or if the topic contained no code blocks - if(!(e is TransformedTopicEventArgs tt) || ((BuildComponentCore)sender).GroupId != this.GroupId || - !topicCodeBlocks.TryGetValue(tt.Key, out Dictionary colorizedCodeBlocks)) + if(!(e is AppliedChangesEventArgs ac) || ac.GroupId != this.GroupId || + ac.ComponentId != "XSL Transform Component" || + !topicCodeBlocks.TryGetValue(ac.Key, out Dictionary colorizedCodeBlocks)) { return; } - topicCodeBlocks.Remove(tt.Key); + topicCodeBlocks.Remove(ac.Key); if(!isOpenXml && !isMarkdown) { @@ -937,22 +940,22 @@ private void TransformComponent_TopicTransformed(object sender, EventArgs e) hasColorizedCodeBlocks = true; // Find the section - head = tt.Document.SelectSingleNode("html/head"); + head = ac.Document.SelectSingleNode("html/head"); if(head == null) { - base.WriteMessage(tt.Key, MessageLevel.Error, " section not found! Could not insert links."); + base.WriteMessage(ac.Key, MessageLevel.Error, " section not found! Could not insert links."); return; } // Add the link to the style sheet - node = tt.Document.CreateNode(XmlNodeType.Element, "link", null); + node = ac.Document.CreateNode(XmlNodeType.Element, "link", null); - attr = tt.Document.CreateAttribute("type"); + attr = ac.Document.CreateAttribute("type"); attr.Value = "text/css"; node.Attributes.Append(attr); - attr = tt.Document.CreateAttribute("rel"); + attr = ac.Document.CreateAttribute("rel"); attr.Value = "stylesheet"; node.Attributes.Append(attr); @@ -963,9 +966,9 @@ private void TransformComponent_TopicTransformed(object sender, EventArgs e) head.AppendChild(node); // Add the link to the script - node = tt.Document.CreateNode(XmlNodeType.Element, "script", null); + node = ac.Document.CreateNode(XmlNodeType.Element, "script", null); - attr = tt.Document.CreateAttribute("type"); + attr = ac.Document.CreateAttribute("type"); attr.Value = "text/javascript"; node.Attributes.Append(attr); @@ -981,7 +984,7 @@ private void TransformComponent_TopicTransformed(object sender, EventArgs e) // The "local-name()" part of the query is for the VS2010 and Open XML styles which add a namespace // to the element. I could have created a context for the namespace but this is quick and it works // for all cases. - foreach(XmlNode codeContainer in tt.Document.SelectNodes( + foreach(XmlNode codeContainer in ac.Document.SelectNodes( "//pre[starts-with(.,'@@_SHFB_')]|//*[(local-name() = 'pre' or local-name() = 't') and starts-with(.,'@@_SHFB_')]")) { XmlNode placeholder = codeContainer; @@ -993,7 +996,7 @@ private void TransformComponent_TopicTransformed(object sender, EventArgs e) // Make sure spacing is preserved if(placeholder.Attributes["xml:space"] == null) { - attr = tt.Document.CreateAttribute("xml:space"); + attr = ac.Document.CreateAttribute("xml:space"); attr.Value = "preserve"; placeholder.Attributes.Append(attr); } @@ -1017,7 +1020,7 @@ private void TransformComponent_TopicTransformed(object sender, EventArgs e) span.InnerText = String.Empty; } else - this.WriteMessage(tt.Key, MessageLevel.Warn, "Unable to locate colorized code for placeholder: " + + this.WriteMessage(ac.Key, MessageLevel.Warn, "Unable to locate colorized code for placeholder: " + placeholder.InnerText); } } diff --git a/SHFB/Source/BuildAssembler/BuildComponents/GlobalSuppressions.cs b/SHFB/Source/BuildAssembler/BuildComponents/GlobalSuppressions.cs index 2210bbe5..6e51f60a 100644 --- a/SHFB/Source/BuildAssembler/BuildComponents/GlobalSuppressions.cs +++ b/SHFB/Source/BuildAssembler/BuildComponents/GlobalSuppressions.cs @@ -86,3 +86,4 @@ [assembly: SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:SandcastleBuilder.Components.SqlResolveReferenceLinksComponent.SqlResolveReferenceLinksComponentFactory")] [assembly: SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "", Scope = "member", Target = "~M:SandcastleBuilder.Components.SqlResolveReferenceLinksComponent.CreateMemberIdResolver(System.Xml.XPath.XPathNavigator)~Sandcastle.Core.BuildAssembler.BuildComponent.IMemberIdUrlResolver")] [assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "", Scope = "member", Target = "~M:SandcastleBuilder.Components.SqlResolveReferenceLinksComponent.CreateTargetDictionary(System.Xml.XPath.XPathNavigator)~Sandcastle.Tools.BuildComponents.Targets.TargetDictionary")] +[assembly: SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:Sandcastle.Tools.BuildComponents.PreTransformDocumentDumpComponent.Factory")] diff --git a/SHFB/Source/BuildAssembler/BuildComponents/PreTransformDocumentDumpComponent.cs b/SHFB/Source/BuildAssembler/BuildComponents/PreTransformDocumentDumpComponent.cs new file mode 100644 index 00000000..a57275d2 --- /dev/null +++ b/SHFB/Source/BuildAssembler/BuildComponents/PreTransformDocumentDumpComponent.cs @@ -0,0 +1,180 @@ +//=============================================================================================================== +// System : Sandcastle Help File Builder Components +// File : PreTransformDocumentDumpComponent.cs +// Author : Eric Woodruff (Eric@EWoodruff.us) +// Updated : 01/18/2022 +// Note : Copyright 2022, Eric Woodruff, All rights reserved +// +// This file contains a build component that is used to save the pre-transform document data for use in testing +// presentation style transformations. +// +// This code is published under the Microsoft Public License (Ms-PL). A copy of the license should be +// distributed with the code and can be found at the project website: https://GitHub.com/EWSoftware/SHFB. This +// notice, the author's name, and all copyright notices must remain intact in all applications, documentation, +// and source files. +// +// Date Who Comments +// ============================================================================================================== +// 01/08/2022 EFW Created the code +//=============================================================================================================== + +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text; +using System.Xml; +using System.Xml.XPath; + +using Sandcastle.Core.BuildAssembler; +using Sandcastle.Core.BuildAssembler.BuildComponent; + +namespace Sandcastle.Tools.BuildComponents +{ + /// + /// This build component is a development aid. It is used to save the pre-transform document data for use + /// in testing presentation style transformations. + /// + /// This is a presentation style development aid. It saves the pre-transformed content of each + /// document to a file in a .\RawDocs subfolder in the project's working folder. These files can be used + /// for testing presentation style transforms without having to do a full project build. + public class PreTransformDocumentDumpComponent : BuildComponentCore + { + #region Private data members + //===================================================================== + + private XmlWriterSettings settings; + private string dumpPath; + + #endregion + + #region Build component factory for MEF + //===================================================================== + + /// + /// This is used to create a new instance of the build component + /// + [BuildComponentExport("Pre-transform Document Dump Component", IsVisible = true, Version = AssemblyInfo.ProductVersion, + Copyright = AssemblyInfo.Copyright, Description = "This is a presentation style development aid. It " + + "saves the pre-transformed content of each document to a file in a .\\RawDocs subfolder in the " + + "project's working folder. These files can be used for testing presentation style transforms " + + "without having to do a full project build.")] + public sealed class Factory : BuildComponentFactory + { + /// + /// Constructor + /// + public Factory() + { + this.ReferenceBuildPlacement = new ComponentPlacement(PlacementAction.Before, + "XSL Transform Component"); + this.ConceptualBuildPlacement = new ComponentPlacement(PlacementAction.Before, + "XSL Transform Component"); + } + + /// + public override BuildComponentCore Create() + { + return new PreTransformDocumentDumpComponent(this.BuildAssembler); + } + + /// + public override string DefaultConfiguration => @""; + } + #endregion + + #region Constructor + //===================================================================== + + /// + /// Constructor + /// + /// A reference to the build assembler + protected PreTransformDocumentDumpComponent(BuildAssemblerCore buildAssembler) : base(buildAssembler) + { + } + #endregion + + #region Abstract method implementations + //===================================================================== + + /// + /// Initialize the build component + /// + /// The component configuration + public override void Initialize(XPathNavigator configuration) + { + if(configuration == null) + throw new ArgumentNullException(nameof(configuration)); + + Assembly asm = Assembly.GetExecutingAssembly(); + FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(asm.Location); + + this.WriteMessage(MessageLevel.Info, "[{0}, version {1}]\r\n RawDocumentDumpComponent Component. {2}", + fvi.ProductName, fvi.ProductVersion, fvi.LegalCopyright); + + dumpPath = configuration.SelectSingleNode("dumpPath").GetAttribute("value", String.Empty); + + if(!Directory.Exists(dumpPath)) + Directory.CreateDirectory(dumpPath); + + settings = new XmlWriterSettings + { + Encoding = Encoding.UTF8, + CloseOutput = true, + Indent = true + }; + + // Hook up the event handler to save the content just prior to XSL transformation + this.BuildAssembler.ComponentEvent += TransformComponent_TopicTransforming; + } + + /// + /// Apply this build component's changes to the document + /// + /// The document to modify + /// The document's key + public override void Apply(XmlDocument document, string key) + { + // Nothing to do here. See below. + } + + /// + /// Save the raw content just before transforming so that all other components have had a chance to + /// add and modify the content such as the Syntax Component. + /// + /// The sender of the event + /// The event arguments + private void TransformComponent_TopicTransforming(object sender, EventArgs e) + { + // Don't bother if not a transforming event or not in our group + if(!(e is ApplyingChangesEventArgs ac) || ac.GroupId != this.GroupId || + ac.ComponentId != "XSL Transform Component") + { + return; + } + + if(ac.Document != null) + { + StringBuilder filename = new StringBuilder(ac.Key); + + foreach(char c in Path.GetInvalidFileNameChars()) + if(ac.Key.IndexOf(c) != -1) + filename.Replace(c, '_'); + + string xmlFile = Path.Combine(dumpPath, filename.ToString()); + + if(xmlFile.Length > 250) + xmlFile = xmlFile.Substring(0, 250); + + xmlFile += ".xml"; + + using(XmlWriter writer = XmlWriter.Create(xmlFile, settings)) + { + ac.Document.Save(writer); + } + } + } + #endregion + } +} diff --git a/SHFB/Source/BuildAssembler/BuildComponents/ResolveArtLinksComponent.cs b/SHFB/Source/BuildAssembler/BuildComponents/ResolveArtLinksComponent.cs index 692253e2..4a617f90 100644 --- a/SHFB/Source/BuildAssembler/BuildComponents/ResolveArtLinksComponent.cs +++ b/SHFB/Source/BuildAssembler/BuildComponents/ResolveArtLinksComponent.cs @@ -252,7 +252,8 @@ protected override void Dispose(bool disposing) File.SetAttributes(kv.Key, FileAttributes.Normal); // Raise an event to indicate that a file was created - OnComponentEvent(new FileCreatedEventArgs(kv.Key, true)); + OnComponentEvent(new FileCreatedEventArgs(this.GroupId, "Resolve Art Links Component", + null, kv.Key, true)); } else this.WriteMessage(MessageLevel.Warn, "The file '{0}' for the art target '{1}' " + diff --git a/SHFB/Source/BuildAssembler/BuildComponents/SaveComponent.cs b/SHFB/Source/BuildAssembler/BuildComponents/SaveComponent.cs index a111040f..9890ae4a 100644 --- a/SHFB/Source/BuildAssembler/BuildComponents/SaveComponent.cs +++ b/SHFB/Source/BuildAssembler/BuildComponents/SaveComponent.cs @@ -293,7 +293,7 @@ private void WriteDocuments() } // Raise an event to indicate that a file was created - this.OnComponentEvent(new FileCreatedEventArgs(path, true)); + this.OnComponentEvent(new FileCreatedEventArgs(this.GroupId, "Save Component", kv.Key, path, true)); } } catch(IOException e) diff --git a/SHFB/Source/BuildAssembler/BuildComponents/SyntaxComponent.cs b/SHFB/Source/BuildAssembler/BuildComponents/SyntaxComponent.cs index f568e462..40fdc6e2 100644 --- a/SHFB/Source/BuildAssembler/BuildComponents/SyntaxComponent.cs +++ b/SHFB/Source/BuildAssembler/BuildComponents/SyntaxComponent.cs @@ -329,10 +329,13 @@ private void TransformComponent_TopicTransforming(object sender, EventArgs e) int order; // Don't bother if not a transforming event or not in our group - if(!(e is TransformingTopicEventArgs tt) || ((BuildComponentCore)sender).GroupId != this.GroupId) + if(!(e is ApplyingChangesEventArgs ac) || ac.GroupId != this.GroupId || + ac.ComponentId != "XSL Transform Component") + { return; + } - XmlDocument document = tt.Document; + XmlDocument document = ac.Document; XPathNavigator root, navDoc = document.CreateNavigator(); List allGroups = new List(); List> extraGroups = new List>(); @@ -352,7 +355,7 @@ private void TransformComponent_TopicTransforming(object sender, EventArgs e) if(root == null) { - this.WriteMessage(tt.Key, MessageLevel.Warn, "Root content node not found. Cannot group " + + this.WriteMessage(ac.Key, MessageLevel.Warn, "Root content node not found. Cannot group " + "and sort code snippets."); return; } diff --git a/SHFB/Source/BuildAssembler/BuildComponents/TransformComponent.cs b/SHFB/Source/BuildAssembler/BuildComponents/TransformComponent.cs index 931bb7b7..f2f426ac 100644 --- a/SHFB/Source/BuildAssembler/BuildComponents/TransformComponent.cs +++ b/SHFB/Source/BuildAssembler/BuildComponents/TransformComponent.cs @@ -8,6 +8,7 @@ // The event uses the new TransformedTopicEventArgs as the event arguments. // 12/24/2013 - EFW - Updated the build component to be discoverable via MEF // 04/27/2014 - EFW - Added support for a "transforming topic" event that happens prior to transformation +// 01/18/2022 - EFW - Replaced the transformed/transforming events with more generic applying/applied changes events using System; using System.Collections.Generic; @@ -165,7 +166,7 @@ public override void Apply(XmlDocument document, string key) throw new ArgumentNullException(nameof(document)); // Raise a component event to signal that the topic is about to be transformed - this.OnComponentEvent(new TransformingTopicEventArgs(key, document)); + this.OnComponentEvent(new ApplyingChangesEventArgs(this.GroupId, "XSL Transform Component", key, document)); foreach(Transform transform in transforms) { @@ -238,7 +239,7 @@ public override void Apply(XmlDocument document, string key) } // Raise a component event to signal that the topic has been transformed - this.OnComponentEvent(new TransformedTopicEventArgs(key, document)); + this.OnComponentEvent(new AppliedChangesEventArgs(this.GroupId, "XSL Transform Component", key, document)); } #endregion } diff --git a/SHFB/Source/BuildAssembler/BuildComponents/TransformedTopicEventArgs.cs b/SHFB/Source/BuildAssembler/BuildComponents/TransformedTopicEventArgs.cs deleted file mode 100644 index 14761f16..00000000 --- a/SHFB/Source/BuildAssembler/BuildComponents/TransformedTopicEventArgs.cs +++ /dev/null @@ -1,49 +0,0 @@ -//=============================================================================================================== -// System : Sandcastle Build Components -// File : TransformedTopicEventArgs.cs -// -// This file contains an event arguments class used by the TransformComponent to indicate that it has finished -// transforming the given topic. -// -// This code is published under the Microsoft Public License (Ms-PL). A copy of the license should be -// distributed with the code and can be found at the project website: https://GitHub.com/EWSoftware/SHFB. This -// notice and all copyright notices must remain intact in all applications, documentation, and source files. -// -// Change History -// 10/14/2012 - EFW - Created the code -//=============================================================================================================== - -using System; -using System.Xml; - -namespace Sandcastle.Tools.BuildComponents -{ - /// - /// This is used by the to indicate that it has finished transforming the - /// given topic. - /// - public class TransformedTopicEventArgs : EventArgs - { - /// - /// This read-only property returns the topic key - /// - public string Key { get; } - - /// - /// This read-only property returns the transformed topic document - /// - /// Event handlers can further modify the topic's XML as needed - public XmlDocument Document { get; } - - /// - /// Constructor - /// - /// The transformed topic key - /// The transformed topic document - public TransformedTopicEventArgs(string key, XmlDocument document) - { - this.Key = key; - this.Document = document; - } - } -} diff --git a/SHFB/Source/BuildAssembler/BuildComponents/TransformingTopicEventArgs.cs b/SHFB/Source/BuildAssembler/BuildComponents/TransformingTopicEventArgs.cs deleted file mode 100644 index 0b3f6a55..00000000 --- a/SHFB/Source/BuildAssembler/BuildComponents/TransformingTopicEventArgs.cs +++ /dev/null @@ -1,49 +0,0 @@ -//=============================================================================================================== -// System : Sandcastle Build Components -// File : TransformingTopicEventArgs.cs -// -// This file contains an event arguments class used by the TransformComponent to indicate that it is about to -// transform the given topic. -// -// This code is published under the Microsoft Public License (Ms-PL). A copy of the license should be -// distributed with the code and can be found at the project website: https://GitHub.com/EWSoftware/SHFB. This -// notice and all copyright notices must remain intact in all applications, documentation, and source files. -// -// Change History -// 04/27/2014 - EFW - Created the code -//=============================================================================================================== - -using System; -using System.Xml; - -namespace Sandcastle.Tools.BuildComponents -{ - /// - /// This is used by the to indicate that it is about to transform the - /// given topic. - /// - public class TransformingTopicEventArgs : EventArgs - { - /// - /// This read-only property returns the topic key - /// - public string Key { get; } - - /// - /// This read-only property returns the topic document that will be transformed - /// - /// Event handlers can modify the topic's XML as needed prior to transformation - public XmlDocument Document { get; } - - /// - /// Constructor - /// - /// The transformed topic key - /// The transformed topic document - public TransformingTopicEventArgs(string key, XmlDocument document) - { - this.Key = key; - this.Document = document; - } - } -} diff --git a/SHFB/Source/BuildAssembler/SyntaxComponents/XSharpDeclarationSyntax.cs b/SHFB/Source/BuildAssembler/SyntaxComponents/XSharpDeclarationSyntax.cs index 572ba690..265274b3 100644 --- a/SHFB/Source/BuildAssembler/SyntaxComponents/XSharpDeclarationSyntax.cs +++ b/SHFB/Source/BuildAssembler/SyntaxComponents/XSharpDeclarationSyntax.cs @@ -82,7 +82,7 @@ public override void WriteClassSyntax(XPathNavigator reflection, SyntaxWriter wr throw new ArgumentNullException(nameof(writer)); string name = reflection.Evaluate(apiNameExpression).ToString(); - InFunctionsClass = (String.Compare(name, "Functions", StringComparison.OrdinalIgnoreCase) == 0); + InFunctionsClass = name.Equals("Functions", StringComparison.OrdinalIgnoreCase); bool isAbstract = (bool)reflection.Evaluate(apiIsAbstractTypeExpression); bool isSealed = (bool)reflection.Evaluate(apiIsSealedTypeExpression); bool isSerializable = (bool)reflection.Evaluate(apiIsSerializableTypeExpression); diff --git a/SHFB/Source/CodeColorizer/ColorizerLibrary/CodeColorizer.cs b/SHFB/Source/CodeColorizer/ColorizerLibrary/CodeColorizer.cs index dc86c5f9..fc8fcb68 100644 --- a/SHFB/Source/CodeColorizer/ColorizerLibrary/CodeColorizer.cs +++ b/SHFB/Source/CodeColorizer/ColorizerLibrary/CodeColorizer.cs @@ -1453,7 +1453,7 @@ private String ReplaceByCode(Match match) CultureInfo.InvariantCulture); // This is used to determine whether to render the colorized code in a or a
 tag
-                inBox = (String.Compare(tag, "pre", StringComparison.OrdinalIgnoreCase) == 0);
+                inBox = tag.Equals("pre", StringComparison.OrdinalIgnoreCase);
 
                 // Tidy up the block by stripping any common leading whitespace and converting tabs to spaces
                 matchText = StripLeadingWhitespace(match.Groups[8].Value, tabSize, outliningLocal, out regions);
@@ -1470,7 +1470,7 @@ private String ReplaceByCode(Match match)
                 matchText = StripLeadingWhitespace(matchText, tabSize, outliningLocal, out regions);
 
                 // This is used to determine whether to render the colorized code in a  or a 
 tag
-                inBox = (String.Compare(tag, "pre", StringComparison.OrdinalIgnoreCase) == 0);
+                inBox = tag.Equals("pre", StringComparison.OrdinalIgnoreCase);
 
                 // If keeping see tags, replace them with a marker character so that they aren't colorized
                 if(keepSeeTagsLocal)
diff --git a/SHFB/Source/GenerateInheritedDocs/GenerateInheritedDocs.cs b/SHFB/Source/GenerateInheritedDocs/GenerateInheritedDocs.cs
index 484409a8..a24b808e 100644
--- a/SHFB/Source/GenerateInheritedDocs/GenerateInheritedDocs.cs
+++ b/SHFB/Source/GenerateInheritedDocs/GenerateInheritedDocs.cs
@@ -2,8 +2,8 @@
 // System  : Sandcastle Help File Builder
 // File    : GenerateInheritedDocs.cs
 // Author  : Eric Woodruff  (Eric@EWoodruff.us)
-// Updated : 07/07/2021
-// Note    : Copyright 2008-2021, Eric Woodruff, All rights reserved
+// Updated : 01/20/2022
+// Note    : Copyright 2008-2022, Eric Woodruff, All rights reserved
 //
 // This file contains the build task that scans XML comments files for  tags and produces a new
 // XML comments file containing the inherited documentation for use by Sandcastle.
@@ -402,7 +402,8 @@ private void InheritDocumentation(XmlNode member)
                 foreach(var m in memberStack.ToArray())
                     sb.AppendFormat(CultureInfo.InvariantCulture, " <-- {0}: {1}", idx--, m);
 
-                this.Log.LogWarning(null, "GID0009", null, null, 0, 0, 0, 0, sb);
+                // NOTE: Use an explicit conversion for the string builder or it picks the wrong overload here
+                this.Log.LogWarning(null, "GID0009", null, null, 0, 0, 0, 0, sb.ToString());
                 return;
             }
 
diff --git a/SHFB/Source/HtmlToMaml/ConvertHtmlToMaml.cs b/SHFB/Source/HtmlToMaml/ConvertHtmlToMaml.cs
index 73f553ec..d759c282 100644
--- a/SHFB/Source/HtmlToMaml/ConvertHtmlToMaml.cs
+++ b/SHFB/Source/HtmlToMaml/ConvertHtmlToMaml.cs
@@ -82,7 +82,7 @@ public static int Main(string[] args)
                     return 2;
                 }
 
-                if(String.Compare(sourceFolder, destFolder, StringComparison.OrdinalIgnoreCase) == 0)
+                if(sourceFolder.Equals(destFolder, StringComparison.OrdinalIgnoreCase))
                 {
                     Console.WriteLine("Destination folder cannot match source folder");
                     return 3;
diff --git a/SHFB/Source/HtmlToMaml/FilePath.cs b/SHFB/Source/HtmlToMaml/FilePath.cs
index 94878cf8..a47fd06b 100644
--- a/SHFB/Source/HtmlToMaml/FilePath.cs
+++ b/SHFB/Source/HtmlToMaml/FilePath.cs
@@ -428,7 +428,7 @@ public static string AbsoluteToRelativePath(string basePath, string absolutePath
             minLength = Math.Min(baseParts.Length, absParts.Length);
 
             for(idx = 0; idx < minLength; idx++)
-                if(String.Compare(baseParts[idx], absParts[idx], StringComparison.OrdinalIgnoreCase) != 0)
+                if(!baseParts[idx].Equals(absParts[idx], StringComparison.OrdinalIgnoreCase))
                     break;
 
             // Use the absolute path if there's nothing in common (i.e. they are on different drives or network
@@ -629,7 +629,7 @@ public override bool Equals(object obj)
 
             FilePath otherPath = obj as FilePath;
 
-            return (String.Compare(this.Path, otherPath.Path, StringComparison.OrdinalIgnoreCase) == 0 &&
+            return (String.Equals(this.Path, otherPath.Path, StringComparison.OrdinalIgnoreCase) &&
               this.IsFixedPath == otherPath.IsFixedPath);
         }
         #endregion
diff --git a/SHFB/Source/HtmlToMaml/HtmlToMaml.cs b/SHFB/Source/HtmlToMaml/HtmlToMaml.cs
index 8440bc25..ef4e4924 100644
--- a/SHFB/Source/HtmlToMaml/HtmlToMaml.cs
+++ b/SHFB/Source/HtmlToMaml/HtmlToMaml.cs
@@ -848,8 +848,7 @@ private string OnMatchAnchor(Match match)
                     inPageLink, ".htm\r\n", target, "");
 
             // Only use the link text if it doesn't match the topic title
-            if(!String.IsNullOrEmpty(linkText) && String.Compare(WebUtility.HtmlDecode(linkText), topic.Title,
-              StringComparison.OrdinalIgnoreCase) != 0)
+            if(!String.IsNullOrEmpty(linkText) && !WebUtility.HtmlDecode(linkText).Equals(topic.Title, StringComparison.OrdinalIgnoreCase))
                 return String.Concat("", linkText, "");
 
             return String.Concat("");
diff --git a/SHFB/Source/License.rtf b/SHFB/Source/License.rtf
index 8696fcc1..de03b0c6 100644
--- a/SHFB/Source/License.rtf
+++ b/SHFB/Source/License.rtf
@@ -36,11 +36,11 @@
 \af0\afs22\alang1025 \ltrch\fcs0 \fs22\lang1033\langfe1033\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
 \ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1
 \widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \fs22\lang1033\langfe1033\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused 
-Normal Table;}}{\*\rsidtbl \rsid1983356\rsid9398689\rsid9664239\rsid9969121\rsid10030684\rsid11474046\rsid16199251}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info
-{\operator Eric Woodruff}{\creatim\yr2017\mo1\dy28\hr12\min31}{\revtim\yr2021\mo4\dy28\hr16\min24}{\version7}{\edmins2}{\nofpages2}{\nofwords561}{\nofchars3204}{\nofcharsws3758}{\vern23}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/
+Normal Table;}}{\*\rsidtbl \rsid1983356\rsid7345137\rsid9398689\rsid9664239\rsid9969121\rsid10030684\rsid11474046\rsid16199251}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}
+{\info{\operator Eric Woodruff}{\creatim\yr2017\mo1\dy28\hr12\min31}{\revtim\yr2022\mo1\dy22\hr13}{\version8}{\edmins3}{\nofpages2}{\nofwords561}{\nofchars3204}{\nofcharsws3758}{\vern39}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/
 wordml}}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect 
 \widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont0\relyonvml0\donotembedlingdata1\grfdocevents0\validatexml0\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors0\horzdoc\dghspace120\dgvspace120\dghorigin1701
-\dgvorigin1984\dghshow0\dgvshow3\jcompress\viewkind1\viewscale100\rsidroot10030684 \fet0{\*\wgrffmtfilter 2450}\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2
+\dgvorigin1984\dghshow0\dgvshow3\jcompress\viewkind1\viewscale115\rsidroot10030684 \fet0{\*\wgrffmtfilter 2450}\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2
 \pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6
 \pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang 
 {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 
@@ -70,30 +70,34 @@ conditions. You may have additional consumer rights under your local laws which
 \hich\af1\dbch\af31505\loch\f1  \hich\af1\dbch\af31505\loch\f1 and non-infringement.
 \par }{\rtlch\fcs1 \ab\af1\afs18 \ltrch\fcs0 \b\f1\fs18\ul\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1 Copyright Notices}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 
 \par \hich\af1\dbch\af31505\loch\f1 The ICSharpCode.TextE}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid10030684 \hich\af1\dbch\af31505\loch\f1 ditor is Copyright (c) 2000}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
-\f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 \hich\af1\dbch\af31505\loch\f1 2021}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
-\f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1  IC#Code, All Rights Reserved.
+\f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 \hich\af1\dbch\af31505\loch\f1 202}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
+\f1\fs18\lang9\langfe1033\langnp9\insrsid7345137 \hich\af1\dbch\af31505\loch\f1 2}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1  IC#Code, All Rights Reserved.
 \par \hich\af1\dbch\af31505\loch\f1 The Weifen Luo DockPanel Suite is Copyright (c) 2007}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
-\f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 \hich\af1\dbch\af31505\loch\f1 2021}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1 
- Weifen Luo and other contributors, All Rights Reserved.
+\f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 \hich\af1\dbch\af31505\loch\f1 202}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid7345137 \hich\af1\dbch\af31505\loch\f1 2}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
+\f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1  Weifen Luo and other contributors, All Rights Reserved.
 \par \hich\af1\dbch\af31505\loch\f1 The NHunspell li}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid10030684 \hich\af1\dbch\af31505\loch\f1 br\hich\af1\dbch\af31505\loch\f1 ary is Copyright (c) 2009}{\rtlch\fcs1 \af1\afs18 
-\ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 \hich\af1\dbch\af31505\loch\f1 2021}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
-\f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1  Maierhofer Software, All Rights Reserved.
+\ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 \hich\af1\dbch\af31505\loch\f1 202}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
+\f1\fs18\lang9\langfe1033\langnp9\insrsid7345137 \hich\af1\dbch\af31505\loch\f1 2}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1  Maierhofer Software, All Rights Reserved.
 \par \hich\af1\dbch\af31505\loch\f1 The Managed ESENT Library is Copyright (c) 2008}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 
-\hich\af1\dbch\af31505\loch\f1 2021}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1  Microsoft Corporation, All Rights Reserved.
-\par \hich\af1\dbch\af31505\loch\f1 Aja\hich\af1\dbch\af31505\loch\f1 xDoc is Copyright (c) 2006}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
-\f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 \hich\af1\dbch\af31505\loch\f1 2021}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1  Bertrand Le Roy, All Rights Reserved.
-\par \hich\af1\dbch\af31505\loch\f1 Sc}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid10030684 \hich\af1\dbch\af31505\loch\f1 ript# is Copyright (c) 2007}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
-\f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 \hich\af1\dbch\af31505\loch\f1 2021}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
-\f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1  Nikhil Kothari, All Rights Reserved.
+\hich\af1\dbch\af31505\loch\f1 202}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid7345137 \hich\af1\dbch\af31505\loch\f1 2}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 
+\hich\af1\dbch\af31505\loch\f1  Microsoft Corporation, All Rights Reserved.
+\par \hich\af1\dbch\af31505\loch\f1 AjaxDoc is Copyright (c) 2006}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 
+\hich\af1\dbch\af31505\loch\f1 202}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid7345137 \hich\af1\dbch\af31505\loch\f1 2}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 
+\hich\af1\dbch\af31505\loch\f1  Bertrand Le Roy, All Rights Reserved.
+\par \hich\af1\dbch\af31505\loch\f1 Sc}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid10030684 \hich\af1\dbch\af31505\loch\f1 ript# is Co\hich\af1\dbch\af31505\loch\f1 pyright (c) 2007}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
+\f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 \hich\af1\dbch\af31505\loch\f1 202}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
+\f1\fs18\lang9\langfe1033\langnp9\insrsid7345137 \hich\af1\dbch\af31505\loch\f1 2}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1  Nikhil Kothari, All Rights Reserved.
 \par \hich\af1\dbch\af31505\loch\f1 SBAppLocale is Copyright (c) 2005}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 
-\hich\af1\dbch\af31505\loch\f1 2021}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1  Steel Bytes, All Rights Reserved.
-\par \hich\af1\dbch\af31505\loch\f1 The code colorizer library is Copyright (\hich\af1\dbch\af31505\loch\f1 c) 2003}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
-\f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 \hich\af1\dbch\af31505\loch\f1 2021}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1  Jonathan de Halleux, All Rights Reserved.
-\par \hich\af1\dbch\af31505\loch\f1 All other}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid10030684 \hich\af1\dbch\af31505\loch\f1  code is Copyright (c) 2006}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
-\f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 \hich\af1\dbch\af31505\loch\f1 2021}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
-\f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1  Eric Woodruff, All Rights Reserved.
-\par \hich\af1\dbch\af31505\loch\f1 The English US dictionary is based on a subset of the original English wordlist created by Kevin Atkinson for Pspell and As\hich\af1\dbch\af31505\loch\f1 
-pell and thus is covered by his original LGPL license.  The affix file is a heavily modified version of the original english.aff file which was released as part of Geoff Kuenning's Ispell and as such is covered by his BSD license.
+\hich\af1\dbch\af31505\loch\f1 202}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid7345137 \hich\af1\dbch\af31505\loch\f1 2}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 
+\hich\af1\dbch\af31505\loch\f1  Steel Bytes, All Rights Reserved.
+\par \hich\af1\dbch\af31505\loch\f1 The code colorizer library is Copyright (c) 2003}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 
+\hich\af1\dbch\af31505\loch\f1 202}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid7345137 \hich\af1\dbch\af31505\loch\f1 2}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 
+\hich\af1\dbch\af31505\loch\f1  Jonathan de Halleux, All Rights Reserved.
+\par \hich\af1\dbch\af31505\loch\f1 All other}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid10030684 \hich\af1\dbch\af31505\loch\f1  code is Copyright \hich\af1\dbch\af31505\loch\f1 (c) 2006}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
+\f1\fs18\lang9\langfe1033\langnp9\insrsid16199251 -}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid1983356 \hich\af1\dbch\af31505\loch\f1 202}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 
+\f1\fs18\lang9\langfe1033\langnp9\insrsid7345137 \hich\af1\dbch\af31505\loch\f1 2}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \f1\fs18\lang9\langfe1033\langnp9\insrsid9969121 \hich\af1\dbch\af31505\loch\f1  Eric Woodruff, All Rights Reserved.
+\par \hich\af1\dbch\af31505\loch\f1 The English US dictionary is based on a subset of the original English wordlist created by Kevin Atkinson for Pspell and Aspell and thus is covered by his original LGPL license.  The affix file is a heavily
+\hich\af1\dbch\af31505\loch\f1  modified version of the original english.aff file which was released as part of Geoff Kuenning's Ispell and as such is covered by his BSD license.
 \par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a
 9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad
 5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6
@@ -234,8 +238,8 @@ fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff
 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
-ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e50000000000000000000000006005
-609a853cd701feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
+ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e500000000000000000000000030f9
+720bd30fd801feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000
 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
 0000000000000000000000000000000000000000000000000105000000000000}}
\ No newline at end of file
diff --git a/SHFB/Source/SandcastleBuilderGUI/ContentEditors/ContentFileEditor.cs b/SHFB/Source/SandcastleBuilderGUI/ContentEditors/ContentFileEditor.cs
index e88056a9..f2b88798 100644
--- a/SHFB/Source/SandcastleBuilderGUI/ContentEditors/ContentFileEditor.cs
+++ b/SHFB/Source/SandcastleBuilderGUI/ContentEditors/ContentFileEditor.cs
@@ -264,7 +264,7 @@ public bool IsEditorFor(string extension)
                     extension = extension.Substring(1);
 
                 foreach(string entry in matchList)
-                    if(entry.Length != 0 && String.Compare(entry, extension, StringComparison.OrdinalIgnoreCase) == 0)
+                    if(entry.Length != 0 && entry.Equals(extension, StringComparison.OrdinalIgnoreCase))
                         return true;
             }
 
diff --git a/SHFB/Source/SandcastleBuilderGUI/ContentEditors/ContentLayoutWindow.cs b/SHFB/Source/SandcastleBuilderGUI/ContentEditors/ContentLayoutWindow.cs
index 5d369d75..90d1394c 100644
--- a/SHFB/Source/SandcastleBuilderGUI/ContentEditors/ContentLayoutWindow.cs
+++ b/SHFB/Source/SandcastleBuilderGUI/ContentEditors/ContentLayoutWindow.cs
@@ -397,7 +397,7 @@ private void cmdEdit_Executed(object sender, ExecutedRoutedEventArgs e)
 
                 // If the document is already open, just activate it
                 foreach(IDockContent content in this.DockPanel.Documents)
-                    if(String.Compare(content.DockHandler.ToolTipText, fullName, StringComparison.OrdinalIgnoreCase) == 0)
+                    if(String.Equals(content.DockHandler.ToolTipText, fullName, StringComparison.OrdinalIgnoreCase))
                     {
                         content.DockHandler.Activate();
                         return;
diff --git a/SHFB/Source/SandcastleBuilderGUI/ContentEditors/PreviewTopicWindow.cs b/SHFB/Source/SandcastleBuilderGUI/ContentEditors/PreviewTopicWindow.cs
index 3ab1c3bd..0276f62d 100644
--- a/SHFB/Source/SandcastleBuilderGUI/ContentEditors/PreviewTopicWindow.cs
+++ b/SHFB/Source/SandcastleBuilderGUI/ContentEditors/PreviewTopicWindow.cs
@@ -192,7 +192,7 @@ private void cmdEdit_Executed(object sender, ExecutedRoutedEventArgs e)
 
                 // If the document is already open, just activate it
                 foreach(IDockContent content in this.DockPanel.Documents)
-                    if(String.Compare(content.DockHandler.ToolTipText, fullName, StringComparison.OrdinalIgnoreCase) == 0)
+                    if(String.Equals(content.DockHandler.ToolTipText, fullName, StringComparison.OrdinalIgnoreCase))
                     {
                         content.DockHandler.Activate();
                         return;
diff --git a/SHFB/Source/SandcastleBuilderGUI/ContentEditors/ProjectExplorerWindow.cs b/SHFB/Source/SandcastleBuilderGUI/ContentEditors/ProjectExplorerWindow.cs
index 61efa60e..5bea77ad 100644
--- a/SHFB/Source/SandcastleBuilderGUI/ContentEditors/ProjectExplorerWindow.cs
+++ b/SHFB/Source/SandcastleBuilderGUI/ContentEditors/ProjectExplorerWindow.cs
@@ -702,7 +702,7 @@ private void EditNodeFile(TreeNode node)
 
             // If the document is already open, just activate it
             foreach(IDockContent content in this.DockPanel.Contents)
-                if(String.Compare(content.DockHandler.ToolTipText, fullName, StringComparison.OrdinalIgnoreCase) == 0)
+                if(String.Equals(content.DockHandler.ToolTipText, fullName, StringComparison.OrdinalIgnoreCase))
                 {
                     content.DockHandler.Activate();
                     return;
@@ -1388,7 +1388,7 @@ private void miOpenWithTextEditor_Click(object sender, EventArgs e)
 
             // If the document is already open, just activate it
             foreach(IDockContent content in this.DockPanel.Contents)
-                if(String.Compare(content.DockHandler.ToolTipText, fullName, StringComparison.OrdinalIgnoreCase) == 0)
+                if(String.Equals(content.DockHandler.ToolTipText, fullName, StringComparison.OrdinalIgnoreCase))
                 {
                     content.DockHandler.Activate();
                     return;
@@ -2445,8 +2445,7 @@ private void miPaste_Click(object sender, EventArgs e)
                     else
                         newPath = Path.Combine(basePath, file.Substring(rootFolder.Length));
 
-                    if((rootFolder != null || dropEffect == 2) &&
-                      String.Compare(file, newPath, StringComparison.OrdinalIgnoreCase) == 0)
+                    if((rootFolder != null || dropEffect == 2) && file.Equals(newPath, StringComparison.OrdinalIgnoreCase))
                     {
                         MessageBox.Show("Files cannot be copied onto themselves", Constants.AppName,
                             MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
diff --git a/SHFB/Source/SandcastleBuilderGUI/ContentEditors/SiteMapEditorWindow.cs b/SHFB/Source/SandcastleBuilderGUI/ContentEditors/SiteMapEditorWindow.cs
index d0cdc0df..6bffb796 100644
--- a/SHFB/Source/SandcastleBuilderGUI/ContentEditors/SiteMapEditorWindow.cs
+++ b/SHFB/Source/SandcastleBuilderGUI/ContentEditors/SiteMapEditorWindow.cs
@@ -400,7 +400,7 @@ private void cmdEdit_Executed(object sender, ExecutedRoutedEventArgs e)
 
                 // If the document is already open, just activate it
                 foreach(IDockContent content in this.DockPanel.Documents)
-                    if(String.Compare(content.DockHandler.ToolTipText, fullName, StringComparison.OrdinalIgnoreCase) == 0)
+                    if(String.Equals(content.DockHandler.ToolTipText, fullName, StringComparison.OrdinalIgnoreCase))
                     {
                         content.DockHandler.Activate();
                         return;
diff --git a/SHFB/Source/SandcastleBuilderGUI/Spelling/FileSpellChecker.cs b/SHFB/Source/SandcastleBuilderGUI/Spelling/FileSpellChecker.cs
index 31261974..b5f0d52e 100644
--- a/SHFB/Source/SandcastleBuilderGUI/Spelling/FileSpellChecker.cs
+++ b/SHFB/Source/SandcastleBuilderGUI/Spelling/FileSpellChecker.cs
@@ -256,8 +256,8 @@ private bool SpellCheckInternal(string text, TextLocation location)
                         break;
                 }
                 else
-                    if(priorWord.Length != 0 && String.Compare(text.Substring(priorWord.Start, priorWord.Length),
-                      currentWord, StringComparison.OrdinalIgnoreCase) == 0 &&
+                    if(priorWord.Length != 0 && text.Substring(priorWord.Start, priorWord.Length).Equals(currentWord,
+                      StringComparison.OrdinalIgnoreCase) &&
                       IsAllWhitespace(text, priorWord.Start + priorWord.Length, word.Start - 1))
                     {
                         se = new SpellingEventArgs(currentWord, location.ToPoint(text, textIdx));
diff --git a/SHFB/Source/SandcastleBuilderMSBuild/BuildHelp.cs b/SHFB/Source/SandcastleBuilderMSBuild/BuildHelp.cs
index fd152d6c..697b370d 100644
--- a/SHFB/Source/SandcastleBuilderMSBuild/BuildHelp.cs
+++ b/SHFB/Source/SandcastleBuilderMSBuild/BuildHelp.cs
@@ -580,25 +580,33 @@ public void Report(BuildProgressEventArgs value)
             // Always log errors and warnings
             if(m.Success)
             {
-                if(String.Compare(m.Groups[3].Value, "warning", StringComparison.OrdinalIgnoreCase) == 0)
-                    Log.LogWarning(null, m.Groups[4].Value, m.Groups[4].Value,
-                        m.Groups[1].Value, 0, 0, 0, 0, m.Groups[5].Value.Trim());
-                else
-                    if(String.Compare(m.Groups[3].Value, "error", StringComparison.OrdinalIgnoreCase) == 0)
-                    Log.LogError(null, m.Groups[4].Value, m.Groups[4].Value,
-                        m.Groups[1].Value, 0, 0, 0, 0, m.Groups[5].Value.Trim());
+                if(m.Groups[3].Value.Equals("warning", StringComparison.OrdinalIgnoreCase))
+                {
+                    Log.LogWarning(null, m.Groups[4].Value, m.Groups[4].Value, m.Groups[1].Value, 0, 0, 0, 0,
+                        m.Groups[5].Value.Trim());
+                }
                 else
-                    Log.LogMessage(MessageImportance.High, value.Message);
+                {
+                    if(m.Groups[3].Value.Equals("error", StringComparison.OrdinalIgnoreCase))
+                    {
+                        Log.LogError(null, m.Groups[4].Value, m.Groups[4].Value, m.Groups[1].Value, 0, 0, 0, 0,
+                            m.Groups[5].Value.Trim());
+                    }
+                    else
+                        Log.LogMessage(MessageImportance.High, value.Message);
+                }
             }
-            else
-                if(this.Verbose)
-                Log.LogMessage(MessageImportance.High, value.Message);
             else
             {
-                // If not doing verbose logging, show warnings and let MSBuild filter them out if not
-                // wanted.  Errors will kill the build so we don't have to deal with them here.
-                if(reWarning.IsMatch(value.Message))
-                    Log.LogWarning(value.Message);
+                if(this.Verbose)
+                    Log.LogMessage(MessageImportance.High, value.Message);
+                else
+                {
+                    // If not doing verbose logging, show warnings and let MSBuild filter them out if not
+                    // wanted.  Errors will kill the build so we don't have to deal with them here.
+                    if(reWarning.IsMatch(value.Message))
+                        Log.LogWarning(value.Message);
+                }
             }
 
             if(value.StepChanged)
diff --git a/SHFB/Source/SandcastleBuilderUtils/BuildEngine/BuildProcess.Namespaces.cs b/SHFB/Source/SandcastleBuilderUtils/BuildEngine/BuildProcess.Namespaces.cs
index 0806cb5d..9e6f1831 100644
--- a/SHFB/Source/SandcastleBuilderUtils/BuildEngine/BuildProcess.Namespaces.cs
+++ b/SHFB/Source/SandcastleBuilderUtils/BuildEngine/BuildProcess.Namespaces.cs
@@ -478,7 +478,7 @@ private IEnumerable GroupNamespaces(IEnumerable namespac
                 var children = kv.Value.Children;
 
                 for(int idx = 0; idx < children.Count; idx++)
-                    if(groups.Keys.Contains(children[idx]))
+                    if(groups.ContainsKey(children[idx]))
                         children[idx] = "G" + children[idx].Substring(1);
             }
 
diff --git a/SHFB/Source/SandcastleBuilderUtils/BuildEngine/SubstitutionTagReplacement.cs b/SHFB/Source/SandcastleBuilderUtils/BuildEngine/SubstitutionTagReplacement.cs
index a1db456a..7ada6e9c 100644
--- a/SHFB/Source/SandcastleBuilderUtils/BuildEngine/SubstitutionTagReplacement.cs
+++ b/SHFB/Source/SandcastleBuilderUtils/BuildEngine/SubstitutionTagReplacement.cs
@@ -117,7 +117,7 @@ public string TransformText(string templateText, params object[] args)
             if(String.IsNullOrWhiteSpace(templateText))
                 return templateText ?? String.Empty;
 
-            if(args.Length != 0)
+            if(args != null && args.Length != 0)
                 templateText = String.Format(CultureInfo.InvariantCulture, templateText, args);
 
             try
diff --git a/SHFB/Source/SandcastleBuilderUtils/ConceptualContent/TocEntryCollection.cs b/SHFB/Source/SandcastleBuilderUtils/ConceptualContent/TocEntryCollection.cs
index 78bbb969..bb334f52 100644
--- a/SHFB/Source/SandcastleBuilderUtils/ConceptualContent/TocEntryCollection.cs
+++ b/SHFB/Source/SandcastleBuilderUtils/ConceptualContent/TocEntryCollection.cs
@@ -161,7 +161,7 @@ public TocEntry this[string id]
 
                 foreach(TocEntry t in this)
                 {
-                    if(String.Compare(t.Id, id, StringComparison.OrdinalIgnoreCase) == 0)
+                    if(String.Equals(t.Id, id, StringComparison.OrdinalIgnoreCase))
                         return t;
 
                     found = t.Children[id];
diff --git a/SHFB/Source/SandcastleBuilderUtils/ConceptualContent/TopicCollection.cs b/SHFB/Source/SandcastleBuilderUtils/ConceptualContent/TopicCollection.cs
index 9ffcee19..af2f81fe 100644
--- a/SHFB/Source/SandcastleBuilderUtils/ConceptualContent/TopicCollection.cs
+++ b/SHFB/Source/SandcastleBuilderUtils/ConceptualContent/TopicCollection.cs
@@ -133,7 +133,7 @@ public Topic this[string id]
                 Topic found = null;
 
                 foreach(Topic t in this)
-                    if(String.Compare(t.Id, id, StringComparison.OrdinalIgnoreCase) == 0)
+                    if(String.Equals(t.Id, id, StringComparison.OrdinalIgnoreCase))
                     {
                         found = t;
                         break;
diff --git a/SHFB/Source/SandcastleBuilderUtils/FileItem.cs b/SHFB/Source/SandcastleBuilderUtils/FileItem.cs
index f40e7d34..f847d787 100644
--- a/SHFB/Source/SandcastleBuilderUtils/FileItem.cs
+++ b/SHFB/Source/SandcastleBuilderUtils/FileItem.cs
@@ -188,8 +188,7 @@ public string Name
                     if(path != newPath)
                     {
                         // If the file exists and it isn't just a case change, disallow it
-                        if(File.Exists(newPath) && String.Compare(path, newPath,
-                          StringComparison.OrdinalIgnoreCase) != 0)
+                        if(File.Exists(newPath) && !path.Equals(newPath, StringComparison.OrdinalIgnoreCase))
                             throw new ArgumentException("A file with that name already exists in the project folder");
 
                         File.Move(path, newPath);
@@ -205,13 +204,12 @@ public string Name
 
                 newPath = Path.Combine(Path.GetDirectoryName(path), value);
 
-                if(Directory.Exists(newPath) && String.Compare(path, newPath,
-                  StringComparison.OrdinalIgnoreCase) != 0)
+                if(Directory.Exists(newPath) && !path.Equals(newPath, StringComparison.OrdinalIgnoreCase))
                     throw new ArgumentException("A folder with that name already exists in the project folder");
 
                 // To allow renaming a folder by changing its case, move it to a temporary name first and then
                 // the new name.
-                if(String.Compare(path, newPath, StringComparison.OrdinalIgnoreCase) == 0)
+                if(path.Equals(newPath, StringComparison.OrdinalIgnoreCase))
                 {
                     tempPath = Guid.NewGuid().ToString();
                     Directory.Move(path, tempPath);
diff --git a/SHFB/Source/SandcastleBuilderUtils/FilePath.cs b/SHFB/Source/SandcastleBuilderUtils/FilePath.cs
index 05ae721b..7eec8871 100644
--- a/SHFB/Source/SandcastleBuilderUtils/FilePath.cs
+++ b/SHFB/Source/SandcastleBuilderUtils/FilePath.cs
@@ -432,7 +432,7 @@ public static string AbsoluteToRelativePath(string basePath, string absolutePath
             minLength = Math.Min(baseParts.Length, absParts.Length);
 
             for(idx = 0; idx < minLength; idx++)
-                if(String.Compare(baseParts[idx], absParts[idx], StringComparison.OrdinalIgnoreCase) != 0)
+                if(!baseParts[idx].Equals(absParts[idx], StringComparison.OrdinalIgnoreCase))
                     break;
 
             // Use the absolute path if there's nothing in common (i.e. they are on different drives or network
@@ -634,7 +634,7 @@ public override bool Equals(object obj)
 
             FilePath otherPath = obj as FilePath;
 
-            return (String.Compare(this.Path, otherPath.Path, StringComparison.OrdinalIgnoreCase) == 0 &&
+            return (String.Equals(this.Path, otherPath.Path, StringComparison.OrdinalIgnoreCase) &&
               this.IsFixedPath == otherPath.IsFixedPath);
         }
         #endregion
diff --git a/SHFB/Source/SandcastleBuilderUtils/MSBuild/MSBuildProject.cs b/SHFB/Source/SandcastleBuilderUtils/MSBuild/MSBuildProject.cs
index 9a0409fd..38dd45b4 100644
--- a/SHFB/Source/SandcastleBuilderUtils/MSBuild/MSBuildProject.cs
+++ b/SHFB/Source/SandcastleBuilderUtils/MSBuild/MSBuildProject.cs
@@ -150,12 +150,13 @@ public string AssemblyName
                     assemblyName = assemblyName.Trim();
 
                     // The values are case insensitive
-                    if(String.Compare(outputType, "Library", StringComparison.OrdinalIgnoreCase) == 0)
+                    if(String.Equals(outputType, "Library", StringComparison.OrdinalIgnoreCase))
                         assemblyName += ".dll";
                     else
-                        if(String.Compare(outputType, "Exe", StringComparison.OrdinalIgnoreCase) == 0 ||
-                          String.Compare(outputType, "WinExe", StringComparison.OrdinalIgnoreCase) == 0 ||
-                          String.Compare(outputType, "AppContainerExe", StringComparison.OrdinalIgnoreCase) == 0)
+                    {
+                        if(String.Equals(outputType, "Exe", StringComparison.OrdinalIgnoreCase) ||
+                          String.Equals(outputType, "WinExe", StringComparison.OrdinalIgnoreCase) ||
+                          String.Equals(outputType, "AppContainerExe", StringComparison.OrdinalIgnoreCase))
                         {
                             string ext = ".exe";
 
@@ -175,10 +176,13 @@ public string AssemblyName
                             assemblyName += ext;
                         }
                         else
-                            if(String.Compare(outputType, "winmdobj", StringComparison.OrdinalIgnoreCase) == 0)
+                        {
+                            if(String.Equals(outputType, "winmdobj", StringComparison.OrdinalIgnoreCase))
                                 assemblyName += ".winmd";
                             else
                                 assemblyName = null;
+                        }
+                    }
 
                     if(assemblyName != null)
                         if(Path.IsPathRooted(outputPath))
@@ -214,7 +218,7 @@ public string AssemblyName
 
                         // Set TargetFramework if necessary as some people use it to as a variable in other paths
                         // to references etc. when multi-targeting.
-                        if(!String.IsNullOrWhiteSpace(targetFramework) && !properties.Keys.Contains("TargetFramework"))
+                        if(!String.IsNullOrWhiteSpace(targetFramework) && !properties.ContainsKey("TargetFramework"))
                         {
                             this.ProjectFile.SetProperty("TargetFramework", targetFramework);
                             this.ProjectFile.ReevaluateIfNecessary();
diff --git a/SHFB/Source/SandcastleBuilderUtils/ProjectElement.cs b/SHFB/Source/SandcastleBuilderUtils/ProjectElement.cs
index 942f18d1..b86e6437 100644
--- a/SHFB/Source/SandcastleBuilderUtils/ProjectElement.cs
+++ b/SHFB/Source/SandcastleBuilderUtils/ProjectElement.cs
@@ -160,8 +160,8 @@ protected void OnPropertyChanged([CallerMemberName]string propertyName = null)
         public bool HasMetadata(string name)
         {
             // Build Action is the name, not metadata.  Include is an attribute, not metadata.
-            if(String.Compare(name, BuildItemMetadata.BuildAction, StringComparison.OrdinalIgnoreCase) == 0 ||
-              String.Compare(name, BuildItemMetadata.IncludePath, StringComparison.OrdinalIgnoreCase) == 0)
+            if(String.Equals(name, BuildItemMetadata.BuildAction, StringComparison.OrdinalIgnoreCase) ||
+              String.Equals(name, BuildItemMetadata.IncludePath, StringComparison.OrdinalIgnoreCase))
                 return true;
 
             return item.HasMetadata(name);
@@ -175,11 +175,11 @@ public bool HasMetadata(string name)
         public string GetMetadata(string name)
         {
             // Build Action is the name, not metadata
-            if(String.Compare(name, BuildItemMetadata.BuildAction, StringComparison.OrdinalIgnoreCase) == 0)
+            if(String.Equals(name, BuildItemMetadata.BuildAction, StringComparison.OrdinalIgnoreCase))
                 return item.ItemType;
 
             // Include is an attribute, not metadata
-            if(String.Compare(name, BuildItemMetadata.IncludePath, StringComparison.OrdinalIgnoreCase) == 0)
+            if(String.Equals(name, BuildItemMetadata.IncludePath, StringComparison.OrdinalIgnoreCase))
                 return item.UnevaluatedInclude;
 
             return item.GetMetadataValue(name);
@@ -193,7 +193,7 @@ public string GetMetadata(string name)
         public void SetMetadata(string name, string value)
         {
             // Build Action is the name, not metadata
-            if(String.Compare(name, BuildItemMetadata.BuildAction, StringComparison.OrdinalIgnoreCase) == 0)
+            if(String.Equals(name, BuildItemMetadata.BuildAction, StringComparison.OrdinalIgnoreCase))
             {
                 item.ItemType = value;
                 this.OnPropertyChanged(name);
@@ -201,7 +201,7 @@ public void SetMetadata(string name, string value)
             }
 
             // Include is an attribute, not metadata
-            if(String.Compare(name, BuildItemMetadata.IncludePath, StringComparison.OrdinalIgnoreCase) == 0)
+            if(String.Equals(name, BuildItemMetadata.IncludePath, StringComparison.OrdinalIgnoreCase))
             {
                 item.UnevaluatedInclude = value;
                 this.OnPropertyChanged(name);
diff --git a/SHFB/Source/SandcastleBuilderUtils/Properties/AssemblyInfoShared.cs b/SHFB/Source/SandcastleBuilderUtils/Properties/AssemblyInfoShared.cs
index 3c585fcb..e09f2efb 100644
--- a/SHFB/Source/SandcastleBuilderUtils/Properties/AssemblyInfoShared.cs
+++ b/SHFB/Source/SandcastleBuilderUtils/Properties/AssemblyInfoShared.cs
@@ -2,8 +2,8 @@
 // System  : Sandcastle Help File Builder
 // File    : AssemblyInfoShared.cs
 // Author  : Eric Woodruff  (Eric@EWoodruff.us)
-// Updated : 11/07/2021
-// Note    : Copyright 2006-2021, Eric Woodruff, All rights reserved
+// Updated : 01/22/2022
+// Note    : Copyright 2006-2022, Eric Woodruff, All rights reserved
 //
 // Sandcastle Help File Builder common assembly attributes.
 //
@@ -90,14 +90,14 @@ internal static partial class AssemblyInfo
     //
     // This is used to set the assembly file version.  This will change with each new release.  MSIs only
     // support a Major value between 0 and 255 so we drop the century from the year on this one.
-    public const string FileVersion = "21.11.7.0";
+    public const string FileVersion = "22.1.22.0";
 
     // Common product version
     //
     // This may contain additional text to indicate Alpha or Beta states.  The version number will always match
     // the file version above but includes the century on the year.
-    public const string ProductVersion = "2021.11.7.0";
+    public const string ProductVersion = "2022.1.22.0";
 
     // Assembly copyright information
-    public const string Copyright = "Copyright \xA9 2006-2021, Eric Woodruff, All Rights Reserved";
+    public const string Copyright = "Copyright \xA9 2006-2022, Eric Woodruff, All Rights Reserved";
 }
diff --git a/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponent/AppliedChangesEventArgs.cs b/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponent/AppliedChangesEventArgs.cs
new file mode 100644
index 00000000..114a8fb1
--- /dev/null
+++ b/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponent/AppliedChangesEventArgs.cs
@@ -0,0 +1,67 @@
+//===============================================================================================================
+// System  : Sandcastle Tools - Sandcastle Tools Core Class Library
+// File    : AppliedChangesEventArgs.cs
+// Author  : Eric Woodruff  (Eric@EWoodruff.us)
+// Updated : 01/18/2022
+// Note    : Copyright 2022, Eric Woodruff, All rights reserved
+//
+// This file contains an event arguments class used by components to indicate that they have finished applying
+// their changes to the given topic.
+//
+// This code is published under the Microsoft Public License (Ms-PL).  A copy of the license should be
+// distributed with the code and can be found at the project website: https://GitHub.com/EWSoftware/SHFB.  This
+// notice, the author's name, and all copyright notices must remain intact in all applications, documentation,
+// and source files.
+//
+//    Date     Who  Comments
+// ==============================================================================================================
+// 01/18/2022  EFW  Replaced the TransformedTopicEventArgs class with this one and moved to Sandcastle.Core
+//===============================================================================================================
+
+using System;
+using System.Xml;
+
+namespace Sandcastle.Core.BuildAssembler.BuildComponent
+{
+    /// 
+    /// This is used by components to indicate that they have finished applying their changes to the given topic
+    /// 
+    public class AppliedChangesEventArgs : EventArgs
+    {
+        /// 
+        /// This read-only property returns the group ID of the component that applied the changes
+        /// 
+        public string GroupId { get; }
+
+        /// 
+        /// This read-only property returns the ID of the component that applied the changes
+        /// 
+        public string ComponentId { get; }
+
+        /// 
+        /// This read-only property returns the topic key
+        /// 
+        public string Key { get; }
+
+        /// 
+        /// This read-only property returns the topic document that was modified
+        /// 
+        /// Event handlers can further modify the topic's XML as needed
+        public XmlDocument Document { get; }
+
+        /// 
+        /// Constructor
+        /// 
+        /// The group ID of the component
+        /// The component ID
+        /// The topic key
+        /// The topic document
+        public AppliedChangesEventArgs(string groupId, string componentId, string key, XmlDocument document)
+        {
+            this.GroupId = groupId;
+            this.ComponentId = componentId;
+            this.Key = key;
+            this.Document = document;
+        }
+    }
+}
diff --git a/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponent/ApplyingChangesEventArgs.cs b/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponent/ApplyingChangesEventArgs.cs
new file mode 100644
index 00000000..836e2078
--- /dev/null
+++ b/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponent/ApplyingChangesEventArgs.cs
@@ -0,0 +1,67 @@
+//===============================================================================================================
+// System  : Sandcastle Tools - Sandcastle Tools Core Class Library
+// File    : ApplyingChangesEventArgs.cs
+// Author  : Eric Woodruff  (Eric@EWoodruff.us)
+// Updated : 01/18/2022
+// Note    : Copyright 2022, Eric Woodruff, All rights reserved
+//
+// This file contains an event arguments class used by components to indicate that they are about to  apply
+// their changes to the given document.
+//
+// This code is published under the Microsoft Public License (Ms-PL).  A copy of the license should be
+// distributed with the code and can be found at the project website: https://GitHub.com/EWSoftware/SHFB.  This
+// notice, the author's name, and all copyright notices must remain intact in all applications, documentation,
+// and source files.
+//
+//    Date     Who  Comments
+// ==============================================================================================================
+// 01/18/2022  EFW  Replaced the TransformingTopicEventArgs class with this one and moved to Sandcastle.Core
+//===============================================================================================================
+
+using System;
+using System.Xml;
+
+namespace Sandcastle.Core.BuildAssembler.BuildComponent
+{
+    /// 
+    /// This is used by components to indicate that they are about to  apply their changes to the given document
+    /// 
+    public class ApplyingChangesEventArgs : EventArgs
+    {
+        /// 
+        /// This read-only property returns the group ID of the component that is about to apply the changes
+        /// 
+        public string GroupId { get; }
+
+        /// 
+        /// This read-only property returns the ID of the component that is about to apply the changes
+        /// 
+        public string ComponentId { get; }
+
+        /// 
+        /// This read-only property returns the topic key
+        /// 
+        public string Key { get; }
+
+        /// 
+        /// This read-only property returns the topic document that will be modified
+        /// 
+        /// Event handlers can modify the topic's XML as needed
+        public XmlDocument Document { get; }
+
+        /// 
+        /// Constructor
+        /// 
+        /// The group ID of the component
+        /// The component ID
+        /// The topic key
+        /// The topic document
+        public ApplyingChangesEventArgs(string groupId, string componentId, string key, XmlDocument document)
+        {
+            this.GroupId = groupId;
+            this.ComponentId = componentId;
+            this.Key = key;
+            this.Document = document;
+        }
+    }
+}
diff --git a/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponent/BuildComponentCore.cs b/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponent/BuildComponentCore.cs
index 2275b90a..590e6df5 100644
--- a/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponent/BuildComponentCore.cs
+++ b/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponent/BuildComponentCore.cs
@@ -143,7 +143,7 @@ protected void OnComponentEvent(EventArgs e)
         public void WriteMessage(MessageLevel level, string message, params object[] args)
         {
             if(level != MessageLevel.Ignore && this.BuildAssembler != null)
-                this.BuildAssembler.WriteMessage(this.GetType(), level, null, (args.Length == 0) ? message :
+                this.BuildAssembler.WriteMessage(this.GetType(), level, null, (args == null || args.Length == 0) ? message :
                     String.Format(CultureInfo.CurrentCulture, message, args));
         }
 
@@ -160,7 +160,7 @@ public void WriteMessage(MessageLevel level, string message, params object[] arg
         public void WriteMessage(string key, MessageLevel level, string message, params object[] args)
         {
             if(level != MessageLevel.Ignore && this.BuildAssembler != null)
-                this.BuildAssembler.WriteMessage(this.GetType(), level, key, (args.Length == 0) ? message :
+                this.BuildAssembler.WriteMessage(this.GetType(), level, key, (args == null || args.Length == 0) ? message :
                     String.Format(CultureInfo.CurrentCulture, message, args));
         }
         #endregion
diff --git a/SHFB/Source/BuildAssembler/BuildComponents/FileCreatedEventArgs.cs b/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponent/FileCreatedEventArgs.cs
similarity index 52%
rename from SHFB/Source/BuildAssembler/BuildComponents/FileCreatedEventArgs.cs
rename to SHFB/Source/SandcastleCore/BuildAssembler/BuildComponent/FileCreatedEventArgs.cs
index 8b02f82b..5024d56d 100644
--- a/SHFB/Source/BuildAssembler/BuildComponents/FileCreatedEventArgs.cs
+++ b/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponent/FileCreatedEventArgs.cs
@@ -1,59 +1,85 @@
-//===============================================================================================================
-// System  : Sandcastle Build Components
-// File    : FileCreatedEventArgs.cs
-//
-// This file contains an event arguments class used by build components to indicate that it has saved a file of
-// some sort.
-//
-// This code is published under the Microsoft Public License (Ms-PL).  A copy of the license should be
-// distributed with the code and can be found at the project website: https://GitHub.com/EWSoftware/SHFB.  This
-// notice and all copyright notices must remain intact in all applications, documentation, and source files.
-//
-// Change History
-// 12/24/2012 - EFW - Moved the class into its own file and changed it to only contain the target filename and
-// a flag indicating whether it is a whole document or just a fragment.  The event handler is responsible for
-// figuring out what to do with the event (i.e. HxFGeneratorComponent should determine where to put its files).
-//===============================================================================================================
-
-using System;
-using System.IO;
-
-namespace Sandcastle.Tools.BuildComponents
-{
-    /// 
-    /// This event arguments class is used by build components to indicate that they have saved a file of some
-    /// sort (help content or fragment).
-    /// 
-    public class FileCreatedEventArgs : EventArgs
-    {
-        #region Properties
-        //=====================================================================
-
-        /// 
-        /// This read-only property returns the path to the saved file
-        /// 
-        public string FilePath { get; }
-
-        /// 
-        /// This read-only property indicates whether or not the file is a help content file
-        /// 
-        public bool IsContentFile { get; }
-
-        #endregion
-
-        #region Constructor
-        //=====================================================================
-
-        /// 
-        /// Constructor
-        /// 
-        /// The path to the saved file
-        /// True if the saved file is a help content file, false if not
-        public FileCreatedEventArgs(string filePath, bool isContentFile)
-        {
-            this.FilePath = Path.GetFullPath(filePath);
-            this.IsContentFile = isContentFile;
-        }
-        #endregion
-    }
-}
+//===============================================================================================================
+// System  : Sandcastle Tools - Sandcastle Tools Core Class Library
+// File    : FileCreatedEventArgs.cs
+// Author  : Eric Woodruff  (Eric@EWoodruff.us)
+// Updated : 01/18/2022
+// Note    : Copyright 2012-2022, Eric Woodruff, All rights reserved
+//
+// This file contains an event arguments class used by build components to indicate that it has saved a file of
+// some sort.
+//
+// This code is published under the Microsoft Public License (Ms-PL).  A copy of the license should be
+// distributed with the code and can be found at the project website: https://GitHub.com/EWSoftware/SHFB.  This
+// notice, the author's name, and all copyright notices must remain intact in all applications, documentation,
+// and source files.
+//
+//    Date     Who  Comments
+// ==============================================================================================================
+// 01/18/2022  EFW  Moved to Sandcastle.Core and added component group, component ID, and topic key properties
+//===============================================================================================================
+
+using System;
+using System.IO;
+
+namespace Sandcastle.Tools.BuildComponents
+{
+    /// 
+    /// This event arguments class is used by build components to indicate that they have saved a file of some
+    /// sort (help content or fragment).  The event handler is responsible for figuring out what to do with the
+    /// event.
+    /// 
+    public class FileCreatedEventArgs : EventArgs
+    {
+        #region Properties
+        //=====================================================================
+
+        /// 
+        /// This read-only property returns the group ID of the component that saved the file
+        /// 
+        public string GroupId { get; }
+
+        /// 
+        /// This read-only property returns the ID of the component that saved the file
+        /// 
+        public string ComponentId { get; }
+
+        /// 
+        /// This read-only property returns the topic key or null if not saved while generating a specific topic
+        /// 
+        public string Key { get; }
+
+        /// 
+        /// This read-only property returns the path to the saved file
+        /// 
+        public string FilePath { get; }
+
+        /// 
+        /// This read-only property indicates whether or not the file is a help content file
+        /// 
+        public bool IsContentFile { get; }
+
+        #endregion
+
+        #region Constructor
+        //=====================================================================
+
+        /// 
+        /// Constructor
+        /// 
+        /// The group ID of the component
+        /// The component ID
+        /// The topic key
+        /// The path to the saved file
+        /// True if the saved file is a help content file, false if not
+        public FileCreatedEventArgs(string groupId, string componentId, string key, string filePath,
+          bool isContentFile)
+        {
+            this.GroupId = groupId;
+            this.ComponentId = componentId;
+            this.Key = key;
+            this.FilePath = Path.GetFullPath(filePath);
+            this.IsContentFile = isContentFile;
+        }
+        #endregion
+    }
+}
diff --git a/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponentUtilities.cs b/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponentUtilities.cs
index b3174e50..732395ea 100644
--- a/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponentUtilities.cs
+++ b/SHFB/Source/SandcastleCore/BuildAssembler/BuildComponentUtilities.cs
@@ -164,13 +164,16 @@ public static string EvalXPathExpr(this IXPathNavigable document, XPathExpressio
         public static string EvalXPathExpr(this IXPathNavigable document, XPathExpression expression,
           params string[] keyValuePairs)
         {
-            if(keyValuePairs.Length % 2 != 0)
-                throw new ArgumentException("There must be a value for every key name specified", nameof(keyValuePairs));
-
             CustomContext cc = new CustomContext();
 
-            for(int i = 0; i < keyValuePairs.Length; i += 2)
-                cc[keyValuePairs[i]] = keyValuePairs[i + 1];
+            if(keyValuePairs != null)
+            {
+                if(keyValuePairs.Length % 2 != 0)
+                    throw new ArgumentException("There must be a value for every key name specified", nameof(keyValuePairs));
+
+                for(int i = 0; i < keyValuePairs.Length; i += 2)
+                    cc[keyValuePairs[i]] = keyValuePairs[i + 1];
+            }
 
             return document.EvalXPathExpr(expression, cc);
         }
diff --git a/SHFB/Source/SandcastleCore/Properties/AssemblyInfoShared.cs b/SHFB/Source/SandcastleCore/Properties/AssemblyInfoShared.cs
index f4ed1c03..ab4fd601 100644
--- a/SHFB/Source/SandcastleCore/Properties/AssemblyInfoShared.cs
+++ b/SHFB/Source/SandcastleCore/Properties/AssemblyInfoShared.cs
@@ -1,8 +1,8 @@
 //===============================================================================================================
 // System  : Sandcastle Tools
 // File    : AssemblyInfoShared.cs
-// Updated : 11/07/2021
-// Note    : Copyright 2006-2021, Microsoft Corporation, All rights reserved
+// Updated : 01/22/2022
+// Note    : Copyright 2006-2022, Microsoft Corporation, All rights reserved
 //
 // Sandcastle tools common assembly attributes.
 //
@@ -73,15 +73,15 @@ internal static partial class AssemblyInfo
     //
     // This is used to set the assembly file version.  This will change with each new release.  MSIs only
     // support a Major value between 0 and 255 so we drop the century from the year on this one.
-    public const string FileVersion = "21.11.7.0";
+    public const string FileVersion = "22.1.22.0";
 
     // Common product version
     //
     // This may contain additional text to indicate Alpha or Beta states.  The version number will always match
     // the file version above but includes the century on the year.
-    public const string ProductVersion = "2021.11.7.0";
+    public const string ProductVersion = "2022.1.22.0";
 
     // Assembly copyright information
-    public const string Copyright = "Copyright \xA9 2006-2021, Microsoft Corporation, All Rights Reserved.\r\n" +
+    public const string Copyright = "Copyright \xA9 2006-2022, Microsoft Corporation, All Rights Reserved.\r\n" +
         "Portions Copyright \xA9 2006-2021, Eric Woodruff, All Rights Reserved.";
 }
diff --git a/SHFB/Source/VSIX_VS2017/source.extension.vsixmanifest b/SHFB/Source/VSIX_VS2017/source.extension.vsixmanifest
index f4899b65..af5702be 100644
--- a/SHFB/Source/VSIX_VS2017/source.extension.vsixmanifest
+++ b/SHFB/Source/VSIX_VS2017/source.extension.vsixmanifest
@@ -1,7 +1,7 @@
 
 
 	
-		
+		
 		SHFB
 		Visual Studio integration for the Sandcastle Help File Builder.
 		https://ewsoftware.github.io/SHFB/html/bd1ddb51-1c4f-434f-bb1a-ce2135d3a909.htm
diff --git a/SHFB/Source/VSIX_VS2022/source.extension.vsixmanifest b/SHFB/Source/VSIX_VS2022/source.extension.vsixmanifest
index 37b315e0..6bfb0f8a 100644
--- a/SHFB/Source/VSIX_VS2022/source.extension.vsixmanifest
+++ b/SHFB/Source/VSIX_VS2022/source.extension.vsixmanifest
@@ -1,7 +1,7 @@
 
 
 	
-		
+		
 		SHFB
 		Visual Studio integration for the Sandcastle Help File Builder.
 		https://ewsoftware.github.io/SHFB/html/bd1ddb51-1c4f-434f-bb1a-ce2135d3a909.htm
diff --git a/TestCaseProject/Doc/TestCaseProject.shfbproj b/TestCaseProject/Doc/TestCaseProject.shfbproj
index ccdc708d..a73b9307 100644
--- a/TestCaseProject/Doc/TestCaseProject.shfbproj
+++ b/TestCaseProject/Doc/TestCaseProject.shfbproj
@@ -58,8 +58,7 @@
 
 
     {@SyntaxFilters}
-
-
+
     
       
     
@@ -150,7 +149,8 @@
     True
     
     
-    ..\..\..\..\DotNet\RawDocumentDumpComponent\bin\Debug\netstandard2.0\
+    
+    
     Msdn
     Msdn
     False