summaryrefslogtreecommitdiffstats
path: root/tools/Sandcastle/Source/BuildAssembler/CopyComponents/InheritDocumentationComponent.cs
diff options
context:
space:
mode:
Diffstat (limited to 'tools/Sandcastle/Source/BuildAssembler/CopyComponents/InheritDocumentationComponent.cs')
-rw-r--r--tools/Sandcastle/Source/BuildAssembler/CopyComponents/InheritDocumentationComponent.cs373
1 files changed, 373 insertions, 0 deletions
diff --git a/tools/Sandcastle/Source/BuildAssembler/CopyComponents/InheritDocumentationComponent.cs b/tools/Sandcastle/Source/BuildAssembler/CopyComponents/InheritDocumentationComponent.cs
new file mode 100644
index 0000000..9df1282
--- /dev/null
+++ b/tools/Sandcastle/Source/BuildAssembler/CopyComponents/InheritDocumentationComponent.cs
@@ -0,0 +1,373 @@
+// ------------------------------------------------------------------------------------------------
+// <copyright file="InheritDocumentationComponent.cs" company="Microsoft">
+// 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.
+// </copyright>
+// <summary>Contains code that indexes XML comments files for <inheritdoc /> tags, reflection files
+// for API information and produces a new XML comments file containing the inherited documentation
+// for use by Sandcastle.
+// </summary>
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Ddue.Tools
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using System.Xml;
+ using System.Xml.XPath;
+ using System.Configuration;
+ using System.Globalization;
+ using Microsoft.Ddue.Tools.CommandLine;
+
+ /// <summary>
+ /// InheritDocumentationComponent class.
+ /// </summary>
+ public class InheritDocumentationComponent : CopyComponent
+ {
+ #region private members
+ /// <summary>
+ /// XPathExpression for API name.
+ /// </summary>
+ private static XPathExpression apiNameExpression = XPathExpression.Compile("string(apidata/@name)");
+
+ /// <summary>
+ /// XPathExpression for API group.
+ /// </summary>
+ private static XPathExpression apiGroupExpression = XPathExpression.Compile("string(apidata/@group)");
+
+ /// <summary>
+ /// XPathExpression for API subgroup.
+ /// </summary>
+ private static XPathExpression apiSubgroupExpression = XPathExpression.Compile("string(apidata/@subgroup)");
+
+ /// <summary>
+ /// XPathExpression for API ancestors.
+ /// </summary>
+ private static XPathExpression typeExpression = XPathExpression.Compile("family/ancestors/type/@api");
+
+ /// <summary>
+ /// XPathExpression for API type interface implementations.
+ /// </summary>
+ private static XPathExpression interfaceImplementationExpression = XPathExpression.Compile("implements/type/@api");
+
+ /// <summary>
+ /// XPathExpression for API containers.
+ /// </summary>
+ private static XPathExpression containerTypeExpression = XPathExpression.Compile("string(containers/type/@api)");
+
+ /// <summary>
+ /// XPathExpression for override members.
+ /// </summary>
+ private static XPathExpression overrideMemberExpression = XPathExpression.Compile("overrides/member/@api");
+
+ /// <summary>
+ /// XPathExpression for API member interface implementaions.
+ /// </summary>
+ private static XPathExpression interfaceImplementationMemberExpression = XPathExpression.Compile("implements/member/@api");
+
+ /// <summary>
+ /// XPathExpression for <inheritdoc /> nodes.
+ /// </summary>
+ private static XPathExpression inheritDocExpression = XPathExpression.Compile("//inheritdoc");
+
+ /// <summary>
+ /// XPathExpression that looks for example, filterpriority, preliminary, remarks, returns, summary, threadsafety and value nodes.
+ /// </summary>
+ private static XPathExpression tagsExpression = XPathExpression.Compile("example|filterpriority|preliminary|remarks|returns|summary|threadsafety|value");
+
+ /// <summary>
+ /// XPathExpression for source nodes.
+ /// </summary>
+ private static XPathExpression sourceExpression;
+
+ /// <summary>
+ /// Document to be parsed.
+ /// </summary>
+ private XmlDocument sourceDocument;
+
+ /// <summary>
+ /// A cache for comment files.
+ /// </summary>
+ private IndexedDocumentCache index;
+
+ /// <summary>
+ /// A cache for reflection files.
+ /// </summary>
+ private IndexedDocumentCache reflectionIndex;
+ #endregion
+
+ #region constructor
+ /// <summary>
+ /// Creates an instance of InheritDocumentationComponent class.
+ /// </summary>
+ /// <param name="configuration">Configuration section to be parsed.</param>
+ /// <param name="data">A dictionary object with string as key and object as value.</param>
+ public InheritDocumentationComponent(XPathNavigator configuration, Dictionary<string, object> data)
+ : base(configuration, data)
+ {
+ // get the copy commands
+ XPathNodeIterator copy_nodes = configuration.Select("copy");
+ foreach (XPathNavigator copy_node in copy_nodes)
+ {
+ // get the comments info
+ string source_name = copy_node.GetAttribute("name", string.Empty);
+ if (String.IsNullOrEmpty(source_name))
+ {
+ throw new ConfigurationErrorsException("Each copy command must specify an index to copy from.");
+ }
+
+ // get the reflection info
+ string reflection_name = copy_node.GetAttribute("use", String.Empty);
+ if (String.IsNullOrEmpty(reflection_name))
+ {
+ throw new ConfigurationErrorsException("Each copy command must specify an index to get reflection information from.");
+ }
+
+ this.index = (IndexedDocumentCache)data[source_name];
+ this.reflectionIndex = (IndexedDocumentCache)data[reflection_name];
+ }
+ }
+ #endregion
+
+ #region methods
+ /// <summary>
+ /// Deletes the specified node and logs the message.
+ /// </summary>
+ /// <param name="inheritDocNodeNavigator">navigator for inheritdoc node</param>
+ /// <param name="key">Id of the topic specified</param>
+ public static void DeleteNode(XPathNavigator inheritDocNodeNavigator, string key)
+ {
+ ConsoleApplication.WriteMessage(LogLevel.Info, string.Format(CultureInfo.InvariantCulture, "Comments are not found for topic:{0}", key));
+ inheritDocNodeNavigator.DeleteSelf();
+ }
+
+ /// <summary>
+ /// Implement inheritDocumentation.
+ /// </summary>
+ /// <param name="document">document to be parsed</param>
+ /// <param name="key">Id pf the topic specified</param>
+ public override void Apply(XmlDocument document, string key)
+ {
+ // default selection filter set not to inherit <overloads>.
+ sourceExpression = XPathExpression.Compile("*[not(local-name()='overloads')]");
+ this.sourceDocument = document;
+ this.InheritDocumentation(key);
+ }
+
+ /// <summary>
+ /// Inherit the documentation.
+ /// </summary>
+ /// <param name="key">Id of the topic specified</param>
+ public void InheritDocumentation(string key)
+ {
+ foreach (XPathNavigator inheritDocNodeNavigator in this.sourceDocument.CreateNavigator().Select(inheritDocExpression))
+ {
+ inheritDocNodeNavigator.MoveToParent();
+
+ XPathNodeIterator iterator = (XPathNodeIterator) inheritDocNodeNavigator.CreateNavigator().Evaluate(tagsExpression);
+
+ // do not inherit the comments if the tags specified in tagsExpression are already present.
+ if (iterator.Count != 0)
+ {
+ inheritDocNodeNavigator.MoveTo(this.sourceDocument.CreateNavigator().SelectSingleNode(inheritDocExpression));
+ inheritDocNodeNavigator.DeleteSelf();
+ continue;
+ }
+
+ inheritDocNodeNavigator.MoveTo(this.sourceDocument.CreateNavigator().SelectSingleNode(inheritDocExpression));
+
+ // Inherit from the specified API [id=cref].
+ string cref = inheritDocNodeNavigator.GetAttribute("cref", string.Empty);
+
+ if (!string.IsNullOrEmpty(cref))
+ {
+ XPathNavigator contentNodeNavigator = this.index.GetContent(cref);
+
+ // if no comments were found for the specified api, delete the <inheritdoc /> node,
+ // otherwise update the <inheritdoc /> node with the comments from the specified api.
+ if (contentNodeNavigator == null)
+ {
+ DeleteNode(inheritDocNodeNavigator, cref);
+ }
+ else
+ {
+ this.UpdateNode(inheritDocNodeNavigator, contentNodeNavigator);
+ if (this.sourceDocument.CreateNavigator().Select(inheritDocExpression).Count != 0)
+ {
+ this.InheritDocumentation(cref);
+ }
+ }
+ }
+ else
+ {
+ XPathNavigator reflectionNodeNavigator = this.reflectionIndex.GetContent(key);
+
+ // no reflection information was found for the api, so delete <inheritdoc /> node.
+ if (reflectionNodeNavigator == null)
+ {
+ DeleteNode(inheritDocNodeNavigator, key);
+ continue;
+ }
+
+ string group = (string)reflectionNodeNavigator.Evaluate(apiGroupExpression);
+ string subgroup = (string)reflectionNodeNavigator.Evaluate(apiSubgroupExpression);
+
+ if (group == "type")
+ {
+ // Inherit from base types
+ XPathNodeIterator typeNodeIterator = (XPathNodeIterator)reflectionNodeNavigator.Evaluate(typeExpression);
+ this.GetComments(typeNodeIterator, inheritDocNodeNavigator);
+
+ // no <inheritdoc /> nodes were found, so continue with next iteration. Otherwise inherit from interface implementation types.
+ if (this.sourceDocument.CreateNavigator().Select(inheritDocExpression).Count == 0)
+ {
+ continue;
+ }
+
+ // Inherit from interface implementation types
+ XPathNodeIterator interfaceNodeIterator = (XPathNodeIterator)reflectionNodeNavigator.Evaluate(interfaceImplementationExpression);
+ this.GetComments(interfaceNodeIterator, inheritDocNodeNavigator);
+ }
+ else if (group == "member")
+ {
+ // constructors do not have override member information in reflection files, so search all the base types for a matching signature.
+ if (subgroup == "constructor")
+ {
+ string name = (string)reflectionNodeNavigator.Evaluate(apiNameExpression);
+ string typeApi = (string) reflectionNodeNavigator.Evaluate(containerTypeExpression);
+
+ // no container type api was found, so delete <inheritdoc /> node.
+ if (string.IsNullOrEmpty(typeApi))
+ {
+ DeleteNode(inheritDocNodeNavigator, key);
+ continue;
+ }
+
+ reflectionNodeNavigator = this.reflectionIndex.GetContent(typeApi);
+
+ // no reflection information for container type api was found, so delete <inheritdoc /> node.
+ if (reflectionNodeNavigator == null)
+ {
+ DeleteNode(inheritDocNodeNavigator, key);
+ continue;
+ }
+
+ XPathNodeIterator containerIterator = reflectionNodeNavigator.Select(typeExpression);
+
+ foreach (XPathNavigator containerNavigator in containerIterator)
+ {
+ string constructorId = string.Format(CultureInfo.InvariantCulture, "M:{0}.{1}", containerNavigator.Value.Substring(2), name.Replace('.', '#'));
+ XPathNavigator contentNodeNavigator = this.index.GetContent(constructorId);
+
+ if (contentNodeNavigator == null)
+ {
+ continue;
+ }
+
+ this.UpdateNode(inheritDocNodeNavigator, contentNodeNavigator);
+
+ if (this.sourceDocument.CreateNavigator().Select(inheritDocExpression).Count == 0)
+ {
+ break;
+ }
+ else
+ {
+ inheritDocNodeNavigator.MoveTo(this.sourceDocument.CreateNavigator().SelectSingleNode(inheritDocExpression));
+ }
+ }
+ }
+ else
+ {
+ // Inherit from override members.
+ XPathNodeIterator memberNodeIterator = (XPathNodeIterator)reflectionNodeNavigator.Evaluate(overrideMemberExpression);
+ this.GetComments(memberNodeIterator, inheritDocNodeNavigator);
+
+ if (this.sourceDocument.CreateNavigator().Select(inheritDocExpression).Count == 0)
+ {
+ continue;
+ }
+
+ // Inherit from interface implementations members.
+ XPathNodeIterator interfaceNodeIterator = (XPathNodeIterator)reflectionNodeNavigator.Evaluate(interfaceImplementationMemberExpression);
+ this.GetComments(interfaceNodeIterator, inheritDocNodeNavigator);
+ }
+ }
+
+ // no comments were found, so delete <iheritdoc /> node.
+ if (this.sourceDocument.CreateNavigator().Select(inheritDocExpression).Count != 0)
+ {
+ DeleteNode(inheritDocNodeNavigator, key);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Updates the node replacing inheritdoc node with comments found.
+ /// </summary>
+ /// <param name="inheritDocNodeNavigator">Navigator for inheritdoc node</param>
+ /// <param name="contentNodeNavigator">Navigator for content</param>
+ public void UpdateNode(XPathNavigator inheritDocNodeNavigator, XPathNavigator contentNodeNavigator)
+ {
+ // retrieve the selection filter if specified.
+ string selectValue = inheritDocNodeNavigator.GetAttribute("select", string.Empty);
+
+ if (!string.IsNullOrEmpty(selectValue))
+ {
+ sourceExpression = XPathExpression.Compile(selectValue);
+ }
+
+ inheritDocNodeNavigator.MoveToParent();
+
+ if (inheritDocNodeNavigator.LocalName != "comments" && inheritDocNodeNavigator.LocalName != "element")
+ {
+ sourceExpression = XPathExpression.Compile(inheritDocNodeNavigator.LocalName);
+ }
+ else
+ {
+ inheritDocNodeNavigator.MoveTo(this.sourceDocument.CreateNavigator().SelectSingleNode(inheritDocExpression));
+ }
+
+ XPathNodeIterator sources = (XPathNodeIterator) contentNodeNavigator.CreateNavigator().Evaluate(sourceExpression);
+ inheritDocNodeNavigator.DeleteSelf();
+
+ // append the source nodes to the target node
+ foreach (XPathNavigator source in sources)
+ {
+ inheritDocNodeNavigator.AppendChild(source);
+ }
+ }
+
+ /// <summary>
+ /// Gets the comments for inheritdoc node.
+ /// </summary>
+ /// <param name="iterator">Iterator for API information</param>
+ /// <param name="inheritDocNodeNavigator">Navigator for inheritdoc node</param>
+ public void GetComments(XPathNodeIterator iterator, XPathNavigator inheritDocNodeNavigator)
+ {
+ foreach (XPathNavigator navigator in iterator)
+ {
+ XPathNavigator contentNodeNavigator = this.index.GetContent(navigator.Value);
+
+ if (contentNodeNavigator == null)
+ {
+ continue;
+ }
+
+ this.UpdateNode(inheritDocNodeNavigator, contentNodeNavigator);
+
+ if (this.sourceDocument.CreateNavigator().Select(inheritDocExpression).Count == 0)
+ {
+ break;
+ }
+ else
+ {
+ inheritDocNodeNavigator.MoveTo(this.sourceDocument.CreateNavigator().SelectSingleNode(inheritDocExpression));
+ }
+ }
+ }
+ #endregion
+ }
+}