Version xon XML funcionando. Revisar si se estan encontrando todos los miembros

This commit is contained in:
Miguel 2025-03-26 13:23:46 +01:00
parent ebbee5414d
commit 3282dd8f2d
10 changed files with 1243 additions and 462 deletions

View File

@ -19,8 +19,8 @@ namespace NetDocsForLLM
private void ConfigureServices(ServiceCollection services)
{
// Register services
services.AddSingleton<IDocFxService, XmlDocGenerator>(); // Usar el generador XML
// Registramos el XmlDocGenerator sin dependencia de LogService
services.AddSingleton<IDocFxService, XmlDocGenerator>();
services.AddSingleton<IDocumentationGenerator, DocumentationGenerator>();
services.AddSingleton<IAssemblyAnalyzer, AssemblyAnalyzer>();

View File

@ -1,37 +1,29 @@
<Window x:Class="NetDocsForLLM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
<Window x:Class="NetDocsForLLM.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:NetDocsForLLM"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:NetDocsForLLM"
xmlns:views="clr-namespace:NetDocsForLLM.Views" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:conv="clr-namespace:NetDocsForLLM.Converters"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d"
Title="NetDocs para LLMs" Height="650" Width="800">
mc:Ignorable="d" Title="NetDocs para LLMs" Height="650" Width="800">
<Window.Resources>
<conv:EnumBooleanConverter x:Key="EnumBooleanConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Encabezado -->
<Border Background="#2c3e50" Padding="15">
<StackPanel>
<TextBlock Text="Generador de Documentación .NET para LLMs"
Foreground="White"
FontSize="22"
HorizontalAlignment="Center"/>
<TextBlock Text="Generador de Documentación .NET para LLMs" Foreground="White" FontSize="22"
HorizontalAlignment="Center" />
<TextBlock Text="Extraiga y estructure documentación de librerías .NET para uso con modelos de lenguaje"
Foreground="#ecf0f1"
FontSize="12"
HorizontalAlignment="Center"
Margin="0,5,0,0"/>
Foreground="#ecf0f1" FontSize="12" HorizontalAlignment="Center" Margin="0,5,0,0" />
</StackPanel>
</Border>
@ -40,54 +32,40 @@
<TabItem Header="Selección de Librería">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button Content="Seleccionar Archivos DLL..."
Command="{Binding SelectAssembliesCommand}"
Padding="10,5"
Margin="0,0,0,10"/>
<Button Content="Seleccionar Archivos DLL..." Command="{Binding SelectAssembliesCommand}"
Padding="10,5" Margin="0,0,0,10" />
<TextBlock Grid.Row="1"
Text="Librerías seleccionadas:"
FontWeight="Bold"
Margin="0,10,0,5"/>
<TextBlock Grid.Row="1" Text="Librerías seleccionadas:" FontWeight="Bold" Margin="0,10,0,5" />
<ListView Grid.Row="2"
ItemsSource="{Binding SelectedAssemblies}"
Margin="0,5">
<ListView Grid.Row="2" ItemsSource="{Binding SelectedAssemblies}" Margin="0,5">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
<TextBlock Text="{Binding FilePath}" FontSize="11" TextWrapping="Wrap"/>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text="{Binding FilePath}" FontSize="11" TextWrapping="Wrap" />
</StackPanel>
<Button Grid.Column="1"
Content="X"
<Button Grid.Column="1" Content="X"
Command="{Binding DataContext.RemoveAssemblyCommand, RelativeSource={RelativeSource AncestorType=ListView}}"
CommandParameter="{Binding}"
Margin="5,0,0,0"/>
CommandParameter="{Binding}" Margin="5,0,0,0" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Grid.Row="3"
Content="Analizar y Generar Documentación"
Command="{Binding GenerateDocumentationCommand}"
IsEnabled="{Binding HasSelectedAssemblies}"
Padding="10,5"
Margin="0,10,0,0"
Background="#2980b9"
Foreground="White"/>
<Button Grid.Row="3" Content="Analizar y Generar Documentación"
Command="{Binding GenerateDocumentationCommand}" IsEnabled="{Binding HasSelectedAssemblies}"
Padding="10,5" Margin="0,10,0,0" Background="#2980b9" Foreground="White" />
</Grid>
</TabItem>
@ -96,54 +74,49 @@
<StackPanel Margin="10">
<GroupBox Header="Opciones de documentación">
<StackPanel Margin="5">
<CheckBox Content="Incluir métodos privados"
IsChecked="{Binding Settings.IncludePrivateMembers}"
Margin="0,5"/>
<CheckBox Content="Incluir miembros heredados"
IsChecked="{Binding Settings.IncludeInheritedMembers}"
Margin="0,5"/>
<CheckBox Content="Incluir ejemplos de código"
IsChecked="{Binding Settings.IncludeExamples}"
Margin="0,5"/>
<CheckBox Content="Procesamiento detallado de comentarios XML"
IsChecked="{Binding Settings.DetailedXmlComments}"
Margin="0,5"/>
<CheckBox Content="Incluir métodos privados"
IsChecked="{Binding Settings.IncludePrivateMembers}" Margin="0,5" />
<CheckBox Content="Incluir miembros heredados"
IsChecked="{Binding Settings.IncludeInheritedMembers}" Margin="0,5" />
<CheckBox Content="Incluir ejemplos de código"
IsChecked="{Binding Settings.IncludeExamples}" Margin="0,5" />
<CheckBox Content="Procesamiento detallado de comentarios XML"
IsChecked="{Binding Settings.DetailedXmlComments}" Margin="0,5" />
</StackPanel>
</GroupBox>
<GroupBox Header="Nivel de detalle" Margin="0,10,0,0">
<StackPanel Margin="5">
<RadioButton Content="Básico - Solo información esencial"
IsChecked="{Binding Settings.DetailLevel, Converter={StaticResource EnumBooleanConverter}, ConverterParameter=Basic}"
GroupName="DetailLevel"
Margin="0,5"/>
<RadioButton Content="Estándar - Equilibrio entre detalle y tamaño"
IsChecked="{Binding Settings.DetailLevel, Converter={StaticResource EnumBooleanConverter}, ConverterParameter=Standard}"
GroupName="DetailLevel"
Margin="0,5"/>
<RadioButton Content="Completo - Toda la información disponible"
IsChecked="{Binding Settings.DetailLevel, Converter={StaticResource EnumBooleanConverter}, ConverterParameter=Full}"
GroupName="DetailLevel"
Margin="0,5"/>
<RadioButton Content="Básico - Solo información esencial"
IsChecked="{Binding Settings.DetailLevel, Converter={StaticResource EnumBooleanConverter}, ConverterParameter=Basic}"
GroupName="DetailLevel" Margin="0,5" />
<RadioButton Content="Estándar - Equilibrio entre detalle y tamaño"
IsChecked="{Binding Settings.DetailLevel, Converter={StaticResource EnumBooleanConverter}, ConverterParameter=Standard}"
GroupName="DetailLevel" Margin="0,5" />
<RadioButton Content="Completo - Toda la información disponible"
IsChecked="{Binding Settings.DetailLevel, Converter={StaticResource EnumBooleanConverter}, ConverterParameter=Full}"
GroupName="DetailLevel" Margin="0,5" />
</StackPanel>
</GroupBox>
<GroupBox Header="Formato de salida" Margin="0,10,0,0">
<StackPanel Margin="5">
<RadioButton Content="JSON"
IsChecked="{Binding Settings.OutputFormat, Converter={StaticResource EnumBooleanConverter}, ConverterParameter=Json}"
GroupName="OutputFormat"
Margin="0,5"/>
<RadioButton Content="YAML"
IsChecked="{Binding Settings.OutputFormat, Converter={StaticResource EnumBooleanConverter}, ConverterParameter=Yaml}"
GroupName="OutputFormat"
Margin="0,5"/>
<RadioButton Content="JSON"
IsChecked="{Binding Settings.OutputFormat, Converter={StaticResource EnumBooleanConverter}, ConverterParameter=Json}"
GroupName="OutputFormat" Margin="0,5" />
<RadioButton Content="XML (Recomendado para LLMs)"
IsChecked="{Binding Settings.OutputFormat, Converter={StaticResource EnumBooleanConverter}, ConverterParameter=Xml}"
GroupName="OutputFormat" Margin="0,5" />
<RadioButton Content="YAML"
IsChecked="{Binding Settings.OutputFormat, Converter={StaticResource EnumBooleanConverter}, ConverterParameter=Yaml}"
GroupName="OutputFormat" Margin="0,5" />
</StackPanel>
</GroupBox>
</StackPanel>
@ -153,51 +126,42 @@
<TabItem Header="Previsualización" IsEnabled="{Binding HasGeneratedDocumentation}">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<Button Content="Copiar al portapapeles"
Command="{Binding CopyToClipboardCommand}"
Padding="10,5"
Margin="0,0,10,0"
IsEnabled="{Binding HasGeneratedDocumentation}"/>
<Button Content="Exportar..."
Command="{Binding ExportDocumentationCommand}"
Padding="10,5"
IsEnabled="{Binding HasGeneratedDocumentation}"/>
<Button Content="Copiar al portapapeles" Command="{Binding CopyToClipboardCommand}"
Padding="10,5" Margin="0,0,10,0" />
<Button Content="Exportar..." Command="{Binding ExportDocumentationCommand}" Padding="10,5" />
</StackPanel>
<!-- 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}"
IsReadOnly="True"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
FontFamily="Consolas"
AcceptsReturn="True"
Padding="10"/>
<TextBox Text="{Binding DocumentationPreview, Mode=OneWay}" IsReadOnly="True"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
FontFamily="Consolas" AcceptsReturn="True" Padding="10" />
</Border>
<TextBlock Grid.Row="2"
Text="{Binding DocumentationStats}"
Margin="0,10,0,0"/>
<TextBlock Grid.Row="2" Text="{Binding DocumentationStats}" Margin="0,10,0,0" />
</Grid>
</TabItem>
<!-- Nueva pestaña de Log -->
<TabItem Header="Log de Análisis">
<views:LogView />
</TabItem>
</TabControl>
<!-- Pie de página -->
<StatusBar Grid.Row="2">
<StatusBarItem>
<TextBlock Text="{Binding StatusMessage}"/>
<TextBlock Text="{Binding StatusMessage}" />
</StatusBarItem>
<StatusBarItem HorizontalAlignment="Right">
<ProgressBar Width="100" Height="15" IsIndeterminate="{Binding IsProcessing}"/>
<ProgressBar Width="100" Height="15" IsIndeterminate="{Binding IsProcessing}" />
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
</Window>

