diff options
Diffstat (limited to 'tools/Sandcastle/Source/BuildAssembler/BuildComponents/ResolveReferenceLinksComponent2.cs')
-rw-r--r-- | tools/Sandcastle/Source/BuildAssembler/BuildComponents/ResolveReferenceLinksComponent2.cs | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/tools/Sandcastle/Source/BuildAssembler/BuildComponents/ResolveReferenceLinksComponent2.cs b/tools/Sandcastle/Source/BuildAssembler/BuildComponents/ResolveReferenceLinksComponent2.cs new file mode 100644 index 0000000..31e4349 --- /dev/null +++ b/tools/Sandcastle/Source/BuildAssembler/BuildComponents/ResolveReferenceLinksComponent2.cs @@ -0,0 +1,473 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Schema; +using System.Xml.XPath; + + +namespace Microsoft.Ddue.Tools { + + // replace the old component with the new one + public class ResolveReferenceLinksComponent : ResolveReferenceLinksComponent2 { + + public ResolveReferenceLinksComponent (BuildAssembler assembler, XPathNavigator configuration) : base(assembler, configuration) { } + + } + + public class ResolveReferenceLinksComponent2 : BuildComponent { + + // instantiation logic + + public ResolveReferenceLinksComponent2 (BuildAssembler assembler, XPathNavigator configuration) + : base(assembler, configuration) { + + // base-url is an xpath expression applied against the current document to pick up the save location of the + // document. If specified, local links will be made relative to the base-url. + string baseUrlValue = configuration.GetAttribute("base-url", String.Empty); + if (!String.IsNullOrEmpty(baseUrlValue)) + baseUrl = XPathExpression.Compile(baseUrlValue); + + // url-format is a string format that is used to format the value of local href attributes. The default is + // "{0}.htm" for backwards compatibility. + string hrefFormatValue = configuration.GetAttribute("href-format", String.Empty); + if (!String.IsNullOrEmpty(hrefFormatValue)) + hrefFormat = hrefFormatValue; + + // the container XPath can be replaced; this is useful + string containerValue = configuration.GetAttribute("container", String.Empty); + if (!String.IsNullOrEmpty(containerValue)) XmlTargetCollectionUtilities.ContainerExpression = containerValue; + + targets = new TargetCollection(); + resolver = new LinkTextResolver(targets); + + XPathNodeIterator targets_nodes = configuration.Select("targets"); + foreach (XPathNavigator targets_node in targets_nodes) { + + // get target type + string typeValue = targets_node.GetAttribute("type", String.Empty); + if (String.IsNullOrEmpty(typeValue)) WriteMessage(MessageLevel.Error, "Each targets element must have a type attribute that specifies which type of links to create."); + + LinkType2 type = LinkType2.None; + try { + type = (LinkType2)Enum.Parse(typeof(LinkType2), typeValue, true); + if ((type == LinkType2.Msdn) && (msdn == null)) { + WriteMessage(MessageLevel.Info, "Creating MSDN URL resolver."); + msdn = new MsdnResolver(); + } + } catch (ArgumentException) { + WriteMessage(MessageLevel.Error, String.Format("'{0}' is not a supported reference link type.", typeValue)); + } + + // get base directory + string baseValue = targets_node.GetAttribute("base", String.Empty); + + // get file pattern + string filesValue = targets_node.GetAttribute("files", String.Empty); + if (String.IsNullOrEmpty(filesValue)) WriteMessage(MessageLevel.Error, "Each targets element must have a files attribute specifying which target files to load."); + + // determine whether to search recursively + bool recurse = false; + string recurseValue = targets_node.GetAttribute("recurse", String.Empty); + if (!String.IsNullOrEmpty(recurseValue)) { + if (String.Compare(recurseValue, Boolean.TrueString, true) == 0) { + recurse = true; + } else if (String.Compare(recurseValue, Boolean.FalseString, true) == 0) { + recurse = false; + } else { + WriteMessage(MessageLevel.Error, String.Format("On the targets element, recurse='{0}' is not an allowed value.", recurseValue)); + } + } + + // turn baseValue and filesValue into directoryPath and filePattern + string fullPath; + if (String.IsNullOrEmpty(baseValue)) { + fullPath = filesValue; + } else { + fullPath = Path.Combine(baseValue, filesValue); + } + fullPath = Environment.ExpandEnvironmentVariables(fullPath); + string directoryPath = Path.GetDirectoryName(fullPath); + if (String.IsNullOrEmpty(directoryPath)) directoryPath = Environment.CurrentDirectory; + string filePattern = Path.GetFileName(fullPath); + + // verify that directory exists + if (!Directory.Exists(directoryPath)) WriteMessage(MessageLevel.Error, String.Format("The targets directory '{0}' does not exist.", directoryPath)); + + // add the specified targets from the directory + WriteMessage(MessageLevel.Info, String.Format("Searching directory '{0}' for targets files of the form '{1}'.", directoryPath, filePattern)); + AddTargets(directoryPath, filePattern, recurse, type); + + } + + WriteMessage(MessageLevel.Info, String.Format("Loaded {0} reference targets.", targets.Count)); + + string locale_value = configuration.GetAttribute("locale", String.Empty); + if (!String.IsNullOrEmpty(locale_value) && msdn != null) msdn.Locale = locale_value; + + string target_value = configuration.GetAttribute("linkTarget", String.Empty); + if (!String.IsNullOrEmpty(target_value)) linkTarget = target_value; + } + + private void AddTargets (string directory, string filePattern, bool recurse, LinkType2 type) { + string[] files = Directory.GetFiles(directory, filePattern); + foreach (string file in files) { + AddTargets(file, type); + } + if (recurse) { + string[] subdirectories = Directory.GetDirectories(directory); + foreach (string subdirectory in subdirectories) { + AddTargets(subdirectory, filePattern, recurse, type); + } + } + } + + private void AddTargets (string file, LinkType2 type) { + try { + XPathDocument document = new XPathDocument(file); + XmlTargetCollectionUtilities.AddTargets(targets, document.CreateNavigator(), type); + } catch (XmlSchemaException e) { + WriteMessage(MessageLevel.Error, String.Format("The reference targets file '{0}' is not valid. The error message is: {1}", file, BuildComponentUtilities.GetExceptionMessage(e))); + } catch (XmlException e) { + WriteMessage(MessageLevel.Error, String.Format("The reference targets file '{0}' is not well-formed XML. The error message is: {1}", file, BuildComponentUtilities.GetExceptionMessage(e))); + } catch (IOException e) { + WriteMessage(MessageLevel.Error, String.Format("An access error occured while opening the reference targets file '{0}'. The error message is: {1}", file, BuildComponentUtilities.GetExceptionMessage(e))); + } + } + + private string linkTarget = "_blank"; + + // target information storage + + private TargetCollection targets; + + private LinkTextResolver resolver; + + private static XPathExpression referenceLinkExpression = XPathExpression.Compile("//referenceLink"); + + // WebDocs target url formatting + + private XPathExpression baseUrl; + + private string hrefFormat = "{0}.htm"; + + // component logic + + public override void Apply (XmlDocument document, string key) { + + // XmlNodeList link_nodes = document.SelectNodes("//referenceLink"); + XPathNodeIterator linkIterator = document.CreateNavigator().Select(referenceLinkExpression); + + XPathNavigator[] linkNodes = BuildComponentUtilities.ConvertNodeIteratorToArray(linkIterator); + + foreach (XPathNavigator linkNode in linkNodes) { + + // extract link information + ReferenceLinkInfo2 link = ReferenceLinkInfo2.Create(linkNode); + + if (link == null) { + WriteMessage(MessageLevel.Warn, "Invalid referenceLink element."); + } else { + + // determine target, link type, and display options + string targetId = link.Target; + DisplayOptions options = link.DisplayOptions; + LinkType2 type = LinkType2.None; + + Target target = targets[targetId]; + if (target == null) { + // no such target known; set link type to none and warn + type = LinkType2.None; + WriteMessage(MessageLevel.Warn, String.Format("Unknown reference link target '{0}'.", targetId)); + } else { + // if overload is prefered and found, change targetId and make link options hide parameters + if (link.PreferOverload) { + MemberTarget member = target as MemberTarget; + if ((member != null) && (!String.IsNullOrEmpty(member.OverloadId))) { + Target overloadTarget = targets[member.OverloadId]; + if (overloadTarget != null) { + target = overloadTarget; + targetId = overloadTarget.Id; + } + } + options = options & ~DisplayOptions.ShowParameters; + } + + // get stored link type + type = target.DefaultLinkType; + + // if link type is local or index, determine which + if (type == LinkType2.LocalOrIndex) { + if ((key != null) && targets.Contains(key) && (target.Container == targets[key].Container)) { + type = LinkType2.Local; + } else { + type = LinkType2.Index; + } + } + } + + // links to this page are not live + if (targetId == key) { + type = LinkType2.Self; + } else if ((target != null) && (key != null) && targets.Contains(key) && (target.File == targets[key].File)) { + type = LinkType2.Self; + } + + // get msdn endpoint, if needed + string msdnUrl = null; + if (type == LinkType2.Msdn) { + if ((msdn == null) || (msdn.IsDisabled)) { + // no msdn resolver + } else { + msdnUrl = msdn.GetMsdnUrl(targetId); + if (String.IsNullOrEmpty(msdnUrl)) { + WriteMessage(MessageLevel.Warn, String.Format("MSDN URL not found for target '{0}'.", targetId)); + } + } + if (String.IsNullOrEmpty(msdnUrl)) type = LinkType2.None; + } + + // write opening link tag and target info + XmlWriter writer = linkNode.InsertAfter(); + switch (type) { + case LinkType2.None: + writer.WriteStartElement("span"); + writer.WriteAttributeString("class", "nolink"); + break; + case LinkType2.Self: + writer.WriteStartElement("span"); + writer.WriteAttributeString("class", "selflink"); + break; + case LinkType2.Local: + // format link with prefix and/or postfix + string href = String.Format(hrefFormat, target.File); + + // make link relative, if we have a baseUrl + if (baseUrl != null) + href = BuildComponentUtilities.GetRelativePath(href, BuildComponentUtilities.EvalXPathExpr(document, baseUrl, "key", key)); + + writer.WriteStartElement("a"); + writer.WriteAttributeString("href", href); + break; + case LinkType2.Index: + writer.WriteStartElement("mshelp", "link", "http://msdn.microsoft.com/mshelp"); + writer.WriteAttributeString("keywords", targetId); + writer.WriteAttributeString("tabindex", "0"); + break; + case LinkType2.Msdn: + writer.WriteStartElement("a"); + writer.WriteAttributeString("href", msdnUrl); + writer.WriteAttributeString("target", linkTarget); + break; + } + + // write the link text + if (String.IsNullOrEmpty(link.DisplayTarget)) { + if (link.Contents == null) { + if (target != null) { + resolver.WriteTarget(target, options, writer); + } else { + //Console.WriteLine("Attemting to create reference"); + Reference reference = TextReferenceUtilities.CreateReference(targetId); + //Console.WriteLine("Returned"); + if (reference is InvalidReference) WriteMessage(MessageLevel.Warn, String.Format("Invalid reference link target '{0}'.", targetId)); + resolver.WriteReference(reference, options, writer); + } + } else { + // write contents to writer + link.Contents.WriteSubtree(writer); + } + } else { + //Console.WriteLine("Display target = {0}", link.DisplayTarget); + if ((String.Compare(link.DisplayTarget, "content", true) == 0) && (link.Contents != null)) { + // Use the contents as an XML representation of the display target + + //Console.WriteLine(link.Contents.NodeType); + Reference reference = XmlTargetCollectionUtilities.CreateReference(link.Contents); + //Console.WriteLine(reference.GetType().FullName); + resolver.WriteReference(reference, options, writer); + } if ((String.Compare(link.DisplayTarget, "format", true) == 0) && (link.Contents != null)) { + // Use the contents as a format string for the display target + + string format = link.Contents.OuterXml; + //Console.WriteLine("format = {0}", format); + + string input = null; + StringWriter textStore = new StringWriter(); + try { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.ConformanceLevel = ConformanceLevel.Fragment; + + XmlWriter xmlStore = XmlWriter.Create(textStore, settings); + try { + if (target != null) { + resolver.WriteTarget(target, options, xmlStore); + } else { + Reference reference = TextReferenceUtilities.CreateReference(targetId); + resolver.WriteReference(reference, options, xmlStore); + } + } finally { + xmlStore.Close(); + } + input = textStore.ToString(); + } finally { + textStore.Close(); + } + //Console.WriteLine("input = {0}", input); + + string output = String.Format(format, input); + //Console.WriteLine("output = {0}", output); + + XmlDocumentFragment fragment = document.CreateDocumentFragment(); + fragment.InnerXml = output; + fragment.WriteTo(writer); + + //writer.WriteRaw(output); + } else { + // Use the display target value as a CER for the display target + + TextReferenceUtilities.SetGenericContext(key); + Reference reference = TextReferenceUtilities.CreateReference(link.DisplayTarget); + //Console.WriteLine("Reference is {0}", reference.GetType().FullName); + resolver.WriteReference(reference, options, writer); + } + } + + // write the closing link tag + writer.WriteEndElement(); + writer.Close(); + } + + // delete the original tag + linkNode.DeleteSelf(); + + } + + } + + // msdn resolver + + private MsdnResolver msdn = null; + + + } + + + internal class ReferenceLinkInfo2 { + + // stored data + + private string target; + + private string displayTarget; + + private DisplayOptions options = DisplayOptions.Default; + + private bool preferOverload = false; + + private XPathNavigator contents; + + // data accessors + + public string Target { + get { + return(target); + } + } + + public string DisplayTarget { + get { + return(displayTarget); + } + } + + public DisplayOptions DisplayOptions { + get { + return(options); + } + } + + public bool PreferOverload { + get { + return(preferOverload); + } + } + + public XPathNavigator Contents { + get { + return(contents); + } + } + + // creation logic + + private ReferenceLinkInfo2 () {} + + public static ReferenceLinkInfo2 Create (XPathNavigator element) { + if (element == null) throw new ArgumentNullException("element"); + + ReferenceLinkInfo2 info = new ReferenceLinkInfo2(); + + info.target = element.GetAttribute("target", String.Empty); + if (String.IsNullOrEmpty(info.target)) return(null); + + info.displayTarget = element.GetAttribute("display-target", String.Empty); + + string showContainer = element.GetAttribute("show-container", String.Empty); + if (String.IsNullOrEmpty(showContainer)) showContainer = element.GetAttribute("qualified", String.Empty); + if (!String.IsNullOrEmpty(showContainer)) { + if (String.Compare(showContainer, Boolean.TrueString, true) == 0) { + info.options = info.options | DisplayOptions.ShowContainer; + } else if (String.Compare(showContainer, Boolean.FalseString, true) == 0) { + info.options = info.options & ~DisplayOptions.ShowContainer; + } else { + return(null); + } + } + + string showTemplates = element.GetAttribute("show-templates", String.Empty); + if (!String.IsNullOrEmpty(showTemplates)) { + if (String.Compare(showTemplates, Boolean.TrueString, true) == 0) { + info.options = info.options | DisplayOptions.ShowTemplates; + } else if (String.Compare(showTemplates, Boolean.FalseString, true) == 0) { + info.options = info.options & ~DisplayOptions.ShowTemplates; + } else { + return(null); + } + } + + string showParameters = element.GetAttribute("show-parameters", String.Empty); + if (!String.IsNullOrEmpty(showParameters)) { + if (String.Compare(showParameters, Boolean.TrueString, true) == 0) { + info.options = info.options | DisplayOptions.ShowParameters; + } else if (String.Compare(showParameters, Boolean.FalseString, true) == 0) { + info.options = info.options & ~DisplayOptions.ShowParameters; + } else { + return(null); + } + } + + + string preferOverload = element.GetAttribute("prefer-overload", String.Empty); + if (String.IsNullOrEmpty(preferOverload)) preferOverload = element.GetAttribute("auto-upgrade", String.Empty); + if (!String.IsNullOrEmpty(preferOverload)) { + if (String.Compare(preferOverload, Boolean.TrueString, true) == 0) { + info.preferOverload = true; + } else if (String.Compare(preferOverload, Boolean.FalseString, true) == 0) { + info.preferOverload = false; + } else { + return(null); + } + } + + info.contents = element.Clone(); + if (!info.contents.MoveToFirstChild()) info.contents = null; + + return(info); + } + + } +} |