Primera version funcionante

This commit is contained in:
Miguel 2025-03-26 12:22:05 +01:00
parent 641bb7baf1
commit ebbee5414d
7 changed files with 1383 additions and 605 deletions

View File

@ -20,7 +20,7 @@ namespace NetDocsForLLM
private void ConfigureServices(ServiceCollection services)
{
// Register services
services.AddSingleton<IDocFxService, ReflectionAnalyzerService>(); // <- Cambio aquí
services.AddSingleton<IDocFxService, XmlDocGenerator>(); // Usar el generador XML
services.AddSingleton<IDocumentationGenerator, DocumentationGenerator>();
services.AddSingleton<IAssemblyAnalyzer, AssemblyAnalyzer>();

View File

@ -162,21 +162,16 @@
<Button Content="Copiar al portapapeles"
Command="{Binding CopyToClipboardCommand}"
Padding="10,5"
Margin="0,0,10,0"/>
Margin="0,0,10,0"
IsEnabled="{Binding HasGeneratedDocumentation}"/>
<Button Content="Exportar..."
Command="{Binding ExportDocumentationCommand}"
Padding="10,5"/>
Padding="10,5"
IsEnabled="{Binding HasGeneratedDocumentation}"/>
</StackPanel>
<xctk:PropertyGrid Grid.Row="1"
SelectedObject="{Binding DocumentationModel}"
AutoGenerateProperties="False">
<xctk:PropertyGrid.PropertyDefinitions>
<xctk:PropertyDefinition Name="Namespaces" DisplayName="Espacios de nombres"/>
<!-- Más definiciones según la estructura del modelo -->
</xctk:PropertyGrid.PropertyDefinitions>
</xctk:PropertyGrid>
<!-- Eliminamos el PropertyGrid que estaba en conflicto con el Border -->
<Border Grid.Row="1" Background="#f5f5f5" BorderBrush="#ddd" BorderThickness="1">
<TextBox Text="{Binding DocumentationPreview, Mode=OneWay}"

View File

@ -1,343 +0,0 @@
using NetDocsForLLM.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace NetDocsForLLM.Services
{
/// <summary>
/// Implementación del servicio de análisis de ensamblados basado en Reflection
/// </summary>
public class ReflectionAnalyzerService : IDocFxService
{
private readonly string _workingDirectory;
public ReflectionAnalyzerService()
{
// 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);
// Para cada ensamblado, generar archivo de metadatos
foreach (var assembly in assemblies)
{
await Task.Run(() => ProcessAssembly(assembly, metadataPath));
}
return metadataPath;
}
private void ProcessAssembly(AssemblyModel assemblyModel, string outputPath)
{
try
{
var assembly = assemblyModel.LoadedAssembly;
if (assembly == null)
throw new ArgumentException("El ensamblado no está cargado", nameof(assemblyModel));
// Cargar comentarios XML si existen
XDocument xmlDoc = null;
if (assemblyModel.HasXmlDocumentation && File.Exists(assemblyModel.XmlDocPath))
{
try
{
xmlDoc = XDocument.Load(assemblyModel.XmlDocPath);
}
catch (Exception)
{
// Si hay error al cargar XML, continuar sin comentarios
}
}
// Procesar todos los tipos exportados
foreach (var type in assembly.GetExportedTypes())
{
try
{
// Generar archivo de metadatos para cada tipo
var typeInfo = new
{
Name = type.Name,
FullName = type.FullName,
Namespace = type.Namespace,
IsClass = type.IsClass,
IsInterface = type.IsInterface,
IsEnum = type.IsEnum,
IsAbstract = type.IsAbstract,
IsSealed = type.IsSealed,
IsPublic = type.IsPublic,
BaseType = type.BaseType?.FullName,
Interfaces = type.GetInterfaces().Select(i => i.FullName).ToArray(),
XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(type)),
Members = GetMembers(type, xmlDoc)
};
// Guardar en archivo JSON
string typePath = Path.Combine(outputPath, $"{type.FullName.Replace('+', '_')}.json");
File.WriteAllText(typePath, JsonConvert.SerializeObject(typeInfo, Formatting.Indented));
}
catch (Exception ex)
{
// Registrar error para este tipo pero continuar con otros
File.WriteAllText(
Path.Combine(outputPath, $"error_{type.FullName.Replace('+', '_')}.txt"),
$"Error procesando tipo {type.FullName}: {ex.Message}\n{ex.StackTrace}");
}
}
}
catch (Exception ex)
{
// Registrar el error pero continuar con otros ensamblados
File.WriteAllText(
Path.Combine(outputPath, $"error_{Path.GetFileNameWithoutExtension(assemblyModel.FilePath)}.txt"),
$"Error procesando ensamblado: {ex.Message}\n{ex.StackTrace}");
}
}
private object[] GetMembers(Type type, XDocument xmlDoc)
{
var result = new List<object>();
// Obtener métodos
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
try
{
if (method.IsSpecialName) // Excluir getters/setters de propiedades
continue;
var parameters = method.GetParameters().Select(p => new
{
Name = p.Name,
Type = GetFriendlyTypeName(p.ParameterType),
IsOptional = p.IsOptional,
DefaultValue = p.IsOptional ? ConvertDefaultValueToString(p.DefaultValue) : null
}).ToArray();
var methodInfo = new
{
Name = method.Name,
MemberType = "Method",
ReturnType = GetFriendlyTypeName(method.ReturnType),
IsStatic = method.IsStatic,
IsPublic = method.IsPublic,
Parameters = parameters,
XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(method))
};
result.Add(methodInfo);
}
catch
{
// Ignorar métodos con problemas
continue;
}
}
// Obtener propiedades
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
try
{
var propertyInfo = new
{
Name = property.Name,
MemberType = "Property",
Type = GetFriendlyTypeName(property.PropertyType),
CanRead = property.CanRead,
CanWrite = property.CanWrite,
IsStatic = property.GetAccessors(true).FirstOrDefault()?.IsStatic ?? false,
XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(property))
};
result.Add(propertyInfo);
}
catch
{
// Ignorar propiedades con problemas
continue;
}
}
// Obtener eventos
foreach (var eventInfo in type.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
try
{
var eventData = new
{
Name = eventInfo.Name,
MemberType = "Event",
EventHandlerType = GetFriendlyTypeName(eventInfo.EventHandlerType),
IsStatic = eventInfo.GetAddMethod()?.IsStatic ?? false,
XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(eventInfo))
};
result.Add(eventData);
}
catch
{
// Ignorar eventos con problemas
continue;
}
}
// Obtener campos
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
try
{
var fieldInfo = new
{
Name = field.Name,
MemberType = "Field",
Type = GetFriendlyTypeName(field.FieldType),
IsStatic = field.IsStatic,
IsConstant = field.IsLiteral && !field.IsInitOnly,
Value = field.IsLiteral ? ConvertDefaultValueToString(field.GetValue(null)) : null,
XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(field))
};
result.Add(fieldInfo);
}
catch
{
// Ignorar campos con problemas
continue;
}
}
return result.ToArray();
}
private string GetFriendlyTypeName(Type type)
{
if (type == null) return "void";
if (type.IsGenericType)
{
var genericArgs = string.Join(", ", type.GetGenericArguments().Select(GetFriendlyTypeName));
return $"{type.Name.Split('`')[0]}<{genericArgs}>";
}
// Usar nombres simplificados para tipos comunes
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(long)) return "long";
if (type == typeof(short)) return "short";
if (type == typeof(byte)) return "byte";
if (type == typeof(char)) return "char";
if (type == typeof(object)) return "object";
return type.FullName ?? type.Name;
}
private string ConvertDefaultValueToString(object defaultValue)
{
if (defaultValue == null) return "null";
// Para strings, agregar comillas
if (defaultValue is string stringValue)
return $"\"{stringValue}\"";
return defaultValue.ToString();
}
private string GetMemberXmlId(MemberInfo member)
{
// Generar ID XML según el formato estándar de documentación XML
string prefix = "";
if (member is MethodInfo)
prefix = "M:";
else if (member is PropertyInfo)
prefix = "P:";
else if (member is EventInfo)
prefix = "E:";
else if (member is FieldInfo)
prefix = "F:";
else if (member is Type)
prefix = "T:";
// Manejar tipos anidados
string declaringFullName;
if (member.DeclaringType != null)
{
declaringFullName = member.DeclaringType.FullName;
}
else if (member is Type typeInfo)
{
declaringFullName = typeInfo.FullName;
}
else
{
declaringFullName = "Unknown";
}
return prefix + declaringFullName + "." + member.Name;
}
private object GetXmlDocumentation(XDocument xmlDoc, string memberId)
{
if (xmlDoc == null)
return null;
try
{
// Buscar nodo de miembro por ID
var memberNode = xmlDoc.Descendants("member")
.FirstOrDefault(m => m.Attribute("name")?.Value == memberId);
if (memberNode == null)
return null;
// Extraer elementos de documentación
var summary = memberNode.Element("summary")?.Value.Trim();
var remarks = memberNode.Element("remarks")?.Value.Trim();
var returns = memberNode.Element("returns")?.Value.Trim();
var parameters = memberNode.Elements("param")
.Select(p => new
{
Name = p.Attribute("name")?.Value,
Description = p.Value.Trim()
})
.Where(p => !string.IsNullOrEmpty(p.Name))
.ToArray();
var examples = memberNode.Elements("example")
.Select(e => e.Value.Trim())
.ToArray();
return new
{
Summary = summary,
Remarks = remarks,
Returns = returns,
Parameters = parameters,
Examples = examples
};
}
catch
{
return null;
}
}
}
}

