// ------------------------------------------------------------------------------------------------ // // 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. // // Contains code that indexes XML comments files for tags, reflection files // for API information and produces a new XML comments file containing the inherited documentation // for use by Sandcastle. // // ------------------------------------------------------------------------------------------------ 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; /// /// InheritDocumentationComponent class. /// public class InheritDocumentationComponent : CopyComponent { #region private members /// /// XPathExpression for API name. /// private static XPathExpression apiNameExpression = XPathExpression.Compile("string(apidata/@name)"); /// /// XPathExpression for API group. /// private static XPathExpression apiGroupExpression = XPathExpression.Compile("string(apidata/@group)"); /// /// XPathExpression for API subgroup. /// private static XPathExpression apiSubgroupExpression = XPathExpression.Compile("string(apidata/@subgroup)"); /// /// XPathExpression for API ancestors. /// private static XPathExpression typeExpression = XPathExpression.Compile("family/ancestors/type/@api"); /// /// XPathExpression for API type interface implementations. /// private static XPathExpression interfaceImplementationExpression = XPathExpression.Compile("implements/type/@api"); /// /// XPathExpression for API containers. /// private static XPathExpression containerTypeExpression = XPathExpression.Compile("string(containers/type/@api)"); /// /// XPathExpression for override members. /// private static XPathExpression overrideMemberExpression = XPathExpression.Compile("overrides/member/@api"); /// /// XPathExpression for API member interface implementaions. /// private static XPathExpression interfaceImplementationMemberExpression = XPathExpression.Compile("implements/member/@api"); /// /// XPathExpression for nodes. /// private static XPathExpression inheritDocExpression = XPathExpression.Compile("//inheritdoc"); /// /// XPathExpression that looks for example, filterpriority, preliminary, remarks, returns, summary, threadsafety and value nodes. /// private static XPathExpression tagsExpression = XPathExpression.Compile("example|filterpriority|preliminary|remarks|returns|summary|threadsafety|value"); /// /// XPathExpression for source nodes. /// private static XPathExpression sourceExpression; /// /// Document to be parsed. /// private XmlDocument sourceDocument; /// /// A cache for comment files. /// private IndexedDocumentCache index; /// /// A cache for reflection files. /// private IndexedDocumentCache reflectionIndex; #endregion #region constructor /// /// Creates an instance of InheritDocumentationComponent class. /// /// Configuration section to be parsed. /// A dictionary object with string as key and object as value. public InheritDocumentationComponent(XPathNavigator configuration, Dictionary 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 /// /// Deletes the specified node and logs the message. /// /// navigator for inheritdoc node /// Id of the topic specified 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(); } /// /// Implement inheritDocumentation. /// /// document to be parsed /// Id pf the topic specified public override void Apply(XmlDocument document, string key) { // default selection filter set not to inherit . sourceExpression = XPathExpression.Compile("*[not(local-name()='overloads')]"); this.sourceDocument = document; this.InheritDocumentation(key); } /// /// Inherit the documentation. /// /// Id of the topic specified 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 node, // otherwise update the 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 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 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 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 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 node. if (this.sourceDocument.CreateNavigator().Select(inheritDocExpression).Count != 0) { DeleteNode(inheritDocNodeNavigator, key); } } } } /// /// Updates the node replacing inheritdoc node with comments found. /// /// Navigator for inheritdoc node /// Navigator for content 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); } } /// /// Gets the comments for inheritdoc node. /// /// Iterator for API information /// Navigator for inheritdoc node 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 } }