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

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 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)).GetXmlDocumentationText()); } 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)).GetXmlDocumentationText()); } 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)) { 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; } else if (Cache[assemblyName.FullName] == null) { return null; } 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)) { 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)).ConfigureAwait(false); Cache[fullName] = value; } else if (Cache[assemblyName.FullName] == null) { return null; } return parameter.GetXmlDocumentation(Cache[assemblyName.FullName]); } finally { _lock.Release(); } } return null; } catch { return null; } } 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 ((object)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; } private static XElement GetXmlDocumentation(this MemberInfo member, XDocument xml) { string memberElementName = GetMemberElementName(member); return ((IEnumerable)DynamicApis.XPathEvaluate(xml, $"""{new object[1] { memberElementName }}""")).OfType<XElement>().FirstOrDefault(); } 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>().FirstOrDefault(); } 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 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; 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; } } } }