");
sb.AppendLine($"
{Escape(title)}
");
sb.AppendLine($"
{Escape(coverSubtitle)}
");
sb.AppendLine($"
{DateTime.Now:yyyy년 MM월 dd일}
");
sb.AppendLine("
");
}
sb.AppendLine("");
if (string.IsNullOrWhiteSpace(coverSubtitle))
sb.AppendLine($"
{Escape(title)}
");
// TOC 생성
if (toc && sections.Count > 1)
{
sb.AppendLine("
");
sb.AppendLine("
📋 목차
");
sb.AppendLine("
");
for (int i = 0; i < sections.Count; i++)
{
var indent = sections[i].Level > 1 ? " style=\"padding-left:20px\"" : "";
sb.AppendLine($"- {Escape(sections[i].Heading)}
");
}
sb.AppendLine("
");
sb.AppendLine("
");
}
// 섹션 본문
for (int i = 0; i < sections.Count; i++)
{
var (heading, content, level) = sections[i];
var tag = level <= 1 ? "h2" : "h3";
sb.AppendLine($"<{tag} id=\"section-{i + 1}\">{Escape(heading)}{tag}>");
sb.AppendLine($"
{content}
");
}
sb.AppendLine("
");
sb.AppendLine("");
sb.AppendLine("");
File.WriteAllText(path, sb.ToString(), Encoding.UTF8);
var issues = ValidateBasic(sb.ToString());
return issues.Count > 0
? $" ⚠ 품질 검증 이슈 {issues.Count}건: {string.Join("; ", issues)}"
: " ✓ 품질 검증 통과";
}
private string AssembleDocx(string path, string title, List<(string Heading, string Content, int Level)> sections,
string? headerText, string? footerText)
{
// DOCX 조립: DocxSkill의 sections 형식으로 변환하여 OpenXML 사용
using var doc = DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Create(
path, DocumentFormat.OpenXml.WordprocessingDocumentType.Document);
var mainPart = doc.AddMainDocumentPart();
mainPart.Document = new DocumentFormat.OpenXml.Wordprocessing.Document();
var body = mainPart.Document.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Body());
// 제목
var titlePara = new DocumentFormat.OpenXml.Wordprocessing.Paragraph();
var titleRun = new DocumentFormat.OpenXml.Wordprocessing.Run();
titleRun.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties
{
Bold = new DocumentFormat.OpenXml.Wordprocessing.Bold(),
FontSize = new DocumentFormat.OpenXml.Wordprocessing.FontSize { Val = "48" }
});
titleRun.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text(title));
titlePara.AppendChild(titleRun);
body.AppendChild(titlePara);
// 빈 줄
body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
// 각 섹션
foreach (var (heading, content, level) in sections)
{
// 섹션 제목
var headPara = new DocumentFormat.OpenXml.Wordprocessing.Paragraph();
var headRun = new DocumentFormat.OpenXml.Wordprocessing.Run();
headRun.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties
{
Bold = new DocumentFormat.OpenXml.Wordprocessing.Bold(),
FontSize = new DocumentFormat.OpenXml.Wordprocessing.FontSize { Val = level <= 1 ? "32" : "28" },
Color = new DocumentFormat.OpenXml.Wordprocessing.Color { Val = "2B579A" },
});
headRun.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text(heading));
headPara.AppendChild(headRun);
body.AppendChild(headPara);
// 섹션 본문 (줄 단위 분할)
var lines = StripHtmlTags(content).Split('\n', StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
var para = new DocumentFormat.OpenXml.Wordprocessing.Paragraph();
var run = new DocumentFormat.OpenXml.Wordprocessing.Run();
run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text(line.Trim())
{
Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve
});
para.AppendChild(run);
body.AppendChild(para);
}
// 섹션 간 빈 줄
body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
}
return " ✓ DOCX 조립 완료";
}
private string AssembleMarkdown(string path, string title, List<(string Heading, string Content, int Level)> sections)
{
var sb = new StringBuilder();
sb.AppendLine($"# {title}");
sb.AppendLine();
sb.AppendLine($"*작성일: {DateTime.Now:yyyy-MM-dd}*");
sb.AppendLine();
// TOC
if (sections.Count > 1)
{
sb.AppendLine("## 목차");
sb.AppendLine();
foreach (var (heading, _, _) in sections)
{
var anchor = heading.Replace(" ", "-").ToLowerInvariant();
sb.AppendLine($"- [{heading}](#{anchor})");
}
sb.AppendLine();
sb.AppendLine("---");
sb.AppendLine();
}
foreach (var (heading, content, level) in sections)
{
var prefix = level <= 1 ? "##" : "###";
sb.AppendLine($"{prefix} {heading}");
sb.AppendLine();
sb.AppendLine(StripHtmlTags(content));
sb.AppendLine();
}
File.WriteAllText(path, sb.ToString(), Encoding.UTF8);
return " ✓ Markdown 조립 완료";
}
private static int EstimateWordCount(string text)
{
if (string.IsNullOrWhiteSpace(text)) return 0;
var plain = StripHtmlTags(text);
// 한국어: 글자 수 / 3 ≈ 단어 수, 영어: 공백 분리
var spaces = plain.Count(c => c == ' ');
var koreanChars = plain.Count(c => c >= 0xAC00 && c <= 0xD7A3);
return spaces + 1 + koreanChars / 3;
}
private static string StripHtmlTags(string html)
{
if (string.IsNullOrEmpty(html)) return "";
return System.Text.RegularExpressions.Regex.Replace(html, "<[^>]+>", " ")
.Replace(" ", " ")
.Replace("&", "&")
.Replace("<", "<")
.Replace(">", ">")
.Replace(" ", " ")
.Trim();
}
private static List