XmlDocumentationExtensions
Provides extension methods for reading XML comments from reflected members.
using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace NJsonSchema.Infrastructure
{
public static class XmlDocumentationExtensions
{
private static readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
private static readonly Dictionary<string, XDocument> Cache = new Dictionary<string, XDocument>(StringComparer.OrdinalIgnoreCase);
public static Task<string> GetXmlSummaryAsync(this Type type)
{
return ((MemberInfo)type.GetTypeInfo()).GetXmlDocumentationTagAsync("summary");
}
public static Task<string> GetXmlRemarksAsync(this Type type)
{
return ((MemberInfo)type.GetTypeInfo()).GetXmlDocumentationTagAsync("remarks");
}
[Obsolete("Use GetXmlSummary instead.")]
public static Task<string> GetXmlDocumentationAsync(this Type type)
{
return type.GetXmlDocumentationTagAsync("summary");
}
public static Task<string> GetXmlDocumentationTagAsync(this Type type, string tagName)
{
return ((MemberInfo)type.GetTypeInfo()).GetXmlDocumentationTagAsync(tagName);
}
public static async Task<string> GetXmlSummaryAsync(this MemberInfo member)
{
return await member.GetXmlDocumentationTagAsync("summary").ConfigureAwait(false);
}
public static async Task<string> GetXmlRemarksAsync(this MemberInfo member)
{
return await member.GetXmlDocumentationTagAsync("remarks").ConfigureAwait(false);
}
public static async Task<XElement> GetXmlDocumentationAsync(this MemberInfo member)
{
if (!DynamicApis.SupportsXPathApis || !DynamicApis.SupportsFileApis || !DynamicApis.SupportsPathApis)
return null;
AssemblyName assemblyName = member.Module.Assembly.GetName();
await _lock.WaitAsync();
try {
if (Cache.ContainsKey(assemblyName.FullName) && Cache[assemblyName.FullName] == null)
return null;
} finally {
_lock.Release();
}
return await member.GetXmlDocumentationAsync(await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false)).ConfigureAwait(false);
}
public static async Task<string> GetXmlDocumentationTagAsync(this MemberInfo member, string tagName)
{
if (!DynamicApis.SupportsXPathApis || !DynamicApis.SupportsFileApis || !DynamicApis.SupportsPathApis)
return string.Empty;
AssemblyName assemblyName = member.Module.Assembly.GetName();
await _lock.WaitAsync();
try {
if (Cache.ContainsKey(assemblyName.FullName) && Cache[assemblyName.FullName] == null)
return string.Empty;
} finally {
_lock.Release();
}
return RemoveLineBreakWhiteSpaces((await member.GetXmlDocumentationAsync(await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false)).ConfigureAwait(false))?.Element(tagName)?.Value);
}
public static async Task<string> GetXmlDocumentationAsync(this ParameterInfo parameter)
{
if (!DynamicApis.SupportsXPathApis || !DynamicApis.SupportsFileApis || !DynamicApis.SupportsPathApis)
return string.Empty;
AssemblyName assemblyName = parameter.Member.Module.Assembly.GetName();
await _lock.WaitAsync();
try {
if (Cache.ContainsKey(assemblyName.FullName) && Cache[assemblyName.FullName] == null)
return string.Empty;
} finally {
_lock.Release();
}
return RemoveLineBreakWhiteSpaces((await parameter.GetXmlDocumentationAsync(await GetXmlDocumentationPathAsync(parameter.Member.Module.Assembly).ConfigureAwait(false)).ConfigureAwait(false))?.Value);
}
public static Task<XElement> GetXmlDocumentationAsync(this Type type, string pathToXmlFile)
{
return ((MemberInfo)type.GetTypeInfo()).GetXmlDocumentationAsync(pathToXmlFile);
}
public static async Task<XElement> GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile)
{
try {
if (pathToXmlFile != null && DynamicApis.SupportsXPathApis && DynamicApis.SupportsFileApis && DynamicApis.SupportsPathApis) {
AssemblyName assemblyName = member.Module.Assembly.GetName();
await _lock.WaitAsync();
try {
if (Cache.ContainsKey(assemblyName.FullName) && Cache[assemblyName.FullName] == null)
return null;
if (!(await DynamicApis.FileExistsAsync(pathToXmlFile).ConfigureAwait(false))) {
Cache[assemblyName.FullName] = null;
return null;
}
if (!Cache.ContainsKey(assemblyName.FullName)) {
string fullName = assemblyName.FullName;
XDocument value = await Task.Factory.StartNew(() => XDocument.Load(pathToXmlFile)).ConfigureAwait(false);
Cache[fullName] = value;
}
return member.GetXmlDocumentation(Cache[assemblyName.FullName]);
} finally {
_lock.Release();
}
}
return null;
} catch {
return null;
}
}
public static async Task<XElement> GetXmlDocumentationAsync(this ParameterInfo parameter, string pathToXmlFile)
{
try {
if (pathToXmlFile != null && DynamicApis.SupportsXPathApis && DynamicApis.SupportsFileApis && DynamicApis.SupportsPathApis) {
AssemblyName assemblyName = parameter.Member.Module.Assembly.GetName();
await _lock.WaitAsync();
try {
if (Cache.ContainsKey(assemblyName.FullName) && Cache[assemblyName.FullName] == null)
return null;
if (!(await DynamicApis.FileExistsAsync(pathToXmlFile).ConfigureAwait(false))) {
Cache[assemblyName.FullName] = null;
return null;
}
if (!Cache.ContainsKey(assemblyName.FullName)) {
string fullName = assemblyName.FullName;
XDocument value = await Task.Factory.StartNew(() => XDocument.Load(pathToXmlFile)).ConfigureAwait(false);
Cache[fullName] = value;
}
return parameter.GetXmlDocumentation(Cache[assemblyName.FullName]);
} finally {
_lock.Release();
}
}
return null;
} catch {
return null;
}
}
private static XElement GetXmlDocumentation(this MemberInfo member, XDocument xml)
{
string memberElementName = GetMemberElementName(member);
return ((IEnumerable)DynamicApis.XPathEvaluate(xml, $"""{new object[1] {
memberElementName
}}""")).OfType<XElement>().First();
}
private static XElement GetXmlDocumentation(this ParameterInfo parameter, XDocument xml)
{
string memberElementName = GetMemberElementName(parameter.Member);
IEnumerable source = (!parameter.IsRetval && !string.IsNullOrEmpty(parameter.Name)) ? ((IEnumerable)DynamicApis.XPathEvaluate(xml, string.Format("/doc/members/member[@name='{0}']/param[@name='{1}']", new object[2] {
memberElementName,
parameter.Name
}))) : ((IEnumerable)DynamicApis.XPathEvaluate(xml, $"""{new object[1] {
memberElementName
}}"""));
return source.OfType<XElement>().First();
}
private static string RemoveLineBreakWhiteSpaces(string documentation)
{
if (string.IsNullOrEmpty(documentation))
return string.Empty;
documentation = "\n" + documentation.Replace("\r", string.Empty).Trim(new char[1] {
'\n'
});
string value = Regex.Match(documentation, "(\\n[ \\t]*)").Value;
documentation = documentation.Replace(value, "\n");
return documentation.Trim(new char[1] {
'\n'
});
}
private static string GetMemberElementName(dynamic member)
{
string text = (string)((member is Type) ? ((Type)member).FullName : (member.DeclaringType.FullName + "." + member.Name));
char c;
switch ((string)member.MemberType.ToString()) {
case "Constructor":
text = text.Replace(".ctor", "#ctor");
goto case "Method";
case "Method": {
c = 'M';
string text2 = string.Join(",", (from x in ((MethodBase)member).GetParameters()
select Regex.Replace(x.ParameterType.FullName, "(`[0-9]+)|(, .*?PublicKeyToken=[0-9a-z]*)", string.Empty).Replace("[[", "{").Replace("]]", "}")).ToArray());
if (!string.IsNullOrEmpty(text2))
text = text + "(" + text2 + ")";
break;
}
case "Event":
c = 'E';
break;
case "Field":
c = 'F';
break;
case "NestedType":
text = text.Replace('+', '.');
goto case "TypeInfo";
case "TypeInfo":
c = 'T';
break;
case "Property":
c = 'P';
break;
default:
throw new ArgumentException("Unknown member type.", "member");
}
return string.Format("{0}:{1}", new object[2] {
c,
text.Replace("+", ".")
});
}
private unsafe static async Task<string> GetXmlDocumentationPathAsync(dynamic assembly)
{
try {
if (assembly == null)
return null;
if (string.IsNullOrEmpty(assembly.Location))
return null;
dynamic assemblyName = assembly.GetName();
if (string.IsNullOrEmpty(assemblyName.Name))
return null;
dynamic path = DynamicApis.PathCombine(DynamicApis.PathGetDirectoryName(assembly.Location), assemblyName.Name + ".xml");
if (<>o__18.<>p__16 == null)
<>o__18.<>p__16 = CallSite<Func<CallSite, object, bool>>.Create(Microsoft.CSharp.RuntimeBinder.Binder.UnaryOperation(CSharpBinderFlags.None, ExpressionType.IsTrue, typeof(XmlDocumentationExtensions), new CSharpArgumentInfo[1] {
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
}));
Func<CallSite, object, bool> target = <>o__18.<>p__16.Target;
CallSite<Func<CallSite, object, bool>> <>p__ = <>o__18.<>p__16;
dynamic val = DynamicApis.FileExistsAsync(path).ConfigureAwait(false).GetAwaiter();
ICriticalNotifyCompletion awaiter;
INotifyCompletion awaiter2;
AsyncTaskMethodBuilder<string> asyncTaskMethodBuilder;
if ((byte)val.IsCompleted != 0) {
object arg = val.GetResult();
if (target(<>p__, arg))
return (string)path;
if ((object)((object)assembly).GetType().GetRuntimeProperty("CodeBase") != null) {
path = DynamicApis.PathCombine(DynamicApis.PathGetDirectoryName(assembly.CodeBase.Replace("file:///", string.Empty)), assemblyName.Name + ".xml").Replace("file:\\", string.Empty);
if (<>o__18.<>p__27 == null)
<>o__18.<>p__27 = CallSite<Func<CallSite, object, bool>>.Create(Microsoft.CSharp.RuntimeBinder.Binder.UnaryOperation(CSharpBinderFlags.None, ExpressionType.IsTrue, typeof(XmlDocumentationExtensions), new CSharpArgumentInfo[1] {
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
}));
target = <>o__18.<>p__27.Target;
<>p__ = <>o__18.<>p__27;
val = DynamicApis.FileExistsAsync(path).ConfigureAwait(false).GetAwaiter();
if (!((byte)val.IsCompleted != 0)) {
awaiter = (val as ICriticalNotifyCompletion);
if (awaiter == null) {
awaiter2 = (INotifyCompletion)val;
asyncTaskMethodBuilder.AwaitOnCompleted(ref awaiter2, ref *(<GetXmlDocumentationPathAsync>d__18*));
awaiter2 = null;
} else
asyncTaskMethodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref *(<GetXmlDocumentationPathAsync>d__18*));
awaiter = null;
;
}
arg = val.GetResult();
if (target(<>p__, arg))
return (string)path;
}
dynamic currentDomain = Type.GetType("System.AppDomain").GetRuntimeProperty("CurrentDomain").GetValue(null);
path = DynamicApis.PathCombine(currentDomain.BaseDirectory, assemblyName.Name + ".xml");
if (<>o__18.<>p__35 == null)
<>o__18.<>p__35 = CallSite<Func<CallSite, object, bool>>.Create(Microsoft.CSharp.RuntimeBinder.Binder.UnaryOperation(CSharpBinderFlags.None, ExpressionType.IsTrue, typeof(XmlDocumentationExtensions), new CSharpArgumentInfo[1] {
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
}));
target = <>o__18.<>p__35.Target;
<>p__ = <>o__18.<>p__35;
val = DynamicApis.FileExistsAsync(path).ConfigureAwait(false).GetAwaiter();
if ((byte)val.IsCompleted != 0) {
arg = val.GetResult();
if (target(<>p__, arg))
return (string)path;
return (string)DynamicApis.PathCombine(currentDomain.BaseDirectory, "bin\\" + assemblyName.Name + ".xml");
}
awaiter = (val as ICriticalNotifyCompletion);
if (awaiter == null) {
awaiter2 = (INotifyCompletion)val;
asyncTaskMethodBuilder.AwaitOnCompleted(ref awaiter2, ref *(<GetXmlDocumentationPathAsync>d__18*));
awaiter2 = null;
} else
asyncTaskMethodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref *(<GetXmlDocumentationPathAsync>d__18*));
awaiter = null;
;
}
awaiter = (val as ICriticalNotifyCompletion);
if (awaiter == null) {
awaiter2 = (INotifyCompletion)val;
asyncTaskMethodBuilder.AwaitOnCompleted(ref awaiter2, ref *(<GetXmlDocumentationPathAsync>d__18*));
awaiter2 = null;
} else
asyncTaskMethodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref *(<GetXmlDocumentationPathAsync>d__18*));
awaiter = null;
;
} catch {
return null;
}
}
}
}