Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ZimM-LostPolygon/3e22d0902cb28e487a1affb0a3a309cc to your computer and use it in GitHub Desktop.
Save ZimM-LostPolygon/3e22d0902cb28e487a1affb0a3a309cc to your computer and use it in GitHub Desktop.
A simple SandCastle BuildComponent for replacing unknown type references with links from XML database
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.Hosting;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using Microsoft.Ddue.Tools.Targets;
using Sandcastle.Core.BuildAssembler;
using Sandcastle.Core.BuildAssembler.BuildComponent;
// Search for "TODO" to find changes that you need to make to this build component template.
namespace ExternalReferenceLinksProvider {
/// <summary>
/// TODO: Set your build component's unique ID and description in the export attribute in the factory class
/// below.
/// </summary>
/// <remarks>
/// The <c>BuildComponentExportAttribute</c> is used to export your component so that the help
/// file builder finds it and can make use of it. The example below shows the basic usage for a common
/// build component. Multiple copies of build components can be created depending on their usage. The
/// host process will create instances as needed and will dispose of them when it is done with them.
/// </remarks>
public class ExternalReferenceLinksProviderComponent : BuildComponentCore {
#region Constructor
/// <summary>
/// Constructor
/// </summary>
/// <param name="buildAssembler">A reference to the build assembler</param>
protected ExternalReferenceLinksProviderComponent(BuildAssemblerCore buildAssembler) : base(buildAssembler) {
}
#endregion
#region Build component factory for MEF
/// <summary>
/// This is used to create a new instance of the build component
/// </summary>
/// <remarks>
/// TODO: If not configurable, remove the <c>IsConfigurable</c> property or set it to false.
/// The <c>IsVisible</c> property is typically set to true so that the component can be exposed in
/// configuration tools such as the Sandcastle Help File Builder. If set to false, the component will
/// be hidden but can be used if referenced in a configuration file or as a dependency.
/// </remarks>
[BuildComponentExport(
"ExternalReferenceLinksProvider",
IsVisible = true,
IsConfigurable = true,
Version = AssemblyInfo.ProductVersion,
Copyright = AssemblyInfo.Copyright,
Description = "ExternalReferenceLinksProvider build component")]
public sealed class Factory : BuildComponentFactory {
public override string DefaultConfiguration =>
@"
<include path=""FIXME"" />
";
public Factory() {
const string componentName = "Multi-format Output Component";
ReferenceBuildPlacement = new ComponentPlacement(PlacementAction.Before, componentName);
ConceptualBuildPlacement = new ComponentPlacement(PlacementAction.Before, componentName);
}
/// <inheritdoc />
public override BuildComponentCore Create() {
return new ExternalReferenceLinksProviderComponent(BuildAssembler);
}
}
#endregion
private static readonly XPathExpression kReferenceLinkExpression = XPathExpression.Compile("//referenceLink");
private Dictionary<string, string> _qualifiedNameToUrlMap;
#region Abstract method implementations
//=====================================================================
/// <summary>
/// Initialize the build component
/// </summary>
/// <param name="configuration">The component configuration</param>
public override void Initialize(XPathNavigator configuration) {
Assembly asm = Assembly.GetExecutingAssembly();
FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(asm.Location);
WriteMessage(MessageLevel.Info, "[{0}, version {1}]\r\n ExternalReferenceLinksProvider Component. {2}",
fvi.ProductName, fvi.ProductVersion, fvi.LegalCopyright);
_qualifiedNameToUrlMap = new Dictionary<string, string>();
// Load external links from XML
foreach (XPathNavigator includeNode in configuration.Select("//include").ToArray()) {
string path = includeNode.GetAttribute("path", "");
if (!File.Exists(path)) {
WriteMessage(MessageLevel.Warn, $"External reference links file {path} doesn't exists");
continue;
}
WriteMessage(MessageLevel.Diagnostic, $"Loading external reference links file {path}");
ParseExternalReferenceLinksFile(path, _qualifiedNameToUrlMap);
}
WriteMessage(MessageLevel.Diagnostic, $"Loaded total of {_qualifiedNameToUrlMap.Count} external reference links");
}
/// <summary>
/// Apply this build component's changes to the document
/// </summary>
/// <param name="document">The document to modify</param>
/// <param name="key">The document's key</param>
public override void Apply(XmlDocument document, string key) {
foreach (XPathNavigator linkNode in document.CreateNavigator().Select(kReferenceLinkExpression).ToArray()) {
ReferenceLinkInfo link = new ReferenceLinkInfo(linkNode);
// Get URL from type name
if (_qualifiedNameToUrlMap.TryGetValue(link.Target, out string externalUrl)) {
WriteMessage(MessageLevel.Diagnostic, $"For target {link.Target} found external URL: {externalUrl}");
// Replace with <a> tag
XmlWriter writer = linkNode.InsertAfter();
writer.WriteStartElement("a");
writer.WriteAttributeString("href", externalUrl);
writer.WriteAttributeString("target", "_blank");
string cutTarget = link.Target;
if (cutTarget.Length > 2 && cutTarget[1] == ':') {
cutTarget = cutTarget.Substring(2);
}
if (cutTarget.StartsWith("UnityEngine.", StringComparison.InvariantCulture)) {
cutTarget = cutTarget.Substring("UnityEngine.".Length);
}
writer.WriteString(cutTarget);
// Write the closing link tag
writer.WriteEndElement();
writer.Close();
linkNode.DeleteSelf();
} else {
//linkNode.InsertAfter($" ({link.Target})");
}
}
}
#endregion
private void ParseExternalReferenceLinksFile(string filePath, IDictionary<string, string> qualifiedNameToUrlMap) {
XDocument document = XDocument.Load(filePath);
foreach (XElement item in document.Root.DescendantNodes()) {
qualifiedNameToUrlMap[item.Attribute("qualifiedname").Value] = item.Attribute("url").Value;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment