Files
AX-Copilot-Codex/.decompiledproj/AxCopilot/Services/Agent/FormatConvertTool.cs

250 lines
8.2 KiB
C#

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<string, ToolProperty>
{
["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<string> list = new List<string>(num);
CollectionsMarshal.SetCount(list, num);
Span<string> span = CollectionsMarshal.AsSpan(list);
span[0] = "source";
span[1] = "target";
obj.Required = list;
return obj;
}
}
public async Task<ToolResult> 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 = $"<!DOCTYPE html>\n<html lang=\"ko\">\n<head>\n<meta charset=\"UTF-8\"/>\n<style>{css}</style>\n</head>\n<body>\n<div class=\"container\">\n{bodyHtml}\n</div>\n</body>\n</html>";
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)[^>]*>.*?</\\1>", "", RegexOptions.IgnoreCase | RegexOptions.Singleline);
input = Regex.Replace(input, "<br\\s*/?>", "\n", RegexOptions.IgnoreCase);
input = Regex.Replace(input, "</(p|div|h[1-6]|li|tr)>", "\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 "<p>빈 CSV 파일</p>";
}
string css = TemplateService.GetCss(mood);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("<!DOCTYPE html>\n<html lang=\"ko\">\n<head>\n<meta charset=\"UTF-8\"/>");
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder3 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(54, 1, stringBuilder2);
handler.AppendLiteral("<style>");
handler.AppendFormatted(css);
handler.AppendLiteral("</style>\n</head>\n<body>\n<div class=\"container\">");
stringBuilder3.AppendLine(ref handler);
stringBuilder.AppendLine("<table><thead><tr>");
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("<th>");
handler.AppendFormatted(WebUtility.HtmlEncode(value));
handler.AppendLiteral("</th>");
stringBuilder4.Append(ref handler);
}
stringBuilder.AppendLine("</tr></thead><tbody>");
for (int j = 1; j < Math.Min(array.Length, 1001); j++)
{
string[] array4 = ParseCsvLine(array[j]);
stringBuilder.Append("<tr>");
string[] array5 = array4;
foreach (string value2 in array5)
{
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder5 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2);
handler.AppendLiteral("<td>");
handler.AppendFormatted(WebUtility.HtmlEncode(value2));
handler.AppendLiteral("</td>");
stringBuilder5.Append(ref handler);
}
stringBuilder.AppendLine("</tr>");
}
stringBuilder.AppendLine("</tbody></table>\n</div>\n</body>\n</html>");
return stringBuilder.ToString();
}
private static string[] ParseCsvLine(string line)
{
List<string> list = new List<string>();
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();
}
}