250 lines
8.2 KiB
C#
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();
|
|
}
|
|
}
|