View File

@ -5,16 +5,12 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using Formatting = Newtonsoft.Json.Formatting;
namespace NetDocsForLLM.Services
{
public interface IDocumentationGenerator
{
Task<DocumentationModel> GenerateDocumentation(IEnumerable<AssemblyModel> assemblies, ExportSettings settings);
string GenerateDocumentationPreview(DocumentationModel documentation, ExportSettings settings);
}
public class DocumentationGenerator : IDocumentationGenerator
{
private readonly IDocFxService _metadataService;
@ -28,21 +24,18 @@ namespace NetDocsForLLM.Services
{
try
{
// Generate metadata using reflection service
// Generate metadata using XmlDocGenerator
var metadataPath = await _metadataService.GenerateMetadataAsync(assemblies);
// Process metadata files
// Create a new documentation model
var documentation = new DocumentationModel();
var namespaces = new Dictionary<string, NamespaceDocumentation>();
// Read JSON files produced by our reflection service
var jsonFiles = Directory.GetFiles(metadataPath, "*.json", SearchOption.AllDirectories);
foreach (var jsonFile in jsonFiles)
// Process XML files produced by our XmlDocGenerator
var xmlFiles = Directory.GetFiles(metadataPath, "*.xml");
foreach (var xmlFile in xmlFiles)
{
if (Path.GetFileName(jsonFile).StartsWith("error_"))
continue; // Skip error files
ProcessJsonMetadata(jsonFile, documentation, namespaces, settings);
ProcessXmlDocumentation(xmlFile, documentation, namespaces, settings);
}
return documentation;
@ -53,108 +46,136 @@ namespace NetDocsForLLM.Services
}
}
private void ProcessJsonMetadata(string jsonFile, DocumentationModel documentation,
Dictionary<string, NamespaceDocumentation> namespaces,
ExportSettings settings)
private void ProcessXmlDocumentation(string xmlFile, DocumentationModel documentation,
Dictionary<string, NamespaceDocumentation> namespaces,
ExportSettings settings)
{
try
{
// Leer el archivo JSON
var jsonContent = File.ReadAllText(jsonFile);
var typeData = JsonConvert.DeserializeObject<dynamic>(jsonContent);
// Cargar el archivo XML
var xmlDoc = XDocument.Load(xmlFile);
if (typeData == null)
// Obtener el nombre del ensamblado
string assemblyName = xmlDoc.Root.Element("assembly")?.Element("name")?.Value;
if (string.IsNullOrEmpty(assemblyName))
return;
// Obtener namespace
string namespaceName = typeData.Namespace?.ToString() ?? "Global";
// Leer todos los miembros
var memberElements = xmlDoc.Root.Element("members")?.Elements("member");
if (memberElements == null)
return;
// Buscar o crear el namespace en nuestra documentación
// Procesar los tipos primero (para la estructura jerárquica)
var typeMembers = memberElements.Where(m =>
m.Attribute("name")?.Value?.StartsWith("T:") == true);
foreach (var typeMember in typeMembers)
{
ProcessTypeMember(typeMember, documentation, namespaces);
}
// Luego procesar miembros de tipos
var nonTypeMembers = memberElements.Where(m =>
m.Attribute("name")?.Value?.StartsWith("T:") == false);
foreach (var member in nonTypeMembers)
{
ProcessMember(member, documentation, namespaces, settings);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error procesando archivo XML {xmlFile}: {ex.Message}");
}
}
private void ProcessTypeMember(XElement typeMember, DocumentationModel documentation,
Dictionary<string, NamespaceDocumentation> namespaces)
{
try
{
string typeId = typeMember.Attribute("name")?.Value;
if (string.IsNullOrEmpty(typeId) || !typeId.StartsWith("T:"))
return;
// Extraer el nombre completo del tipo
string fullTypeName = typeId.Substring(2); // Quitar "T:"
// Obtener namespace y nombre del tipo
string namespaceName = "Global";
string typeName = fullTypeName;
int lastDotPos = fullTypeName.LastIndexOf('.');
if (lastDotPos > 0)
{
namespaceName = fullTypeName.Substring(0, lastDotPos);
typeName = fullTypeName.Substring(lastDotPos + 1);
}
// Buscar o crear el namespace
if (!namespaces.TryGetValue(namespaceName, out var namespaceDoc))
{
namespaceDoc = new NamespaceDocumentation
{
Name = namespaceName,
Description = $"Contiene tipos del ensamblado"
Description = $"Contains types from the assembly"
};
namespaces[namespaceName] = namespaceDoc;
documentation.Namespaces.Add(namespaceDoc);
}
// Obtener información del tipo desde XML
string description = typeMember.Element("summary")?.Value?.Trim() ?? "No description available";
string remarks = typeMember.Element("remarks")?.Value?.Trim();
// Determinar el tipo de tipo
string typeKind = "Class";
if (description.Contains("interface:"))
typeKind = "Interface";
else if (description.Contains("structure:"))
typeKind = "Struct";
else if (description.Contains("enumeration:"))
typeKind = "Enum";
else if (description.Contains("abstract class:"))
typeKind = "Abstract Class";
else if (description.Contains("static class:"))
typeKind = "Static Class";
// Crear documentación de tipo
var typeDoc = new TypeDocumentation
{
Name = typeData.Name,
FullName = typeData.FullName,
Description = typeData.XmlDocumentation?.Summary ?? "Sin documentación disponible",
TypeKind = GetTypeKind(typeData)
Name = typeName,
FullName = fullTypeName,
Description = description,
TypeKind = typeKind
};
// Agregar base types e interfaces si están disponibles
if (typeData.BaseType != null && typeData.BaseType.ToString() != "System.Object")
// Procesar herencia e interfaces si están disponibles en remarks
if (!string.IsNullOrEmpty(remarks))
{
typeDoc.BaseTypes.Add(typeData.BaseType.ToString());
}
// Agregar interfaces
if (typeData.Interfaces != null)
{
foreach (var interfaceType in typeData.Interfaces)
if (remarks.Contains("Inherits from"))
{
typeDoc.Interfaces.Add(interfaceType.ToString());
int start = remarks.IndexOf("Inherits from") + "Inherits from".Length;
int end = remarks.IndexOf("\n", start);
if (end < 0) end = remarks.Length;
string baseTypeName = remarks.Substring(start, end - start).Trim();
typeDoc.BaseTypes.Add(baseTypeName);
}
}
// Procesar miembros
if (typeData.Members != null)
{
foreach (var member in typeData.Members)
if (remarks.Contains("Implements:"))
{
// Filtrar miembros privados si la configuración lo indica
if (!settings.IncludePrivateMembers &&
member.IsPublic != null && !(bool)member.IsPublic)
continue;
int start = remarks.IndexOf("Implements:") + "Implements:".Length;
int end = remarks.IndexOf("\n", start);
if (end < 0) end = remarks.Length;
var memberDoc = new MemberDocumentation
string interfacesStr = remarks.Substring(start, end - start).Trim();
string[] interfaces = interfacesStr.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var interfaceName in interfaces)
{
Name = member.Name,
Description = member.XmlDocumentation?.Summary ?? "Sin documentación disponible",
MemberType = member.MemberType,
Signature = member.Signature ?? GenerateSignature(member),
ReturnType = GetReturnType(member),
ReturnDescription = member.XmlDocumentation?.Returns ?? ""
};
// Agregar parámetros si es un método
if (member.MemberType?.ToString() == "Method" && member.Parameters != null)
{
foreach (var param in member.Parameters)
{
var paramDoc = new ParameterDocumentation
{
Name = param.Name,
Type = param.Type,
IsOptional = param.IsOptional != null && (bool)param.IsOptional,
DefaultValue = param.DefaultValue?.ToString() ?? "",
Description = GetParameterDescription(member.XmlDocumentation, param.Name?.ToString())
};
memberDoc.Parameters.Add(paramDoc);
}
typeDoc.Interfaces.Add(interfaceName.Trim());
}
// Agregar ejemplos si están disponibles y configurados
if (settings.IncludeExamples &&
member.XmlDocumentation?.Examples != null)
{
foreach (var example in member.XmlDocumentation.Examples)
{
if (example != null && !string.IsNullOrWhiteSpace(example.ToString()))
memberDoc.Examples.Add(example.ToString());
}
}
typeDoc.Members.Add(memberDoc);
}
}
@ -162,102 +183,197 @@ namespace NetDocsForLLM.Services
}
catch (Exception ex)
{
// Si hay error al procesar, lo registramos pero continuamos
Console.WriteLine($"Error al procesar {jsonFile}: {ex.Message}");
Console.WriteLine($"Error procesando tipo: {ex.Message}");
}
}
private string GetTypeKind(dynamic typeData)
private void ProcessMember(XElement memberElement, DocumentationModel documentation,
Dictionary<string, NamespaceDocumentation> namespaces,
ExportSettings settings)
{
if (typeData.IsClass != null && (bool)typeData.IsClass)
return "Class";
if (typeData.IsInterface != null && (bool)typeData.IsInterface)
return "Interface";
if (typeData.IsEnum != null && (bool)typeData.IsEnum)
return "Enum";
return "Type";
}
private string GenerateSignature(dynamic member)
{
// Generar una firma simple basada en el tipo de miembro
string memberType = member.MemberType?.ToString();
string name = member.Name?.ToString() ?? "Unknown";
if (memberType == "Method")
try
{
string returnType = member.ReturnType?.ToString() ?? "void";
string parameters = "";
string memberId = memberElement.Attribute("name")?.Value;
if (string.IsNullOrEmpty(memberId))
return;
if (member.Parameters != null)
// Determinar el tipo de miembro
char memberTypeChar = memberId[0];
if (memberId.Length < 3 || memberId[1] != ':')
return;
string memberType;
switch (memberTypeChar)
{
var paramList = new List<string>();
foreach (var param in member.Parameters)
{
string paramType = param.Type?.ToString() ?? "object";
string paramName = param.Name?.ToString() ?? "param";
paramList.Add($"{paramType} {paramName}");
}
parameters = string.Join(", ", paramList);
case 'M': memberType = "Method"; break;
case 'P': memberType = "Property"; break;
case 'F': memberType = "Field"; break;
case 'E': memberType = "Event"; break;
default: return; // No es un miembro que procesamos
}
return $"{returnType} {name}({parameters})";
// Extraer información del tipo al que pertenece el miembro
string fullMemberId = memberId.Substring(2); // Quitar "X:"
int lastDotPos = fullMemberId.LastIndexOf('.');
if (lastDotPos <= 0)
return;
string fullTypeName = fullMemberId.Substring(0, lastDotPos);
string memberName = fullMemberId.Substring(lastDotPos + 1);
// Para métodos con parámetros, extraer el nombre real
if (memberType == "Method" && memberName.Contains("("))
{
memberName = memberName.Substring(0, memberName.IndexOf('('));
}
// Encontrar el namespace y tipo
string namespaceName = "Global";
string typeName = fullTypeName;
int lastTypeNameDotPos = fullTypeName.LastIndexOf('.');
if (lastTypeNameDotPos > 0)
{
namespaceName = fullTypeName.Substring(0, lastTypeNameDotPos);
typeName = fullTypeName.Substring(lastTypeNameDotPos + 1);
}
// Buscar el namespace
if (!namespaces.TryGetValue(namespaceName, out var namespaceDoc))
{
return; // No encontramos el namespace, lo cual es extraño
}
// Buscar el tipo
var typeDoc = namespaceDoc.Types.FirstOrDefault(t => t.FullName == fullTypeName || t.Name == typeName);
if (typeDoc == null)
{
return; // No encontramos el tipo, lo cual es extraño
}
// Extraer descripción y otra información
string description = memberElement.Element("summary")?.Value?.Trim() ?? "No description available";
string returns = memberElement.Element("returns")?.Value?.Trim();
string value = memberElement.Element("value")?.Value?.Trim();
// Crear documentación del miembro
var memberDoc = new MemberDocumentation
{
Name = memberName,
MemberType = memberType,
Description = description,
ReturnType = DeriveReturnType(memberType, returns, value)
};
// Para métodos, procesar parámetros
if (memberType == "Method")
{
foreach (var paramElement in memberElement.Elements("param"))
{
string paramName = paramElement.Attribute("name")?.Value;
string paramDesc = paramElement.Value?.Trim();
if (!string.IsNullOrEmpty(paramName))
{
// Extraer tipo del parámetro de la descripción
string paramType = "object";
if (!string.IsNullOrEmpty(paramDesc) && paramDesc.StartsWith("A "))
{
int spacePos = paramDesc.IndexOf(' ', 2);
if (spacePos > 0)
{
paramType = paramDesc.Substring(2, spacePos - 2);
}
}
var paramDoc = new ParameterDocumentation
{
Name = paramName,
Type = paramType,
Description = paramDesc,
IsOptional = paramDesc?.Contains("Optional") == true
};
memberDoc.Parameters.Add(paramDoc);
}
}
// Generar firma basada en parámetros
string returnType = memberDoc.ReturnType ?? "void";
string parameters = string.Join(", ", memberDoc.Parameters.Select(p => $"{p.Type} {p.Name}"));
memberDoc.Signature = $"{returnType} {memberName}({parameters})";
}
else if (memberType == "Property")
{
// Generar firma para propiedades
string propType = memberDoc.ReturnType ?? "object";
string accessors = "";
if (description.Contains("Gets"))
{
accessors += "get; ";
}
if (description.Contains("sets"))
{
accessors += "set; ";
}
memberDoc.Signature = $"{propType} {memberName} {{ {accessors}}}";
}
else
{
// Firma simple para otros miembros
memberDoc.Signature = $"{memberDoc.ReturnType ?? "void"} {memberName}";
}
// Agregar el miembro al tipo
typeDoc.Members.Add(memberDoc);
}
else if (memberType == "Property")
catch (Exception ex)
{
string propType = member.Type?.ToString() ?? "object";
string accessors = "";
bool canRead = member.CanRead != null && (bool)member.CanRead;
bool canWrite = member.CanWrite != null && (bool)member.CanWrite;
if (canRead && canWrite)
accessors = " { get; set; }";
else if (canRead)
accessors = " { get; }";
else if (canWrite)
accessors = " { set; }";
return $"{propType} {name}{accessors}";
Console.WriteLine($"Error procesando miembro: {ex.Message}");
}
else if (memberType == "Field")
{
string fieldType = member.Type?.ToString() ?? "object";
return $"{fieldType} {name}";
}
else if (memberType == "Event")
{
string eventType = member.EventHandlerType?.ToString() ?? "EventHandler";
return $"event {eventType} {name}";
}
return name;
}
private string GetReturnType(dynamic member)
private string DeriveReturnType(string memberType, string returns, string value)
{
string memberType = member.MemberType?.ToString();
if (memberType == "Method")
return member.ReturnType?.ToString() ?? "void";
else if (memberType == "Property")
return member.Type?.ToString() ?? "object";
return "";
}
private string GetParameterDescription(dynamic xmlDocumentation, string paramName)
{
if (xmlDocumentation?.Parameters == null || paramName == null)
return "";
foreach (var param in xmlDocumentation.Parameters)
// Intentar extraer el tipo de retorno de las etiquetas returns o value
if (!string.IsNullOrEmpty(returns))
{
if (param.Name?.ToString() == paramName)
return param.Description?.ToString() ?? "";
// Intentar extraer de algo como "A string value."
if (returns.StartsWith("A ") || returns.StartsWith("An "))
{
int spacePos = returns.IndexOf(' ', 2);
if (spacePos > 0)
{
return returns.Substring(2, spacePos - 2);
}
}
}
return "";
if (!string.IsNullOrEmpty(value))
{
// Intentar extraer de algo como "A string representing..."
if (value.StartsWith("A ") || value.StartsWith("An "))
{
int spacePos = value.IndexOf(' ', 2);
if (spacePos > 0)
{
return value.Substring(2, spacePos - 2);
}
}
}
// Valores por defecto para diferentes tipos de miembros
switch (memberType)
{
case "Method": return "void";
case "Property": return "object";
case "Field": return "object";
case "Event": return "EventHandler";
default: return null;
}
}
public string GenerateDocumentationPreview(DocumentationModel documentation, ExportSettings settings)
@ -276,14 +392,140 @@ namespace NetDocsForLLM.Services
else // YAML
{
// En un escenario real, usaríamos YamlDotNet
// Para esta implementación, usaremos una conversión manual simplificada
return ConvertJsonToSimpleYaml(
JsonConvert.SerializeObject(documentation, Formatting.None,
new JsonSerializerSettings
// Para esta implementación, usaremos una conversión manual simplificada a XML
var doc = new XDocument(
new XDeclaration("1.0", "utf-8", null),
new XElement("documentation",
new XElement("namespaces")
)
);
var namespacesElement = doc.Root.Element("namespaces");
foreach (var ns in documentation.Namespaces)
{
var nsElement = new XElement("namespace",
new XAttribute("name", ns.Name),
new XElement("description", ns.Description),
new XElement("types")
);
var typesElement = nsElement.Element("types");
foreach (var type in ns.Types)
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
}));
var typeElement = new XElement("type",
new XAttribute("name", type.Name),
new XAttribute("fullName", type.FullName),
new XAttribute("kind", type.TypeKind),
new XElement("description", type.Description),
new XElement("members")
);
if (type.BaseTypes.Count > 0)
{
var baseTypesElement = new XElement("baseTypes");
foreach (var baseType in type.BaseTypes)
{
baseTypesElement.Add(new XElement("baseType", baseType));
}
typeElement.Add(baseTypesElement);
}
if (type.Interfaces.Count > 0)
{
var interfacesElement = new XElement("interfaces");
foreach (var iface in type.Interfaces)
{
interfacesElement.Add(new XElement("interface", iface));
}
typeElement.Add(interfacesElement);
}
var membersElement = typeElement.Element("members");
foreach (var member in type.Members)
{
var memberElement = new XElement("member",
new XAttribute("name", member.Name),
new XAttribute("type", member.MemberType),
new XElement("description", member.Description),
new XElement("signature", member.Signature)
);
if (!string.IsNullOrEmpty(member.ReturnType))
{
memberElement.Add(new XElement("returnType", member.ReturnType));
if (!string.IsNullOrEmpty(member.ReturnDescription))
{
memberElement.Add(new XElement("returnDescription", member.ReturnDescription));
}
}
if (member.Parameters.Count > 0)
{
var paramsElement = new XElement("parameters");
foreach (var param in member.Parameters)
{
var paramElement = new XElement("parameter",
new XAttribute("name", param.Name),
new XAttribute("type", param.Type),
new XElement("description", param.Description ?? "")
);
if (param.IsOptional)
{
paramElement.Add(new XAttribute("optional", "true"));
if (!string.IsNullOrEmpty(param.DefaultValue))
{
paramElement.Add(new XAttribute("defaultValue", param.DefaultValue));
}
}
paramsElement.Add(paramElement);
}
memberElement.Add(paramsElement);
}
if (member.Examples.Count > 0)
{
var examplesElement = new XElement("examples");
foreach (var example in member.Examples)
{
examplesElement.Add(new XElement("example", example));
}
memberElement.Add(examplesElement);
}
membersElement.Add(memberElement);
}
typesElement.Add(typeElement);
}
namespacesElement.Add(nsElement);
}
// Convertir a string con formato
using (var stringWriter = new StringWriter())
{
using (var xmlWriter = new XmlTextWriter(stringWriter)
{
Formatting = (System.Xml.Formatting)Formatting.Indented,
Indentation = 2
})
{
doc.Save(xmlWriter);
}
return stringWriter.ToString();
}
}
}
catch (Exception ex)
@ -291,84 +533,5 @@ namespace NetDocsForLLM.Services
throw new InvalidOperationException($"Error al generar vista previa: {ex.Message}", ex);
}
}
private string ConvertJsonToSimpleYaml(string json)
{
// Deserialize JSON
var obj = JsonConvert.DeserializeObject<dynamic>(json);
// Basic YAML builder
var yaml = new System.Text.StringBuilder();
yaml.AppendLine("namespaces:");
foreach (var ns in obj.Namespaces)
{
yaml.AppendLine($" - name: {ns.Name}");
yaml.AppendLine($" description: {ns.Description}");
yaml.AppendLine(" types:");
foreach (var type in ns.Types)
{
yaml.AppendLine($" - name: {type.Name}");
yaml.AppendLine($" fullName: {type.FullName}");
yaml.AppendLine($" typeKind: {type.TypeKind}");
yaml.AppendLine($" description: {type.Description}");
yaml.AppendLine(" members:");
foreach (var member in type.Members)
{
yaml.AppendLine($" - name: {member.Name}");
yaml.AppendLine($" memberType: {member.MemberType}");
yaml.AppendLine($" signature: \"{EscapeYamlString(member.Signature)}\"");
yaml.AppendLine($" description: \"{EscapeYamlString(member.Description)}\"");
if (member.Parameters != null && member.Parameters.Count > 0)
{
yaml.AppendLine(" parameters:");
foreach (var param in member.Parameters)
{
yaml.AppendLine($" - name: {param.Name}");
yaml.AppendLine($" type: {param.Type}");
yaml.AppendLine($" description: \"{EscapeYamlString(param.Description)}\"");
yaml.AppendLine($" isOptional: {param.IsOptional.ToString().ToLower()}");
if (!string.IsNullOrEmpty(param.DefaultValue))
yaml.AppendLine($" defaultValue: \"{EscapeYamlString(param.DefaultValue)}\"");
}
}
if (!string.IsNullOrEmpty(member.ReturnType))
{
yaml.AppendLine($" returnType: {member.ReturnType}");
if (!string.IsNullOrEmpty(member.ReturnDescription))
yaml.AppendLine($" returnDescription: \"{EscapeYamlString(member.ReturnDescription)}\"");
}
if (member.Examples != null && member.Examples.Count > 0)
{
yaml.AppendLine(" examples:");
foreach (var example in member.Examples)
{
yaml.AppendLine($" - |\n {example.Replace("\n", "\n ")}");
}
}
}
}
}
return yaml.ToString();
}
private string EscapeYamlString(string input)
{
if (string.IsNullOrEmpty(input))
return "";
return input
.Replace("\\", "\\\\")
.Replace("\"", "\\\"")
.Replace("\n", "\\n")
.Replace("\r", "\\r")
.Replace("\t", "\\t");
}
}
}

