431 lines
16 KiB
C#
431 lines
16 KiB
C#
using NetDocsForLLM.Models;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Xml;
|
|
using System.Xml.Linq;
|
|
|
|
namespace NetDocsForLLM.Services
|
|
{
|
|
public class XmlDocGenerator : IDocFxService
|
|
{
|
|
private readonly string _workingDirectory;
|
|
private TextWriter _logWriter;
|
|
|
|
public XmlDocGenerator()
|
|
{
|
|
// Crear un directorio temporal para trabajar
|
|
_workingDirectory = Path.Combine(Path.GetTempPath(), "NetDocsForLLM_" + Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(_workingDirectory);
|
|
}
|
|
|
|
public async Task<string> GenerateMetadataAsync(IEnumerable<AssemblyModel> assemblies)
|
|
{
|
|
// Crear directorio para metadatos
|
|
var metadataPath = Path.Combine(_workingDirectory, "metadata");
|
|
Directory.CreateDirectory(metadataPath);
|
|
|
|
// Crear archivo de log
|
|
var logPath = Path.Combine(metadataPath, "analysis_log.txt");
|
|
using (_logWriter = new StreamWriter(logPath, false))
|
|
{
|
|
_logWriter.WriteLine($"Iniciando generación de documentación XML: {DateTime.Now}");
|
|
|
|
// Para cada ensamblado, generar documentación XML
|
|
foreach (var assembly in assemblies)
|
|
{
|
|
await Task.Run(() => GenerateXmlDocumentation(assembly, metadataPath));
|
|
}
|
|
}
|
|
|
|
return metadataPath;
|
|
}
|
|
|
|
private void GenerateXmlDocumentation(AssemblyModel assemblyModel, string outputPath)
|
|
{
|
|
try
|
|
{
|
|
Assembly assembly = assemblyModel.LoadedAssembly;
|
|
if (assembly == null)
|
|
{
|
|
_logWriter.WriteLine($"ERROR: El ensamblado {assemblyModel.Name} no está cargado");
|
|
return;
|
|
}
|
|
|
|
_logWriter.WriteLine($"\n=== Procesando ensamblado: {assemblyModel.Name} ===");
|
|
|
|
// Crear el documento XML
|
|
XDocument doc = new XDocument(
|
|
new XDeclaration("1.0", "utf-8", null),
|
|
new XElement("doc",
|
|
new XElement("assembly",
|
|
new XElement("name", assembly.GetName().Name)
|
|
),
|
|
new XElement("members")
|
|
)
|
|
);
|
|
|
|
// Obtener el elemento members para agregar miembros
|
|
var membersElement = doc.Root.Element("members");
|
|
|
|
// Procesar todos los tipos exportados
|
|
foreach (var type in assembly.GetExportedTypes().Where(t => t.IsPublic))
|
|
{
|
|
try
|
|
{
|
|
// Generar documentación para el tipo
|
|
GenerateTypeDocumentation(type, membersElement);
|
|
|
|
// Generar documentación para métodos
|
|
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)
|
|
.Where(m => !m.IsSpecialName))
|
|
{
|
|
GenerateMethodDocumentation(method, membersElement);
|
|
}
|
|
|
|
// Generar documentación para propiedades
|
|
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
|
|
{
|
|
GeneratePropertyDocumentation(property, membersElement);
|
|
}
|
|
|
|
// Generar documentación para eventos
|
|
foreach (var eventInfo in type.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
|
|
{
|
|
GenerateEventDocumentation(eventInfo, membersElement);
|
|
}
|
|
|
|
// Generar documentación para campos
|
|
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
|
|
{
|
|
GenerateFieldDocumentation(field, membersElement);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logWriter.WriteLine($"Error procesando tipo {type.FullName}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
// Guardar la documentación XML
|
|
string xmlFileName = $"{assembly.GetName().Name}.xml";
|
|
string xmlFilePath = Path.Combine(outputPath, xmlFileName);
|
|
|
|
// Guardar con formato
|
|
using (var writer = new XmlTextWriter(xmlFilePath, Encoding.UTF8))
|
|
{
|
|
writer.Formatting = Formatting.Indented;
|
|
writer.Indentation = 4;
|
|
doc.Save(writer);
|
|
}
|
|
|
|
_logWriter.WriteLine($"Documentación XML guardada en: {xmlFilePath}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logWriter.WriteLine($"ERROR general procesando ensamblado {assemblyModel.Name}: {ex.Message}");
|
|
_logWriter.WriteLine(ex.StackTrace);
|
|
}
|
|
}
|
|
|
|
private void GenerateTypeDocumentation(Type type, XElement membersElement)
|
|
{
|
|
string typeId = $"T:{type.FullName}";
|
|
var memberElement = new XElement("member", new XAttribute("name", typeId));
|
|
|
|
// Agregar summary
|
|
memberElement.Add(new XElement("summary",
|
|
$"Represents a {GetTypeKindDescription(type)}: {type.Name}"));
|
|
|
|
// Si es una clase que hereda de otra (excepto Object), agregar esa información
|
|
if (type.IsClass && type.BaseType != null && type.BaseType != typeof(object))
|
|
{
|
|
memberElement.Add(new XElement("remarks",
|
|
$"Inherits from {type.BaseType.Name}"));
|
|
}
|
|
|
|
// Si implementa interfaces, mencionarlas
|
|
var interfaces = type.GetInterfaces();
|
|
if (interfaces.Length > 0)
|
|
{
|
|
var implementsText = $"Implements: {string.Join(", ", interfaces.Select(i => i.Name))}";
|
|
|
|
if (memberElement.Element("remarks") != null)
|
|
{
|
|
memberElement.Element("remarks").Value += $"\n{implementsText}";
|
|
}
|
|
else
|
|
{
|
|
memberElement.Add(new XElement("remarks", implementsText));
|
|
}
|
|
}
|
|
|
|
membersElement.Add(memberElement);
|
|
}
|
|
|
|
private void GenerateMethodDocumentation(MethodInfo method, XElement membersElement)
|
|
{
|
|
try
|
|
{
|
|
string methodId = GetMethodXmlId(method);
|
|
var memberElement = new XElement("member", new XAttribute("name", methodId));
|
|
|
|
// Agregar summary básico
|
|
memberElement.Add(new XElement("summary",
|
|
$"{method.Name} {GetMethodDescription(method)}"));
|
|
|
|
// Agregar parámetros
|
|
foreach (var param in method.GetParameters())
|
|
{
|
|
memberElement.Add(new XElement("param",
|
|
new XAttribute("name", param.Name),
|
|
GetParameterDescription(param)));
|
|
}
|
|
|
|
// Agregar returns si no es void
|
|
if (method.ReturnType != typeof(void))
|
|
{
|
|
memberElement.Add(new XElement("returns",
|
|
$"A {GetSimpleTypeName(method.ReturnType)} value."));
|
|
}
|
|
|
|
membersElement.Add(memberElement);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logWriter.WriteLine($"Error generando documentación para método {method.Name}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void GeneratePropertyDocumentation(PropertyInfo property, XElement membersElement)
|
|
{
|
|
try
|
|
{
|
|
string propertyId = $"P:{property.DeclaringType.FullName}.{property.Name}";
|
|
var memberElement = new XElement("member", new XAttribute("name", propertyId));
|
|
|
|
// Accesibilidad (get/set)
|
|
string access = "";
|
|
if (property.CanRead) access += "get; ";
|
|
if (property.CanWrite) access += "set; ";
|
|
|
|
// Agregar summary
|
|
memberElement.Add(new XElement("summary",
|
|
$"Gets{(property.CanWrite ? " or sets" : "")} the {property.Name} property."));
|
|
|
|
// Agregar valor
|
|
memberElement.Add(new XElement("value",
|
|
$"A {GetSimpleTypeName(property.PropertyType)} representing the {property.Name}."));
|
|
|
|
membersElement.Add(memberElement);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logWriter.WriteLine($"Error generando documentación para propiedad {property.Name}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void GenerateEventDocumentation(EventInfo eventInfo, XElement membersElement)
|
|
{
|
|
try
|
|
{
|
|
string eventId = $"E:{eventInfo.DeclaringType.FullName}.{eventInfo.Name}";
|
|
var memberElement = new XElement("member", new XAttribute("name", eventId));
|
|
|
|
// Agregar summary
|
|
memberElement.Add(new XElement("summary",
|
|
$"Occurs when {eventInfo.Name} is raised."));
|
|
|
|
// Agregar remarks con el tipo del handler
|
|
memberElement.Add(new XElement("remarks",
|
|
$"Event handler type: {GetSimpleTypeName(eventInfo.EventHandlerType)}"));
|
|
|
|
membersElement.Add(memberElement);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logWriter.WriteLine($"Error generando documentación para evento {eventInfo.Name}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void GenerateFieldDocumentation(FieldInfo field, XElement membersElement)
|
|
{
|
|
try
|
|
{
|
|
string fieldId = $"F:{field.DeclaringType.FullName}.{field.Name}";
|
|
var memberElement = new XElement("member", new XAttribute("name", fieldId));
|
|
|
|
// Modificadores
|
|
string modifiers = "";
|
|
if (field.IsStatic) modifiers += "static ";
|
|
if (field.IsInitOnly) modifiers += "readonly ";
|
|
if (field.IsLiteral) modifiers += "constant ";
|
|
|
|
// Agregar summary
|
|
memberElement.Add(new XElement("summary",
|
|
$"Represents the {modifiers}{field.Name} field."));
|
|
|
|
membersElement.Add(memberElement);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logWriter.WriteLine($"Error generando documentación para campo {field.Name}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
// Métodos auxiliares
|
|
|
|
private string GetMethodXmlId(MethodInfo method)
|
|
{
|
|
var parameters = method.GetParameters();
|
|
if (parameters.Length == 0)
|
|
{
|
|
return $"M:{method.DeclaringType.FullName}.{method.Name}";
|
|
}
|
|
|
|
var paramTypes = string.Join(",", parameters.Select(p => GetParameterTypeName(p.ParameterType)));
|
|
return $"M:{method.DeclaringType.FullName}.{method.Name}({paramTypes})";
|
|
}
|
|
|
|
private string GetParameterTypeName(Type type)
|
|
{
|
|
// El formato de los parámetros en los IDs XML de documentación es específico
|
|
|
|
// Arrays
|
|
if (type.IsArray)
|
|
{
|
|
return $"{GetParameterTypeName(type.GetElementType())}[]";
|
|
}
|
|
|
|
// Genéricos
|
|
if (type.IsGenericType)
|
|
{
|
|
var genericTypeDef = type.GetGenericTypeDefinition();
|
|
var genericArgs = string.Join(",", type.GetGenericArguments().Select(GetParameterTypeName));
|
|
|
|
// Para tipos como List<T>, devolver algo como System.Collections.Generic.List{T}
|
|
if (genericTypeDef == typeof(System.Collections.Generic.List<>))
|
|
{
|
|
return $"System.Collections.Generic.List{{{genericArgs}}}";
|
|
}
|
|
|
|
// Para otros tipos genéricos, usar el nombre completo con {}
|
|
return $"{genericTypeDef.FullName.Split('`')[0]}{{{genericArgs}}}";
|
|
}
|
|
|
|
// Para tipos normales, usar el nombre completo
|
|
return type.FullName;
|
|
}
|
|
|
|
private string GetTypeKindDescription(Type type)
|
|
{
|
|
if (type.IsEnum) return "enumeration";
|
|
if (type.IsInterface) return "interface";
|
|
if (type.IsValueType && !type.IsPrimitive) return "structure";
|
|
if (type.IsClass)
|
|
{
|
|
if (type.IsAbstract && type.IsSealed) return "static class";
|
|
if (type.IsAbstract) return "abstract class";
|
|
return "class";
|
|
}
|
|
return "type";
|
|
}
|
|
|
|
private string GetMethodDescription(MethodInfo method)
|
|
{
|
|
// Crear una descripción básica basada en el nombre y parámetros
|
|
var parameters = method.GetParameters();
|
|
|
|
if (parameters.Length == 0)
|
|
{
|
|
if (method.ReturnType == typeof(void))
|
|
{
|
|
return "performs an operation.";
|
|
}
|
|
return $"gets a {GetSimpleTypeName(method.ReturnType)} value.";
|
|
}
|
|
|
|
return $"with {parameters.Length} parameter{(parameters.Length > 1 ? "s" : "")}.";
|
|
}
|
|
|
|
private string GetParameterDescription(ParameterInfo param)
|
|
{
|
|
string desc = $"A {GetSimpleTypeName(param.ParameterType)}";
|
|
|
|
if (param.IsOptional)
|
|
{
|
|
object defaultValue = param.DefaultValue;
|
|
string defaultValueStr = defaultValue?.ToString() ?? "null";
|
|
|
|
if (param.ParameterType == typeof(string) && defaultValue != null)
|
|
{
|
|
defaultValueStr = $"\"{defaultValueStr}\"";
|
|
}
|
|
|
|
desc += $" (Optional, defaults to {defaultValueStr})";
|
|
}
|
|
|
|
if (param.IsOut)
|
|
{
|
|
desc += " (Output)";
|
|
}
|
|
else if (param.ParameterType.IsByRef)
|
|
{
|
|
desc += " (Reference)";
|
|
}
|
|
|
|
return desc;
|
|
}
|
|
|
|
private string GetSimpleTypeName(Type type)
|
|
{
|
|
if (type == null) return "void";
|
|
|
|
// Si es un tipo por referencia (como out o ref parámetros)
|
|
if (type.IsByRef)
|
|
{
|
|
return GetSimpleTypeName(type.GetElementType());
|
|
}
|
|
|
|
// Tipos primitivos
|
|
if (type == typeof(void)) return "void";
|
|
if (type == typeof(int)) return "int";
|
|
if (type == typeof(string)) return "string";
|
|
if (type == typeof(bool)) return "bool";
|
|
if (type == typeof(double)) return "double";
|
|
if (type == typeof(float)) return "float";
|
|
if (type == typeof(decimal)) return "decimal";
|
|
if (type == typeof(object)) return "object";
|
|
|
|
// Arrays
|
|
if (type.IsArray)
|
|
{
|
|
return $"{GetSimpleTypeName(type.GetElementType())}[]";
|
|
}
|
|
|
|
// Genéricos
|
|
if (type.IsGenericType)
|
|
{
|
|
string baseName = type.Name;
|
|
int tickIndex = baseName.IndexOf('`');
|
|
if (tickIndex > 0)
|
|
{
|
|
baseName = baseName.Substring(0, tickIndex);
|
|
}
|
|
|
|
var argTypes = type.GetGenericArguments()
|
|
.Select(GetSimpleTypeName)
|
|
.ToArray();
|
|
|
|
return $"{baseName}<{string.Join(", ", argTypes)}>";
|
|
}
|
|
|
|
// Para tipos normales, usar solo el nombre simple
|
|
return type.Name;
|
|
}
|
|
}
|
|
} |