C# Word 文档用 mergefield 替换 PlainText

C# Word Document Replace PlainText with mergefield

我有一个 word 文档模板和一个 CSV 文件,我想将其与邮件合并。

在 word 文档中,我用 <<>> 包围了文本,如果我想将它用于邮件合并,这与我的 csv 中的 headers 相匹配。例如,我的 word 文档中有 <<Salutation>>,而我的 csv 中有字段名称 Salutation。

是否有一种简单的方法可以将 <<>> 包围的文本替换为与其在 CSV 中的 header 相对应的邮件合并字段?

我目前用于读取数据的代码是:

Microsoft.Office.Interop.Word.Application _wordApp = new Microsoft.Office.Interop.Word.Application();
        Microsoft.Office.Interop.Word.Document oDoc = _wordApp.Documents.Add(@"C:\Eyre\Template.docx");
        _wordApp.Visible = true;
        oDoc.MailMerge.MainDocumentType = Microsoft.Office.Interop.Word.WdMailMergeMainDocType.wdFormLetters;
        oDoc.MailMerge.OpenDataSource(@"C:\Eyre\CSV.csv", false, false, true);

        oDoc.MailMerge.Destination = Microsoft.Office.Interop.Word.WdMailMergeDestination.wdSendToNewDocument;
        oDoc.MailMerge.Execute(false);
        Microsoft.Office.Interop.Word.Document oLetters = _wordApp.ActiveDocument;
        oLetters.SaveAs2(@"C:\Eyre\letters.docx",
             Microsoft.Office.Interop.Word.WdSaveFormat.wdFormatDocumentDefault);

如有任何帮助,我们将不胜感激

---编辑---

这似乎让一些人感到困惑。我有一个带有纯文本的 word 模板,例如 Salutation,需要一个 C# 程序来用来自 csv 的合并字段替换这个纯文本。

根据您更广泛的要求,有多种方法。如果您 运行 在您的 Windows 机器上完成简单/小型任务所需的工具,那么 VBA/macro 方法可能是最好的,因为您已经拥有所需的东西。

另一种方法需要更多编码和对 DOCX 的理解,但您可以在没有 MS Office 库的机器上扩展它并运行它。由于 DocX 是开放的并且是基于文本的,您可以解压缩它处理 XML 内容并重新压缩。有一些陷阱,因为 XML 不是微不足道的。如果您这样做,使用 Word Merge Fields 比纯文本更好(对于程序员),因为查找字段更简单。纯文本更适合使用 Document/Template 的人,因为他们不必处理合并字段,但缺点是 XML 处理会变得更加复杂。模板 <<Salutation>> 中的文本可能不容易找到 XML - 它可以拆分成多个部分。

另一个解决方案是使用类似 Docmosis 的东西(商业产品 - 请注意我为 Docmosis 工作)。好处是 Docmosis 可以完成替换和更复杂的要求(例如条件和循环结构,PDF 转换)。缺点是你必须学习 API 并安装软件(或调用云),并将你的数据转换为一种格式以传递给引擎。

希望对您有所帮助。

这是一个 C# 版本的代码,用于用合并字段替换 Word 文档中的 "placeholders"。 (对于寻找 VB 版本的读者,请参阅 。)