View File

@ -0,0 +1,28 @@
using NetDocsForLLM.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace NetDocsForLLM.Services
{
/// <summary>
/// Interfaz para servicios de generación de documentación a partir de ensamblados
/// </summary>
public interface IDocumentationGenerator
{
/// <summary>
/// Genera un modelo de documentación a partir de los ensamblados proporcionados
/// </summary>
/// <param name="assemblies">Ensamblados para documentar</param>
/// <param name="settings">Configuración de exportación</param>
/// <returns>Modelo de documentación estructurado</returns>
Task<DocumentationModel> GenerateDocumentation(IEnumerable<AssemblyModel> assemblies, ExportSettings settings);
/// <summary>
/// Genera una vista previa de la documentación en formato texto (JSON o YAML)
/// </summary>
/// <param name="documentation">Modelo de documentación</param>
/// <param name="settings">Configuración de exportación</param>
/// <returns>Representación textual de la documentación</returns>
string GenerateDocumentationPreview(DocumentationModel documentation, ExportSettings settings);
}
}

View File

@ -0,0 +1,504 @@
using NetDocsForLLM.Models;
using Newtonsoft.Json;
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.Linq;
namespace NetDocsForLLM.Services
{
public class ReflectionAnalyzerService : IDocFxService
{
private readonly string _workingDirectory;
private TextWriter _logWriter;
private XDocument _xmlDoc; // Variable de clase para acceder al XML desde cualquier método
public ReflectionAnalyzerService()
{
// 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 análisis simplificado para LLMs: {DateTime.Now}");
// Para cada ensamblado, generar archivo de metadatos
foreach (var assembly in assemblies)
{
await Task.Run(() => ProcessAssembly(assembly, metadataPath));
}
}
return metadataPath;
}
private void ProcessAssembly(AssemblyModel assemblyModel, string outputPath)
{
try
{
_logWriter.WriteLine($"\n=== Procesando ensamblado: {assemblyModel.Name} ===");
Assembly assembly = assemblyModel.LoadedAssembly;
if (assembly == null)
{
_logWriter.WriteLine("ERROR: El ensamblado no está cargado");
return;
}
// Cargar documentación XML si existe
_xmlDoc = null;
if (assemblyModel.HasXmlDocumentation && File.Exists(assemblyModel.XmlDocPath))
{
try
{
_xmlDoc = XDocument.Load(assemblyModel.XmlDocPath);
_logWriter.WriteLine($"XML cargado: {assemblyModel.XmlDocPath}");
}
catch (Exception ex)
{
_logWriter.WriteLine($"Error cargando XML: {ex.Message}");
}
}
// Procesar todos los tipos exportados
var exportedTypes = assembly.GetExportedTypes()
.Where(t => t.IsPublic && !t.IsSpecialName) // Solo tipos públicos y no especiales
.ToList();
_logWriter.WriteLine($"Tipos exportados encontrados: {exportedTypes.Count}");
// Agrupar tipos por namespace
var namespaces = new List<object>();
foreach (var nsGroup in exportedTypes.GroupBy(t => t.Namespace ?? "Global"))
{
string namespaceName = nsGroup.Key;
var types = new List<object>();
_logWriter.WriteLine($"\nProcesando namespace: {namespaceName}");
foreach (var type in nsGroup)
{
// Solo incluir tipos que no sean anidados o que sean útiles
if (!type.IsNested || type.IsNestedPublic)
{
types.Add(ExtractSimplifiedTypeInfo(type));
}
}
if (types.Count > 0)
{
namespaces.Add(new
{
Name = namespaceName,
Types = types.ToArray()
});
}
}
// Guardar toda la documentación en un solo archivo
var docStructure = new
{
AssemblyName = assembly.GetName().Name,
Version = assembly.GetName().Version.ToString(),
Namespaces = namespaces.ToArray()
};
string outputFile = Path.Combine(outputPath, $"assembly_{assemblyModel.Name}.json");
File.WriteAllText(outputFile, JsonConvert.SerializeObject(docStructure, Formatting.Indented));
_logWriter.WriteLine($"Documentación guardada en: {outputFile}");
}
catch (Exception ex)
{
_logWriter.WriteLine($"ERROR procesando ensamblado: {ex.Message}");
_logWriter.WriteLine(ex.StackTrace);
}
}
private object ExtractSimplifiedTypeInfo(Type type)
{
try
{
_logWriter.WriteLine($" Documentando tipo: {type.Name}");
// Obtener comentario XML del tipo
string description = GetXmlSummary(type);
// Obtener miembros
var members = new List<object>();
// Métodos públicos (no especiales como getters/setters)
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)
.Where(m => !m.IsSpecialName))
{
var memberObject = DocumentMethod(method);
if (memberObject != null)
members.Add(memberObject);
}
// Propiedades públicas
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
var memberObject = DocumentProperty(property);
if (memberObject != null)
members.Add(memberObject);
}
// Campos públicos
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
var memberObject = DocumentField(field);
if (memberObject != null)
members.Add(memberObject);
}
// Eventos públicos
foreach (var eventInfo in type.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
var memberObject = DocumentEvent(eventInfo);
if (memberObject != null)
members.Add(memberObject);
}
_logWriter.WriteLine($" Miembros documentados: {members.Count}");
// Crear objeto de información de tipo
var typeInfo = new
{
Name = type.Name,
FullName = type.FullName,
Kind = GetTypeKind(type),
Description = description,
// Solo incluir interfaces si hay alguna
Interfaces = type.GetInterfaces().Length > 0
? type.GetInterfaces().Select(i => i.Name).ToArray()
: null,
// Solo incluir base si no es Object
BaseType = (type.BaseType != null && type.BaseType != typeof(object))
? type.BaseType.Name
: null,
// Solo incluir miembros si hay alguno
Members = members.Count > 0 ? members.ToArray() : null
};
return typeInfo;
}
catch (Exception ex)
{
_logWriter.WriteLine($" ERROR documentando tipo {type.Name}: {ex.Message}");
// Devolver información mínima
return new
{
Name = type.Name,
FullName = type.FullName,
Kind = GetTypeKind(type),
Error = "Error al extraer información"
};
}
}
private object DocumentMethod(MethodInfo method)
{
try
{
string description = GetXmlSummary(method);
// Parametros
var parameters = method.GetParameters();
List<object> paramDocs = new List<object>();
if (parameters.Length > 0)
{
foreach (var param in parameters)
{
string paramDesc = GetXmlParamDescription(method, param.Name);
paramDocs.Add(new
{
Name = param.Name,
Type = GetSimpleTypeName(param.ParameterType),
Description = !string.IsNullOrEmpty(paramDesc) ? paramDesc : null
});
}
}
// Información de retorno
string returnDesc = GetXmlReturnDescription(method);
return new
{
Name = method.Name,
Type = "Method",
Description = description,
Returns = GetSimpleTypeName(method.ReturnType),
ReturnDescription = !string.IsNullOrEmpty(returnDesc) ? returnDesc : null,
Parameters = paramDocs.Count > 0 ? paramDocs.ToArray() : null,
IsStatic = method.IsStatic
};
}
catch
{
return null; // Si hay error, simplemente no incluir este método
}
}
private object DocumentProperty(PropertyInfo property)
{
try
{
string description = GetXmlSummary(property);
return new
{
Name = property.Name,
Type = "Property",
Description = description,
PropertyType = GetSimpleTypeName(property.PropertyType),
CanRead = property.CanRead,
CanWrite = property.CanWrite,
IsStatic = property.GetAccessors(true).FirstOrDefault()?.IsStatic ?? false
};
}
catch
{
return null;
}
}
private object DocumentField(FieldInfo field)
{
try
{
string description = GetXmlSummary(field);
return new
{
Name = field.Name,
Type = "Field",
Description = description,
FieldType = GetSimpleTypeName(field.FieldType),
IsStatic = field.IsStatic,
IsConstant = field.IsLiteral
};
}
catch
{
return null;
}
}
private object DocumentEvent(EventInfo eventInfo)
{
try
{
string description = GetXmlSummary(eventInfo);
return new
{
Name = eventInfo.Name,
Type = "Event",
Description = description,
EventType = GetSimpleTypeName(eventInfo.EventHandlerType),
IsStatic = eventInfo.GetAddMethod()?.IsStatic ?? false
};
}
catch
{
return null;
}
}
// Métodos auxiliares
private string GetTypeKind(Type type)
{
if (type.IsEnum) return "Enum";
if (type.IsInterface) return "Interface";
if (type.IsValueType) return "Struct";
if (type.IsClass)
{
if (type.IsAbstract && type.IsSealed) return "Static Class";
if (type.IsAbstract) return "Abstract Class";
return "Class";
}
return "Type";
}
private string GetSimpleTypeName(Type type)
{
if (type == null) return "void";
// 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(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;
}
// Métodos para extraer documentación XML
private string GetXmlSummary(MemberInfo member)
{
if (_xmlDoc == null) return null;
try
{
string xmlId = GetXmlDocId(member);
var docNode = _xmlDoc.Descendants("member")
.FirstOrDefault(m => m.Attribute("name")?.Value == xmlId);
if (docNode != null)
{
var summaryNode = docNode.Element("summary");
if (summaryNode != null)
{
return CleanXmlComment(summaryNode.Value);
}
}
}
catch { /* Ignorar errores */ }
return null;
}
private string GetXmlParamDescription(MethodInfo method, string paramName)
{
if (_xmlDoc == null) return null;
try
{
string xmlId = GetXmlDocId(method);
var docNode = _xmlDoc.Descendants("member")
.FirstOrDefault(m => m.Attribute("name")?.Value == xmlId);
if (docNode != null)
{
var paramNode = docNode.Elements("param")
.FirstOrDefault(p => p.Attribute("name")?.Value == paramName);
if (paramNode != null)
{
return CleanXmlComment(paramNode.Value);
}
}
}
catch { /* Ignorar errores */ }
return null;
}
private string GetXmlReturnDescription(MethodInfo method)
{
if (_xmlDoc == null) return null;
try
{
string xmlId = GetXmlDocId(method);
var docNode = _xmlDoc.Descendants("member")
.FirstOrDefault(m => m.Attribute("name")?.Value == xmlId);
if (docNode != null)
{
var returnNode = docNode.Element("returns");
if (returnNode != null)
{
return CleanXmlComment(returnNode.Value);
}
}
}
catch { /* Ignorar errores */ }
return null;
}
private string GetXmlDocId(MemberInfo member)
{
string prefix;
if (member is Type)
prefix = "T:";
else if (member is MethodInfo)
prefix = "M:";
else if (member is PropertyInfo)
prefix = "P:";
else if (member is EventInfo)
prefix = "E:";
else if (member is FieldInfo)
prefix = "F:";
else
return null;
// Para tipos
if (member is Type type)
return $"{prefix}{type.FullName}";
// Para miembros
string declaringType = member.DeclaringType?.FullName ?? "Unknown";
return $"{prefix}{declaringType}.{member.Name}";
}
private string CleanXmlComment(string comment)
{
if (string.IsNullOrEmpty(comment))
return null;
// Eliminar espacios en blanco extra
comment = comment.Trim();
// Convertir saltos de línea y tabulaciones a espacios simples
comment = comment.Replace("\r", " ")
.Replace("\n", " ")
.Replace("\t", " ");
// Reemplazar múltiples espacios con uno solo
while (comment.Contains(" "))
comment = comment.Replace(" ", " ");
return comment;
}
}
}

431
Services/XmlDocGenerator.cs Normal file
View File

@ -0,0 +1,431 @@
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;
}
}
}