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 GenerateMetadataAsync(IEnumerable 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, 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; } } }