Ejemplo n.º 1
0
        /// <inheritdoc />
        public override void Apply(XmlDocument document, string key)
        {
            Target target = null, keyTarget;
            string msdnUrl = null;

            foreach (XPathNavigator linkNode in document.CreateNavigator().Select(referenceLinkExpression).ToArray())
            {
                // Extract link information
                ReferenceLinkInfo link = new ReferenceLinkInfo(linkNode);

                // Determine target, link type, and display options
                string            targetId = link.Target;
                DisplayOptions    options  = link.DisplayOptions;
                ReferenceLinkType type     = ReferenceLinkType.None;

                if (String.IsNullOrWhiteSpace(targetId))
                {
                    this.WriteMessage(key, MessageLevel.Warn, "The target attribute is missing or has no " +
                                      "value.  You have most likely omitted a cref attribute or left it blank on an XML " +
                                      "comments element such as see, seealso, or exception.");
                    continue;
                }

                bool targetFound = targets.TryGetValue(targetId, out target, out type);

                // If it's an overload ID that wasn't found, it's possible that the overloads got excluded.
                // As such, see if we can find a match for a method using the same ID regardless of any
                // parameters.
                if (!targetFound && targetId.StartsWith("Overload:", StringComparison.Ordinal) ||
                    targetId.StartsWith("O:", StringComparison.Ordinal))
                {
                    string methodTargetId = "M:" + targetId.Substring(targetId.IndexOf(':') + 1);
                    methodTargetId = targets.Keys.FirstOrDefault(k => k.StartsWith(methodTargetId));

                    if (methodTargetId != null)
                    {
                        targetId    = methodTargetId;
                        targetFound = targets.TryGetValue(targetId, out target, out type);
                        options    |= DisplayOptions.ShowParameters;

                        // Don't use the content as it may not be appropriate for the method.  The default is
                        // "{0} Overload" which no longer applies.  Instead we'll show the method name and
                        // its parameters.
                        if (link.DisplayTarget.Equals("format", StringComparison.OrdinalIgnoreCase))
                        {
                            link.DisplayTarget = null;
                            link.Contents      = null;
                        }
                    }
                }

                // If not found and it starts with "System." or "Microsoft." we'll go with the assumption that
                // it's part of a Microsoft class library that is not part of the core framework but does have
                // documentation available on MSDN.  Worst case it doesn't and we get an unresolved link warning
                // instead of an unknown reference target warning.
                if (!targetFound && ((targetId.Length > 9 && targetId.Substring(2).StartsWith("System.",
                                                                                              StringComparison.Ordinal)) || (targetId.Length > 12 && targetId.Substring(2).StartsWith(
                                                                                                                                 "Microsoft.", StringComparison.Ordinal))))
                {
                    // Use the same link type as a core framework class
                    targetFound = targets.TryGetValue("T:System.Object", out target, out type);

                    // We don't have a target in this case so links to overloads pages won't work.  Also note
                    // that the link text will be generated from the ID which shouldn't make much of a difference
                    // in most cases.  If either case is an issue, the Additional Reference Links SHFB plug-in
                    // can be used to generate valid link target data.
                    target = null;
                }

                if (!targetFound)
                {
                    // If not being rendered as a link, don't report a warning
                    if (link.RenderAsLink && targetId != key)
                    {
                        this.WriteMessage(key, MessageLevel.Warn, "Unknown reference link target '{0}'.", targetId);
                    }

                    // !EFW - Turn off the Show Parameters option for unresolved elements except methods.  If
                    // not, it outputs an empty "()" after the member name which looks odd.
                    if (targetId[0] != 'M')
                    {
                        options &= ~DisplayOptions.ShowParameters;
                    }
                }
                else
                {
                    // If overload is preferred and found, change targetId and make link options hide parameters
                    if (link.PreferOverload && target != null)
                    {
                        bool isConversionOperator = false;

                        MethodTarget method = target as MethodTarget;

                        if (method != null)
                        {
                            isConversionOperator = method.IsConversionOperator;
                        }

                        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;
                        }
                    }
                }

                // Suppress the link if so requested.  Links to this page are not live.
                if (!link.RenderAsLink)
                {
                    type = ReferenceLinkType.None;
                }
                else
                if (targetId == key)
                {
                    type = ReferenceLinkType.Self;
                }
                else
                if (target != null && targets.TryGetValue(key, out keyTarget) && target.File == keyTarget.File)
                {
                    type = ReferenceLinkType.Self;
                }

                // !EFW - Redirect enumeration fields to the containing enumerated type so that we
                // get a valid link target.  Enum fields don't have a topic to themselves.
                if (type != ReferenceLinkType.None && type != ReferenceLinkType.Self && type != ReferenceLinkType.Local &&
                    targetId.StartsWith("F:", StringComparison.OrdinalIgnoreCase))
                {
                    MemberTarget member = target as MemberTarget;

                    if (member != null)
                    {
                        SimpleTypeReference typeRef = member.ContainingType as SimpleTypeReference;

                        if (typeRef != null && targets[typeRef.Id] is EnumerationTarget)
                        {
                            targetId = typeRef.Id;
                        }
                    }
                }

                // Get MSDN endpoint if needed
                if (type == ReferenceLinkType.Msdn)
                {
                    if (msdnResolver != null && !msdnResolver.IsDisabled)
                    {
                        msdnUrl = msdnResolver.GetMsdnUrl(targetId);

                        if (String.IsNullOrEmpty(msdnUrl))
                        {
                            // If the web service failed, report the reason
                            if (msdnResolver.IsDisabled)
                            {
                                this.WriteMessage(key, MessageLevel.Warn, "MSDN web service failed.  No " +
                                                  "further look ups will be performed for this build.\r\nReason: {0}",
                                                  msdnResolver.DisabledReason);
                            }
                            else
                            {
                                this.WriteMessage(key, MessageLevel.Warn, "MSDN URL not found for target '{0}'.",
                                                  targetId);
                            }

                            type = ReferenceLinkType.None;
                        }
                    }
                    else
                    {
                        type = ReferenceLinkType.None;
                    }
                }

                // Write opening link tag and target info
                XmlWriter writer = linkNode.InsertAfter();

                switch (type)
                {
                case ReferenceLinkType.None:
                    writer.WriteStartElement("span");

                    // If the link was intentionally suppressed, write it out as an identifier (i.e. links
                    // in the syntax section).
                    if (link.RenderAsLink)
                    {
                        writer.WriteAttributeString("class", "nolink");
                    }
                    else
                    {
                        writer.WriteAttributeString("class", "identifier");
                    }
                    break;

                case ReferenceLinkType.Self:
                    writer.WriteStartElement("span");
                    writer.WriteAttributeString("class", "selflink");
                    break;

                case ReferenceLinkType.Local:
                    // Format link with prefix and/or postfix
                    string href = String.Format(CultureInfo.InvariantCulture, hrefFormat, target.File);

                    // Make link relative, if we have a baseUrl
                    if (baseUrl != null)
                    {
                        href = href.GetRelativePath(document.EvalXPathExpr(baseUrl, "key", key));
                    }

                    writer.WriteStartElement("a");
                    writer.WriteAttributeString("href", href);
                    break;

                case ReferenceLinkType.Msdn:
                    writer.WriteStartElement("a");
                    writer.WriteAttributeString("href", msdnUrl);
                    writer.WriteAttributeString("target", linkTarget);
                    break;

                case ReferenceLinkType.Id:
                    writer.WriteStartElement("a");
                    writer.WriteAttributeString("href", ("ms-xhelp:///?Id=" + targetId).Replace("#", "%23"));
                    break;
                }

                // Write the link text
                if (String.IsNullOrEmpty(link.DisplayTarget))
                {
                    if (link.Contents == null)
                    {
                        if (target != null)
                        {
                            resolver.WriteTarget(target, options, writer);
                        }
                        else
                        {
                            Reference reference = TextReferenceUtilities.CreateReference(targetId);

                            if (reference is InvalidReference)
                            {
                                this.WriteMessage(key, MessageLevel.Warn,
                                                  "Invalid reference link target '{0}'.", targetId);
                            }

                            resolver.WriteReference(reference, options, writer);
                        }
                    }
                    else
                    {
                        do
                        {
                            link.Contents.WriteSubtree(writer);
                        } while(link.Contents.MoveToNext());
                    }
                }
                else
                {
                    if (link.DisplayTarget.Equals("content", StringComparison.OrdinalIgnoreCase) &&
                        link.Contents != null)
                    {
                        // Use the contents as an XML representation of the display target
                        Reference reference = XmlTargetDictionaryUtilities.CreateReference(link.Contents);
                        resolver.WriteReference(reference, options, writer);
                    }

                    if (link.DisplayTarget.Equals("format", StringComparison.OrdinalIgnoreCase) &&
                        link.Contents != null)
                    {
                        // Use the contents as a format string for the display target
                        string format = link.Contents.OuterXml;
                        string input  = null;

                        using (StringWriter textStore = new StringWriter(CultureInfo.InvariantCulture))
                        {
                            XmlWriterSettings settings = new XmlWriterSettings();
                            settings.ConformanceLevel = ConformanceLevel.Fragment;

                            using (XmlWriter xmlStore = XmlWriter.Create(textStore, settings))
                            {
                                if (target != null)
                                {
                                    resolver.WriteTarget(target, options, xmlStore);
                                }
                                else
                                {
                                    Reference reference = TextReferenceUtilities.CreateReference(targetId);
                                    resolver.WriteReference(reference, options, xmlStore);
                                }
                            }

                            input = textStore.ToString();
                        }

                        string output = String.Format(CultureInfo.InvariantCulture, format, input);

                        XmlDocumentFragment fragment = document.CreateDocumentFragment();
                        fragment.InnerXml = output;
                        fragment.WriteTo(writer);
                    }
                    else if (link.DisplayTarget.Equals("extension", StringComparison.OrdinalIgnoreCase) &&
                             link.Contents != null)
                    {
                        Reference extMethodReference = XmlTargetDictionaryUtilities.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);
                        resolver.WriteReference(reference, options, writer);
                    }
                }

                // Write the closing link tag
                writer.WriteEndElement();
                writer.Close();

                // Delete the original tag
                linkNode.DeleteSelf();
            }
        }