简体   繁体   中英

C# Word Document Replace PlainText with mergefield

I've got a word document template and a CSV i would like to mailmerge it with.

In the word document i have text surrounded with <<>> if i want to use it to mailmerge, this matches the headers in my csv. For example i have <<Salutation>> in my word document and the field name Salutation in my csv.

Is there an easy way to replace the text surrounded by <<>> with a mailmerge field corresponding to its header in the CSV?

The code i have so far for reading the data in is:

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);

Any help would be much appreciated

---EDIT---

This seems to be confusing some people. I have a word template with plain text such as Salutation and need a C# program that will replace this plain text with a merge field from a csv.

Here's a C# version of code to replace "placeholders" in a Word document with merge fields. (For readers looking for a VB-version, see https://stackoverflow.com/a/50159375/3077495 .)

My code uses an already running instance of Word, so the part that interests you starts at foreach (Word.MailMergeDataField ...

The Find/Replace actions are in their own procedure ReplaceTextWithMergeField , to which the name of the data source field (as Word sees it!), and the target Range for the search are passed.

Note how the angled bracket pairs are appended to the data field name in this procedure.

The Find/Replace actions are standard, re-setting the Range object of continuing the search for a data field name is a bit different because it's necessary to get the position outside the merge field - after inserting the field the Range is inside the field code. If this isn't done, Find could end up in the same field "infinitely". (Note: Not in this case, with the double angled brackets. But if anyone were to use the code without them, then the problem would occur.)

EDIT: In order to find and replace in Shape objects, those objects must be looped separately. Anything formatted with text wrapping is in a different layer of the document and is not part of Document.Content . I've adapted the find procedure in a third procedure to search through the document's ShapeRange , testing for Text Boxes, specifically.

    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;
    }

There are a number of approaches depending on your broader requirements. If you will run the tool as needed for simple / small tasks on your Windows machine, then the VBA/macro approach is probably best since you already have the things in place you need.

Another approach requires more coding and understanding of DOCX, but you could potentially scale it and run it on machines without MS Office libraries. Since DocX is open and text based, you can unzip it process the XML contents and re-zip. There are some gotchas because the XML is not trivial. If you were doing this, using Word Merge Fields is better (for the programmer) than plain text since finding fields is simpler. Plain text is better for the person working with the Document/Template since they don't have to deal with merge fields, but the downside is the XML processing can become much more complicated. The text in the template <<Salutation>> might not be easy to find the XML - it could be split into pieces.

Another solution is to use something like Docmosis (a commercial product - please note I work for Docmosis). The upsides are that Docmosis can do the replacement and more complex requirements (conditional and looping structures, PDF conversion for example). The downside is you have to learn the API and install the software (or call out to the cloud) and also get your data into a format to pass to the engine.

I hope that helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM