最近專案上接到一個需求:要從既有的 Word 文檔中,將 ${Number}
、${variable}
這類文字內容替換為實際資料。
乍看之下,這是個簡單的任務—-核心就是把 A 字換成 B 字。但做下去才發現,事情沒那麼單純,讓我們來看看我怎麼處理的。
找資源時,我的篩選條件是:
- 免費
- 夠穩定
- 避免使用 GPL 授權
使用DocumentFormat.OpenXml 替換文字
最先找到的是微軟官方的 DocumentFormat.OpenXml,是支援 .NET Core 的開源方案。

以下實作:
<!-- In .csproj file 引入套件-->
<ItemGroup>
<PackageReference Include="DocumentFormat.OpenXml" Version="2.20.0" />
</ItemGroup>
//定義想替換的文字與想替換的內容
//例:${Name} 要換成王小明
private static readonly Dictionary<string, string> VariableReplacements = new()
{
{"${NUMBER}", "12345"},
{"${Name}", "王小明"},
{"${Date}", "2025-07-17"},
{"${BIRTH_DATE}", "2020-01-01"}
};
//Program.cs
static void Main()
{
const string inputPath = @"D:\testFIles\New.docx";
const string outputPath = "output.docx";
try
{
Console.WriteLine("\n=== USING OPENXML APPROACH ===");
ProcessWithOpenXml(inputPath, "output_openxml.docx");
}
catch (Exception ex)
{
Console.WriteLine($"犯錯了: {ex.Message}");
}
}
// 使用OpenXml處理文件
private static void ProcessWithOpenXml(string inputPath, string outputPath)
{
//複製一份File到outputPath,不直接修改原始檔案
File.Copy(inputPath, outputPath, true);
//用OpenXml套件開啟Word文件,設定為可寫(true)來對文件做修改
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(outputPath, true))
{
//取得所有文字節點(Text)
var body = wordDoc.MainDocumentPart.Document.Body;
var textElements = body.Descendants<Text>().ToList();
Console.WriteLine($"Found {textElements.Count} text elements");
// 處理所有文字元素
for (int i = 0; i < textElements.Count; i++)
{
var currentText = textElements[i].Text;
Console.WriteLine($"Text element {i}: {currentText}");
// 若Text符合想替換的文字,執行替換
textElements[i].Text = ReplaceVariablesInText(currentText);
}
//存檔
wordDoc.MainDocumentPart.Document.Save();
}
Console.WriteLine("OpenXml 文字取代完成");
}
//執行替換
private static string ReplaceVariablesInText(string text)
{
var result = text;
foreach (var (variable, replacement) in VariableReplacements)
{
result = result.Replace(variable, replacement);
}
return result;
}
注意:這裡以遍歷”Text”的方式去替換字串
寫完後試著將程式跑跑看,如果是正常的文件那做到這邊就能完成了。
部分文字未被替換?
程式執行: dotnet build , dotnet run

搞闢阿,這不是有好幾個字串沒有替換嗎? 尤其是${NUMBER}。
這是因為我沒苦硬吃,在測試文件中埋了一個陷阱
(但也是實務中有可能會遇到的狀況)。
你看見的字串不等於OpenXml的實際節點結構
其實Word 不是純文字編輯器,而是格式化文書編輯器。
當你輸入${NUMBER} 時,只要中途改變了一點點格式(例如字體、大小、粗細、顏色、語言、甚至複製貼上來源不同),Word 背後就會將這段文字拆成不同的 Run / Text 節點來記錄這些格式差異。
也就是說,同一段字串如果長這樣 ${NUMBER}$ 或是格式、字形稍有不同,那麼Word背後就會拆成:
${NU
MB
ER}$
這就導致我們的程式抓不到 ${NUMBER} ,也就出現無法替換的狀況。
所以如果照著做發現沒有成功替換文字,可以把你嘗試替換的Text印出來 看看有沒有被拆掉。
解決碎片文字 — 改用Spire.Doc
Spire.Doc 是一個免費的 .NET 文件操作函式庫,可以不依賴 Microsoft Office,直接處理 Word 文件。
常見功能包括:
- 替換文字、加入段落或圖片
- 操作表格與頁面格式
- Word 轉 PDF / HTML / 圖片
- 匯出含格式的內容
- 批次處理 Word 文件
但要注意
免費版限制:
項目 | 限制說明 |
---|---|
Paragraphs | 最多 500 段落 |
Tables | 最多 25 個表格 |
文件大小 | 超大文件可能失敗 |
商用用途 | 不可用於商業產品,需授權 |
https://www.e-iceblue.com/forum/limitation-t10856.html
*其實我都做完才發現不可用於商業用途…..

程式碼使用 Spire.Doc
static void Main()
{
const string inputPath = @"D:\testFIles\New.docx";
const string outputPath = "output.docx";
try
{
Console.WriteLine("=== USING SPIRE.DOC APPROACH ===");
ProcessWithSpireDoc(inputPath, "output_spire.docx");
Console.WriteLine("\n=== USING OPENXML APPROACH ===");
ProcessWithOpenXml(inputPath, "output_openxml.docx");
}
catch (Exception ex)
{
Console.WriteLine($"Error processing document: {ex.Message}");
}
}
// 使用Spire.Doc處理文件
private static void ProcessWithSpireDoc(string inputPath, string outputPath)
{
// 使用Spire.Doc載入文件
Spire.Doc.Document document = new Spire.Doc.Document();
document.LoadFromFile(inputPath);
Console.WriteLine($"Document has {document.Sections.Count} sections");
// 處理所有段落和表格中的變數
foreach (Spire.Doc.Section section in document.Sections)
{
Console.WriteLine($"Processing section with {section.Paragraphs.Count} paragraphs and {section.Tables.Count} tables");
// 處理段落中的Text
foreach (Spire.Doc.Documents.Paragraph paragraph in section.Paragraphs)
{
Console.WriteLine($"Paragraph text: {paragraph.Text}");
// 替換段落中的文字
string originalText = paragraph.Text;
string replacedText = ReplaceVariablesInText(originalText);
if (originalText != replacedText)
{
paragraph.Text = replacedText;
Console.WriteLine($"Replaced: {originalText} -> {replacedText}");
}
}
// 處理表格中的文字
foreach (Spire.Doc.Table table in section.Tables)
{
Console.WriteLine($"Processing table with {table.Rows.Count} rows");
foreach (Spire.Doc.TableRow row in table.Rows)
{
foreach (Spire.Doc.TableCell cell in row.Cells)
{
// 處理格子裡的段落
foreach (Spire.Doc.Documents.Paragraph paragraph in cell.Paragraphs)
{
Console.WriteLine($"Table cell text: {paragraph.Text}");
// 替換單元格段落中的文字
string originalText = paragraph.Text;
string replacedText = ReplaceVariablesInText(originalText);
if (originalText != replacedText)
{
paragraph.Text = replacedText;
Console.WriteLine($"Table cell replaced: {originalText} -> {replacedText}");
}
}
}
}
}
}
// 儲存修改後的文件
document.SaveToFile(outputPath, Spire.Doc.FileFormat.Docx);
document.Close();
Console.WriteLine("Spire.Doc 文字取代完成");
}
// 在文字中替換變數
private static string ReplaceVariablesInText(string text)
{
var result = text;
foreach (var (variable, replacement) in VariableReplacements)
{
result = result.Replace(variable, replacement);
}
return result;
}

成功置換
發佈留言