using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Markdig; namespace AxCopilot.Services.Agent; public class FormatConvertTool : IAgentTool { public string Name => "format_convert"; public string Description => "Convert a document between formats. Supports: md→html (Markdown to styled HTML with mood CSS), html→text (strip HTML tags to plain text), csv→html (CSV to HTML table). For complex conversions (docx↔html, xlsx↔csv), read the source with document_read/file_read, then use the appropriate creation skill (html_create, docx_create, etc.)."; public ToolParameterSchema Parameters { get { ToolParameterSchema obj = new ToolParameterSchema { Properties = new Dictionary { ["source"] = new ToolProperty { Type = "string", Description = "Source file path to convert" }, ["target"] = new ToolProperty { Type = "string", Description = "Target output file path (extension determines format)" }, ["mood"] = new ToolProperty { Type = "string", Description = "Design mood for HTML output (default: modern). Only used for md→html conversion." } } }; int num = 2; List list = new List(num); CollectionsMarshal.SetCount(list, num); Span span = CollectionsMarshal.AsSpan(list); span[0] = "source"; span[1] = "target"; obj.Required = list; return obj; } } public async Task ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct) { string source = args.GetProperty("source").GetString() ?? ""; string target = args.GetProperty("target").GetString() ?? ""; JsonElement m; string mood = (args.TryGetProperty("mood", out m) ? (m.GetString() ?? "modern") : "modern"); string srcPath = FileReadTool.ResolvePath(source, context.WorkFolder); string tgtPath = FileReadTool.ResolvePath(target, context.WorkFolder); if (context.ActiveTab == "Cowork") { tgtPath = AgentContext.EnsureTimestampedPath(tgtPath); } if (!context.IsPathAllowed(srcPath)) { return ToolResult.Fail("소스 경로 접근 차단: " + srcPath); } if (!context.IsPathAllowed(tgtPath)) { return ToolResult.Fail("대상 경로 접근 차단: " + tgtPath); } if (!File.Exists(srcPath)) { return ToolResult.Fail("소스 파일 없음: " + srcPath); } if (!(await context.CheckWritePermissionAsync("format_convert", tgtPath))) { return ToolResult.Fail("쓰기 권한이 거부되었습니다."); } string srcExt = Path.GetExtension(srcPath).ToLowerInvariant(); string tgtExt = Path.GetExtension(tgtPath).ToLowerInvariant(); string convKey = srcExt + "→" + tgtExt; try { string srcContent = await File.ReadAllTextAsync(srcPath, ct); string result; switch (convKey) { case ".md→.html": { MarkdownPipeline pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); string bodyHtml = Markdown.ToHtml(srcContent, pipeline); string css = TemplateService.GetCss(mood); result = $"\n\n\n\n\n\n\n
\n{bodyHtml}\n
\n\n"; break; } case ".html→.txt": case ".htm→.txt": result = StripHtmlTags(srcContent); break; case ".csv→.html": result = CsvToHtmlTable(srcContent, mood); break; case ".md→.txt": result = StripMarkdown(srcContent); break; default: return ToolResult.Fail("직접 변환 미지원: " + convKey + "\n대안: source를 file_read/document_read로 읽은 뒤, 적절한 생성 스킬(html_create, docx_create, excel_create 등)을 사용하세요."); } string dir = Path.GetDirectoryName(tgtPath); if (!string.IsNullOrEmpty(dir)) { Directory.CreateDirectory(dir); } await File.WriteAllTextAsync(tgtPath, result, ct); string srcName = Path.GetFileName(srcPath); string tgtName = Path.GetFileName(tgtPath); return ToolResult.Ok($"변환 완료: {srcName} → {tgtName}\n변환 유형: {convKey}\n출력 크기: {result.Length:N0}자", tgtPath); } catch (Exception ex) { return ToolResult.Fail("변환 오류: " + ex.Message); } } private static string StripHtmlTags(string html) { string input = Regex.Replace(html, "<(script|style)[^>]*>.*?", "", RegexOptions.IgnoreCase | RegexOptions.Singleline); input = Regex.Replace(input, "", "\n", RegexOptions.IgnoreCase); input = Regex.Replace(input, "", "\n", RegexOptions.IgnoreCase); input = Regex.Replace(input, "<[^>]+>", ""); input = WebUtility.HtmlDecode(input); input = Regex.Replace(input, "\\n{3,}", "\n\n"); return input.Trim(); } private static string StripMarkdown(string md) { string input = md; input = Regex.Replace(input, "^#{1,6}\\s+", "", RegexOptions.Multiline); input = Regex.Replace(input, "\\*\\*(.+?)\\*\\*", "$1"); input = Regex.Replace(input, "\\*(.+?)\\*", "$1"); input = Regex.Replace(input, "`(.+?)`", "$1"); input = Regex.Replace(input, "^\\s*[-*+]\\s+", "", RegexOptions.Multiline); input = Regex.Replace(input, "^\\s*\\d+\\.\\s+", "", RegexOptions.Multiline); input = Regex.Replace(input, "\\[(.+?)\\]\\(.+?\\)", "$1"); return input.Trim(); } private static string CsvToHtmlTable(string csv, string mood) { string[] array = csv.Split('\n', StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0) { return "

빈 CSV 파일

"; } string css = TemplateService.GetCss(mood); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("\n\n\n"); StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(54, 1, stringBuilder2); handler.AppendLiteral("\n\n\n
"); stringBuilder3.AppendLine(ref handler); stringBuilder.AppendLine(""); string[] array2 = ParseCsvLine(array[0]); string[] array3 = array2; foreach (string value in array3) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2); handler.AppendLiteral(""); stringBuilder4.Append(ref handler); } stringBuilder.AppendLine(""); for (int j = 1; j < Math.Min(array.Length, 1001); j++) { string[] array4 = ParseCsvLine(array[j]); stringBuilder.Append(""); string[] array5 = array4; foreach (string value2 in array5) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2); handler.AppendLiteral(""); stringBuilder5.Append(ref handler); } stringBuilder.AppendLine(""); } stringBuilder.AppendLine("
"); handler.AppendFormatted(WebUtility.HtmlEncode(value)); handler.AppendLiteral("
"); handler.AppendFormatted(WebUtility.HtmlEncode(value2)); handler.AppendLiteral("
\n
\n\n"); return stringBuilder.ToString(); } private static string[] ParseCsvLine(string line) { List list = new List(); StringBuilder stringBuilder = new StringBuilder(); bool flag = false; for (int i = 0; i < line.Length; i++) { char c = line[i]; if (flag) { if (c == '"' && i + 1 < line.Length && line[i + 1] == '"') { stringBuilder.Append('"'); i++; } else if (c == '"') { flag = false; } else { stringBuilder.Append(c); } continue; } switch (c) { case '"': flag = true; break; case ',': list.Add(stringBuilder.ToString()); stringBuilder.Clear(); break; default: stringBuilder.Append(c); break; } } list.Add(stringBuilder.ToString()); return list.ToArray(); } }