/// <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();
            }
        }
示例#2
0
        /// <summary>
        /// This is overridden to load the SQL dictionary in a thread-safe manner
        /// </summary>
        /// <param name="maxDegreeOfParallelism">This can be used to override the maximum degree of parallelism.
        /// By default, it is -1 to allow as many threads as possible.</param>
        protected override void LoadTargetDictionary(int maxDegreeOfParallelism = -1)
        {
            var namespaceFileFilter = this.NamespaceFileFilter;

            Parallel.ForEach(Directory.EnumerateFiles(this.DirectoryPath, this.FilePattern, this.Recurse ?
                                                      SearchOption.AllDirectories : SearchOption.TopDirectoryOnly),
                             new ParallelOptions {
                MaxDegreeOfParallelism = maxDegreeOfParallelism
            },
                             file =>
            {
                using (SqlConnection cn = new SqlConnection(connectionString))
                    using (SqlCommand cmd = new SqlCommand())
                    {
                        cn.Open();

                        cmd.Connection = cn;
                        cmd.Parameters.Add(new SqlParameter("@key", SqlDbType.VarChar, 768));

                        cmd.CommandText = String.Format(CultureInfo.InvariantCulture,
                                                        "Select TargetKey From Targets Where GroupId = '{0}' And TargetKey = @key",
                                                        base.DictionaryId);
                        cmd.Parameters[0].Value = "N:" + Path.GetFileNameWithoutExtension(file);

                        // Skip the file if not in a defined filter or if it's already in the dictionary
                        if ((namespaceFileFilter.Count != 0 && !namespaceFileFilter.Contains(Path.GetFileName(file))) ||
                            cmd.ExecuteScalar() != null)
                        {
                            return;
                        }

                        this.BuildComponent.WriteMessage(MessageLevel.Info, "Indexing targets in {0}", file);

                        cmd.CommandText = String.Format(CultureInfo.InvariantCulture,
                                                        "IF NOT EXISTS(Select * From Targets Where GroupId = '{0}' And TargetKey = @key) " +
                                                        "Insert Targets (GroupId, TargetKey, TargetValue) Values ('{0}', @key, @value) " +
                                                        "ELSE Update Targets Set TargetValue = @value Where GroupId = '{0}' " +
                                                        "And TargetKey = @key", base.DictionaryId);

                        cmd.Parameters.Add(new SqlParameter("@value", SqlDbType.VarBinary));

                        BinaryFormatter bf = new BinaryFormatter
                        {
                            Context = new StreamingContext(StreamingContextStates.Persistence)
                        };

                        try
                        {
                            XPathDocument document = new XPathDocument(file);

                            foreach (var t in XmlTargetDictionaryUtilities.EnumerateTargets(document.CreateNavigator()))
                            {
                                cmd.Parameters[0].Value = t.Id;

                                using (MemoryStream ms = new MemoryStream())
                                {
                                    bf.Serialize(ms, t);
                                    cmd.Parameters[1].Value = ms.GetBuffer();
                                    cmd.ExecuteNonQuery();
                                }

                                // Enumeration targets have members we need to add too.  We can't use the
                                // Target.Add() method here so we must do it manually.
                                EnumerationTarget et = t as EnumerationTarget;

                                if (et != null)
                                {
                                    foreach (var el in et.Elements)
                                    {
                                        cmd.Parameters[0].Value = el.Id;

                                        using (MemoryStream ms = new MemoryStream())
                                        {
                                            bf.Serialize(ms, el);
                                            cmd.Parameters[1].Value = ms.GetBuffer();
                                            cmd.ExecuteNonQuery();
                                        }
                                    }
                                }
                            }
                        }
                        catch (XmlSchemaException e)
                        {
                            throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
                                                                              "The reference targets file '{0}' is not valid. The error message is: {1}", file,
                                                                              e.GetExceptionMessage()));
                        }
                        catch (XmlException e)
                        {
                            throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
                                                                              "The reference targets file '{0}' is not well-formed XML.  The error message is: {1}",
                                                                              file, e.GetExceptionMessage()));
                        }
                        catch (IOException e)
                        {
                            throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
                                                                              "An access error occured while opening the reference targets file '{0}'. The error " +
                                                                              "message is: {1}", file, e.GetExceptionMessage()));
                        }
                    }
            });
        }