Объединить несколько документов Word в один Open Xml

У меня есть около 10 документов Word, которые я генерирую, используя открытый XML и другие вещи. Теперь я хотел бы создать еще один документ Word, и один за другим я хотел бы присоединить их к этому вновь созданному документу. Я хочу использовать open xml, любой намек будет заметен. Ниже мой код:

 private void CreateSampleWordDocument()
    {
        //string sourceFile = Path.Combine("D:\\GeneralLetter.dot");
        //string destinationFile = Path.Combine("D:\\New.doc");
        string sourceFile = Path.Combine("D:\\GeneralWelcomeLetter.docx");
        string destinationFile = Path.Combine("D:\\New.docx");
        try
        {
            // Create a copy of the template file and open the copy
            //File.Copy(sourceFile, destinationFile, true);
            using (WordprocessingDocument document = WordprocessingDocument.Open(destinationFile, true))
            {
                // Change the document type to Document
                document.ChangeDocumentType(DocumentFormat.OpenXml.WordprocessingDocumentType.Document);
                //Get the Main Part of the document
                MainDocumentPart mainPart = document.MainDocumentPart;
                mainPart.Document.Save();
            }
        }
        catch
        {
        }
    }

Обновление (используя AltChunks):

using (WordprocessingDocument myDoc = WordprocessingDocument.Open("D:\\Test.docx", true))
        {
            string altChunkId = "AltChunkId" + DateTime.Now.Ticks.ToString().Substring(0, 2) ;
            MainDocumentPart mainPart = myDoc.MainDocumentPart;
            AlternativeFormatImportPart chunk = mainPart.AddAlternativeFormatImportPart(
                AlternativeFormatImportPartType.WordprocessingML, altChunkId);
            using (FileStream fileStream = File.Open("D:\\Test1.docx", FileMode.Open))
                chunk.FeedData(fileStream);
            AltChunk altChunk = new AltChunk();
            altChunk.Id = altChunkId;
            mainPart.Document
                .Body
                .InsertAfter(altChunk, mainPart.Document.Body.Elements<Paragraph>().Last());
            mainPart.Document.Save();
        } 

Почему этот код перезаписывает содержимое последнего файла, когда я использую несколько файлов?Обновление 2:

 using (WordprocessingDocument myDoc = WordprocessingDocument.Open("D:\\Test.docx", true))
        {

            MainDocumentPart mainPart = myDoc.MainDocumentPart;
            string altChunkId = "AltChunkId" + DateTime.Now.Ticks.ToString().Substring(0, 3);
            AlternativeFormatImportPart chunk = mainPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.WordprocessingML, altChunkId);
            using (FileStream fileStream = File.Open("d:\\Test1.docx", FileMode.Open))
            {
                chunk.FeedData(fileStream);
                AltChunk altChunk = new AltChunk();
                altChunk.Id = altChunkId;
                mainPart.Document
                    .Body
                    .InsertAfter(altChunk, mainPart.Document.Body
                    .Elements<Paragraph>().Last());
                mainPart.Document.Save();
            }
            using (FileStream fileStream = File.Open("d:\\Test2.docx", FileMode.Open))
            {
                chunk.FeedData(fileStream);
                AltChunk altChunk = new AltChunk();
                altChunk.Id = altChunkId;
                mainPart.Document
                    .Body
                    .InsertAfter(altChunk, mainPart.Document.Body
                    .Elements<Paragraph>().Last());
            }
            using (FileStream fileStream = File.Open("d:\\Test3.docx", FileMode.Open))
            {
                chunk.FeedData(fileStream);
                AltChunk altChunk = new AltChunk();
                altChunk.Id = altChunkId;
                mainPart.Document
                    .Body
                    .InsertAfter(altChunk, mainPart.Document.Body
                    .Elements<Paragraph>().Last());
            } 
        }

Этот код добавляет данные Test2 дважды, а также данные Test1. Значит, я получаю:

Test
Test2
Test2

вместо:

Test
Test1
Test2

5 ответов

Решение