我的代码使用了一个已经 运行 的 Word 实例,所以您感兴趣的部分从 foreach (Word.MailMergeDataField...

开始

Find/Replace 操作在它们自己的过程中 ReplaceTextWithMergeField,数据源字段的名称(如 Word 所见!)和搜索的目标范围被传递到该过程。

请注意在此过程中如何将尖括号对附加到数据字段名称。

Find/Replace动作是标准的,重新设置Range继续搜索数据字段名的对象有点不同,因为需要获取位置 合并字段之外 - 插入字段后,范围在字段代码内。如果不这样做,Find 可能会在同一字段中结束 "infinitely"。 (注意:在这种情况下,没有双尖括号。但是如果有人在没有它们的情况下使用代码,那么问题就会出现。)

编辑:为了在 Shape 对象中查找和替换,这些对象必须单独循环。任何带有文本换行格式的内容都在文档的不同层中,而不是 Document.Content 的一部分。我在第三个过程中调整了查找过程以搜索文档的 ShapeRange,特别是测试文本框。

    private void btnDataToMergeFields_Click(object sender, EventArgs e)
    {
        getWordInstance();
        if (wdApp != null)
        {
            if (wdApp.Documents.Count > 0)
            {
            Word.Document doc = wdApp.ActiveDocument;
            Word.Range rng = doc.Content;
            Word.ShapeRange rngShapes = rng.ShapeRange;

            if (doc.MailMerge.MainDocumentType != Word.WdMailMergeMainDocType.wdNotAMergeDocument)
                foreach (Word.MailMergeDataField mmDataField in doc.MailMerge.DataSource.DataFields)
                {
                  System.Diagnostics.Debug.Print(ReplaceTextWithMergeField(mmDataField.Name, ref rng).ToString() 
                      + " merge fields inserted for " + mmDataField.Name);
                  rng = doc.Content;
                  System.Diagnostics.Debug.Print(ReplaceTextWithMergeFieldInShapes(mmDataField.Name, ref rngShapes)
                      + " mergefields inserted for " + mmDataField.Name);

                }
            }
        }
    }

    //returns the number of times the merge field was inserted
    public int ReplaceTextWithMergeField(string sFieldName, ref Word.Range oRng)
    {
        int iFieldCounter = 0;
        Word.Field fldMerge;
        bool bFound;

        oRng.Find.ClearFormatting();
        oRng.Find.Forward = true;
        oRng.Find.Wrap = Word.WdFindWrap.wdFindStop;
        oRng.Find.Format = false;
        oRng.Find.MatchCase = false;
        oRng.Find.MatchWholeWord = false;
        oRng.Find.MatchWildcards = false;
        oRng.Find.MatchSoundsLike = false;
        oRng.Find.MatchAllWordForms = false;
        oRng.Find.Text = "<<" + sFieldName + ">>";
        bFound = oRng.Find.Execute();
        while (bFound)
        {
            iFieldCounter = iFieldCounter + 1;
            fldMerge = oRng.Fields.Add(oRng, Word.WdFieldType.wdFieldMergeField, sFieldName, false);
            oRng = fldMerge.Result;
            oRng.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
            oRng.MoveStart(Word.WdUnits.wdCharacter, 2);
            oRng.End = oRng.Document.Content.End;
            oRng.Find.Text = "<<" + sFieldName + ">>";
            bFound = oRng.Find.Execute();
        }
        return iFieldCounter;
    }

    public int ReplaceTextWithMergeFieldInShapes(string sFieldName,
                               ref Word.ShapeRange oRng)
    {
        int iFieldCounter = 0;
        Word.Field fldMerge;
        bool bFound;

        foreach (Word.Shape shp in oRng)
        {
            if (shp.Type == Office.MsoShapeType.msoTextBox)
            {
                Word.Range rngText = shp.TextFrame.TextRange;
                rngText.Find.ClearFormatting();
                rngText.Find.Forward = true;
                rngText.Find.Wrap = Word.WdFindWrap.wdFindStop;
                rngText.Find.Format = false;
                rngText.Find.MatchCase = false;
                rngText.Find.MatchWholeWord = false;
                rngText.Find.MatchWildcards = false;
                rngText.Find.MatchSoundsLike = false;
                rngText.Find.MatchAllWordForms = false;
                rngText.Find.Text = "<<" + sFieldName + ">>";
                bFound = rngText.Find.Execute();
                while (bFound)
                {
                    iFieldCounter = iFieldCounter + 1;
                    fldMerge = rngText.Fields.Add(rngText, Word.WdFieldType.wdFieldMergeField, sFieldName, false);
                    rngText = fldMerge.Result;
                    rngText.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
                    rngText.MoveStart(Word.WdUnits.wdCharacter, 2);
                    rngText.End = shp.TextFrame.TextRange.End;
                    rngText.Find.Text = sFieldName;
                    bFound = rngText.Find.Execute();
                }
            }
        }
        return iFieldCounter;
    }