Merge Word Documents
- 11 minutes to read
This topic describes how to use the Word Processing Document API to merge documents and keep document formatting.
Merge Documents into a Word File
Use one of the following methods to insert the content of one document into another:
- SubDocument.AppendDocumentContent
- Allows you to append content from the specified file, range, or stream.
- SubDocument.InsertDocumentContent
- Allows you to insert content from a file, range, or stream in the specified document position.
Call the RichEditDocumentServer.SaveDocument method to save the resulting document.
using (var wordProcessor = new RichEditDocumentServer()) {
// Load a document
wordProcessor.LoadDocument("document1.docx");
// Append another document's content
wordProcessor.Document.AppendDocumentContent("document2.docx");
// Save the result
wordProcessor.SaveDocument("result.docx", DocumentFormat.OpenXml);
}
Merge Documents into a PDF File
Call the RichEditDocumentServer.ExportToPdf method to export the result to a PDF file. You can also use the PDF Document API to export all documents to separate PDF files and merge them. Refer to the following topic for more information:
Keep Document Formatting
Direct Formatting and Styles
Pass one of the InsertOptions enumeration members to the AppendDocumentContent or InsertDocumentContent method as an insertOptions parameter to specify how the formatting is applied to the inserted content.
The table below shows how formatting is applied to the pasted document elements depending on the InsertOptions property value:
Enumeration Value | Direct Formatting | Styles (character, paragraph, table) | Non-textual objects (tables, floating objects, comments, etc.) | Page Properties and Tabs |
---|---|---|---|---|
The inserted text retains its formatting. | New styles (styles that don’t exist in the target document) are copied. If character or paragraph style conflicts occur, all inserted style properties are applied as direct formatting, but the style itself is not transferred. Destination document styles with default settings are replaced with the inserted styles. Conflicting table and numbering list styles are copied to the document — the new style name is a combination of conflicting style names (for example, Normal1 and Normal1 are combined into Normal11). | Non-textual elements are retained. | All page properties are retained. | |
The inserted text loses its formatting and takes on direct formatting applied to the target text range. Numbering lists are converted to simple text. | All inserted styles are not retained. | Floating objects, shapes and comments are discarded. Tables are converted into a series of paragraphs. | All page properties, including tabs, are lost. | |
The inserted text retains its formatting. | All inserted styles are not retained. | Non-textual elements are retained. | All page properties are retained. | |
The inserted text retains the following character properties: Other character properties and all paragraph properties are ignored. | Inserted style’s character properties are applied as direct, but the style itself is not copied to the target document. Style paragraph properties are ignored. Table styles are not retained. Numbering list styles are transferred to the target document. | Non-textual elements are retained. | Page properties are retained. Tabs are ignored. | |
The inserted text keeps its format options. | New styles (styles that don’t exist in the target document) are copied. If style conflicts occur, the inserted content’s styles are ignored. The destination document’s styles with default settings are replaced with the inserted styles. | Non-textual elements are retained. | All page properties are retained. |
Note
When you merge documents with themes, the result may lose theme-related formatting (color, paragraph spacing).
Multiple Sections with Different Page Layout Settings
If you merge documents with different page layout settings or with multiple sections, insert document content into a new section. Remember to copy all required section information to the target section.
The code sample below shows how to merge multiple documents with different section parameters. The SectionsMerger
class is used to copy section parameters, and append each document to a new page.
using DevExpress.XtraRichEdit;
using DevExpress.XtraRichEdit.API.Native;
using System.Diagnostics;
using (var wordProcessor = new RichEditDocumentServer())
{
// Define a list of documents to merge
List<string> filenames = new List<string>() {
@"Documents\FirstLook.docx",
@"Documents\HeadersFooters.docx",
@"Documents\HansInLuck.docx"
};
// Merge documents
Document mergedDoc = MergeDocuments(filenames);
// Append the resulting content to the word processor
wordProcessor.Document.AppendDocumentContent(mergedDoc.Range);
// Save the result
wordProcessor.SaveDocument("result.docx", DocumentFormat.OpenXml);
Process.Start(new ProcessStartInfo("result.docx") { UseShellExecute = true });
}
static Document MergeDocuments(List<string> filenames)
{
// Create two word processor instances
RichEditDocumentServer targetProcessor = new RichEditDocumentServer();
Document targetDoc = targetProcessor.Document;
RichEditDocumentServer sourceProcessor = new RichEditDocumentServer();
Document sourceDoc = sourceProcessor.Document;
// Load each document to the source processor
for (int i = 0; i < filenames.Count; i++)
{
sourceProcessor.LoadDocument(filenames[i]);
foreach (Section section in sourceDoc.Sections)
{
// Append each document section one-by-one
SectionsMerger.AppendSection(targetDoc, sourceDoc, section);
}
}
return targetProcessor.Document;
}
public class SectionsMerger
{
public static void AppendSection(Document target, Document source, Section sourceSection)
{
Section section;
if (target.IsEmpty)
section = target.Sections[0];
else
// Append a section break
// to insert each merged section to the new page
section = target.AppendSection();
section.StartType = SectionStartType.NextPage;
target.Unit = source.Unit;
// Copy a section's page layout parameters
ApplySectionSettings(sourceSection, section);
// Append section content
DocumentRange range = target.AppendDocumentContent(sourceSection.Range, InsertOptions.KeepSourceFormatting);
// Align shape z-order
ReadOnlyShapeCollection targetShapes = target.Shapes.Get(range);
foreach (Shape shape in targetShapes)
shape.ZOrder = 0;
}
// This method copies page layout parameters
static void ApplySectionSettings(Section sourceSection, Section targetSection)
{
SectionPage pageSettings = targetSection.Page;
pageSettings.PaperKind = sourceSection.Page.PaperKind;
if (pageSettings.PaperKind == DevExpress.Drawing.Printing.DXPaperKind.Custom)
{
pageSettings.Width = sourceSection.Page.Width;
pageSettings.Height = sourceSection.Page.Height;
}
pageSettings.Landscape = sourceSection.Page.Landscape;
SectionMargins margins = targetSection.Margins;
margins.Left = sourceSection.Margins.Left;
margins.Right = sourceSection.Margins.Right;
margins.Top = sourceSection.Margins.Top;
margins.Bottom = sourceSection.Margins.Bottom;
margins.HeaderOffset = sourceSection.Margins.HeaderOffset;
margins.FooterOffset = sourceSection.Margins.FooterOffset;
targetSection.DifferentFirstPage = sourceSection.DifferentFirstPage;
targetSection.StartType = sourceSection.StartType;
SectionColumnCollection columns = sourceSection.Columns.GetColumns();
targetSection.Columns.SetColumns(columns);
}
}
Headers and Footers
Headers and footers belong to document sections. To merge documents with different headers or footers, insert document content into a new section. Remember to copy all required header and footer information to the target section.
The code sample below shows how to merge documents with different headers and footers. The SectionsMerger
class is used to copy headers and footers of each document section.
using DevExpress.XtraRichEdit;
using DevExpress.XtraRichEdit.API.Native;
using System.Diagnostics;
using (var wordProcessor = new RichEditDocumentServer())
{
// Define a list of documents to merge
List<string> filenames = new List<string>() {
@"Documents\FirstLook.docx",
@"Documents\HeadersFooters.docx",
@"Documents\HansInLuck.docx"
};
// Merge documents
Document mergedDoc = MergeDocuments(filenames);
// Append the resulting content to the word processor
wordProcessor.Document.AppendDocumentContent(mergedDoc.Range);
// Save the result
wordProcessor.SaveDocument("result.docx", DocumentFormat.OpenXml);
Process.Start(new ProcessStartInfo("result.docx") { UseShellExecute = true });
}
static Document MergeDocuments(List<string> filenames)
{
// Create two word processor instances
RichEditDocumentServer targetProcessor = new RichEditDocumentServer();
Document targetDoc = targetProcessor.Document;
RichEditDocumentServer sourceProcessor = new RichEditDocumentServer();
Document sourceDoc = sourceProcessor.Document;
for (int i = 0; i < filenames.Count; i++)
{
sourceProcessor.LoadDocument(filenames[i]);
// Unlink all headers and footers
Section targetSection = targetDoc.Sections[targetDoc.Sections.Count - 1];
targetSection.UnlinkHeaderFromPrevious();
targetSection.UnlinkFooterFromPrevious();
targetSection.UnlinkHeaderFromPrevious(HeaderFooterType.First);
targetSection.UnlinkFooterFromPrevious(HeaderFooterType.First);
targetSection.UnlinkHeaderFromPrevious(HeaderFooterType.Even);
targetSection.UnlinkFooterFromPrevious(HeaderFooterType.Even);
// Append each document section one-by-one
SectionsMerger.Append(sourceDoc, targetDoc);
if (i == filenames.Count - 1)
return targetDoc;
// Append a new section that starts
// on the new page
targetDoc.AppendSection();
targetDoc.Sections.Last().StartType = SectionStartType.NextPage;
}
return targetDoc;
}
public class SectionsMerger
{
public static void Append(Document source, Document target)
{
int lastSectionIndexBeforeAppending = target.Sections.Count - 1;
int sourceSectionCount = source.Sections.Count;
// Append document body
target.AppendDocumentContent(source.Range);
target.DifferentOddAndEvenPages = source.DifferentOddAndEvenPages;
for (int i = 0; i < sourceSectionCount; i++)
{
Section sourceSection = source.Sections[i];
Section targetSection = target.Sections[lastSectionIndexBeforeAppending + i];
// Copy headers and footers
AppendHeader(sourceSection, targetSection, HeaderFooterType.Odd);
AppendFooter(sourceSection, targetSection, HeaderFooterType.Odd);
AppendHeader(sourceSection, targetSection, HeaderFooterType.Even);
AppendFooter(sourceSection, targetSection, HeaderFooterType.Even);
AppendHeader(sourceSection, targetSection, HeaderFooterType.First);
AppendFooter(sourceSection, targetSection, HeaderFooterType.First);
}
}
private static void AppendHeader(Section sourceSection, Section targetSection, HeaderFooterType headerFooterType)
{
// Clear headers in the target document
if (!sourceSection.HasHeader(headerFooterType))
{
if (targetSection.HasHeader(headerFooterType))
{
SubDocument targetHeader = targetSection.BeginUpdateHeader(headerFooterType);
targetHeader.Delete(targetHeader.Range);
targetSection.EndUpdateHeader(targetHeader);
}
return;
}
// Insert header content
SubDocument source = sourceSection.BeginUpdateHeader(headerFooterType);
SubDocument target = targetSection.BeginUpdateHeader(headerFooterType);
target.Delete(target.Range);
target.InsertDocumentContent(target.Range.Start, source.Range, InsertOptions.KeepSourceFormatting);
// Delete empty paragraphs
DocumentRange emptyParagraph = target.CreateRange(target.Range.End.ToInt() - 1, 1);
target.Delete(emptyParagraph);
sourceSection.EndUpdateHeader(source);
targetSection.EndUpdateHeader(target);
}
private static void AppendFooter(Section sourceSection, Section targetSection, HeaderFooterType headerFooterType)
{
// Clear footers in the target document
if (!sourceSection.HasFooter(headerFooterType))
{
if (targetSection.HasFooter(headerFooterType))
{
SubDocument targetFooter = targetSection.BeginUpdateFooter(headerFooterType);
targetFooter.Delete(targetFooter.Range);
targetSection.EndUpdateFooter(targetFooter);
}
return;
}
// Insert footer content
SubDocument source = sourceSection.BeginUpdateFooter(headerFooterType);
SubDocument target = targetSection.BeginUpdateFooter(headerFooterType);
target.Delete(target.Range);
target.InsertDocumentContent(target.Range.Start, source.Range, InsertOptions.KeepSourceFormatting);
// Delete empty paragraphs
DocumentRange emptyParagraph = target.CreateRange(target.Range.End.ToInt() - 1, 1);
target.Delete(emptyParagraph);
sourceSection.EndUpdateFooter(source);
targetSection.EndUpdateFooter(target);
}
}