Используя только openXML SDK, вы можете использовать AltChunk элемент для объединения нескольких документов в один.

Эта ссылка "простой в сборке документ из нескольких слов" и " Как использовать altChunk для сборки документов" предоставляют некоторые примеры.

РЕДАКТИРОВАТЬ 1

На основе вашего кода, который использует altchunk В обновленном вопросе (обновление № 1) приведен код VB.Net, который я протестировал, и он мне подходит:

Using myDoc = DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Open("D:\\Test.docx", True)
        Dim altChunkId = "AltChunkId" + DateTime.Now.Ticks.ToString().Substring(0, 2)
        Dim mainPart = myDoc.MainDocumentPart
        Dim chunk = mainPart.AddAlternativeFormatImportPart(
            DocumentFormat.OpenXml.Packaging.AlternativeFormatImportPartType.WordprocessingML, altChunkId)
        Using fileStream As IO.FileStream = IO.File.Open("D:\\Test1.docx", IO.FileMode.Open)
            chunk.FeedData(fileStream)
        End Using
        Dim altChunk = New DocumentFormat.OpenXml.Wordprocessing.AltChunk()
        altChunk.Id = altChunkId
        mainPart.Document.Body.InsertAfter(altChunk, mainPart.Document.Body.Elements(Of DocumentFormat.OpenXml.Wordprocessing.Paragraph).Last())
        mainPart.Document.Save()
End Using

РЕДАКТИРОВАТЬ 2

Второй выпуск (обновление № 2)

Этот код добавляет данные Test2 дважды, а также данные Test1.

относится к altchunkid,

Для каждого документа, который вы хотите объединить с основным документом, вам необходимо:

  1. добавить AlternativeFormatImportPart в mainDocumentPart с Id который должен быть уникальным. Этот элемент содержит вставленные данные
  2. добавить в тело Altchunk элемент, в котором вы устанавливаете id ссылаться на предыдущий AlternativeFormatImportPart,

В вашем коде вы используете один и тот же идентификатор для всех AltChunks, Вот почему вы видите много раз один и тот же текст.

Я не уверен, что altchunkid будет уникальным с вашим кодом: string altChunkId = "AltChunkId" + DateTime.Now.Ticks.ToString().Substring(0, 2);

Если вам не нужно устанавливать конкретное значение, я рекомендую вам не устанавливать явно AltChunkId когда вы добавляете AlternativeFormatImportPart, Вместо этого вы получаете один сгенерированный SDK следующим образом:

VB.Net

Dim chunk As AlternativeFormatImportPart = mainPart.AddAlternativeFormatImportPart(DocumentFormat.OpenXml.Packaging.AlternativeFormatImportPartType.WordprocessingML)
Dim altchunkid As String = mainPart.GetIdOfPart(chunk)

C#

AlternativeFormatImportPart chunk = mainPart.AddAlternativeFormatImportPart(DocumentFormat.OpenXml.Packaging.AlternativeFormatImportPartType.WordprocessingML);
string altchunkid = mainPart.GetIdOfPart(chunk);

Существует замечательный API-оболочка (Document Builder 2.2) для открытого XML, специально разработанный для объединения документов, с гибкостью выбора абзацев для объединения и т. Д. Вы можете скачать его отсюда (обновление: перенесено в github).

Документация и скриншоты о том, как его использовать, находятся здесь.

Обновление: Пример кода

 var sources = new List<Source>();
 //Document Streams (File Streams) of the documents to be merged.
 foreach (var stream in documentstreams)
 {
        var tempms = new MemoryStream();
        stream.CopyTo(tempms);
        sources.Add(new Source(new WmlDocument(stream.Length.ToString(), tempms), true));
 }

  var mergedDoc = DocumentBuilder.BuildDocument(sources);
  mergedDoc.SaveAs(@"C:\TargetFilePath");

Типы Source а также WmlDocument взяты из Document Builder API.

Вы даже можете добавить пути к файлам напрямую, если выберете:

