RobotNet/RobotNet.WebApp/Scripts/Monaco/Documentation/DocumentationComment.cs
2025-10-15 15:15:53 +07:00

196 lines
8.3 KiB
C#

using System.Text;
using System.Xml;
namespace RobotNet.WebApp.Scripts.Monaco.Documentation;
public class DocumentationComment(
string summaryText = "",
DocumentationItem[]? typeParamElements = null,
DocumentationItem[]? paramElements = null,
string returnsText = "",
string remarksText = "",
string exampleText = "",
string valueText = "",
DocumentationItem[]? exception = null)
{
public string SummaryText { get; } = summaryText;
public DocumentationItem[] TypeParamElements { get; } = typeParamElements ?? [];
public DocumentationItem[] ParamElements { get; } = paramElements ?? [];
public string ReturnsText { get; } = returnsText;
public string RemarksText { get; } = remarksText;
public string ExampleText { get; } = exampleText;
public string ValueText { get; } = valueText;
public DocumentationItem[] Exception { get; } = exception ?? [];
public static DocumentationComment? From(string xmlDocumentation, string lineEnding)
{
if (string.IsNullOrEmpty(xmlDocumentation))
return Empty;
var reader = new StringReader("<docroot>" + xmlDocumentation + "</docroot>");
var summaryText = new StringBuilder();
var typeParamElements = new List<DocumentationItemBuilder>();
var paramElements = new List<DocumentationItemBuilder>();
var returnsText = new StringBuilder();
var remarksText = new StringBuilder();
var exampleText = new StringBuilder();
var valueText = new StringBuilder();
var exception = new List<DocumentationItemBuilder>();
using (var xml = XmlReader.Create(reader))
{
try
{
xml.Read();
string? elementName = null;
StringBuilder? currentSectionBuilder = null;
do
{
if (xml.NodeType == XmlNodeType.Element)
{
elementName = xml.Name.ToLowerInvariant();
switch (elementName)
{
case "filterpriority":
xml.Skip();
break;
case "remarks":
currentSectionBuilder = remarksText;
break;
case "example":
currentSectionBuilder = exampleText;
break;
case "exception":
DocumentationItemBuilder exceptionInstance = new(GetCref(xml["cref"]).TrimEnd());
currentSectionBuilder = exceptionInstance.Documentation;
exception.Add(exceptionInstance);
break;
case "returns":
currentSectionBuilder = returnsText;
break;
case "summary":
currentSectionBuilder = summaryText;
break;
case "see":
if (currentSectionBuilder is null) continue;
currentSectionBuilder.Append(GetCref(xml["cref"]));
currentSectionBuilder.Append(xml["langword"]);
break;
case "seealso":
if (currentSectionBuilder is null) continue;
currentSectionBuilder.Append("See also: ");
currentSectionBuilder.Append(GetCref(xml["cref"]));
break;
case "paramref":
if (currentSectionBuilder is null) continue;
currentSectionBuilder.Append(xml["name"]);
currentSectionBuilder.Append(' ');
break;
case "param":
DocumentationItemBuilder paramInstance = new(TrimMultiLineString(xml["name"] ?? "", lineEnding));
currentSectionBuilder = paramInstance.Documentation;
paramElements.Add(paramInstance);
break;
case "typeparamref":
if (currentSectionBuilder is null) continue;
currentSectionBuilder.Append(xml["name"]);
currentSectionBuilder.Append(' ');
break;
case "typeparam":
DocumentationItemBuilder typeParamInstance = new(TrimMultiLineString(xml["name"] ?? "", lineEnding));
currentSectionBuilder = typeParamInstance.Documentation;
typeParamElements.Add(typeParamInstance);
break;
case "value":
currentSectionBuilder = valueText;
break;
case "br":
case "para":
if (currentSectionBuilder is null) continue;
currentSectionBuilder.Append(lineEnding);
break;
}
}
else if (xml.NodeType == XmlNodeType.Text && currentSectionBuilder != null)
{
if (elementName == "code")
{
currentSectionBuilder.Append(xml.Value);
}
else
{
currentSectionBuilder.Append(TrimMultiLineString(xml.Value, lineEnding));
}
}
} while (xml.Read());
}
catch (Exception)
{
return null;
}
}
return new DocumentationComment(
summaryText.ToString(),
typeParamElements.Select(s => s.ConvertToDocumentedObject()).ToArray(),
paramElements.Select(s => s.ConvertToDocumentedObject()).ToArray(),
returnsText.ToString(),
remarksText.ToString(),
exampleText.ToString(),
valueText.ToString(),
exception.Select(s => s.ConvertToDocumentedObject()).ToArray());
}
private static string TrimMultiLineString(string input, string lineEnding)
{
var lines = input.Split(separator, StringSplitOptions.RemoveEmptyEntries);
return string.Join(lineEnding, lines.Select(l => TrimStartRetainingSingleLeadingSpace(l)));
}
private static string GetCref(string? cref)
{
if (cref == null || cref.Trim().Length == 0)
{
return "";
}
if (cref.Length < 2)
{
return cref;
}
if (cref.Substring(1, 1) == ":")
{
return string.Concat(cref.AsSpan(2, cref.Length - 2), " ");
}
return cref + " ";
}
private static string TrimStartRetainingSingleLeadingSpace(string input)
{
if (string.IsNullOrWhiteSpace(input))
return string.Empty;
if (!char.IsWhiteSpace(input[0]))
return input;
return $" {input.TrimStart()}";
}
public string GetParameterText(string name)
=> Array.Find(ParamElements, parameter => parameter.Name == name)?.Documentation ?? string.Empty;
public string GetTypeParameterText(string name)
=> Array.Find(TypeParamElements, typeParam => typeParam.Name == name)?.Documentation ?? string.Empty;
public static readonly DocumentationComment Empty = new();
private static readonly string[] separator = ["\n", "\r\n"];
}
class DocumentationItemBuilder(string name)
{
public string Name { get; set; } = name;
public StringBuilder Documentation { get; set; } = new StringBuilder();
public DocumentationItem ConvertToDocumentedObject()
{
return new DocumentationItem(Name, Documentation.ToString());
}
}