Search code examples
c#openxmlmergefield

Change MergeField to text only


I need to replace text into MergeField and do it simply text. I found question on the topic. It works for me. This code changes the text, but leaves the field as MergeField. And other question that change MergeField to text. But if there are several such fields in one line, the second one will be deleted.

At this point, I've tweaked the code from the first link a bit. What do I need to add to change MergeField to text?

string sourceFile = @"C:\Users\Owl\Desktop\Template.docm";
string targetFile = @"C:\Users\Owl\Desktop\Result.docx";
File.Copy(sourceFile, targetFile, true);
using (WordprocessingDocument document = WordprocessingDocument.Open(targetFile, true))
{
    document.ChangeDocumentType(WordprocessingDocumentType.Document);

    foreach (FieldCode field in document.MainDocumentPart
                                        .RootElement.Descendants<FieldCode>())
    {
        int indexEndName = field.Text.IndexOf("\\");
        string fieldName = string.Format("«{0}»", 
                                  field.Text.Substring(11, indexEndName - 11).Trim());

        foreach (Run run in document.MainDocumentPart.Document.Descendants<Run>())
        {
            foreach (Text txtFromRun in run.Descendants<Text>()
                                           .Where(a => a.Text == fieldName))
            {
                txtFromRun.Text = "some text";
            }
        }
    }

    document.MainDocumentPart.Document.Save();
}

UPDATE

I made a mistake. The code on the second link. The second field in the line not deleted. It stays. But does not fall into the cycle, is ignored. I don't understand why.


Solution

  • Problem

    The code from the topic only works when MergeField is in different paragraphs. If there are more than one paragraph, only the first MergeField will be processed. The rest are ignored.

    This is because the parent element is removed.

    Run rFldCode = (Run)field.Parent; 
    ...
    rFldCode.Remove();
    

    The next element of the foreach will be from the next paragraph. I mean the first loop of my question code.

    Solution

    Collect all parts of a MergeField in the List.

    Run rFldParent = (Run)field.Parent;
    List<Run> runs = new List<Run>();
    
    runs.Add(rFldParent.PreviousSibling<Run>()); // begin
    runs.Add(rFldParent.NextSibling<Run>()); // separate
    runs.Add(runs.Last().NextSibling<Run>()); // text
    runs.Add(runs.Last().NextSibling<Run>()); // end
    

    Picture for clarity. It helped me a lot to understand.

    enter image description here

    Now remove these items

    foreach(Run run in runs)
    {
        run.Remove();
    }
    

    As well as the text field <w:instrText xml:space="preserve"> MERGEFIELD...

    field.Remove(); // instrText
    

    And add new text

    rFldParent.Append(new Text(replacementText));
    

    Now, instead of just the MergeField is text only.

    enter image description here

    Full code

    string sourceFile = @"C:\Users\Owl\Desktop\Template.docm";
    string targetFile = @"C:\Users\Owl\Desktop\Result.docx";
    File.Copy(sourceFile, targetFile, true);
    using (WordprocessingDocument document = WordprocessingDocument.Open(targetFile, true))
    {
        document.ChangeDocumentType(WordprocessingDocumentType.Document);
    
        foreach (FieldCode field in document.MainDocumentPart.RootElement.Descendants<FieldCode>())
        {
            ReplaceMergeFieldWithText(field, "some text");
        }
    
        document.MainDocumentPart.Document.Save();
    }
    
    private void ReplaceMergeFieldWithText(FieldCode field, string replacementText)
    {
        if (field == null || replacementText == string.Empty)
        {
            return;
        }
    
        Run rFldParent = (Run)field.Parent;
        List<Run> runs = new List<Run>();
    
        runs.Add(rFldParent.PreviousSibling<Run>()); // begin
        runs.Add(rFldParent.NextSibling<Run>()); // separate
        runs.Add(runs.Last().NextSibling<Run>()); // text
        runs.Add(runs.Last().NextSibling<Run>()); // end
    
        foreach(Run run in runs)
        {
            run.Remove();
        }
    
        field.Remove(); // instrText
        rFldParent.Append(new Text(replacementText));
    }
    

    Bonus

    To insert different values, you must create a Dictionary. Where the key is the MergeField name and the value is the text to insert.

    To short the field name

    int indexEndName = field.Text.IndexOf("\\");
    string fieldName = field.Text.Substring(11, indexEndName - 11).Trim();
    

    For example

    Dictionary<string, string> dict = new Dictionary<string, string>();
    dict.Add("key 1", "value 1");
    dict.Add("key 2", "value 2");
    dict.Add("key 3", "value 3");
    
    ...
    
    foreach (FieldCode field in document.MainDocumentPart.RootElement.Descendants<FieldCode>())
    {
        int indexEndName = field.Text.IndexOf("\\");
        string fieldName = field.Text.Substring(11, indexEndName - 11).Trim();
    
        ReplaceMergeFieldWithText(field, dict[fieldName]);
    }