sources.Add(new Source(new WmlDocument(@"C:\FileToBeMerged1.docx"));
sources.Add(new Source(new WmlDocument(@"C:\FileToBeMerged2.docx"));

Нашел это Хорошее Сравнение между AltChunk а также Document Builder подходы к объединению документов - полезно выбирать исходя из своих требований.

Вы также можете использовать библиотеку DocX для объединения документов, но я предпочитаю Document Builder, а не для объединения документов.

Надеюсь это поможет.

Единственное, чего не хватает в этих ответах, так это for петля.

Для тех, кто просто хочет скопировать / вставить:

void MergeInNewFile(string resultFile, IList<string> filenames)
{
    using (WordprocessingDocument document = WordprocessingDocument.Create(resultFile, WordprocessingDocumentType.Document))
    {
        MainDocumentPart mainPart = document.AddMainDocumentPart();
        mainPart.Document = new Document(new Body());

        foreach (string filename in filenames)
        {
            AlternativeFormatImportPart chunk = mainPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.WordprocessingML);
            string altChunkId = mainPart.GetIdOfPart(chunk);

            using (FileStream fileStream = File.Open(filename, FileMode.Open))
            {
                chunk.FeedData(fileStream);
            }

            AltChunk altChunk = new AltChunk { Id = altChunkId };
            mainPart.Document.Body.AppendChild(altChunk);
        }

        mainPart.Document.Save();
    }
}

Все кредиты принадлежат Крису и yonexbat

Легко использовать в C#:

using System;
using System.IO;
using System.Linq;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

namespace WordMergeProject
{
    public class Program
    {
        private static void Main(string[] args)
        {
            byte[] word1 = File.ReadAllBytes(@"..\..\word1.docx");
            byte[] word2 = File.ReadAllBytes(@"..\..\word2.docx");

            byte[] result = Merge(word1, word2);

            File.WriteAllBytes(@"..\..\word3.docx", result);
        }

        private static byte[] Merge(byte[] dest, byte[] src)
        {
            string altChunkId = "AltChunkId" + DateTime.Now.Ticks.ToString();

            var memoryStreamDest = new MemoryStream();
            memoryStreamDest.Write(dest, 0, dest.Length);
            memoryStreamDest.Seek(0, SeekOrigin.Begin);
            var memoryStreamSrc = new MemoryStream(src);

            using (WordprocessingDocument doc = WordprocessingDocument.Open(memoryStreamDest, true))
            {
                MainDocumentPart mainPart = doc.MainDocumentPart;
                AlternativeFormatImportPart altPart =
                    mainPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.WordprocessingML, altChunkId);
                altPart.FeedData(memoryStreamSrc);
                var altChunk = new AltChunk();
                altChunk.Id = altChunkId;
                              OpenXmlElement lastElem = mainPart.Document.Body.Elements<AltChunk>().LastOrDefault();
            if(lastElem == null)
            {
                lastElem = mainPart.Document.Body.Elements<Paragraph>().Last();
            }


            //Page Brake einfügen
            Paragraph pageBreakP = new Paragraph();
            Run pageBreakR = new Run();
            Break pageBreakBr = new Break() { Type = BreakValues.Page };

            pageBreakP.Append(pageBreakR);
            pageBreakR.Append(pageBreakBr);                

            return memoryStreamDest.ToArray();
        }
    }
}

Мое решение:

      using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

namespace TestFusionWord
{
    internal class Program
    {
        public static void MergeDocx(List<string> ListPathFilesToMerge, string DestinationPathFile, bool OverWriteDestination, bool WithBreakPage)
        {
            #region Control arguments

            List<string> ListError = new List<string>();
            if (ListPathFilesToMerge == null || ListPathFilesToMerge.Count == 0)
            {
                ListError.Add("Il n'y a aucun fichier à fusionner dans la liste passée en paramètre ListPathFilesToMerge");
            }
            else
            {
                foreach (var item in ListPathFilesToMerge.Where(x => Path.GetExtension(x.ToLower()) != ".docx"))
                {
                    ListError.Add(string.Format("Le fichier '{0}' indiqué dans la liste passée en paramètre ListPathFilesToMerge n'a pas l'extension .docx", item));
                }

                foreach (var item in ListPathFilesToMerge.Where(x => !File.Exists(x)))
                {
                    ListError.Add(string.Format("Le fichier '{0}' indiqué dans la liste passée en paramètre ListPathFilesToMerge n'existe pas", item));
                }
            }

            if (string.IsNullOrWhiteSpace(DestinationPathFile))
            {
                ListError.Add("Le fichier destination FinalPathFile passé en paramètre ne peut être vide");
            }
            else
            {
                if (Path.GetExtension(DestinationPathFile.ToLower()) != ".docx")
                {
                    ListError.Add(string.Format("Le fichier destination '{0}' indiqué dans le paramètre DestinationPathFile n'a pas l'extension .docx", DestinationPathFile));
                }

                if (File.Exists(DestinationPathFile) && !OverWriteDestination)
                {
                    ListError.Add(string.Format("Le fichier destination '{0}' existe déjà. Utilisez l'argument OverWriteDestination si vous souhaitez l'écraser", DestinationPathFile));
                }
            }

            if (ListError.Any())
            {
                string MessageError = "Des erreurs ont été rencontrés, détail : " + Environment.NewLine + ListError.Select(x => "- " + x).Aggregate((x, y) => x + Environment.NewLine + y);
                throw new ArgumentException(MessageError);
            }

            #endregion Control arguments

            #region Merge Files

            //Suppression du fichier destination (aucune erreur déclenchée si le fichier n'existe pas)
            File.Delete(DestinationPathFile);

            //Création du fichier destination à vide
            using (WordprocessingDocument document = WordprocessingDocument.Create(DestinationPathFile, WordprocessingDocumentType.Document))
            {
                MainDocumentPart mainPart = document.AddMainDocumentPart();
                mainPart.Document = new Document(new Body());
                document.MainDocumentPart.Document.Save();
            }

            //Fusion des documents
            using (WordprocessingDocument myDoc = WordprocessingDocument.Open(DestinationPathFile, true))
            {
                MainDocumentPart mainPart = myDoc.MainDocumentPart;
                Body body = mainPart.Document.Body;

                for (int i = 0; i < ListPathFilesToMerge.Count; i++)
                {
                    string currentpathfile = ListPathFilesToMerge[i];
                    AlternativeFormatImportPart chunk = mainPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.WordprocessingML);
                    string altchunkid = mainPart.GetIdOfPart(chunk);

                    using (FileStream fileStream = File.Open(currentpathfile, FileMode.Open))
                        chunk.FeedData(fileStream);

                    AltChunk altChunk = new AltChunk();
                    altChunk.Id = altchunkid;

                    OpenXmlElement last = body.Elements().LastOrDefault(e => e is AltChunk || e is Paragraph);
                    body.InsertAfter(altChunk, last);

                    if (WithBreakPage && i < ListPathFilesToMerge.Count - 1) // If its not the last file, add breakpage
                    {
                        last = body.Elements().LastOrDefault(e => e is AltChunk || e is Paragraph);
                        last.InsertAfterSelf(new Paragraph(new Run(new Break() { Type = BreakValues.Page })));
                    }
                }

                mainPart.Document.Save();
            }

            #endregion Merge Files
        }

        private static int Main(string[] args)
        {
            try
            {
                string DestinationPathFile = @"C:\temp\testfusion\docfinal.docx";

                List<string> ListPathFilesToMerge = new List<string>()
                                    {
                                        @"C:\temp\testfusion\fichier1.docx",
                                        @"C:\temp\testfusion\fichier2.docx",
                                        @"C:\temp\testfusion\fichier3.docx"
                                    };

                ListPathFilesToMerge.Sort(); //Sort for always have the same file

                MergeDocx(ListPathFilesToMerge, DestinationPathFile, true, true);

#if DEBUG
                Process.Start(DestinationPathFile); //open file
#endif
                return 0;
            }
            catch (Exception Ex)
            {
                Console.Error.WriteLine(Ex.Message);
                //Log exception here
                return -1;
            }
            

        }
    }
}
Другие вопросы по тегам