190 lines
7.0 KiB
C#
190 lines
7.0 KiB
C#
using System.IO;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
|
|
namespace AxCopilot.Services.Agent;
|
|
|
|
/// <summary>파일 인코딩 감지 및 변환 도구.</summary>
|
|
public class EncodingTool : IAgentTool
|
|
{
|
|
public string Name => "encoding_tool";
|
|
public string Description =>
|
|
"Detect and convert file text encoding. Actions: " +
|
|
"'detect' — detect file encoding (UTF-8, EUC-KR, etc.); " +
|
|
"'convert' — convert file from one encoding to another; " +
|
|
"'list' — list common encoding names.";
|
|
|
|
public ToolParameterSchema Parameters => new()
|
|
{
|
|
Properties = new()
|
|
{
|
|
["action"] = new()
|
|
{
|
|
Type = "string",
|
|
Description = "Action: detect, convert, list",
|
|
Enum = ["detect", "convert", "list"],
|
|
},
|
|
["path"] = new()
|
|
{
|
|
Type = "string",
|
|
Description = "File path",
|
|
},
|
|
["from_encoding"] = new()
|
|
{
|
|
Type = "string",
|
|
Description = "Source encoding name (e.g. 'euc-kr', 'shift-jis'). Auto-detected if omitted.",
|
|
},
|
|
["to_encoding"] = new()
|
|
{
|
|
Type = "string",
|
|
Description = "Target encoding name (default: 'utf-8')",
|
|
},
|
|
},
|
|
Required = ["action"],
|
|
};
|
|
|
|
public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
|
|
{
|
|
var action = args.GetProperty("action").GetString() ?? "";
|
|
|
|
if (action == "list")
|
|
return ListEncodings();
|
|
|
|
var rawPath = args.TryGetProperty("path", out var pv) ? pv.GetString() ?? "" : "";
|
|
if (string.IsNullOrEmpty(rawPath))
|
|
return ToolResult.Fail("'path'가 필요합니다.");
|
|
|
|
var path = Path.IsPathRooted(rawPath) ? rawPath : Path.Combine(context.WorkFolder, rawPath);
|
|
if (!context.IsPathAllowed(path))
|
|
return ToolResult.Fail($"경로 접근 차단: {path}");
|
|
if (!File.Exists(path))
|
|
return ToolResult.Fail($"파일 없음: {path}");
|
|
|
|
try
|
|
{
|
|
return action switch
|
|
{
|
|
"detect" => DetectEncoding(path),
|
|
"convert" => await ConvertEncoding(path, args, context),
|
|
_ => ToolResult.Fail($"Unknown action: {action}"),
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return ToolResult.Fail($"인코딩 처리 오류: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private static ToolResult DetectEncoding(string path)
|
|
{
|
|
var bytes = File.ReadAllBytes(path);
|
|
var detected = DetectEncodingFromBytes(bytes);
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine($"File: {Path.GetFileName(path)}");
|
|
sb.AppendLine($"Size: {bytes.Length:N0} bytes");
|
|
sb.AppendLine($"Detected Encoding: {detected.EncodingName}");
|
|
sb.AppendLine($"Code Page: {detected.CodePage}");
|
|
sb.AppendLine($"BOM Present: {HasBom(bytes)}");
|
|
return ToolResult.Ok(sb.ToString());
|
|
}
|
|
|
|
private static async Task<ToolResult> ConvertEncoding(string path, JsonElement args, AgentContext context)
|
|
{
|
|
var toName = args.TryGetProperty("to_encoding", out var te) ? te.GetString() ?? "utf-8" : "utf-8";
|
|
|
|
// 쓰기 권한 확인
|
|
var allowed = await context.CheckWritePermissionAsync("encoding_tool", path);
|
|
if (!allowed) return ToolResult.Fail("파일 쓰기 권한이 거부되었습니다.");
|
|
|
|
// 소스 인코딩 결정
|
|
Encoding fromEnc;
|
|
if (args.TryGetProperty("from_encoding", out var fe) && !string.IsNullOrEmpty(fe.GetString()))
|
|
{
|
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
|
fromEnc = Encoding.GetEncoding(fe.GetString()!);
|
|
}
|
|
else
|
|
{
|
|
var rawBytes = File.ReadAllBytes(path);
|
|
fromEnc = DetectEncodingFromBytes(rawBytes);
|
|
}
|
|
|
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
|
var toEnc = Encoding.GetEncoding(toName);
|
|
|
|
var content = File.ReadAllText(path, fromEnc);
|
|
File.WriteAllText(path, content, toEnc);
|
|
|
|
return ToolResult.Ok(
|
|
$"변환 완료: {fromEnc.EncodingName} → {toEnc.EncodingName}\nFile: {path}",
|
|
filePath: path);
|
|
}
|
|
|
|
private static ToolResult ListEncodings()
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("주요 인코딩 목록:");
|
|
sb.AppendLine(" utf-8 — UTF-8 (유니코드, 기본)");
|
|
sb.AppendLine(" utf-16 — UTF-16 LE");
|
|
sb.AppendLine(" utf-16BE — UTF-16 BE");
|
|
sb.AppendLine(" euc-kr — EUC-KR (한국어)");
|
|
sb.AppendLine(" ks_c_5601-1987 — 한글 완성형");
|
|
sb.AppendLine(" shift_jis — Shift-JIS (일본어)");
|
|
sb.AppendLine(" gb2312 — GB2312 (중국어 간체)");
|
|
sb.AppendLine(" iso-8859-1 — Latin-1 (서유럽)");
|
|
sb.AppendLine(" ascii — US-ASCII");
|
|
sb.AppendLine(" utf-32 — UTF-32");
|
|
return ToolResult.Ok(sb.ToString());
|
|
}
|
|
|
|
private static Encoding DetectEncodingFromBytes(byte[] bytes)
|
|
{
|
|
// BOM 감지
|
|
if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
|
|
return Encoding.UTF8;
|
|
if (bytes.Length >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE)
|
|
return Encoding.Unicode; // UTF-16 LE
|
|
if (bytes.Length >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF)
|
|
return Encoding.BigEndianUnicode;
|
|
|
|
// 간단한 UTF-8 유효성 검사
|
|
if (IsValidUtf8(bytes))
|
|
return Encoding.UTF8;
|
|
|
|
// 한국어 환경 기본값
|
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
|
try { return Encoding.GetEncoding("euc-kr"); }
|
|
catch { return Encoding.Default; }
|
|
}
|
|
|
|
private static bool IsValidUtf8(byte[] bytes)
|
|
{
|
|
var i = 0;
|
|
var hasMultibyte = false;
|
|
while (i < bytes.Length)
|
|
{
|
|
if (bytes[i] <= 0x7F) { i++; continue; }
|
|
int extra;
|
|
if ((bytes[i] & 0xE0) == 0xC0) extra = 1;
|
|
else if ((bytes[i] & 0xF0) == 0xE0) extra = 2;
|
|
else if ((bytes[i] & 0xF8) == 0xF0) extra = 3;
|
|
else return false;
|
|
|
|
if (i + extra >= bytes.Length) return false;
|
|
for (var j = 1; j <= extra; j++)
|
|
if ((bytes[i + j] & 0xC0) != 0x80) return false;
|
|
hasMultibyte = true;
|
|
i += extra + 1;
|
|
}
|
|
return hasMultibyte || bytes.Length < 100; // 순수 ASCII도 UTF-8으로 간주
|
|
}
|
|
|
|
private static bool HasBom(byte[] bytes)
|
|
{
|
|
if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) return true;
|
|
if (bytes.Length >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE) return true;
|
|
if (bytes.Length >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF) return true;
|
|
return false;
|
|
}
|
|
}
|