View File

@ -12,6 +12,7 @@ namespace NetDocsForLLM.Models
public enum OutputFormat
{
Json,
Xml,
Yaml
}
@ -68,7 +69,7 @@ namespace NetDocsForLLM.Models
_includeExamples = true;
_detailedXmlComments = true;
_detailLevel = DetailLevel.Standard;
_outputFormat = OutputFormat.Json;
_outputFormat = OutputFormat.Xml; // Cambiado a XML por defecto
}
}
}
}

182
Services/AppLogger.cs Normal file
View File

@ -0,0 +1,182 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows.Threading;
namespace NetDocsForLLM.Services
{
public enum LogLevel
{
Error = 0,
Warning = 1,
Info = 2,
Debug = 3
}
/// <summary>
/// Clase estática para logs que no depende de inyección de dependencias
/// </summary>
public static class AppLogger
{
private static readonly List<LogEntry> _logEntries = new List<LogEntry>();
private static int _errorCount = 0;
private static int _warningCount = 0;
private static int _typeCount = 0;
private static int _memberCount = 0;
private static string _logFilePath;
private static StreamWriter _fileWriter;
// Evento que se dispara cuando hay nuevos mensajes
public static event EventHandler LogUpdated;
static AppLogger()
{
// Configurar archivo de log
string tempFolder = Path.Combine(Path.GetTempPath(), "NetDocsForLLM_" + DateTime.Now.ToString("yyyyMMdd_HHmmss"));
Directory.CreateDirectory(tempFolder);
_logFilePath = Path.Combine(tempFolder, "app_log.txt");
_fileWriter = new StreamWriter(_logFilePath, true) { AutoFlush = true };
LogInfo($"Log inicializado en: {_logFilePath}");
}
public static void LogDebug(string message)
{
AddLogEntry(LogLevel.Debug, message);
}
public static void LogInfo(string message)
{
AddLogEntry(LogLevel.Info, message);
}
public static void LogWarning(string message)
{
_warningCount++;
AddLogEntry(LogLevel.Warning, message);
}
public static void LogError(string message)
{
_errorCount++;
AddLogEntry(LogLevel.Error, message);
}
public static void LogException(Exception ex)
{
_errorCount++;
StringBuilder sb = new StringBuilder();
sb.AppendLine($"Excepción: {ex.Message}");
if (ex.StackTrace != null)
{
sb.AppendLine("Stack Trace:");
sb.AppendLine(ex.StackTrace);
}
if (ex.InnerException != null)
{
sb.AppendLine($"Inner Exception: {ex.InnerException.Message}");
}
AddLogEntry(LogLevel.Error, sb.ToString());
}
public static int ErrorCount => _errorCount;
public static int WarningCount => _warningCount;
public static int TypeCount => _typeCount;
public static int MemberCount => _memberCount;
public static void IncrementTypeCount()
{
_typeCount++;
NotifyLogUpdated();
}
public static void IncrementMemberCount()
{
_memberCount++;
NotifyLogUpdated();
}
private static void AddLogEntry(LogLevel level, string message)
{
var entry = new LogEntry
{
Timestamp = DateTime.Now,
Level = level,
Message = message
};
_logEntries.Add(entry);
// Escribir al archivo de log
string levelText = GetLevelText(level);
string logMessage = $"[{entry.Timestamp:yyyy-MM-dd HH:mm:ss}] [{levelText}] {message}";
_fileWriter.WriteLine(logMessage);
// Notificar a los escuchadores
NotifyLogUpdated();
}
private static void NotifyLogUpdated()
{
// Invocar el evento en el hilo de la UI si está disponible
LogUpdated?.Invoke(null, EventArgs.Empty);
}
public static string GetContent(LogLevel maxLevel = LogLevel.Debug)
{
StringBuilder sb = new StringBuilder();
foreach (var entry in _logEntries)
{
if (entry.Level <= maxLevel)
{
string levelText = GetLevelText(entry.Level);
sb.AppendLine($"[{entry.Timestamp:yyyy-MM-dd HH:mm:ss}] [{levelText}] {entry.Message}");
}
}
return sb.ToString();
}
private static string GetLevelText(LogLevel level)
{
switch (level)
{
case LogLevel.Error:
return "ERROR";
case LogLevel.Warning:
return "WARN";
case LogLevel.Info:
return "INFO";
case LogLevel.Debug:
return "DEBUG";
default:
return level.ToString();
}
}
public static void Clear()
{
_logEntries.Clear();
_errorCount = 0;
_warningCount = 0;
// No reiniciar los contadores de tipos y miembros
// ya que representan el progreso del análisis
NotifyLogUpdated();
}
private class LogEntry
{
public DateTime Timestamp { get; set; }
public LogLevel Level { get; set; }
public string Message { get; set; }
}
}
}

