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