Search code examples
c#ms-wordoffice-interopcom-interop

Copy document content (including formatting and page format) to another using Word Interop in c# with 100% fidelity


I want to copy the content of a document created by the user to an existing document. The existing document content must be an exact mirror to the document created by the user.

I cannot simply copy the file using System.IO or saving a copy of the document created by the user using SaveAs methods in Word Interop. This is because the existing document is a document that is generated from a webserver and has VBA modules for uploading it back to the server.

The document generated by the webserver (existing document) is a Word 2003 document, but the document created by the user is either a Word 2003 document or Word 2007+.

Having these limitations in mind, I first created the following method:

string tempsave = //location of user created document;
string savelocation = //location of existing document;
Word.Application objWordOpen = new Word.Application();
Document doclocal = objWordOpen.Documents.Open(tempsave);
Document d1 = objWordOpen.Documents.Open(savelocation);
Word.Range oRange = doclocal.Content;
oRange.Copy();
d1.Activate();
d1.UpdateStyles();
d1.ActiveWindow.Selection.WholeStory();
d1.ActiveWindow.Selection.PasteAndFormat(Word.WdRecoveryType.wdFormatOriginalFormatting);

This is generally working. However, the tables are messed up.

Also, if there is a Page Break, the output is different.

The user created document:

enter image description here

The output - existing document:

enter image description here

Also, at the end of the document a paragraph mark is added, as follows:

The user created document:

enter image description here

The output - existing document:

enter image description here

The page format is also messed up, the output having mirror margins set up.

The user created document:

enter image description here

The output - existing document:

enter image description here

I have also tried using Range.Insert() method and setting the range without copying as described here https://stackoverflow.com/a/54500605/10468231, but I am still having these issues.

I have also tried adding the VBA modules to the document, but there are also Document Variables and other custom properties and I don't want to mess with the file being uploaded to the server. How do I handle these issues? Both the documents are based on Normal template.

I am open to another suggestion regarding this topic, but I know that .doc files are not handled as easily as .docx format, this is why I think I am stuck with COM Interop.

Thank you.

UPDATE Based on Macropod code posted by Charles Kenyon, I have managed to copy more of the formatting from the source to target. Still, there is the difference at the page break - the paragraph mark is places on the new page, instead on the same page. Also, the text is slightly larger, even though the Font Size is the same.

            Word.Range oRange;
            oRange = Source.Content;
            Target.Content.FormattedText = oRange.FormattedText;
            LayoutTransfer(Source, Target);

LayoutTransfer method:

private void LayoutTransfer(Document source, Document target)
        {
            float sPageHght;
            float sPageWdth;
            float sHeaderDist;
            float sFooterDist;
            float sTMargin;
            float sBMargin;
            float sLMargin;
            float sRMargin;
            float sGutter;
            WdGutterStyle sGutterPos;
            WdPaperSize lPaperSize;
            WdGutterStyleOld lGutterStyle;
            int lMirrorMargins;
            WdVerticalAlignment lVerticalAlignment;
            WdSectionStart lScnStart;
            WdSectionDirection lScnDir;
            int lOddEvenHdFt;
            int lDiffFirstHdFt;
            bool bTwoPagesOnOne;
            bool bBkFldPrnt;
            int bBkFldPrnShts;
            bool bBkFldRevPrnt;
            WdOrientation lOrientation;
            foreach (Word.Section section in source.Sections)
            {
                lPaperSize = section.PageSetup.PaperSize;
                lGutterStyle = section.PageSetup.GutterStyle;
                lOrientation = section.PageSetup.Orientation;
                lMirrorMargins = section.PageSetup.MirrorMargins;
                lScnStart = section.PageSetup.SectionStart;
                lScnDir = section.PageSetup.SectionDirection;
                lOddEvenHdFt = section.PageSetup.OddAndEvenPagesHeaderFooter;
                lDiffFirstHdFt = section.PageSetup.DifferentFirstPageHeaderFooter;
                lVerticalAlignment = section.PageSetup.VerticalAlignment;
                sPageHght = section.PageSetup.PageHeight;
                sPageWdth = section.PageSetup.PageWidth;
                sTMargin = section.PageSetup.TopMargin;
                sBMargin = section.PageSetup.BottomMargin;
                sLMargin = section.PageSetup.LeftMargin;
                sRMargin = section.PageSetup.RightMargin;
                sGutter = section.PageSetup.Gutter;
                sGutterPos = section.PageSetup.GutterPos;
                sHeaderDist = section.PageSetup.HeaderDistance;
                sFooterDist = section.PageSetup.FooterDistance;
                bTwoPagesOnOne = section.PageSetup.TwoPagesOnOne;
                bBkFldPrnt = section.PageSetup.BookFoldPrinting;
                bBkFldPrnShts = section.PageSetup.BookFoldPrintingSheets;
                bBkFldRevPrnt = section.PageSetup.BookFoldRevPrinting;

                var index = section.Index;


                target.Sections[index].PageSetup.PaperSize = lPaperSize;
                target.Sections[index].PageSetup.GutterStyle = lGutterStyle;
                target.Sections[index].PageSetup.Orientation = lOrientation;
                target.Sections[index].PageSetup.MirrorMargins = lMirrorMargins;
                target.Sections[index].PageSetup.SectionStart = lScnStart;
                target.Sections[index].PageSetup.SectionDirection = lScnDir;
                target.Sections[index].PageSetup.OddAndEvenPagesHeaderFooter = lOddEvenHdFt;
                target.Sections[index].PageSetup.DifferentFirstPageHeaderFooter = lDiffFirstHdFt;
                target.Sections[index].PageSetup.VerticalAlignment = lVerticalAlignment;
                target.Sections[index].PageSetup.PageHeight = sPageHght;
                target.Sections[index].PageSetup.PageWidth = sPageWdth;
                target.Sections[index].PageSetup.TopMargin = sTMargin;
                target.Sections[index].PageSetup.BottomMargin = sBMargin;
                target.Sections[index].PageSetup.LeftMargin = sLMargin;
                target.Sections[index].PageSetup.RightMargin = sRMargin;
                target.Sections[index].PageSetup.Gutter = sGutter;
                target.Sections[index].PageSetup.GutterPos = sGutterPos;
                target.Sections[index].PageSetup.HeaderDistance = sHeaderDist;
                target.Sections[index].PageSetup.FooterDistance = sFooterDist;
                target.Sections[index].PageSetup.TwoPagesOnOne = bTwoPagesOnOne;
                target.Sections[index].PageSetup.BookFoldPrinting = bBkFldPrnt;
                target.Sections[index].PageSetup.BookFoldPrintingSheets = bBkFldPrnShts;
                target.Sections[index].PageSetup.BookFoldRevPrinting = bBkFldRevPrnt;
            }
        }