View File

@ -380,152 +380,22 @@ namespace NetDocsForLLM.Services
{
try
{
if (settings.OutputFormat == OutputFormat.Json)
switch (settings.OutputFormat)
{
return JsonConvert.SerializeObject(documentation, Formatting.Indented,
new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
});
}
else // YAML
{
// En un escenario real, usaríamos YamlDotNet
// 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)
{
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)
case OutputFormat.Json:
return JsonConvert.SerializeObject(documentation, Formatting.Indented,
new JsonSerializerSettings
{
var baseTypesElement = new XElement("baseTypes");
foreach (var baseType in type.BaseTypes)
{
baseTypesElement.Add(new XElement("baseType", baseType));
}
typeElement.Add(baseTypesElement);
}
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
});
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);
}
case OutputFormat.Xml:
return GenerateXmlOutput(documentation);
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();
}
case OutputFormat.Yaml:
default:
return GenerateYamlOutput(documentation);
}
}
catch (Exception ex)
@ -533,5 +403,265 @@ namespace NetDocsForLLM.Services
throw new InvalidOperationException($"Error al generar vista previa: {ex.Message}", ex);
}
}
}
private string GenerateXmlOutput(DocumentationModel documentation)
{
var doc = new XDocument(
new XDeclaration("1.0", "utf-8", null),
new XElement("doc",
new XElement("assembly",
new XElement("name", "DocumentationForLLM")
),
new XElement("members")
)
);
var membersElement = doc.Root.Element("members");
// Agregar tipos
foreach (var ns in documentation.Namespaces)
{
foreach (var type in ns.Types)
{
// Agregar elemento de tipo
var typeMember = new XElement("member",
new XAttribute("name", $"T:{type.FullName}"),
new XElement("summary", type.Description)
);
// Agregar información de base y interfaces si corresponde
if (type.BaseTypes.Count > 0 || type.Interfaces.Count > 0)
{
var remarks = new XElement("remarks");
if (type.BaseTypes.Count > 0)
{
remarks.Add(new XText($"Inherits from {type.BaseTypes[0]}"));
}
if (type.Interfaces.Count > 0)
{
if (type.BaseTypes.Count > 0)
remarks.Add(new XText("\n"));
remarks.Add(new XText($"Implements: {string.Join(", ", type.Interfaces)}"));
}
typeMember.Add(remarks);
}
membersElement.Add(typeMember);
// Agregar miembros
foreach (var member in type.Members)
{
XElement memberElement;
// Agregar con formato correcto según el tipo
switch (member.MemberType)
{
case "Method":
memberElement = CreateMethodElement(type, member);
break;
case "Property":
memberElement = CreatePropertyElement(type, member);
break;
case "Event":
memberElement = CreateEventElement(type, member);
break;
case "Field":
memberElement = CreateFieldElement(type, member);
break;
default:
continue; // Tipo no soportado
}
membersElement.Add(memberElement);
}
}
}
// Usar un StringWriter para convertir el XML a texto
using (var stringWriter = new StringWriter())
{
using (var writer = new XmlTextWriter(stringWriter))
{
writer.Formatting = (System.Xml.Formatting)Formatting.Indented;
writer.Indentation = 4;
doc.Save(writer);
}
return stringWriter.ToString();
}
}
private XElement CreateMethodElement(TypeDocumentation type, MemberDocumentation method)
{
// Crear ID XML para el método
string methodId;
if (method.Parameters.Count > 0)
{
var paramTypes = string.Join(",", method.Parameters.Select(p => p.Type));
methodId = $"M:{type.FullName}.{method.Name}({paramTypes})";
}
else
{
methodId = $"M:{type.FullName}.{method.Name}";
}
var element = new XElement("member",
new XAttribute("name", methodId),
new XElement("summary", method.Description)
);
// Agregar parámetros
foreach (var param in method.Parameters)
{
element.Add(new XElement("param",
new XAttribute("name", param.Name),
new XText(param.Description ?? $"A {param.Type} parameter.")
));
}
// Agregar información de retorno si no es void
if (!string.IsNullOrEmpty(method.ReturnType) && method.ReturnType != "void")
{
element.Add(new XElement("returns",
method.ReturnDescription ?? $"A {method.ReturnType} value."
));
}
// Agregar ejemplos si hay
foreach (var example in method.Examples)
{
element.Add(new XElement("example", example));
}
return element;
}
private XElement CreatePropertyElement(TypeDocumentation type, MemberDocumentation property)
{
var element = new XElement("member",
new XAttribute("name", $"P:{type.FullName}.{property.Name}"),
new XElement("summary", property.Description)
);
if (!string.IsNullOrEmpty(property.ReturnType))
{
element.Add(new XElement("value",
$"A {property.ReturnType} representing the property value."
));
}
return element;
}
private XElement CreateEventElement(TypeDocumentation type, MemberDocumentation eventMember)
{
var element = new XElement("member",
new XAttribute("name", $"E:{type.FullName}.{eventMember.Name}"),
new XElement("summary", eventMember.Description)
);
return element;
}
private XElement CreateFieldElement(TypeDocumentation type, MemberDocumentation field)
{
var element = new XElement("member",
new XAttribute("name", $"F:{type.FullName}.{field.Name}"),
new XElement("summary", field.Description)
);
return element;
}
private string GenerateYamlOutput(DocumentationModel documentation)
{
// Implementación simplificada de YAML (mantenemos la existente)
// Deserialize JSON primero, luego a YAML
var json = JsonConvert.SerializeObject(documentation, Formatting.None,
new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
});
// Convierte a YAML usando una implementación simple
var yaml = new System.Text.StringBuilder();
yaml.AppendLine("namespaces:");
var obj = JsonConvert.DeserializeObject<dynamic>(json);
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: {EscapeYamlString(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

@ -14,13 +14,14 @@ 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);
AppLogger.LogInfo($"Directorio de trabajo creado: {_workingDirectory}");
}
public async Task<string> GenerateMetadataAsync(IEnumerable<AssemblyModel> assemblies)
@ -29,17 +30,14 @@ namespace NetDocsForLLM.Services
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}");
AppLogger.LogInfo($"Directorio de metadatos creado: {metadataPath}");
AppLogger.LogInfo($"Iniciando análisis en {DateTime.Now}");
AppLogger.LogInfo($"Ensamblados a procesar: {assemblies.Count()}");
// Para cada ensamblado, generar documentación XML
foreach (var assembly in assemblies)
{
await Task.Run(() => GenerateXmlDocumentation(assembly, metadataPath));
}
// Para cada ensamblado, generar documentación XML
foreach (var assembly in assemblies)
{
await Task.Run(() => GenerateXmlDocumentation(assembly, metadataPath));
}
return metadataPath;
@ -52,11 +50,13 @@ namespace NetDocsForLLM.Services
Assembly assembly = assemblyModel.LoadedAssembly;
if (assembly == null)
{
_logWriter.WriteLine($"ERROR: El ensamblado {assemblyModel.Name} no está cargado");
AppLogger.LogError($"ERROR: El ensamblado {assemblyModel.Name} no está cargado");
return;
}
_logWriter.WriteLine($"\n=== Procesando ensamblado: {assemblyModel.Name} ===");
AppLogger.LogInfo($"\n=== Procesando ensamblado: {assemblyModel.Name} ===");
AppLogger.LogInfo($"Ruta: {assemblyModel.FilePath}");
AppLogger.LogInfo($"Versión: {assemblyModel.Version}");
// Crear el documento XML
XDocument doc = new XDocument(
@ -72,192 +72,427 @@ namespace NetDocsForLLM.Services
// 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))
// Cargar documentación XML existente si hay
XDocument existingXmlDoc = null;
if (assemblyModel.HasXmlDocumentation && File.Exists(assemblyModel.XmlDocPath))
{
try
{
// Generar documentación para el tipo
GenerateTypeDocumentation(type, membersElement);
existingXmlDoc = XDocument.Load(assemblyModel.XmlDocPath);
AppLogger.LogInfo($"Documentación XML cargada: {assemblyModel.XmlDocPath}");
// 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);
}
// Mostrar estadísticas del XML existente
var memberNodes = existingXmlDoc.Descendants("member").ToList();
AppLogger.LogInfo($"Entradas en XML existente: {memberNodes.Count}");
// Generar documentación para propiedades
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
// Log de los primeros miembros para depuración
foreach (var node in memberNodes.Take(5))
{
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);
string id = node.Attribute("name")?.Value;
AppLogger.LogDebug($" Miembro XML: {id}");
}
}
catch (Exception ex)
{
_logWriter.WriteLine($"Error procesando tipo {type.FullName}: {ex.Message}");
AppLogger.LogWarning($"Error al cargar XML: {ex.Message}");
existingXmlDoc = null;
}
}
else
{
AppLogger.LogWarning("No se encontró documentación XML para este ensamblado");
}
// Procesar todos los tipos exportados
var exportedTypes = assembly.GetExportedTypes().ToList();
AppLogger.LogInfo($"Tipos exportados encontrados: {exportedTypes.Count}");
// Procesar todos los tipos exportados
int typesWithMembers = 0;
int typesWithoutMembers = 0;
int totalMethods = 0;
int totalProperties = 0;
int totalEvents = 0;
int totalFields = 0;
foreach (var type in exportedTypes)
{
try
{
AppLogger.IncrementTypeCount();
AppLogger.LogDebug($"Procesando tipo: {type.FullName}");
// Generar documentación para el tipo
bool hasMembers = GenerateTypeDocumentation(type, membersElement, existingXmlDoc);
if (hasMembers)
{
typesWithMembers++;
}
else
{
typesWithoutMembers++;
AppLogger.LogWarning($"⚠️ No se encontraron miembros para el tipo: {type.FullName}");
}
// Contar miembros por tipo para estadísticas
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)
.Where(m => !m.IsSpecialName).ToList();
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly).ToList();
var events = type.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly).ToList();
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly).ToList();
totalMethods += methods.Count;
totalProperties += properties.Count;
totalEvents += events.Count;
totalFields += fields.Count;
AppLogger.LogDebug($" Miembros del tipo {type.Name}: Métodos={methods.Count}, Propiedades={properties.Count}, Eventos={events.Count}, Campos={fields.Count}");
// Generar entradas XML para los miembros
if (methods.Count > 0)
{
foreach (var method in methods)
{
AppLogger.IncrementMemberCount();
GenerateMethodDocumentation(method, membersElement, existingXmlDoc);
}
}
if (properties.Count > 0)
{
foreach (var property in properties)
{
AppLogger.IncrementMemberCount();
GeneratePropertyDocumentation(property, membersElement, existingXmlDoc);
}
}
if (events.Count > 0)
{
foreach (var eventInfo in events)
{
AppLogger.IncrementMemberCount();
GenerateEventDocumentation(eventInfo, membersElement, existingXmlDoc);
}
}
if (fields.Count > 0)
{
foreach (var field in fields)
{
AppLogger.IncrementMemberCount();
GenerateFieldDocumentation(field, membersElement, existingXmlDoc);
}
}
}
catch (Exception ex)
{
AppLogger.LogError($"Error procesando tipo {type.FullName}: {ex.Message}");
}
}
// Guardar la documentación XML
string xmlFileName = $"{assembly.GetName().Name}.xml";
// Estadísticas finales
AppLogger.LogInfo($"Estadísticas de procesamiento para {assemblyModel.Name}:");
AppLogger.LogInfo($" Tipos totales: {exportedTypes.Count}");
AppLogger.LogInfo($" Tipos con miembros: {typesWithMembers}");
AppLogger.LogInfo($" Tipos sin miembros: {typesWithoutMembers}");
AppLogger.LogInfo($" Total de métodos: {totalMethods}");
AppLogger.LogInfo($" Total de propiedades: {totalProperties}");
AppLogger.LogInfo($" Total de eventos: {totalEvents}");
AppLogger.LogInfo($" Total de campos: {totalFields}");
// Guardar la documentación XML generada
string xmlFileName = $"{assembly.GetName().Name}_generated.xml";
string xmlFilePath = Path.Combine(outputPath, xmlFileName);
// Guardar con formato
using (var writer = new XmlTextWriter(xmlFilePath, Encoding.UTF8))
{
writer.Formatting = Formatting.Indented;
writer.Formatting = System.Xml.Formatting.Indented;
writer.Indentation = 4;
doc.Save(writer);
}
_logWriter.WriteLine($"Documentación XML guardada en: {xmlFilePath}");
AppLogger.LogInfo($"Documentación XML guardada en: {xmlFilePath}");
}
catch (Exception ex)
{
_logWriter.WriteLine($"ERROR general procesando ensamblado {assemblyModel.Name}: {ex.Message}");
_logWriter.WriteLine(ex.StackTrace);
AppLogger.LogError($"ERROR general procesando ensamblado {assemblyModel.Name}: {ex.Message}");
AppLogger.LogError(ex.StackTrace);
}
}
private void GenerateTypeDocumentation(Type type, XElement membersElement)
private bool GenerateTypeDocumentation(Type type, XElement membersElement, XDocument existingXmlDoc)
{
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}"));
// Intentar encontrar documentación existente
string summary = null;
string remarks = null;
// Si es una clase que hereda de otra (excepto Object), agregar esa información
if (type.IsClass && type.BaseType != null && type.BaseType != typeof(object))
if (existingXmlDoc != null)
{
memberElement.Add(new XElement("remarks",
$"Inherits from {type.BaseType.Name}"));
var existingMember = existingXmlDoc.Descendants("member")
.FirstOrDefault(m => m.Attribute("name")?.Value == typeId);
if (existingMember != null)
{
AppLogger.LogDebug($" Encontrada documentación XML existente para {typeId}");
summary = existingMember.Element("summary")?.Value?.Trim();
remarks = existingMember.Element("remarks")?.Value?.Trim();
}
}
// Si implementa interfaces, mencionarlas
var interfaces = type.GetInterfaces();
if (interfaces.Length > 0)
// Si no hay documentación existente, generar una descripción genérica
if (string.IsNullOrEmpty(summary))
{
var implementsText = $"Implements: {string.Join(", ", interfaces.Select(i => i.Name))}";
summary = $"Represents a {GetTypeKindDescription(type)}: {type.Name}";
}
if (memberElement.Element("remarks") != null)
var memberElement = new XElement("member", new XAttribute("name", typeId));
memberElement.Add(new XElement("summary", summary));
// Agregar observaciones si están disponibles o generarlas
if (!string.IsNullOrEmpty(remarks))
{
memberElement.Add(new XElement("remarks", remarks));
}
else
{
var remarksBuilder = new StringBuilder();
// 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.Element("remarks").Value += $"\n{implementsText}";
remarksBuilder.AppendLine($"Inherits from {type.BaseType.Name}");
}
else
// Si implementa interfaces, mencionarlas
var interfaces = type.GetInterfaces();
if (interfaces.Length > 0)
{
memberElement.Add(new XElement("remarks", implementsText));
if (remarksBuilder.Length > 0)
remarksBuilder.AppendLine();
remarksBuilder.Append($"Implements: {string.Join(", ", interfaces.Select(i => i.Name))}");
}
if (remarksBuilder.Length > 0)
{
memberElement.Add(new XElement("remarks", remarksBuilder.ToString()));
}
}
membersElement.Add(memberElement);
// Verificar si el tipo tiene miembros
bool hasMembers =
type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly).Any(m => !m.IsSpecialName) ||
type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly).Any() ||
type.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly).Any() ||
type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly).Any();
return hasMembers;
}
private void GenerateMethodDocumentation(MethodInfo method, XElement membersElement)
private void GenerateMethodDocumentation(MethodInfo method, XElement membersElement, XDocument existingXmlDoc)
{
try
{
// Generar ID XML para el método
string methodId = GetMethodXmlId(method);
var memberElement = new XElement("member", new XAttribute("name", methodId));
AppLogger.LogDebug($" Procesando método: {methodId}");
// Agregar summary básico
memberElement.Add(new XElement("summary",
$"{method.Name} {GetMethodDescription(method)}"));
// Intentar encontrar documentación existente
string summary = null;
string returns = null;
Dictionary<string, string> paramDescriptions = new Dictionary<string, string>();
if (existingXmlDoc != null)
{
var existingMember = existingXmlDoc.Descendants("member")
.FirstOrDefault(m => m.Attribute("name")?.Value == methodId);
if (existingMember != null)
{
summary = existingMember.Element("summary")?.Value?.Trim();
returns = existingMember.Element("returns")?.Value?.Trim();
foreach (var paramElement in existingMember.Elements("param"))
{
string paramName = paramElement.Attribute("name")?.Value;
string paramDesc = paramElement.Value?.Trim();
if (!string.IsNullOrEmpty(paramName) && !string.IsNullOrEmpty(paramDesc))
{
paramDescriptions[paramName] = paramDesc;
}
}
}
}
// Si no hay documentación existente, generar una descripción genérica
if (string.IsNullOrEmpty(summary))
{
summary = $"{method.Name} {GetMethodDescription(method)}";
}
var element = new XElement("member",
new XAttribute("name", methodId),
new XElement("summary", summary)
);
// Agregar parámetros
foreach (var param in method.GetParameters())
var parameters = method.GetParameters();
foreach (var param in parameters)
{
memberElement.Add(new XElement("param",
string paramDesc;
if (!paramDescriptions.TryGetValue(param.Name, out paramDesc))
{
paramDesc = GetParameterDescription(param);
}
element.Add(new XElement("param",
new XAttribute("name", param.Name),
GetParameterDescription(param)));
new XText(paramDesc)
));
}
// Agregar returns si no es void
// Agregar información de retorno si no es void
if (method.ReturnType != typeof(void))
{
memberElement.Add(new XElement("returns",
$"A {GetSimpleTypeName(method.ReturnType)} value."));
if (string.IsNullOrEmpty(returns))
{
returns = $"A {GetSimpleTypeName(method.ReturnType)} value.";
}
element.Add(new XElement("returns", returns));
}
membersElement.Add(memberElement);
membersElement.Add(element);
}
catch (Exception ex)
{
_logWriter.WriteLine($"Error generando documentación para método {method.Name}: {ex.Message}");
AppLogger.LogError($"Error generando documentación para método {method.Name}: {ex.Message}");
}
}
private void GeneratePropertyDocumentation(PropertyInfo property, XElement membersElement)
private void GeneratePropertyDocumentation(PropertyInfo property, XElement membersElement, XDocument existingXmlDoc)
{
try
{
string propertyId = $"P:{property.DeclaringType.FullName}.{property.Name}";
var memberElement = new XElement("member", new XAttribute("name", propertyId));
AppLogger.LogDebug($" Procesando propiedad: {propertyId}");
// Accesibilidad (get/set)
string access = "";
if (property.CanRead) access += "get; ";
if (property.CanWrite) access += "set; ";
// Intentar encontrar documentación existente
string summary = null;
string value = null;
// Agregar summary
memberElement.Add(new XElement("summary",
$"Gets{(property.CanWrite ? " or sets" : "")} the {property.Name} property."));
if (existingXmlDoc != null)
{
var existingMember = existingXmlDoc.Descendants("member")
.FirstOrDefault(m => m.Attribute("name")?.Value == propertyId);
// Agregar valor
memberElement.Add(new XElement("value",
$"A {GetSimpleTypeName(property.PropertyType)} representing the {property.Name}."));
if (existingMember != null)
{
summary = existingMember.Element("summary")?.Value?.Trim();
value = existingMember.Element("value")?.Value?.Trim();
}
}
membersElement.Add(memberElement);
// Si no hay documentación existente, generar una descripción genérica
if (string.IsNullOrEmpty(summary))
{
summary = $"Gets{(property.CanWrite ? " or sets" : "")} the {property.Name} property.";
}
var element = new XElement("member",
new XAttribute("name", propertyId),
new XElement("summary", summary)
);
// Agregar descripción de valor si está disponible o generarla
if (string.IsNullOrEmpty(value))
{
value = $"A {GetSimpleTypeName(property.PropertyType)} representing the {property.Name}.";
}
element.Add(new XElement("value", value));
membersElement.Add(element);
}
catch (Exception ex)
{
_logWriter.WriteLine($"Error generando documentación para propiedad {property.Name}: {ex.Message}");
AppLogger.LogError($"Error generando documentación para propiedad {property.Name}: {ex.Message}");
}
}
private void GenerateEventDocumentation(EventInfo eventInfo, XElement membersElement)
private void GenerateEventDocumentation(EventInfo eventInfo, XElement membersElement, XDocument existingXmlDoc)
{
try
{
string eventId = $"E:{eventInfo.DeclaringType.FullName}.{eventInfo.Name}";
var memberElement = new XElement("member", new XAttribute("name", eventId));
AppLogger.LogDebug($" Procesando evento: {eventId}");
// Agregar summary
memberElement.Add(new XElement("summary",
$"Occurs when {eventInfo.Name} is raised."));
// Intentar encontrar documentación existente
string summary = null;
if (existingXmlDoc != null)
{
var existingMember = existingXmlDoc.Descendants("member")
.FirstOrDefault(m => m.Attribute("name")?.Value == eventId);
if (existingMember != null)
{
summary = existingMember.Element("summary")?.Value?.Trim();
}
}
// Si no hay documentación existente, generar una descripción genérica
if (string.IsNullOrEmpty(summary))
{
summary = $"Occurs when {eventInfo.Name} is raised.";
}
var element = new XElement("member",
new XAttribute("name", eventId),
new XElement("summary", summary)
);
// Agregar remarks con el tipo del handler
memberElement.Add(new XElement("remarks",
$"Event handler type: {GetSimpleTypeName(eventInfo.EventHandlerType)}"));
element.Add(new XElement("remarks",
$"Event handler type: {GetSimpleTypeName(eventInfo.EventHandlerType)}"
));
membersElement.Add(memberElement);
membersElement.Add(element);
}
catch (Exception ex)
{
_logWriter.WriteLine($"Error generando documentación para evento {eventInfo.Name}: {ex.Message}");
AppLogger.LogError($"Error generando documentación para evento {eventInfo.Name}: {ex.Message}");
}
}
private void GenerateFieldDocumentation(FieldInfo field, XElement membersElement)
private void GenerateFieldDocumentation(FieldInfo field, XElement membersElement, XDocument existingXmlDoc)
{
try
{
string fieldId = $"F:{field.DeclaringType.FullName}.{field.Name}";
var memberElement = new XElement("member", new XAttribute("name", fieldId));
AppLogger.LogDebug($" Procesando campo: {fieldId}");
// Intentar encontrar documentación existente
string summary = null;
if (existingXmlDoc != null)
{
var existingMember = existingXmlDoc.Descendants("member")
.FirstOrDefault(m => m.Attribute("name")?.Value == fieldId);
if (existingMember != null)
{
summary = existingMember.Element("summary")?.Value?.Trim();
}
}
// Modificadores
string modifiers = "";
@ -265,15 +500,22 @@ namespace NetDocsForLLM.Services
if (field.IsInitOnly) modifiers += "readonly ";
if (field.IsLiteral) modifiers += "constant ";
// Agregar summary
memberElement.Add(new XElement("summary",
$"Represents the {modifiers}{field.Name} field."));
// Si no hay documentación existente, generar una descripción genérica
if (string.IsNullOrEmpty(summary))
{
summary = $"Represents the {modifiers}{field.Name} field.";
}
membersElement.Add(memberElement);
var element = new XElement("member",
new XAttribute("name", fieldId),
new XElement("summary", summary)
);
membersElement.Add(element);
}
catch (Exception ex)
{
_logWriter.WriteLine($"Error generando documentación para campo {field.Name}: {ex.Message}");
AppLogger.LogError($"Error generando documentación para campo {field.Name}: {ex.Message}");
}
}
@ -281,44 +523,64 @@ namespace NetDocsForLLM.Services
private string GetMethodXmlId(MethodInfo method)
{
var parameters = method.GetParameters();
if (parameters.Length == 0)
try
{
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})";
}
catch (Exception ex)
{
AppLogger.LogError($"Error generando XML ID para método {method.Name}: {ex.Message}");
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)
try
{
return $"{GetParameterTypeName(type.GetElementType())}[]";
}
// El formato de los parámetros en los IDs XML de documentación es específico
// 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<>))
// Arrays
if (type.IsArray)
{
return $"System.Collections.Generic.List{{{genericArgs}}}";
return $"{GetParameterTypeName(type.GetElementType())}[]";
}
// Para otros tipos genéricos, usar el nombre completo con {}
return $"{genericTypeDef.FullName.Split('`')[0]}{{{genericArgs}}}";
}
// Genéricos
if (type.IsGenericType)
{
var genericTypeDef = type.GetGenericTypeDefinition();
var genericArgs = string.Join(",", type.GetGenericArguments().Select(GetParameterTypeName));
// Para tipos normales, usar el nombre completo
return type.FullName;
// Para tipos como List<T>, devolver algo como System.Collections.Generic.List{T}
string baseTypeName;
if (genericTypeDef.FullName != null && genericTypeDef.FullName.Contains('`'))
{
baseTypeName = genericTypeDef.FullName.Split('`')[0];
}
else
{
baseTypeName = genericTypeDef.Namespace + "." + genericTypeDef.Name.Split('`')[0];
}
return $"{baseTypeName}{{{genericArgs}}}";
}
// Para tipos normales, usar el nombre completo
return type.FullName;
}
catch (Exception)
{
// En caso de error, devolver un nombre genérico
return "System.Object";
}
}
private string GetTypeKindDescription(Type type)
@ -346,7 +608,7 @@ namespace NetDocsForLLM.Services
{
return "performs an operation.";
}
return $"gets a {GetSimpleTypeName(method.ReturnType)} value.";
return $"returns a {GetSimpleTypeName(method.ReturnType)} value.";
}
return $"with {parameters.Length} parameter{(parameters.Length > 1 ? "s" : "")}.";
@ -383,49 +645,57 @@ namespace NetDocsForLLM.Services
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)
try
{
return GetSimpleTypeName(type.GetElementType());
}
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(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)
// Si es un tipo por referencia (como out o ref parámetros)
if (type.IsByRef)
{
baseName = baseName.Substring(0, tickIndex);
return GetSimpleTypeName(type.GetElementType());
}
var argTypes = type.GetGenericArguments()
.Select(GetSimpleTypeName)
.ToArray();
// 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";
return $"{baseName}<{string.Join(", ", argTypes)}>";
// 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;
}
catch (Exception)
{
// En caso de error, devolver un nombre genérico
return "object";
}
// Para tipos normales, usar solo el nombre simple
return type.Name;
}
}
}

