using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using Microsoft.Ddue.Tools;
namespace Microsoft.Ddue.Tools
{
///
/// Sandcastle component converting Microsoft Help 2.0 output to Microsoft Help System output.
///
public class MSHCComponent : BuildComponent
{
// component tag names in the configuration file
private class ConfigurationTag
{
public const string Data = "data";
}
// component attribute names in the configuration file
private class ConfigurationAttr
{
public const string Locale = "locale";
public const string SelfBranded = "self-branded";
public const string TopicVersion = "topic-version";
public const string TocFile = "toc-file";
public const string TocParent = "toc-parent";
public const string TocParentVersion = "toc-parent-version";
}
// XPath expressions to navigate the TOC file
private class TocXPath
{
public const string Topics = "/topics";
public const string Topic = "topic";
}
// attribute names in the TOC file
private class TocAttr
{
public const string Id = "id";
}
// Microsoft Help 2.0 namespace info
private class Help2Namespace
{
public const string Prefix = "MSHelp";
public const string Uri = "http://msdn.microsoft.com/mshelp";
}
// XPath expressions to navigate Microsoft Help 2.0 data in the document
private class Help2XPath
{
public const string Head = "head";
public const string Xml = "xml";
public const string TocTitle = "MSHelp:TOCTitle";
public const string Attr = "MSHelp:Attr[@Name='{0}']";
public const string Keyword = "MSHelp:Keyword[@Index='{0}']";
}
// Microsoft Help 2.0 tag attributes in the document
private class Help2Attr
{
public const string Value = "Value";
public const string Term = "Term";
public const string Title = "Title";
}
// Microsoft Help 2.0 attribute values in the document
private class Help2Value
{
public const string K = "K";
public const string F = "F";
public const string Locale = "Locale";
public const string AssetID = "AssetID";
public const string DevLang = "DevLang";
public const string Abstract = "Abstract";
}
// Microsoft Help System tags
private class MHSTag
{
public const string Meta = "meta";
}
// Microsoft Help System meta tag attributes
private class MHSMetaAttr
{
public const string Name = "name";
public const string Content = "content";
}
// Microsoft Help System meta names
private class MHSMetaName
{
public const string SelfBranded = "SelfBranded";
public const string ContentType = "ContentType";
public const string Locale = "Microsoft.Help.Locale";
public const string TopicLocale = "Microsoft.Help.TopicLocale";
public const string Id = "Microsoft.Help.Id";
public const string TopicVersion = "Microsoft.Help.TopicVersion";
public const string TocParent = "Microsoft.Help.TocParent";
public const string TocParentVersion = "Microsoft.Help.TOCParentTopicVersion";
public const string TocOrder = "Microsoft.Help.TocOrder";
public const string Title = "Title";
public const string Keywords = "Microsoft.Help.Keywords";
public const string F1 = "Microsoft.Help.F1";
public const string Category = "Microsoft.Help.Category";
public const string Description = "Description";
}
// Microsoft Help System meta default values
private class MHSDefault
{
public const bool SelfBranded = true;
public const string Locale = "en-us";
public const string Reference = "Reference";
public const string TopicVersion = "100";
public const string TocParent = "-1";
public const string TocParentVersion = "100";
public const string TocFile = "./toc.xml";
public const string ShortName = "MHS";
}
// TOC information of a document
private class TocInfo
{
private string _parent;
private string _parentVersion;
private int _order;
public TocInfo(string parent, string parentVersion, int order)
{
_parent = parent;
_parentVersion = parentVersion;
_order = order;
}
public string Parent { get { return _parent; }}
public string ParentVersion { get { return _parentVersion; } }
public int Order { get { return _order; } }
}
private XmlDocument _document;
private XmlNode _head;
private XmlNode _xml;
private string _locale = string.Empty;
private bool _selfBranded = MHSDefault.SelfBranded;
private string _topicVersion = MHSDefault.TopicVersion;
private string _tocParent = MHSDefault.TocParent;
private string _tocParentVersion = MHSDefault.TocParentVersion;
private Dictionary _toc = new Dictionary();
///
/// Creates a new instance of the class.
///
/// The active .
/// The current of the configuration.
public MSHCComponent(BuildAssembler assembler, XPathNavigator configuration)
: base(assembler, configuration)
{
string tocFile = MHSDefault.TocFile;
XPathNavigator data = configuration.SelectSingleNode(ConfigurationTag.Data);
if (data != null)
{
string value = data.GetAttribute(ConfigurationAttr.Locale, string.Empty);
if (!string.IsNullOrEmpty(value))
_locale = value;
value = data.GetAttribute(ConfigurationAttr.SelfBranded, string.Empty);
if (!string.IsNullOrEmpty(value))
_selfBranded = bool.Parse(value);
value = data.GetAttribute(ConfigurationAttr.TopicVersion, string.Empty);
if (!string.IsNullOrEmpty(value))
_topicVersion = value;
value = data.GetAttribute(ConfigurationAttr.TocParent, string.Empty);
if (!string.IsNullOrEmpty(value))
_tocParent = value;
value = data.GetAttribute(ConfigurationAttr.TocParentVersion, string.Empty);
if (!string.IsNullOrEmpty(value))
_tocParentVersion = value;
value = data.GetAttribute(ConfigurationAttr.TocFile, string.Empty);
if (!string.IsNullOrEmpty(value))
tocFile = value;
}
LoadToc(Path.GetFullPath(Environment.ExpandEnvironmentVariables(tocFile)));
}
#region Public
///
/// Applies Microsoft Help System transformation to the output document.
///
/// The to apply transformation to.
/// Topic key of the output document.
public override void Apply(XmlDocument document, string key)
{
_document = document;
ModifyAttribute("id", "mainSection");
ModifyAttribute("class", "members");
FixHeaderBottomBackground("nsrBottom", "headerBottom");
XmlElement html = _document.DocumentElement;
_head = html.SelectSingleNode(Help2XPath.Head);
if (_head == null)
{
_head = document.CreateElement(Help2XPath.Head);
if (!html.HasChildNodes)
html.AppendChild(_head);
else
html.InsertBefore(_head, html.FirstChild);
}
AddMHSMeta(MHSMetaName.SelfBranded, _selfBranded.ToString().ToLower());
AddMHSMeta(MHSMetaName.ContentType, MHSDefault.Reference);
AddMHSMeta(MHSMetaName.TopicVersion, _topicVersion);
string locale = _locale;
string id = Guid.NewGuid().ToString();
_xml = _head.SelectSingleNode(Help2XPath.Xml);
if (_xml != null)
{
XmlNamespaceManager nsmgr = new XmlNamespaceManager(_document.NameTable);
if (!nsmgr.HasNamespace(Help2Namespace.Prefix))
nsmgr.AddNamespace(Help2Namespace.Prefix, Help2Namespace.Uri);
XmlElement elem = _xml.SelectSingleNode(Help2XPath.TocTitle, nsmgr) as XmlElement;
if (elem != null)
AddMHSMeta(MHSMetaName.Title, elem.GetAttribute(Help2Attr.Title));
foreach (XmlElement keyword in _xml.SelectNodes(string.Format(Help2XPath.Keyword, Help2Value.K), nsmgr))
AddMHSMeta(MHSMetaName.Keywords, keyword.GetAttribute(Help2Attr.Term), true);
foreach (XmlElement keyword in _xml.SelectNodes(string.Format(Help2XPath.Keyword, Help2Value.F), nsmgr))
AddMHSMeta(MHSMetaName.F1, keyword.GetAttribute(Help2Attr.Term), true);
foreach (XmlElement lang in _xml.SelectNodes(string.Format(Help2XPath.Attr, Help2Value.DevLang), nsmgr))
AddMHSMeta(MHSMetaName.Category, Help2Value.DevLang + ":" + lang.GetAttribute(Help2Attr.Value), true);
elem = _xml.SelectSingleNode(string.Format(Help2XPath.Attr, Help2Value.Abstract), nsmgr) as XmlElement;
if (elem != null)
AddMHSMeta(MHSMetaName.Description, elem.GetAttribute(Help2Attr.Value));
elem = _xml.SelectSingleNode(string.Format(Help2XPath.Attr, Help2Value.AssetID), nsmgr) as XmlElement;
if (elem != null)
id = elem.GetAttribute(Help2Attr.Value);
if (string.IsNullOrEmpty(locale))
{
elem = _xml.SelectSingleNode(string.Format(Help2XPath.Attr, Help2Value.Locale), nsmgr) as XmlElement;
if (elem != null)
locale = elem.GetAttribute(Help2Attr.Value);
}
}
if (string.IsNullOrEmpty(locale))
locale = MHSDefault.Locale;
AddMHSMeta(MHSMetaName.Locale, locale);
AddMHSMeta(MHSMetaName.TopicLocale, locale);
AddMHSMeta(MHSMetaName.Id, id);
if (_toc.ContainsKey(id))
{
TocInfo tocInfo = _toc[id];
AddMHSMeta(MHSMetaName.TocParent, tocInfo.Parent);
if (tocInfo.Parent != MHSDefault.TocParent)
AddMHSMeta(MHSMetaName.TocParentVersion, tocInfo.ParentVersion);
AddMHSMeta(MHSMetaName.TocOrder, tocInfo.Order.ToString());
}
}
#endregion
#region Private
// loads TOC structure from a file
private void LoadToc(string path)
{
_toc.Clear();
using (Stream stream = File.OpenRead(path))
{
XPathDocument document = new XPathDocument(stream);
XPathNavigator navigator = document.CreateNavigator();
LoadToc(navigator.SelectSingleNode(TocXPath.Topics), _tocParent, _tocParentVersion);
}
}
// loads TOC structure from an XPathNavigator
private void LoadToc(XPathNavigator navigator, string parent, string parentVersion)
{
int i = -1;
XPathNodeIterator interator = navigator.SelectChildren(TocXPath.Topic, string.Empty);
while (interator.MoveNext())
{
XPathNavigator current = interator.Current;
string id = current.GetAttribute(TocAttr.Id, string.Empty);
if (!string.IsNullOrEmpty(id))
{
TocInfo info = new TocInfo(parent, parentVersion, ++i);
_toc.Add(id, info);
LoadToc(current, id, _topicVersion);
}
}
}
// Adds Microsoft Help System meta data to the output document
private XmlElement AddMHSMeta(string name, string content)
{
return AddMHSMeta(name, content, false);
}
// Adds Microsoft Help System meta data to the output document
private XmlElement AddMHSMeta(string name, string content, bool multiple)
{
if (string.IsNullOrEmpty(content))
return null;
XmlElement elem = null;
if (!multiple)
elem = _document.SelectSingleNode(string.Format(@"//meta[@{0}]", name)) as XmlElement;
if (elem == null)
{
elem = _document.CreateElement(MHSTag.Meta);
elem.SetAttribute(MHSMetaAttr.Name, name);
elem.SetAttribute(MHSMetaAttr.Content, content);
_head.AppendChild(elem);
}
return elem;
}
// Modifies an attribute value to prevent conflicts with Microsoft Help System branding
private void ModifyAttribute(string name, string value)
{
XmlNodeList list = _document.SelectNodes(string.Format(@"//*[@{0}='{1}']", name, value));
foreach (XmlElement elem in list)
elem.SetAttribute(name, value + MHSDefault.ShortName);
}
// Works around a Microsoft Help System issue ('background' attribute isn't supported):
// adds a hidden image so that its path will be transformed by MHS runtime handler,
// sets the 'background' attribute to the transformed path on page load
private void FixHeaderBottomBackground(string className, string newId)
{
XmlElement elem = _document.SelectSingleNode(string.Format(@"//*[@class='{0}']", className)) as XmlElement;
if (elem == null)
return;
string src = elem.GetAttribute("background");
if (string.IsNullOrEmpty(src))
return;
elem.SetAttribute("id", newId);
XmlElement img = _document.CreateElement("img");
img.SetAttribute("src", src);
img.SetAttribute("id", newId + "Image");
img.SetAttribute("style", "display: none");
elem.AppendChild(img);
}
#endregion
}
}