<PackageReference Include="NJsonSchema" Version="9.13.1" />

XmlDocumentationExtensions

public static class XmlDocumentationExtensions
Provides extension methods for reading XML comments from reflected members.
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; namespace NJsonSchema.Infrastructure { public static class XmlDocumentationExtensions { private class AsyncLock : IDisposable { private SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); public AsyncLock Lock() { _semaphoreSlim.Wait(); return this; } public void Dispose() { _semaphoreSlim.Release(); } } private static readonly AsyncLock Lock = new AsyncLock(); private static readonly Dictionary<string, XDocument> Cache = new Dictionary<string, XDocument>(StringComparer.OrdinalIgnoreCase); public static Task<string> GetXmlSummaryAsync(this Type type) { return type.GetTypeInfo().GetXmlDocumentationTagAsync("summary"); } public static Task<string> GetXmlRemarksAsync(this Type type) { return 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<string> GetDescriptionAsync(this MemberInfo memberInfo, IEnumerable<Attribute> attributes) { dynamic val = attributes.TryGetIfAssignableTo("System.ComponentModel.DescriptionAttribute", TypeNameStyle.FullName); dynamic val2 = val != null; if ((val2 ? false : true) ? val2 : (val2 & !string.IsNullOrEmpty(val.Description))) return (string)val.Description; dynamic val3 = attributes.TryGetIfAssignableTo("System.ComponentModel.DataAnnotations.DisplayAttribute", TypeNameStyle.FullName); val2 = (val3 != null); if ((val2 ? false : true) ? val2 : (val2 & !string.IsNullOrEmpty(val3.Description))) return (string)val3.Description; if (memberInfo != (MemberInfo)null) { string text = await memberInfo.GetXmlSummaryAsync().ConfigureAwait(false); if (text != string.Empty) return text; } return null; } public static async Task<string> GetDescriptionAsync(this ParameterInfo parameter, IEnumerable<Attribute> attributes) { dynamic val = attributes.TryGetIfAssignableTo("System.ComponentModel.DescriptionAttribute", TypeNameStyle.FullName); dynamic val2 = val != null; if ((val2 ? false : true) ? val2 : (val2 & !string.IsNullOrEmpty(val.Description))) return (string)val.Description; dynamic val3 = attributes.TryGetIfAssignableTo("System.ComponentModel.DataAnnotations.DisplayAttribute", TypeNameStyle.FullName); val2 = (val3 != null); if ((val2 ? false : true) ? val2 : (val2 & !string.IsNullOrEmpty(val3.Description))) return (string)val3.Description; if (parameter != null) { string text = await parameter.GetXmlDocumentationAsync().ConfigureAwait(false); if (text != string.Empty) return text; } return null; } public static string GetXmlDocumentationText(this XElement element) { if (element != null) { StringBuilder stringBuilder = new StringBuilder(); foreach (XNode item in element.Nodes()) { XElement xElement; if ((xElement = (item as XElement)) != null) { if (xElement.Name == (XName)"see") { XAttribute xAttribute = xElement.Attribute("langword"); if (xAttribute != null) stringBuilder.Append(xAttribute.Value); else if (!string.IsNullOrEmpty(xElement.Value)) { stringBuilder.Append(xElement.Value); } else { xAttribute = xElement.Attribute("cref"); if (xAttribute != null) stringBuilder.Append(xAttribute.Value.Trim('!', ':').Trim().Split(new char[1] { '.' }) .Last()); else { xAttribute = xElement.Attribute("href"); if (xAttribute != null) stringBuilder.Append(xAttribute.Value); } } } else stringBuilder.Append(xElement.Value); } else stringBuilder.Append(item); } return stringBuilder.ToString(); } return null; } public static Task ClearCacheAsync() { using (Lock.Lock()) { Cache.Clear(); return DynamicApis.FromResult<object>(null); } } public static async Task<string> GetXmlDocumentationTagAsync(this MemberInfo member, string tagName) { if (!DynamicApis.SupportsXPathApis || !DynamicApis.SupportsFileApis || !DynamicApis.SupportsPathApis) return string.Empty; AssemblyName name = member.Module.Assembly.GetName(); using (Lock.Lock()) { if (IgnoreAssembly(name)) return string.Empty; return RemoveLineBreakWhiteSpaces(((await member.GetXmlDocumentationWithoutLockAsync(await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false)).ConfigureAwait(false))?.Element(tagName)).GetXmlDocumentationText()); } } public static async Task<string> GetXmlDocumentationAsync(this ParameterInfo parameter) { if (!DynamicApis.SupportsXPathApis || !DynamicApis.SupportsFileApis || !DynamicApis.SupportsPathApis) return string.Empty; AssemblyName name = parameter.Member.Module.Assembly.GetName(); using (Lock.Lock()) { if (IgnoreAssembly(name)) return string.Empty; return RemoveLineBreakWhiteSpaces((await parameter.GetXmlDocumentationWithoutLockAsync(await GetXmlDocumentationPathAsync(parameter.Member.Module.Assembly).ConfigureAwait(false)).ConfigureAwait(false)).GetXmlDocumentationText()); } } public static async Task<XElement> GetXmlDocumentationAsync(this Type type, string pathToXmlFile) { using (Lock.Lock()) return await type.GetTypeInfo().GetXmlDocumentationWithoutLockAsync(pathToXmlFile).ConfigureAwait(false); } public static async Task<XElement> GetXmlDocumentationAsync(this ParameterInfo parameter, string pathToXmlFile) { try { if (pathToXmlFile == null || !DynamicApis.SupportsXPathApis || !DynamicApis.SupportsFileApis || !DynamicApis.SupportsPathApis) return null; parameter.Member.Module.Assembly.GetName(); using (Lock.Lock()) return await parameter.GetXmlDocumentationWithoutLockAsync(pathToXmlFile).ConfigureAwait(false); } catch { return null; } } public static async Task<XElement> GetXmlDocumentationAsync(this MemberInfo member) { using (Lock.Lock()) return await member.GetXmlDocumentationWithoutLockAsync().ConfigureAwait(false); } public static async Task<XElement> GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile) { using (Lock.Lock()) return await member.GetXmlDocumentationWithoutLockAsync(pathToXmlFile).ConfigureAwait(false); } private static async Task<XElement> GetXmlDocumentationWithoutLockAsync(this ParameterInfo parameter, string pathToXmlFile) { try { if (!DynamicApis.SupportsXPathApis || !DynamicApis.SupportsFileApis || !DynamicApis.SupportsPathApis) return null; XDocument xDocument = await TryGetXmlDocumentAsync(parameter.Member.Module.Assembly.GetName(), pathToXmlFile).ConfigureAwait(false); if (xDocument == null) return null; return await parameter.GetXmlDocumentationAsync(xDocument).ConfigureAwait(false); } catch { return null; } } private static async Task<XElement> GetXmlDocumentationWithoutLockAsync(this MemberInfo member) { if (!DynamicApis.SupportsXPathApis || !DynamicApis.SupportsFileApis || !DynamicApis.SupportsPathApis) return null; if (IgnoreAssembly(member.Module.Assembly.GetName())) return null; return await member.GetXmlDocumentationWithoutLockAsync(await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false)).ConfigureAwait(false); } private static async Task<XElement> GetXmlDocumentationWithoutLockAsync(this MemberInfo member, string pathToXmlFile) { try { if (!DynamicApis.SupportsXPathApis || !DynamicApis.SupportsFileApis || !DynamicApis.SupportsPathApis) return null; XDocument xDocument = await TryGetXmlDocumentAsync(member.Module.Assembly.GetName(), pathToXmlFile).ConfigureAwait(false); if (xDocument == null) return null; XElement element = member.GetXmlDocumentation(xDocument); await member.ReplaceInheritdocElementsAsync(element).ConfigureAwait(false); return element; } catch { return null; } } private static async Task<XDocument> TryGetXmlDocumentAsync(AssemblyName assemblyName, string pathToXmlFile) { if (!Cache.ContainsKey(assemblyName.FullName)) { if (!(await DynamicApis.FileExistsAsync(pathToXmlFile).ConfigureAwait(false))) { Cache[assemblyName.FullName] = null; return null; } string fullName = assemblyName.FullName; XDocument value = await Task.Factory.StartNew(() => XDocument.Load(pathToXmlFile, LoadOptions.PreserveWhitespace)).ConfigureAwait(false); Cache[fullName] = value; } return Cache[assemblyName.FullName]; } private static bool IgnoreAssembly(AssemblyName assemblyName) { if (Cache.ContainsKey(assemblyName.FullName) && Cache[assemblyName.FullName] == null) return true; return false; } private static XElement GetXmlDocumentation(this MemberInfo member, XDocument xml) { string memberElementName = GetMemberElementName(member); return ((IEnumerable)DynamicApis.XPathEvaluate(xml, "/doc/members/member[@name='" + memberElementName + "']")).OfType<XElement>().FirstOrDefault(); } private static async Task<XElement> GetXmlDocumentationAsync(this ParameterInfo parameter, XDocument xml) { string name = GetMemberElementName(parameter.Member); IEnumerable source = (IEnumerable)DynamicApis.XPathEvaluate(xml, "/doc/members/member[@name='" + name + "']"); XElement element = source.OfType<XElement>().First(); await parameter.Member.ReplaceInheritdocElementsAsync(element).ConfigureAwait(false); source = ((!parameter.IsRetval && !string.IsNullOrEmpty(parameter.Name)) ? ((IEnumerable)DynamicApis.XPathEvaluate(xml, "/doc/members/member[@name='" + name + "']/param[@name='" + parameter.Name + "']")) : ((IEnumerable)DynamicApis.XPathEvaluate(xml, "/doc/members/member[@name='" + name + "']/returns"))); return source.OfType<XElement>().FirstOrDefault(); } private static async Task ReplaceInheritdocElementsAsync(this MemberInfo member, XElement element) { if (element != null) { List<XNode> source = element.Nodes().ToList(); foreach (XElement item in source.OfType<XElement>()) { if (item.Name.LocalName.ToLowerInvariant() == "inheritdoc") { Type baseType = member.DeclaringType.GetTypeInfo().BaseType; MemberInfo memberInfo = ((object)baseType != null) ? baseType.GetTypeInfo().DeclaredMembers.SingleOrDefault((MemberInfo m) => m.Name == member.Name) : null; if (memberInfo != (MemberInfo)null) { XElement xElement = await memberInfo.GetXmlDocumentationWithoutLockAsync().ConfigureAwait(false); if (xElement != null) item.ReplaceWith(xElement.Nodes().OfType<object>().ToArray()); else await member.ProcessInheritdocInterfaceElementsAsync(item).ConfigureAwait(false); } else await member.ProcessInheritdocInterfaceElementsAsync(item).ConfigureAwait(false); } } } } private static async Task ProcessInheritdocInterfaceElementsAsync(this MemberInfo member, XElement child) { foreach (Type implementedInterface in member.DeclaringType.GetTypeInfo().ImplementedInterfaces) { MemberInfo memberInfo = ((object)implementedInterface != null) ? implementedInterface.GetTypeInfo().DeclaredMembers.SingleOrDefault((MemberInfo m) => m.Name == member.Name) : null; if (memberInfo != (MemberInfo)null) { XElement xElement = await memberInfo.GetXmlDocumentationWithoutLockAsync().ConfigureAwait(false); if (xElement != null) child.ReplaceWith(xElement.Nodes().OfType<object>().ToArray()); } } } 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) { Type type; dynamic val = ((object)(type = (member as Type)) != null && !string.IsNullOrEmpty(type.FullName)) ? type.FullName : (member.DeclaringType.FullName + "." + member.Name); char c; switch ((string)member.MemberType.ToString()) { case "Constructor": val = val.Replace(".ctor", "#ctor"); goto case "Method"; case "Method": { c = 'M'; string text = 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(text)) val += "(" + text + ")"; break; } case "Event": c = 'E'; break; case "Field": c = 'F'; break; case "NestedType": val = val.Replace('+', '.'); goto case "TypeInfo"; case "TypeInfo": c = 'T'; break; case "Property": c = 'P'; break; default: throw new ArgumentException("Unknown member type.", "member"); } return (string)string.Format("{0}:{1}", c, val.Replace("+", ".")); } private 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; if (Cache.ContainsKey(assemblyName.FullName)) return null; string path4 = DynamicApis.PathGetDirectoryName((string)assembly.Location); string path = DynamicApis.PathCombine(path4, (string)assemblyName.Name + ".xml"); if (await DynamicApis.FileExistsAsync(path).ConfigureAwait(false)) return path; if (ReflectionExtensions.HasProperty(assembly, "CodeBase")) { path = (string)DynamicApis.PathCombine(DynamicApis.PathGetDirectoryName(assembly.CodeBase.Replace("file:///", string.Empty)), assemblyName.Name + ".xml").Replace("file:\\", string.Empty); if (await DynamicApis.FileExistsAsync(path).ConfigureAwait(false)) return path; } object value = Type.GetType("System.AppDomain").GetRuntimeProperty("CurrentDomain").GetValue(null); if (value.HasProperty("BaseDirectory")) { string baseDirectory = value.TryGetPropertyValue("BaseDirectory", ""); path = (string)DynamicApis.PathCombine(baseDirectory, assemblyName.Name + ".xml"); if (await DynamicApis.FileExistsAsync(path).ConfigureAwait(false)) return path; return (string)DynamicApis.PathCombine(baseDirectory, "bin\\" + assemblyName.Name + ".xml"); } return null; } catch { return null; } } } }