UPDATE 2

Actually, the page break not remaining in line with paragraph format is not an issue of copying fidelity, but rather an issue of conversion from .doc to .docx. (https://support.microsoft.com/en-us/help/923183/the-layout-of-a-document-that-contains-a-page-break-may-be-different-i) Maybe someone thought of a method to overcome this.


Solution

  • The following code by Paul Edstein (macropod) may assist you. It will at least give you an idea of the complexities you are facing.

    ' ============================================================================================================
    ' KEEP NEXT THREE TOGETHER 
    ' ============================================================================================================
    '
    Sub CombineDocuments()
    ' Paul Edstein
    ' https://www.msofficeforums.com/word-vba/43339-combine-multiple-word-documents.html
    '
    ' Users occasionally need to combine multiple documents that may of may not have the same page layouts,
    '   Style definitions, and so on. Consequently, combining multiple documents is often rather more complex than
    '   simply copying & pasting content from one document to another. Problems arise when the documents have
    '   different page layouts, headers, footers, page numbering, bookmarks & cross-references,
    '   Tables of Contents, Indexes, etc., etc., and especially when those documents have used the same Style
    '   names with different definitions.
    '
    ' The following Word macro (for Windows PCs only) handles the more common issues that arise when combining
    '   documents; it does not attempt to resolve conflicts with paragraph auto-numbering,
    '   document -vs- section page numbering in 'page x of y' numbering schemes, Tables of Contents or Indexing issues.
    '   Neither does it attempt to deal with the effects on footnote or endnote numbering & positioning or with the
    '   consequences of duplicated bookmarks (only one of which can exist in the merged document) and any corresponding
    '   cross-references.
    '
    ' The macro includes a folder browser. Simply select the folder to process and all documents in that folder
    '   will be combined into the currently-active document. Word's .doc, .docx, and .docm formats will all be processed,
    '   even if different formats exist in the selected folder.
    '
        Application.ScreenUpdating = False
        Dim strFolder As String, strFile As String, strTgt As String
        Dim wdDocTgt As Document, wdDocSrc As Document, HdFt As HeaderFooter
        strFolder = GetFolder: If strFolder = "" Then Exit Sub
        Set wdDocTgt = ActiveDocument: strTgt = ActiveDocument.fullname
        strFile = Dir(strFolder & "\*.doc", vbNormal)
        While strFile <> ""
          If strFolder & strFile <> strTgt Then
            Set wdDocSrc = Documents.Open(FileName:=strFolder & "\" & strFile, AddToRecentFiles:=False, Visible:=False)
            With wdDocTgt
              .Characters.Last.InsertBefore vbCr
              .Characters.Last.InsertBreak (wdSectionBreakNextPage)
              With .Sections.Last
                For Each HdFt In .Headers
                  With HdFt
                    .LinkToPrevious = False
                    .range.Text = vbNullString
                    .PageNumbers.RestartNumberingAtSection = True
                    .PageNumbers.StartingNumber = wdDocSrc.Sections.First.Headers(HdFt.Index).PageNumbers.StartingNumber
                  End With
                Next
                For Each HdFt In .Footers
                  With HdFt
                    .LinkToPrevious = False
                    .range.Text = vbNullString
                    .PageNumbers.RestartNumberingAtSection = True
                    .PageNumbers.StartingNumber = wdDocSrc.Sections.First.Headers(HdFt.Index).PageNumbers.StartingNumber
                  End With
                Next
              End With
              Call LayoutTransfer(wdDocTgt, wdDocSrc)
              .range.Characters.Last.FormattedText = wdDocSrc.range.FormattedText
              With .Sections.Last
                For Each HdFt In .Headers
                  With HdFt
                    .range.FormattedText = wdDocSrc.Sections.Last.Headers(.Index).range.FormattedText
                    .range.Characters.Last.Delete
                  End With
                Next
                For Each HdFt In .Footers
                  With HdFt
                    .range.FormattedText = wdDocSrc.Sections.Last.Footers(.Index).range.FormattedText
                    .range.Characters.Last.Delete
                  End With
                Next
              End With
            End With
            wdDocSrc.Close SaveChanges:=False
          End If
          strFile = Dir()
        Wend
        With wdDocTgt
          ' Save & close the combined document
          .SaveAs FileName:=strFolder & "Forms.docx", FileFormat:=wdFormatXMLDocument, AddToRecentFiles:=False
          ' and/or:
          .SaveAs FileName:=strFolder & "Forms.pdf", FileFormat:=wdFormatPDF, AddToRecentFiles:=False
          .Close SaveChanges:=False
        End With
        Set wdDocSrc = Nothing: Set wdDocTgt = Nothing
        Application.ScreenUpdating = True
    End Sub
    ' ============================================================================================================
    Private Function GetFolder() As String
    ' used by CombineDocument macro by Paul Edstein, keep together in same module
    ' https://www.msofficeforums.com/word-vba/43339-combine-multiple-word-documents.html
    
        Dim oFolder As Object
        GetFolder = ""
        Set oFolder = CreateObject("Shell.Application").BrowseForFolder(0, "Choose a folder", 0)
        If (Not oFolder Is Nothing) Then GetFolder = oFolder.Items.Item.Path
        Set oFolder = Nothing
    End Function
    
    Sub LayoutTransfer(wdDocTgt As Document, wdDocSrc As Document)
    ' works with previous Combine Documents macro from Paul Edstein, keep together
    ' https://www.msofficeforums.com/word-vba/43339-combine-multiple-word-documents.html
    '
        Dim sPageHght As Single, sPageWdth As Single
        Dim sHeaderDist As Single, sFooterDist As Single
        Dim sTMargin As Single, sBMargin As Single
        Dim sLMargin As Single, sRMargin As Single
        Dim sGutter As Single, sGutterPos As Single
        Dim lPaperSize As Long, lGutterStyle As Long
        Dim lMirrorMargins As Long, lVerticalAlignment As Long
        Dim lScnStart As Long, lScnDir As Long
        Dim lOddEvenHdFt As Long, lDiffFirstHdFt As Long
        Dim bTwoPagesOnOne As Boolean, bBkFldPrnt As Boolean
        Dim bBkFldPrnShts As Boolean, bBkFldRevPrnt As Boolean
        Dim lOrientation As Long
        With wdDocSrc.Sections.Last.PageSetup
          lPaperSize = .PaperSize
          lGutterStyle = .GutterStyle
          lOrientation = .Orientation
          lMirrorMargins = .MirrorMargins
          lScnStart = .SectionStart
          lScnDir = .SectionDirection
          lOddEvenHdFt = .OddAndEvenPagesHeaderFooter
          lDiffFirstHdFt = .DifferentFirstPageHeaderFooter
          lVerticalAlignment = .VerticalAlignment
          sPageHght = .PageHeight
          sPageWdth = .PageWidth
          sTMargin = .TopMargin
          sBMargin = .BottomMargin
          sLMargin = .LeftMargin
          sRMargin = .RightMargin
          sGutter = .Gutter
          sGutterPos = .GutterPos
          sHeaderDist = .HeaderDistance
          sFooterDist = .FooterDistance
          bTwoPagesOnOne = .TwoPagesOnOne
          bBkFldPrnt = .BookFoldPrinting
          bBkFldPrnShts = .BookFoldPrintingSheets
          bBkFldRevPrnt = .BookFoldRevPrinting
        End With
        With wdDocTgt.Sections.Last.PageSetup
          .GutterStyle = lGutterStyle
          .MirrorMargins = lMirrorMargins
          .SectionStart = lScnStart
          .SectionDirection = lScnDir
          .OddAndEvenPagesHeaderFooter = lOddEvenHdFt
          .DifferentFirstPageHeaderFooter = lDiffFirstHdFt
          .VerticalAlignment = lVerticalAlignment
          .PageHeight = sPageHght
          .PageWidth = sPageWdth
          .TopMargin = sTMargin
          .BottomMargin = sBMargin
          .LeftMargin = sLMargin
          .RightMargin = sRMargin
          .Gutter = sGutter
          .GutterPos = sGutterPos
          .HeaderDistance = sHeaderDist
          .FooterDistance = sFooterDist
          .TwoPagesOnOne = bTwoPagesOnOne
          .BookFoldPrinting = bBkFldPrnt
          .BookFoldPrintingSheets = bBkFldPrnShts
          .BookFoldRevPrinting = bBkFldRevPrnt
          .PaperSize = lPaperSize
          .Orientation = lOrientation
        End With
    End Sub
     
    ' ============================================================================================================