用 C# 替換 Word 文檔中的內容:從 OpenXML 到 Spire.Doc

最近專案上接到一個需求:要從既有的 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;
}

成功置換


留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *