diff --git a/Radzen.Server.sln b/Radzen.Server.sln index d7218499..f24d163a 100644 --- a/Radzen.Server.sln +++ b/Radzen.Server.sln @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadzenBlazorDemos.Server", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadzenBlazorDemos.Host", "RadzenBlazorDemos.Host\RadzenBlazorDemos.Host.csproj", "{8B163BC8-BB91-464A-ACF9-F2D7AF70D3CA}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadzenBlazorDemos.Tools", "RadzenBlazorDemos.Tools\RadzenBlazorDemos.Tools.csproj", "{8726EFA9-554D-41D4-8B99-27744DCC7017}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {8B163BC8-BB91-464A-ACF9-F2D7AF70D3CA}.Debug|Any CPU.Build.0 = Debug|Any CPU {8B163BC8-BB91-464A-ACF9-F2D7AF70D3CA}.Release|Any CPU.ActiveCfg = Release|Any CPU {8B163BC8-BB91-464A-ACF9-F2D7AF70D3CA}.Release|Any CPU.Build.0 = Release|Any CPU + {8726EFA9-554D-41D4-8B99-27744DCC7017}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8726EFA9-554D-41D4-8B99-27744DCC7017}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8726EFA9-554D-41D4-8B99-27744DCC7017}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8726EFA9-554D-41D4-8B99-27744DCC7017}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Radzen.WebAssembly.sln b/Radzen.WebAssembly.sln index 8e3d22fa..afcb2d01 100644 --- a/Radzen.WebAssembly.sln +++ b/Radzen.WebAssembly.sln @@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadzenBlazorDemos", "Radzen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadzenBlazorDemos.Host", "RadzenBlazorDemos.Host\RadzenBlazorDemos.Host.csproj", "{18702B3F-791E-45F3-BCFD-1792A1300AAB}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadzenBlazorDemos.Tools", "RadzenBlazorDemos.Tools\RadzenBlazorDemos.Tools.csproj", "{8726EFA9-554D-41D4-8B99-27744DCC7017}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +37,10 @@ Global {EC869401-304A-45BC-93CA-C1CDFAAA7F7B}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC869401-304A-45BC-93CA-C1CDFAAA7F7B}.Release|Any CPU.ActiveCfg = Release|Any CPU {EC869401-304A-45BC-93CA-C1CDFAAA7F7B}.Release|Any CPU.Build.0 = Release|Any CPU + {8726EFA9-554D-41D4-8B99-27744DCC7017}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8726EFA9-554D-41D4-8B99-27744DCC7017}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8726EFA9-554D-41D4-8B99-27744DCC7017}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8726EFA9-554D-41D4-8B99-27744DCC7017}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Radzen.sln b/Radzen.sln index fe69e719..11b6a5f7 100644 --- a/Radzen.sln +++ b/Radzen.sln @@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadzenBlazorDemos.Host", "R EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadzenBlazorDemos.Server", "RadzenBlazorDemos.Server\RadzenBlazorDemos.Server.csproj", "{EC869401-304A-45BC-93CA-C1CDFAAA7F7B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadzenBlazorDemos.Tools", "RadzenBlazorDemos.Tools\RadzenBlazorDemos.Tools.csproj", "{8726EFA9-554D-41D4-8B99-27744DCC7017}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{972C3730-C322-4A6F-A106-18D53BF8E08D}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig @@ -44,6 +46,10 @@ Global {EC869401-304A-45BC-93CA-C1CDFAAA7F7B}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC869401-304A-45BC-93CA-C1CDFAAA7F7B}.Release|Any CPU.ActiveCfg = Release|Any CPU {EC869401-304A-45BC-93CA-C1CDFAAA7F7B}.Release|Any CPU.Build.0 = Release|Any CPU + {8726EFA9-554D-41D4-8B99-27744DCC7017}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8726EFA9-554D-41D4-8B99-27744DCC7017}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8726EFA9-554D-41D4-8B99-27744DCC7017}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8726EFA9-554D-41D4-8B99-27744DCC7017}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/RadzenBlazorDemos.Tools/Program.cs b/RadzenBlazorDemos.Tools/Program.cs new file mode 100644 index 00000000..9f4b738b --- /dev/null +++ b/RadzenBlazorDemos.Tools/Program.cs @@ -0,0 +1,689 @@ +using System.Text; +using System.Text.RegularExpressions; + +namespace RadzenBlazorDemos.Tools; + +class Program +{ + static int Main(string[] args) + { + if (args.Length < 2) + { + Console.Error.WriteLine("Usage: GenerateLlmsTxt [servicesPath] [modelsPath]"); + return 1; + } + + var outputPath = args[0]; + var pagesPath = args[1]; + var servicesPath = args.Length > 2 && !string.IsNullOrWhiteSpace(args[2]) ? args[2] : null; + var modelsPath = args.Length > 3 && !string.IsNullOrWhiteSpace(args[3]) ? args[3] : null; + + if (!Directory.Exists(pagesPath)) + { + Console.Error.WriteLine($"Pages path does not exist: {pagesPath}"); + return 1; + } + + try + { + Generate(outputPath, pagesPath, servicesPath, modelsPath); + Console.WriteLine($"Successfully generated llms.txt at: {outputPath}"); + return 0; + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error generating llms.txt: {ex.Message}"); + Console.Error.WriteLine(ex.StackTrace); + return 1; + } + } + + static void Generate(string outputPath, string pagesPath, string servicesPath, string modelsPath) + { + var sb = new StringBuilder(); + + sb.AppendLine("# Radzen Blazor Components - Demo Application"); + sb.AppendLine(); + sb.AppendLine("This file contains all demo pages and examples from the Radzen Blazor Components demo application."); + sb.AppendLine($"Generated: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss UTC}"); + sb.AppendLine(); + sb.AppendLine("## Table of Contents"); + sb.AppendLine(); + + // Collect all demo files + var allPages = Directory.GetFiles(pagesPath, "*.razor", SearchOption.AllDirectories) + .Where(f => !Path.GetFileName(f).StartsWith("_")) + .ToList(); + + // Separate main pages (with @page directive) from example components + var mainPages = new List(); + var examplePages = new List(); + + foreach (var page in allPages) + { + var content = File.ReadAllText(page, Encoding.UTF8); + // Check if it has @page directive - main pages have routes + if (Regex.IsMatch(content, @"^\s*@page\s+", RegexOptions.Multiline | RegexOptions.IgnoreCase)) + { + mainPages.Add(page); + } + else + { + examplePages.Add(page); + } + } + + // Sort main pages ascending by filename + mainPages = mainPages.OrderBy(p => Path.GetFileName(p)).ToList(); + + // Keep all pages for content generation (sorted ascending) + var pages = allPages.OrderBy(p => Path.GetFileName(p)).ToList(); + + var pageCs = Directory.GetFiles(pagesPath, "*.cs", SearchOption.AllDirectories) + .OrderBy(f => Path.GetFileName(f)) + .ToList(); + + var services = !string.IsNullOrEmpty(servicesPath) && Directory.Exists(servicesPath) + ? Directory.GetFiles(servicesPath, "*.cs", SearchOption.AllDirectories).OrderBy(f => Path.GetFileName(f)).ToList() + : Enumerable.Empty(); + + var models = !string.IsNullOrEmpty(modelsPath) && Directory.Exists(modelsPath) + ? Directory.GetFiles(modelsPath, "*.cs", SearchOption.AllDirectories).OrderBy(f => Path.GetFileName(f)).ToList() + : Enumerable.Empty(); + + // Generate table of contents - only include main pages + sb.AppendLine("### Demo Pages"); + foreach (var page in mainPages) + { + var relativePath = Path.GetRelativePath(pagesPath, page).Replace('\\', '/'); + var fileName = Path.GetFileNameWithoutExtension(page); + sb.AppendLine($"- [{fileName}](#{SanitizeAnchor(fileName)}) - `{relativePath}`"); + } + + if (pageCs.Any()) + { + sb.AppendLine(); + sb.AppendLine("### Code-Behind Files"); + foreach (var csFile in pageCs) + { + var fileName = Path.GetFileNameWithoutExtension(csFile); + sb.AppendLine($"- [{fileName}](#{SanitizeAnchor(fileName)}-code-behind)"); + } + } + + sb.AppendLine(); + sb.AppendLine("---"); + sb.AppendLine(); + + // Add demo pages + sb.AppendLine("## Demo Pages"); + sb.AppendLine(); + + foreach (var page in pages) + { + var relativePath = Path.GetRelativePath(pagesPath, page).Replace('\\', '/'); + var fileName = Path.GetFileNameWithoutExtension(page); + var fileContent = File.ReadAllText(page, Encoding.UTF8); + var extractedContent = ExtractDescriptionsAndExamples(fileContent, page); + + if (string.IsNullOrWhiteSpace(extractedContent)) + continue; + + // Add explicit HTML anchor to ensure TOC links work across all markdown parsers + var anchor = SanitizeAnchor(fileName); + sb.AppendLine($""); + sb.AppendLine($"### {fileName}"); + sb.AppendLine(); + sb.AppendLine($"**Path:** `{relativePath}`"); + sb.AppendLine(); + sb.AppendLine(extractedContent); + sb.AppendLine(); + sb.AppendLine("---"); + sb.AppendLine(); + } + + // Add C# code-behind files + if (pageCs.Any()) + { + sb.AppendLine("## Code-Behind Files"); + sb.AppendLine(); + + foreach (var csFile in pageCs) + { + var relativePath = Path.GetRelativePath(pagesPath, csFile).Replace('\\', '/'); + var fileName = Path.GetFileNameWithoutExtension(csFile); + var fileContent = File.ReadAllText(csFile, Encoding.UTF8); + + var codeBehindAnchor = SanitizeAnchor($"{fileName}-code-behind"); + sb.AppendLine($""); + sb.AppendLine($"### {fileName} (Code-Behind)"); + sb.AppendLine(); + sb.AppendLine($"**Path:** `{relativePath}`"); + sb.AppendLine(); + sb.AppendLine("```csharp"); + sb.AppendLine(fileContent); + sb.AppendLine("```"); + sb.AppendLine(); + sb.AppendLine("---"); + sb.AppendLine(); + } + } + + // Add services + if (services.Any()) + { + sb.AppendLine("## Services"); + sb.AppendLine(); + + foreach (var service in services) + { + var relativePath = Path.GetRelativePath(servicesPath, service).Replace('\\', '/'); + var fileName = Path.GetFileNameWithoutExtension(service); + var fileContent = File.ReadAllText(service, Encoding.UTF8); + + sb.AppendLine($"### {fileName}"); + sb.AppendLine(); + sb.AppendLine($"**Path:** `{relativePath}`"); + sb.AppendLine(); + sb.AppendLine("```csharp"); + sb.AppendLine(fileContent); + sb.AppendLine("```"); + sb.AppendLine(); + sb.AppendLine("---"); + sb.AppendLine(); + } + } + + // Add models + if (models.Any()) + { + sb.AppendLine("## Data Models"); + sb.AppendLine(); + + foreach (var model in models) + { + var relativePath = Path.GetRelativePath(modelsPath, model).Replace('\\', '/'); + var fileName = Path.GetFileNameWithoutExtension(model); + var fileContent = File.ReadAllText(model, Encoding.UTF8); + + sb.AppendLine($"### {fileName}"); + sb.AppendLine(); + sb.AppendLine($"**Path:** `{relativePath}`"); + sb.AppendLine(); + sb.AppendLine("```csharp"); + sb.AppendLine(fileContent); + sb.AppendLine("```"); + sb.AppendLine(); + sb.AppendLine("---"); + sb.AppendLine(); + } + } + + // Write to file + var outputDir = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(outputDir)) + { + Directory.CreateDirectory(outputDir); + } + File.WriteAllText(outputPath, sb.ToString(), Encoding.UTF8); + } + + static string SanitizeAnchor(string text) + { + if (string.IsNullOrWhiteSpace(text)) + return ""; + + // Markdown heading anchors are generated by: + // 1. Convert to lowercase + // 2. Replace spaces and special chars with hyphens + // 3. Remove multiple consecutive hyphens + // 4. Trim hyphens from start/end + + var anchor = text.ToLower() + .Replace(" ", "-") + .Replace("_", "-") + .Replace(".", "-") + .Replace("(", "") + .Replace(")", "") + .Replace("[", "") + .Replace("]", "") + .Replace("{", "") + .Replace("}", "") + .Replace("&", "") + .Replace(":", "") + .Replace(";", "") + .Replace("!", "") + .Replace("?", "") + .Replace("*", "") + .Replace("/", "-") + .Replace("\\", "-") + .Replace("+", "-") + .Replace("=", "-") + .Replace("@", "") + .Replace("#", "") + .Replace("%", "") + .Replace("$", "") + .Replace("^", "") + .Replace("~", "") + .Replace("`", "") + .Replace("'", "") + .Replace("\"", ""); + + // Remove multiple consecutive hyphens + anchor = Regex.Replace(anchor, @"-+", "-"); + + // Trim hyphens from start and end + anchor = anchor.Trim('-'); + + return anchor; + } + + static string ExtractDescriptionsAndExamples(string razorContent, string pagePath) + { + var result = new StringBuilder(); + var pagesDirectory = Path.GetDirectoryName(pagePath) ?? ""; + + // Remove @code blocks entirely + razorContent = Regex.Replace(razorContent, + @"@code\s*\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}", + "", + RegexOptions.Singleline | RegexOptions.IgnoreCase); + + // Remove @page, @inject, @layout directives + razorContent = Regex.Replace(razorContent, + @"@(page|inject|layout|using|namespace|implements)[^\r\n]*", + "", + RegexOptions.IgnoreCase | RegexOptions.Multiline); + + // Split content into lines to process sequentially + var lines = razorContent.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + + for (int i = 0; i < lines.Length; i++) + { + var line = lines[i]; + + // Check for RadzenText + if (line.Contains("", RegexOptions.IgnoreCase); + + depth += openMatches.Count - closeMatches.Count; + + if (closeMatches.Count > 0 && depth == 0) + { + index = i; + break; + } + } + + var tagContent = fullTag.ToString(); + + // Extract attributes - check both TextStyle and TagName for heading detection + var textStyleMatch = Regex.Match(tagContent, @"TextStyle=""TextStyle\.(H[2-6])""", RegexOptions.IgnoreCase); + var tagNameMatch = Regex.Match(tagContent, @"TagName=""TagName\.(H[1-6])""", RegexOptions.IgnoreCase); + + bool isHeading = false; + int headingLevel = 0; + + // Prefer TextStyle for heading level, but also check TagName + if (textStyleMatch.Success) + { + var hMatch = Regex.Match(textStyleMatch.Groups[1].Value, @"H(\d)"); + if (hMatch.Success) + { + headingLevel = int.Parse(hMatch.Groups[1].Value); + isHeading = headingLevel >= 2 && headingLevel <= 6; + } + } + + // If no TextStyle heading but TagName suggests heading, use that + if (!isHeading && tagNameMatch.Success) + { + var hMatch = Regex.Match(tagNameMatch.Groups[1].Value, @"H(\d)"); + if (hMatch.Success) + { + var tagLevel = int.Parse(hMatch.Groups[1].Value); + if (tagLevel >= 2 && tagLevel <= 6) + { + headingLevel = tagLevel; + isHeading = true; + } + } + } + + // Extract inner content + var contentMatch = Regex.Match(tagContent, @"]*>([\s\S]*?)", RegexOptions.IgnoreCase); + if (!contentMatch.Success) + return (string.Empty, false); + + var content = contentMatch.Groups[1].Value.Trim(); + + // Convert tags to markdown inline code BEFORE converting links + content = ConvertCodeTagsToMarkdown(content); + + // Convert RadzenLink to markdown links BEFORE removing HTML tags + content = ConvertRadzenLinksToMarkdown(content); + + // Remove all HTML tags (except markdown links which are already converted) + content = Regex.Replace(content, @"<[^>]+>", ""); + + // Remove @ expressions + content = Regex.Replace(content, @"@[A-Za-z0-9_.()]+", ""); + + // Clean up whitespace + content = Regex.Replace(content, @"\s+", " ").Trim(); + + // Format as heading if needed + if (isHeading && !string.IsNullOrWhiteSpace(content)) + { + // Map heading levels: H2 -> ####, H4 -> ####, H5 -> #####, H6 -> ###### + int markdownLevel = 4; // Default to #### + if (headingLevel == 4) markdownLevel = 4; // H4 -> #### + else if (headingLevel == 5) markdownLevel = 5; // H5 -> ##### + else if (headingLevel == 6) markdownLevel = 6; // H6 -> ###### + else if (headingLevel == 2) markdownLevel = 4; // H2 -> #### + + content = new string('#', markdownLevel) + " " + content; + } + + return (content, isHeading); + } + + static string ConvertCodeTagsToMarkdown(string content) + { + // Match ... tags + var codeMatches = Regex.Matches(content, + @"([\s\S]*?)", + RegexOptions.IgnoreCase); + + // Process in reverse to maintain indices + var matchesArray = new Match[codeMatches.Count]; + for (int i = 0; i < codeMatches.Count; i++) + { + matchesArray[i] = codeMatches[i]; + } + for (int i = matchesArray.Length - 1; i >= 0; i--) + { + var match = matchesArray[i]; + var codeContent = match.Groups[1].Value.Trim(); + + // Remove nested HTML tags from code content + codeContent = Regex.Replace(codeContent, @"<[^>]+>", ""); + + // Clean up @("@bind-Selected") to @bind-Selected (remove extra quotes and parentheses) + // Match: @("...") pattern and extract the content inside quotes + // Pattern: @("@bind-Selected") -> @bind-Selected + // Use non-verbatim string to avoid quote escaping issues + codeContent = Regex.Replace(codeContent, "@\\(\"([^\"]+)\"\\)", "$1"); + codeContent = Regex.Replace(codeContent, "@\\('([^']+)'\\)", "$1"); + + if (!string.IsNullOrWhiteSpace(codeContent)) + { + var markdownCode = $"`{codeContent}`"; + content = content.Substring(0, match.Index) + markdownCode + + content.Substring(match.Index + match.Length); + } + } + + return content; + } + + static string ConvertRadzenLinksToMarkdown(string content) + { + // Handle both self-closing and opening/closing RadzenLink tags + // Match the entire tag first, then extract attributes + + // Pattern for self-closing: + var selfClosingPattern = @"]*?)\s*/>"; + // Pattern for opening/closing: ... + var openClosePattern = @"]*?)>([\s\S]*?)"; + + // Process self-closing tags + var selfClosingMatches = Regex.Matches(content, selfClosingPattern, RegexOptions.IgnoreCase); + var selfClosingArray = new Match[selfClosingMatches.Count]; + for (int i = 0; i < selfClosingMatches.Count; i++) + { + selfClosingArray[i] = selfClosingMatches[i]; + } + for (int i = selfClosingArray.Length - 1; i >= 0; i--) + { + var match = selfClosingArray[i]; + var attributes = match.Groups[1].Value; + var (path, text) = ExtractLinkAttributes(attributes, ""); + + if (!string.IsNullOrWhiteSpace(path)) + { + string linkText = !string.IsNullOrWhiteSpace(text) ? text : path; + var markdownLink = $"[{linkText}]({path})"; + content = content.Substring(0, match.Index) + markdownLink + + content.Substring(match.Index + match.Length); + } + } + + // Process opening/closing tags + var linkMatches = Regex.Matches(content, openClosePattern, RegexOptions.IgnoreCase); + var matchesArray = new Match[linkMatches.Count]; + for (int i = 0; i < linkMatches.Count; i++) + { + matchesArray[i] = linkMatches[i]; + } + for (int i = matchesArray.Length - 1; i >= 0; i--) + { + var match = matchesArray[i]; + var attributes = match.Groups[1].Value; + var innerContent = match.Groups[2].Value.Trim(); + var (path, text) = ExtractLinkAttributes(attributes, innerContent); + + if (!string.IsNullOrWhiteSpace(path)) + { + string linkText = !string.IsNullOrWhiteSpace(text) ? text : + (!string.IsNullOrWhiteSpace(innerContent) ? Regex.Replace(innerContent, @"<[^>]+>", "").Trim() : path); + + if (string.IsNullOrWhiteSpace(linkText)) + linkText = path; + + var markdownLink = $"[{linkText}]({path})"; + content = content.Substring(0, match.Index) + markdownLink + + content.Substring(match.Index + match.Length); + } + } + + return content; + } + + static (string Path, string Text) ExtractLinkAttributes(string attributes, string innerContent) + { + string path = ""; + string text = ""; + + // Extract Path attribute (can be in any order) + var pathMatch = Regex.Match(attributes, @"Path=[""]?([^""\s>]+)[""]?", RegexOptions.IgnoreCase); + if (pathMatch.Success) + { + path = pathMatch.Groups[1].Value; + } + + // Extract Text attribute + var textMatch = Regex.Match(attributes, @"Text=[""]?([^""]+)[""]?", RegexOptions.IgnoreCase); + if (textMatch.Success) + { + text = textMatch.Groups[1].Value.Trim(); + } + + // If no Text attribute and there's inner content, use that + if (string.IsNullOrWhiteSpace(text) && !string.IsNullOrWhiteSpace(innerContent)) + { + text = Regex.Replace(innerContent, @"<[^>]+>", "").Trim(); + } + + return (path, text); + } + + static string ExtractRadzenExampleContent(string[] lines, ref int index, string pagesDirectory) + { + var fullTag = new StringBuilder(); + var depth = 0; + + // Collect the complete RadzenExample tag + for (int i = index; i < lines.Length; i++) + { + var line = lines[i]; + fullTag.AppendLine(line); + + var openMatches = Regex.Matches(line, @"", RegexOptions.IgnoreCase); + + depth += openMatches.Count - closeMatches.Count; + + if (closeMatches.Count > 0 && depth == 0) + { + index = i; + break; + } + } + + var tagContent = fullTag.ToString(); + + // Extract Example attribute + var exampleMatch = Regex.Match(tagContent, @"Example=[""]?([^""\s>]+)[""]?", RegexOptions.IgnoreCase); + if (exampleMatch.Success) + { + var exampleName = exampleMatch.Groups[1].Value.Trim(); + var exampleFilePath = Path.Combine(pagesDirectory, $"{exampleName}.razor"); + + if (File.Exists(exampleFilePath)) + { + var exampleContent = File.ReadAllText(exampleFilePath, Encoding.UTF8); + return CleanExampleFile(exampleContent); + } + } + + // Fallback: extract inline content + var inlineMatch = Regex.Match(tagContent, @"]*>([\s\S]*?)", RegexOptions.IgnoreCase); + if (inlineMatch.Success) + { + return CleanExampleContent(inlineMatch.Groups[1].Value); + } + + return string.Empty; + } + + static string CleanTextContent(string content) + { + if (string.IsNullOrWhiteSpace(content)) + return string.Empty; + + var result = content; + + // Remove all HTML tags (including , , , etc.) + result = Regex.Replace(result, @"<[^>]+>", ""); + + // Remove @ expressions (like @variable, @ExampleService) + result = Regex.Replace(result, @"@[A-Za-z0-9_.()]+", ""); + + // Clean up multiple spaces but preserve single newlines for readability + result = Regex.Replace(result, @"[ \t]+", " "); + + // Clean up multiple newlines (max 2 consecutive) + result = Regex.Replace(result, @"(\r?\n){3,}", Environment.NewLine + Environment.NewLine); + + return result.Trim(); + } + + static string CleanExampleContent(string content) + { + if (string.IsNullOrWhiteSpace(content)) + return string.Empty; + + var result = content; + + // Remove nested RadzenExample tags if any + result = Regex.Replace(result, + @"]*>[\s\S]*?", + "", + RegexOptions.IgnoreCase); + + // Trim each line and remove empty lines + var lines = result.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None) + .Select(l => l.Trim()) + .Where(l => !string.IsNullOrWhiteSpace(l)) + .ToList(); + + return string.Join(Environment.NewLine, lines); + } + + static string CleanExampleFile(string content) + { + if (string.IsNullOrWhiteSpace(content)) + return string.Empty; + + var result = content; + + // Remove @code blocks + result = Regex.Replace(result, + @"@code\s*\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}", + "", + RegexOptions.Singleline | RegexOptions.IgnoreCase); + + // Remove @using, @inject, @page directives + result = Regex.Replace(result, + @"@(using|inject|page|layout|namespace|implements)[^\r\n]*", + "", + RegexOptions.IgnoreCase | RegexOptions.Multiline); + + // Clean up multiple blank lines + result = Regex.Replace(result, @"(\r?\n\s*){3,}", Environment.NewLine + Environment.NewLine); + + return result.Trim(); + } +} + diff --git a/RadzenBlazorDemos.Tools/RadzenBlazorDemos.Tools.csproj b/RadzenBlazorDemos.Tools/RadzenBlazorDemos.Tools.csproj new file mode 100644 index 00000000..09e8f6eb --- /dev/null +++ b/RadzenBlazorDemos.Tools/RadzenBlazorDemos.Tools.csproj @@ -0,0 +1,9 @@ + + + Exe + net8.0 + enable + enable + + + diff --git a/RadzenBlazorDemos/RadzenBlazorDemos.csproj b/RadzenBlazorDemos/RadzenBlazorDemos.csproj index e7f4b521..ce8896a0 100644 --- a/RadzenBlazorDemos/RadzenBlazorDemos.csproj +++ b/RadzenBlazorDemos/RadzenBlazorDemos.csproj @@ -50,4 +50,32 @@ + + + + + + $(ProjectDir)wwwroot\llms.txt + $(MSBuildThisFileDirectory)..\RadzenBlazorDemos.Tools\RadzenBlazorDemos.Tools.csproj + $(MSBuildThisFileDirectory)..\RadzenBlazorDemos.Tools\bin\$(Configuration)\net8.0\ + $(LlmsTxtToolDir)RadzenBlazorDemos.Tools.exe + $(LlmsTxtToolDir)RadzenBlazorDemos.Tools + + + + + + + + + + + + \ No newline at end of file