Объединить несколько документов 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
,
Для каждого документа, который вы хотите объединить с основным документом, вам необходимо:
- добавить
AlternativeFormatImportPart
вmainDocumentPart
сId
который должен быть уникальным. Этот элемент содержит вставленные данные - добавить в тело
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;
}
}
}
}