Search code examples
c#templatesms-wordoffice-interop

How to link document to different structured template


I am try to automate a process for changing the document templates of word files.

If the templates are similar structure, ie they both use heading1, then when the document is linked to the new template, it works.

However, the template structure is completely different, heading1 is no longer used, it is now section1. How can I change these section titles with code? Something along the lines of if(heading1) rename to section1;

I am using Interop.Word to perform these operations.

Below is the code I'm using:

public string UpdateDocumentWithNewTemplate(string document, string theme, string template, Word.Application wordApp)
        {
            try
            {
                object missing = System.Reflection.Missing.Value;
                Word.Document aDoc = null;
                object notReadOnly = false;
                object isVisible = false;
                wordApp.Visible = false;
                // create objects from variables for wordApp
                object documentObject = document;

                // open existing document
                aDoc = wordApp.Documents.Open(ref documentObject, ref missing, ref notReadOnly, ref missing, ref missing,
                    ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref isVisible,
                    ref missing, ref missing, ref missing, ref missing);
                aDoc.Activate();

                // set template and theme to overwrite the existing styles
                aDoc.CopyStylesFromTemplate(template);
                aDoc.ApplyDocumentTheme(theme);
                aDoc.UpdateStyles();

                // save the file with the changes
                aDoc.SaveAs(ref documentObject, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing,
                    ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);

                // close the document
                aDoc.Close(ref missing, ref missing, ref missing);
                if (aDoc != null)
                    System.Runtime.InteropServices.Marshal.ReleaseComObject(aDoc);
                aDoc = null;

                return documentObject.ToString();
            }
            catch (Exception exception)
            {
                return "Error: " + exception;
            }
        }

Solution

  • For the specific example you need to first import the styles from the other template, then do a Find/Replace to replace the styles applied. I see from your code that you've got the first part (aDoc.CopyStylesFromTemplate(template); aDoc.ApplyDocumentTheme(theme); aDoc.UpdateStyles();).

    What many don't realize about Word's Find/Replace functionality is that it can also work with formatting. The best way to get the necessary syntax is to record a successful Find/Replace in a macro, then port the VBA to C#. In the UI:

    1. Ctrl+H to open the Replace dialog box
    2. With the cursor in the "Find what" box, click "More" then "Format" and choose "Style"
    3. Select the name of the style you want to find and have replaced
    4. Click in the "Replace with" box
    5. Use Format/Style, again, to choose the style you want to use
    6. Click "Replace All".

    enter image description here

    Here's the result I get:

    Selection.Find.ClearFormatting
    Selection.Find.Style = ActiveDocument.styles("Heading 1")
    Selection.Find.Replacement.ClearFormatting
    Selection.Find.Replacement.Style = ActiveDocument.styles("section2")
    With Selection.Find
        .Text = ""
        .Replacement.Text = ""
        .Forward = True
        .wrap = wdFindContinue
        .Format = True
        .MatchCase = False
        .MatchWholeWord = False
        .MatchByte = False
        .CorrectHangulEndings = False
        .HanjaPhoneticHangul = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
    End With
    Selection.Find.Execute Replace:=wdReplaceAll
    

    You should use Range, not Selection. So the C# code would look something like the following code block. Note how

    1. I get the Range of the entire document
    2. Create a Find object for the Range and use that
    3. To reference Styles for the Find; I show two possibilities
    4. You can list almost all the properties for Find before using Find.Execute. It would also be possible to create object objects for each of these, with only one necessary for true and false then list these "by ref" in Find.Execute. As far as I know, this is simply a matter of personal preference. I did it this way to the most literal "translation" of the VBA to C# code.
    5. In any case, Find.Execute "remembers" these settings, so ref missing can then be used for all the parameters you don't set specifically. In this case, only the "replace all" command is used specifically in the method.

          Word.Document doc = wdApp.ActiveDocument;
          Word.Range rngFind = doc.Content;
          Word.Find fd = rngFind.Find;
          fd.ClearFormatting();
          Word.Style stylFind = doc.Styles["Heading 1"];
          fd.set_Style(stylFind);
          fd.Replacement.ClearFormatting();
          fd.Replacement.set_Style(doc.Styles["section2"]);
          fd.Text = "";
          fd.Replacement.Text = "";
          fd.Forward = true;
          fd.Wrap = Word.WdFindWrap.wdFindStop;
          fd.Format = true;
          fd.MatchCase = false;
          fd.MatchWholeWord = false;
          fd.MatchByte = false;
          fd.CorrectHangulEndings = false;
          fd.HanjaPhoneticHangul = false;
          fd.MatchWildcards = false;
          fd.MatchSoundsLike = false;
          fd.MatchAllWordForms = false;
          object replaceAll = Word.WdReplace.wdReplaceAll;
          object missing = Type.Missing;
          fd.Execute(ref missing, ref missing, ref missing, ref missing, ref missing, 
              ref missing, ref missing, ref missing, ref missing, ref missing, 
              ref replaceAll, ref missing, ref missing, ref missing, ref missing);