// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.RegularExpressions; using System.Xml; namespace EpicGames.Core { /// /// Cache for XML documentation /// public class XmlDocReader { readonly ConcurrentDictionary _cachedDocumentation = new ConcurrentDictionary(); /// /// Adds a parsed file as the documentation for a particular assembly /// public void Add(Assembly assembly, XmlDocument? xmlDoc) { _cachedDocumentation[assembly] = xmlDoc; } /// /// Gets a property description from Xml documentation file /// public string? GetDescription(Type type) { return GetSummaryFromXmlDoc(type, "T", null); } /// /// Gets a property description from Xml documentation file /// public string? GetDescription(PropertyInfo propertyInfo) { return GetSummaryFromXmlDoc(propertyInfo.DeclaringType!, "P", propertyInfo.Name); } /// /// Gets a property description from Xml documentation file /// public string? GetDescription(Type type, string field) { return GetSummaryFromXmlDoc(type, "F", field); } /// /// Gets a description from Xml documentation file /// /// Type to retrieve summary for /// Type of element to retrieve /// Name of the member /// Summary string, or null if it's not available string? GetSummaryFromXmlDoc(Type type, string qualifier, string? member) { XmlDocument? xmlDoc; if (!TryReadXmlDoc(type.Assembly, out xmlDoc)) { return null; } string? fullName = type.FullName; if (member != null) { fullName = $"{fullName}.{member}"; } XmlNode? node = xmlDoc.SelectSingleNode($"//member[@name='{qualifier}:{fullName}']"); if (node == null) { return null; } XmlNode? summaryNode = node.SelectSingleNode("summary"); if (summaryNode == null) { if (node.SelectSingleNode("inheritdoc") != null) { foreach (Type baseType in FindBaseTypes(type)) { string? summary = GetSummaryFromXmlDoc(baseType, qualifier, member); if (summary != null) { return summary; } } } return null; } string text = summaryNode.InnerText.Trim().Replace("\r\n", "\n", StringComparison.Ordinal); text = Regex.Replace(text, @"[\t ]*\n[\t ]*", "\n"); text = Regex.Replace(text, @"(? FindBaseTypes(Type type) { if (type.BaseType != null) { yield return type.BaseType; } foreach (Type interfaceType in type.GetInterfaces()) { yield return interfaceType; } } bool TryReadXmlDoc(Assembly assembly, [NotNullWhen(true)] out XmlDocument? xmlDoc) { XmlDocument? documentation; if (!_cachedDocumentation.TryGetValue(assembly, out documentation)) { FileReference inputDocumentationFile = new FileReference(assembly.Location).ChangeExtension(".xml"); if (FileReference.Exists(inputDocumentationFile)) { documentation = new XmlDocument(); documentation.Load(inputDocumentationFile.FullName); } _cachedDocumentation.TryAdd(assembly, documentation); } if (documentation != null) { xmlDoc = documentation; return true; } else { xmlDoc = null; return false; } } } }