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