130
ViewModels/LogViewModel.cs Normal file
View File

@ -0,0 +1,130 @@
//--- LogViewModel.cs ---
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NetDocsForLLM.Services;
using NetDocsForLLM.ViewModels;
using Ookii.Dialogs.Wpf;
using System;
using System.Drawing.Printing;
using System.IO;
using System.Reflection.Metadata;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Media.Media3D;
using System.Windows;
using System.Windows.Threading;
using static System.Net.Mime.MediaTypeNames;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.TrayNotify;
namespace NetDocsForLLM.ViewModels
{
public class LogViewModel : ObservableObject
{
private string _logContent;
private int _logLevel;
private readonly DispatcherTimer _refreshTimer;
public string LogContent
{
get => _logContent;
set => SetProperty(ref _logContent, value);
}
public int LogLevel
{
get => _logLevel;
set
{
if (SetProperty(ref _logLevel, value))
{
UpdateLogContent();
}
}
}
public int ErrorCount => AppLogger.ErrorCount;
public int WarningCount => AppLogger.WarningCount;
public int TypeCount => AppLogger.TypeCount;
public int MemberCount => AppLogger.MemberCount;
public IRelayCommand ClearLogCommand { get; }
public IRelayCommand SaveLogCommand { get; }
public IRelayCommand RefreshLogCommand { get; }
public LogViewModel()
{
_logLevel = LogLevel; // Por defecto, mostrar INFO y errores
_logContent = "";
ClearLogCommand = new RelayCommand(ClearLog);
SaveLogCommand = new RelayCommand(SaveLog);
RefreshLogCommand = new RelayCommand(UpdateLogContent);
// Crear timer para actualizar el contenido
_refreshTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(1)
};
_refreshTimer.Tick += (s, e) => UpdateProperties();
_refreshTimer.Start();
// Suscribirse al evento de actualización de logs
AppLogger.LogUpdated += OnLogUpdated;
UpdateLogContent();
}
private void OnLogUpdated(object sender, EventArgs e)
{
// Este método podría ser llamado desde otro hilo, así que usamos el Dispatcher
App.Current.Dispatcher.InvokeAsync(() =>
{
UpdateLogContent();
UpdateProperties();
});
}
private void UpdateProperties()
{
OnPropertyChanged(nameof(ErrorCount));
OnPropertyChanged(nameof(WarningCount));
OnPropertyChanged(nameof(TypeCount));
OnPropertyChanged(nameof(MemberCount));
}
private void UpdateLogContent()
{
LogContent = AppLogger.GetContent((LogLevel)_logLevel);
}
private void ClearLog()
{
AppLogger.Clear();
UpdateLogContent();
}
private void SaveLog()
{
var dialog = new VistaSaveFileDialog
{
Filter = "Archivos de texto (*.txt)|*.txt|Archivos de log (*.log)|*.log",
DefaultExt = ".txt",
Title = "Guardar archivo de log"
};
if (dialog.ShowDialog() == true)
{
try
{
File.WriteAllText(dialog.FileName, AppLogger.GetContent());
AppLogger.LogInfo($"Log guardado en: {dialog.FileName}");
}
catch (Exception ex)
{
AppLogger.LogError($"Error al guardar el log: {ex.Message}");
}
}
}
}
}

