💡 完整可運作的範例可在 GitHub 上取得: parse-docx-to-markdown-chunks-dotnet
以往的方式很痛苦
以標題為基礎的 Markdown 分塊是 .NET 的檢索前置流程,會在嵌入之前將 Office 與 PDF 檔案轉換成段落大小的文字。若不使用此流程,團隊常會把整個 Word 檔案一次性丟入單一向量。封面段落與責任條款共用同一個嵌入,導致餘弦相似度搜尋將錯誤的段落排在前面。我在一份三頁的內部指南上就遇到過這種情況:每次查詢都先抓到標題區塊,因為索引從未看到作者已寫好的 # / ## 結構。
自行手寫 DOCX 轉 Markdown 轉換器意味著要遍歷 Open XML 樣式、對映清單層級,且在下一次上傳的是 PDF、其格式化的 Markdown 路徑回傳 null 時仍會失敗。固定大小的 token 視窗會忽略這些標題。隔夜的語料庫因此會把空殼與龐大的手冊混在一起,因為沒有人先以頁數或檔案大小作門檻。
整檔嵌入的真正成本:錯誤段落的答案、在垃圾上傳上浪費的提取費用,以及在批次失敗時操作人員無法開啟的
chunk-01.md檔案。
有更好的方法
GroupDocs.Parser for .NET 用四個聚焦的呼叫取代了這種混亂:GetDocumentInfo、GetFormattedText(FormattedTextMode.Markdown)、當 Markdown 為 null 時的 GetText,以及寫入 chunk-NN.md 的小型標題走訪器。相同的 Parser 類型同時支援 Word DOCX 解析與 PDF 文字回退,讓你只需維護一條 C# 服務路徑。
您將需要:
- .NET 8 SDK
- GroupDocs.Parser 26.10.0 (temporary license)
- 一個 Word DOCX(範例為 3 頁 / 14820 位元組)以及可選的 PDF
dotnet add package GroupDocs.Parser --version 26.10.0
新的方法:檢查、提取、回退、分割
以下是切換後相同的檢索前置流程。parse-docx-to-markdown-chunks-dotnet 目錄下的主控台範例會寫入資訊、Markdown、PDF 文字,以及四個標題分塊給隨附的 DOCX。
步驟 1 - 使用 GetDocumentInfo 門檻
在支付完整提取費用之前,先詢問檔案類型、頁數與大小。
using var parser = new Parser(sourcePath);
var info = parser.GetDocumentInfo();
var sb = new StringBuilder();
sb.AppendLine($"Source: {Path.GetFileName(sourcePath)}");
sb.AppendLine($"File type: {info?.FileType}");
sb.AppendLine($"Pages: {info?.PageCount}");
sb.AppendLine($"Size: {info?.Size} bytes");
File.WriteAllText(outputPath, sb.ToString());
將這些事實記錄在工作編號旁。對於範例 DOCX,您應該會看到「Microsoft Word Open XML Document」、3 頁、14820 位元組。
步驟 2 - 偏好使用 GetFormattedText 取得 Markdown
對於支援格式化提取的 Word 及其他格式,保留 ATX 標題供分割器使用。
using var parser = new Parser(sourcePath);
using TextReader? reader = parser.GetFormattedText(
new FormattedTextOptions(FormattedTextMode.Markdown));
if (reader == null)
{
return null;
}
string markdown = reader.ReadToEnd();
File.WriteAllText(outputPath, markdown, Encoding.UTF8);
return markdown;
null 是切換路徑的訊號,而非程式崩潰。使用 UTF-8 寫入,讓分塊工具與 git diff 保持可讀。
步驟 3 - 使用 GetText 回退 PDF
當 Markdown 模式回傳 null 時,使用純文字層繼續處理檔案。
using var parser = new Parser(sourcePath);
using TextReader? reader = parser.GetText();
if (reader == null)
{
return null;
}
string text = reader.ReadToEnd();
File.WriteAllText(outputPath, text, Encoding.UTF8);
return text;
預期得到 .txt,而不是自行產生的 # 標題。範例主機會在 PDF 分支寫入 Result/sample-pdf.txt。
步驟 4 - 依 # 與 ## 分割 Markdown
逐行走訪;當出現新的 ATX # 或 ## 時即刷新;寫入 chunk-NN.md。
var chunks = new List<string>();
var current = new StringBuilder();
using (var reader = new StringReader(markdown))
{
string? line;
while ((line = reader.ReadLine()) != null)
{
bool isHeading = line.StartsWith("# ", StringComparison.Ordinal)
|| line.StartsWith("## ", StringComparison.Ordinal);
if (isHeading && current.Length > 0)
{
chunks.Add(current.ToString().TrimEnd());
current.Clear();
}
current.AppendLine(line);
}
}
先刪除過時的 chunk-*.md 檔案。較深的 ### 行仍保留在當前段落內。範例 DOCX 會在 Result/chunks/ 產生四個檔案。
什麼時候應該以此路徑取代整檔嵌入?
當輸入同時混合 Word DOCX 與 PDF,且索引需要段落大小的 Markdown 時,請改用此方式取代整檔嵌入。先執行 GetDocumentInfo,對 Office 檔案嘗試 Markdown,當格式化讀取器為 null 時呼叫 GetText,僅在存在 # / ## 標記時才分割。對未結構化的 PDF 文字保持原樣,直到您另行制定分塊策略。此順序與範例主控台應用程式相符,並讓操作人員的失敗模式保持明確。
對操作人員而言的變更
| 之前 | 之後 |
|---|---|
| 每個檔案一個嵌入 | 每個標題區塊一個嵌入 |
| 自訂 Open XML Markdown | GetFormattedText Markdown 模式 |
| PDF 在格式化讀取器為 null 時被丟棄 | GetText 寫入文字檔案 |
| 沒有廉價的前置檢查 | 以頁數與大小作門檻檢查 |
釋放每個 Parser 與 TextReader。在大量夜間處理前套用授權或 temporary license。將上傳視為不可信輸入,並在隔離的工作者中解析。
在自己的檔案上試試看
Clone parse-docx-to-markdown-chunks-dotnet,還原 GroupDocs.Parser 26.10.0,然後對 documents/sample.docx 執行。接著將同樣的四個方法指向您的一本手冊,打開產生的 chunk-*.md 檔案。