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