View File

@ -12,11 +12,11 @@ using System.Windows;
namespace NetDocsForLLM.ViewModels
{
public class MainViewModel : ObservableObject
public partial class MainViewModel : ObservableObject
{
private readonly IAssemblyAnalyzer _assemblyAnalyzer;
private readonly IDocumentationGenerator _documentationGenerator;
private ObservableCollection<AssemblyModel> _selectedAssemblies;
private DocumentationModel _documentationModel;
private ExportSettings _settings;
@ -24,6 +24,8 @@ namespace NetDocsForLLM.ViewModels
private string _documentationStats;
private string _statusMessage;
private bool _isProcessing;
[ObservableProperty]
private bool _hasGeneratedDocumentation;
public ObservableCollection<AssemblyModel> SelectedAssemblies
@ -68,20 +70,25 @@ namespace NetDocsForLLM.ViewModels
set => SetProperty(ref _isProcessing, value);
}
public bool HasGeneratedDocumentation
{
get => _hasGeneratedDocumentation;
set => SetProperty(ref _hasGeneratedDocumentation, value);
}
public bool HasSelectedAssemblies => SelectedAssemblies.Count > 0;
// Comandos
public IRelayCommand SelectAssembliesCommand { get; }
public IRelayCommand<AssemblyModel> RemoveAssemblyCommand { get; }
public IRelayCommand GenerateDocumentationCommand { get; }
public IRelayCommand CopyToClipboardCommand { get; }
public IRelayCommand ExportDocumentationCommand { get; }
// Estos campos se actualizan desde propiedades computadas
private IRelayCommand _copyToClipboardCommand;
private IRelayCommand _exportDocumentationCommand;
// Redefine los comandos con canExecute actualizado
public IRelayCommand CopyToClipboardCommand =>
_copyToClipboardCommand ??= new RelayCommand(CopyToClipboard, () => HasGeneratedDocumentation);
public IRelayCommand ExportDocumentationCommand =>
_exportDocumentationCommand ??= new RelayCommand(ExportDocumentation, () => HasGeneratedDocumentation);
// Constructor
public MainViewModel(IAssemblyAnalyzer assemblyAnalyzer, IDocumentationGenerator documentationGenerator)
{
_assemblyAnalyzer = assemblyAnalyzer ?? throw new ArgumentNullException(nameof(assemblyAnalyzer));
@ -99,16 +106,27 @@ namespace NetDocsForLLM.ViewModels
SelectAssembliesCommand = new RelayCommand(SelectAssemblies);
RemoveAssemblyCommand = new RelayCommand<AssemblyModel>(RemoveAssembly);
GenerateDocumentationCommand = new AsyncRelayCommand(GenerateDocumentationAsync);
CopyToClipboardCommand = new RelayCommand(CopyToClipboard, () => HasGeneratedDocumentation);
ExportDocumentationCommand = new RelayCommand(ExportDocumentation, () => HasGeneratedDocumentation);
// Subscribe to collection changes to update HasSelectedAssemblies
_selectedAssemblies.CollectionChanged += (sender, e) =>
_selectedAssemblies.CollectionChanged += (sender, e) =>
{
OnPropertyChanged(nameof(HasSelectedAssemblies));
};
}
// Asegúrate de que UpdateCanExecuteForCommands se llame cuando cambie HasGeneratedDocumentation
partial void OnHasGeneratedDocumentationChanged(bool value)
{
UpdateCanExecuteForCommands();
}
// Método para actualizar el CanExecute de los comandos
private void UpdateCanExecuteForCommands()
{
(CopyToClipboardCommand as RelayCommand)?.NotifyCanExecuteChanged();
(ExportDocumentationCommand as RelayCommand)?.NotifyCanExecuteChanged();
}
private void SelectAssemblies()
{
var dialog = new VistaOpenFileDialog
@ -156,7 +174,7 @@ namespace NetDocsForLLM.ViewModels
{
if (SelectedAssemblies.Count == 0)
{
MessageBox.Show("Por favor, seleccione al menos una librería para generar documentación.",
MessageBox.Show("Por favor, seleccione al menos una librería para generar documentación.",
"Sin librerías", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
@ -167,27 +185,31 @@ namespace NetDocsForLLM.ViewModels
StatusMessage = "Generando documentación...";
// Generate documentation
DocumentationModel = await Task.Run(() =>
DocumentationModel = await Task.Run(() =>
_documentationGenerator.GenerateDocumentation(SelectedAssemblies, Settings));
// Generate preview
DocumentationPreview = await Task.Run(() =>
DocumentationPreview = await Task.Run(() =>
_documentationGenerator.GenerateDocumentationPreview(DocumentationModel, Settings));
// Update stats
var totalTypes = DocumentationModel.Namespaces.Sum(n => n.Types.Count);
var totalMembers = DocumentationModel.Namespaces.Sum(n =>
var totalMembers = DocumentationModel.Namespaces.Sum(n =>
n.Types.Sum(t => t.Members.Count));
DocumentationStats = $"Resumen: {DocumentationModel.Namespaces.Count} namespaces, " +
$"{totalTypes} tipos, {totalMembers} miembros";
// Estas dos líneas son cruciales - aseguran que HasGeneratedDocumentation se actualice
// y que los comandos se habiliten
HasGeneratedDocumentation = true;
UpdateCanExecuteForCommands();
StatusMessage = "Documentación generada correctamente";
}
catch (Exception ex)
{
MessageBox.Show($"Error al generar la documentación: {ex.Message}",
MessageBox.Show($"Error al generar la documentación: {ex.Message}",
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
StatusMessage = "Error al generar documentación";
}
@ -209,7 +231,7 @@ namespace NetDocsForLLM.ViewModels
}
catch (Exception ex)
{
MessageBox.Show($"Error al copiar al portapapeles: {ex.Message}",
MessageBox.Show($"Error al copiar al portapapeles: {ex.Message}",
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@ -219,13 +241,33 @@ namespace NetDocsForLLM.ViewModels
if (string.IsNullOrEmpty(DocumentationPreview))
return;
// Determinar extensión y filtro basado en formato seleccionado
string extension;
string filter;
switch (Settings.OutputFormat)
{
case OutputFormat.Xml:
extension = ".xml";
filter = "Archivos XML (*.xml)|*.xml";
break;
case OutputFormat.Json:
extension = ".json";
filter = "Archivos JSON (*.json)|*.json";
break;
case OutputFormat.Yaml:
default:
extension = ".yaml";
filter = "Archivos YAML (*.yaml;*.yml)|*.yaml;*.yml";
break;
}
var dialog = new VistaSaveFileDialog
{
Filter = Settings.OutputFormat == OutputFormat.Json
? "Archivos JSON (*.json)|*.json"
: "Archivos YAML (*.yaml;*.yml)|*.yaml;*.yml",
DefaultExt = Settings.OutputFormat == OutputFormat.Json ? ".json" : ".yaml",
Title = "Guardar documentación"
Filter = filter,
DefaultExt = extension,
Title = "Guardar documentación",
OverwritePrompt = true
};
if (dialog.ShowDialog() == true)
@ -237,10 +279,10 @@ namespace NetDocsForLLM.ViewModels
}
catch (Exception ex)
{
MessageBox.Show($"Error al guardar el archivo: {ex.Message}",
MessageBox.Show($"Error al guardar el archivo: {ex.Message}",
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}
}
}

47
Views/LogView.xaml Normal file
View File

@ -0,0 +1,47 @@
<UserControl x:Class="NetDocsForLLM.Views.LogView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:NetDocsForLLM.Views"
mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<Button Content="Limpiar Log" Command="{Binding ClearLogCommand}" Padding="10,5" Margin="0,0,10,0" />
<Button Content="Guardar Log" Command="{Binding SaveLogCommand}" Padding="10,5" Margin="0,0,10,0" />
<Button Content="Actualizar" Command="{Binding RefreshLogCommand}" Padding="10,5" Margin="0,0,10,0" />
<TextBlock VerticalAlignment="Center" Text="Nivel de detalle:" />
<ComboBox Margin="5,0,0,0" SelectedIndex="{Binding LogLevel}" Width="120">
<ComboBoxItem Content="Errores" />
<ComboBoxItem Content="Advertencias" />
<ComboBoxItem Content="Información" />
<ComboBoxItem Content="Depuración" />
</ComboBox>
</StackPanel>
<TextBox Grid.Row="1" Text="{Binding LogContent, Mode=OneWay}" IsReadOnly="True"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" FontFamily="Consolas"
FontSize="12" AcceptsReturn="True" TextWrapping="NoWrap" Background="#f5f5f5" Padding="10" />
<StackPanel Grid.Row="2" Margin="0,10,0,0" Orientation="Horizontal">
<TextBlock Text="Errores:" Foreground="Red" FontWeight="Bold" Margin="0,0,5,0" />
<TextBlock Text="{Binding ErrorCount}" />
<TextBlock Text="Advertencias:" Foreground="Orange" FontWeight="Bold" Margin="15,0,5,0" />
<TextBlock Text="{Binding WarningCount}" />
<TextBlock Text="Total tipos analizados:" FontWeight="Bold" Margin="15,0,5,0" />
<TextBlock Text="{Binding TypeCount}" />
<TextBlock Text="Total miembros analizados:" FontWeight="Bold" Margin="15,0,5,0" />
<TextBlock Text="{Binding MemberCount}" />
</StackPanel>
</Grid>
</UserControl>

15
Views/LogView.xaml.cs Normal file
View File

@ -0,0 +1,15 @@
//--- LogView.xaml.cs ---
using NetDocsForLLM.ViewModels;
using System.Windows.Controls;
namespace NetDocsForLLM.Views
{
public partial class LogView : UserControl
{
public LogView()
{
InitializeComponent();
DataContext = new LogViewModel();
}
}
}