// Copyright Microsoft Corporation.
// This source file is subject to the Microsoft Permissive License.
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
// All other 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) {
bool isConversionOperator = false;
MethodTarget method = target as MethodTarget;
if (method != null) {
isConversionOperator = method.conversionOperator;
}
MemberTarget member = target as MemberTarget;
// if conversion operator is found, always link to individual topic.
if ((member != null) && (!String.IsNullOrEmpty(member.OverloadId)) && !isConversionOperator) {
Target overloadTarget = targets[member.OverloadId];
if (overloadTarget != null) {
target = overloadTarget;
targetId = overloadTarget.Id;
}
}
// if individual conversion operator is found, always display parameters.
if (isConversionOperator && member != null && (!string.IsNullOrEmpty(member.OverloadId))) {
options = options | DisplayOptions.ShowParameters;
} else {
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 if ((String.Compare(link.DisplayTarget, "extension", true) == 0) && (link.Contents != null))
{
Reference extMethodReference = XmlTargetCollectionUtilities.CreateExtensionMethodReference(link.Contents);
resolver.WriteReference(extMethodReference, options, writer);
} 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);
}
}
}
|