Сложите несколько файлов DOCX вместе

Мне нужно программно использовать C #, чтобы добавить несколько уже существующих docx файлов в один длинный docx файл, включая специальные разметки, такие как маркеры и изображения. Информация верхнего и нижнего колонтитулов будет удалена, поэтому они не будут вызывать никаких проблем.

Я могу найти много информации об управлении отдельным docx файлом с помощью .NET Framework 3, но нет ничего простого или очевидного в том, как объединить файлы. Также существует сторонняя программа (Acronis.Words), которая сделает это, но она слишком дорога.

Обновлять:

Предлагалась автоматизация через Word, но мой код будет работать в ASP.NET на веб-сервере IIS, поэтому переход в Word для меня не вариант. Извините, что не упомянул об этом в первую очередь.


person ShootTheCore    schedule 29.10.2008    source источник


Ответы (8)


Несмотря на все представленные хорошие предложения и решения, я разработал альтернативу. На мой взгляд, вам следует полностью избегать использования Word в серверных приложениях. Итак, я работал с OpenXML, но он не работал с AltChunk. Я добавил текст в исходное тело, я получаю список байтов [] вместо списка имен файлов, но вы можете легко изменить код в соответствии с вашими потребностями.

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

namespace OfficeMergeControl
{
    public class CombineDocs
    {
        public byte[] OpenAndCombine( IList<byte[]> documents )
        {
            MemoryStream mainStream = new MemoryStream();

            mainStream.Write(documents[0], 0, documents[0].Length);
            mainStream.Position = 0;

            int pointer = 1;
            byte[] ret;
            try
            {
                using (WordprocessingDocument mainDocument = WordprocessingDocument.Open(mainStream, true))
                {

                    XElement newBody = XElement.Parse(mainDocument.MainDocumentPart.Document.Body.OuterXml);

                    for (pointer = 1; pointer < documents.Count; pointer++)
                    {
                        WordprocessingDocument tempDocument = WordprocessingDocument.Open(new MemoryStream(documents[pointer]), true);
                        XElement tempBody = XElement.Parse(tempDocument.MainDocumentPart.Document.Body.OuterXml);

                        newBody.Add(tempBody);
                        mainDocument.MainDocumentPart.Document.Body = new Body(newBody.ToString());
                        mainDocument.MainDocumentPart.Document.Save();
                        mainDocument.Package.Flush();
                    }
                }
            }
            catch (OpenXmlPackageException oxmle)
            {
                throw new OfficeMergeControlException(string.Format(CultureInfo.CurrentCulture, "Error while merging files. Document index {0}", pointer), oxmle);
            }
            catch (Exception e)
            {
                throw new OfficeMergeControlException(string.Format(CultureInfo.CurrentCulture, "Error while merging files. Document index {0}", pointer), e);
            }
            finally
            {
                ret = mainStream.ToArray();
                mainStream.Close();
                mainStream.Dispose();
            }
            return (ret);
        }
    }
}

Я надеюсь, это поможет вам.

person GRGodoi    schedule 17.03.2010
comment
Добавляет ли он еще и разрыв страницы? - person MadBoy; 17.03.2010
comment
Привет, MadBoy, я проверил, и он сохраняет исходные разрывы страниц и при необходимости добавляет новые разрывы страниц. - person GRGodoi; 17.03.2010
comment
Будет ли это работать с изображениями, верхними и нижними колонтитулами (разными верхними и нижними колонтитулами, изображениями в каждом документе)? - person MadBoy; 08.10.2010
comment
@MadBoy: это не так. Для этого вы можете использовать OpenXML PowerTools от CodePlex. Я написал эквивалентный код на Java поверх docx4j. - person JasonPlutext; 14.11.2010
comment
@MadBoy - он не будет явно создавать новые разрывы страниц между объединенными документами, он просто будет перемещаться по новым страницам по мере необходимости. Однако вы можете явно добавлять разрывы страниц между документами, выполнив следующие действия (перед первой строкой внутри цикла for): newBody.Add(XElement.Parse(new Paragraph(new Run(new Break { Type = BreakValues.Page })).OuterXml)); - person Alconja; 15.03.2011
comment
@GRGodoi, не могли бы вы сказать мне или дать мне код, чтобы сделать то же самое для PowerPoint? - person Ali; 01.12.2012
comment
Нужно ли сохранять и сбрасывать на каждой итерации вместо того, чтобы просто компилировать тела и сохранять и сбрасывать в конце? - person Ingó Vals; 15.05.2013

Вам не нужно использовать автоматизацию. Файлы DOCX основаны на форматах OpenXML. Это просто zip-файлы с кучей XML и двоичных частей (файлов think) внутри. Вы можете открыть их с помощью Packaging API (System.IO.Packaging в WindowsBase.dll) и управлять ими с помощью любого из классов XML в Framework.

Дополнительные сведения см. На OpenXMLDeveloper.org.

person Rob Windsor    schedule 30.10.2008
comment
Автоматизация от сатаны. Хороший ответ, Роб. - person Dave Markle; 30.10.2008
comment
Правильно, однако для всех документов, кроме тривиальных, необходимо многое сделать для обеспечения целостности документа. Лучшее бесплатное решение описано на странице openxmldeveloper.org/wiki/w/wiki/documentbuilder.aspx, но если вам нужно больше, вы можете попробовать наш коммерческий продукт MergeDocx.NET в Интернете по адресу plutext.com/m/index.php/products - person JasonPlutext; 05.07.2013

Это очень поздно для исходного вопроса, и в нем есть несколько изменений, но я подумал, что поделюсь тем, как я написал свою логику слияния. При этом используются Open XML Power Tools

public byte[] CreateDocument(IList<byte[]> documentsToMerge)
{
    List<Source> documentBuilderSources = new List<Source>();
    foreach (byte[] documentByteArray in documentsToMerge)
    {
        documentBuilderSources.Add(new Source(new WmlDocument(string.Empty, documentByteArray), false));
    }

    WmlDocument mergedDocument = DocumentBuilder.BuildDocument(documentBuilderSources);
    return mergedDocument.DocumentByteArray;
}

В настоящее время это очень хорошо работает в нашем приложении. Я немного изменил код, потому что мои требования заключаются в том, чтобы каждый документ должен был быть обработан в первую очередь. Таким образом, передается объект DTO с массивом байтов шаблона и различными значениями, которые необходимо заменить. Вот как сейчас выглядит мой код. Это продвигает код немного дальше.

public byte[] CreateDocument(IList<DocumentSection> documentTemplates)
{
    List<Source> documentBuilderSources = new List<Source>();
    foreach (DocumentSection documentTemplate in documentTemplates.OrderBy(dt => dt.Rank))
    {
        // Take the template replace the items and then push it into the chunk
        using (MemoryStream templateStream = new MemoryStream())
        {
            templateStream.Write(documentTemplate.Template, 0, documentTemplate.Template.Length);

            this.ProcessOpenXMLDocument(templateStream, documentTemplate.Fields);

            documentBuilderSources.Add(new Source(new WmlDocument(string.Empty, templateStream.ToArray()), false));
        }
    }

    WmlDocument mergedDocument = DocumentBuilder.BuildDocument(documentBuilderSources);
    return mergedDocument.DocumentByteArray;
}
person Mike B    schedule 23.08.2012

Некоторое время назад я написал небольшое тестовое приложение для этого. Мое тестовое приложение работало с документами Word 2003 (.doc), а не с .docx, но я полагаю, что процесс такой же - я думаю, все, что вам нужно изменить, - это использовать более новую версию Primary Interop Assembly. Этот код выглядел бы намного аккуратнее с новыми функциями C # 4.0 ...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Office.Interop.Word;
using Microsoft.Office.Core;
using System.Runtime.InteropServices;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            new Program().Start();
        }

        private void Start()
        {
            object fileName = Path.Combine(Environment.CurrentDirectory, @"NewDocument.doc");
            File.Delete(fileName.ToString());

            try
            {
                WordApplication = new ApplicationClass();
                var doc = WordApplication.Documents.Add(ref missing, ref missing, ref missing, ref missing);
                try
                {
                    doc.Activate();

                    AddDocument(@"D:\Projects\WordTests\ConsoleApplication1\Documents\Doc1.doc", doc, false);
                    AddDocument(@"D:\Projects\WordTests\ConsoleApplication1\Documents\Doc2.doc", doc, true);

                    doc.SaveAs(ref fileName,
                        ref missing, ref missing, ref missing, ref missing,     ref missing,
                        ref missing, ref missing, ref missing, ref missing, ref missing,
                        ref missing, ref missing, ref missing, ref missing, ref missing);
                }
                finally
                {
                    doc.Close(ref missing, ref missing, ref missing);
                }
            }
            finally
            {
                WordApplication.Quit(ref missing, ref missing, ref missing);
            }
        }

        private void AddDocument(string path, Document doc, bool lastDocument)
        {
            object subDocPath = path;
            var subDoc = WordApplication.Documents.Open(ref subDocPath, ref missing, ref missing, ref missing,
                ref missing, ref missing, ref missing, ref missing, ref missing,
                ref missing, ref missing, ref missing, ref missing, ref missing,
                ref missing, ref missing);
            try
            {

                object docStart = doc.Content.End - 1;
                object docEnd = doc.Content.End;

                object start = subDoc.Content.Start;
                object end = subDoc.Content.End;

                Range rng = doc.Range(ref docStart, ref docEnd);
                rng.FormattedText = subDoc.Range(ref start, ref end);

                if (!lastDocument)
                {
                    InsertPageBreak(doc);
                }
            }
            finally
            {
                subDoc.Close(ref missing, ref missing, ref missing);
            }
        }

        private static void InsertPageBreak(Document doc)
        {
            object docStart = doc.Content.End - 1;
            object docEnd = doc.Content.End;
            Range rng = doc.Range(ref docStart, ref docEnd);

            object pageBreak = WdBreakType.wdPageBreak;
            rng.InsertBreak(ref pageBreak);
        }

        private ApplicationClass WordApplication { get; set; }

        private object missing = Type.Missing;
    }
}
person Terence Lewis    schedule 29.10.2008
comment
Отлично работает, но не хватает верхнего и нижнего колонтитулов. Вы знаете, как объединить все верхние и нижние колонтитулы? - person MadBoy; 08.10.2010

Вы хотите использовать AltChunks и OpenXml SDK 1.0 (как минимум 2.0, если можете). Посетите блог Эрика Уайта для получения более подробной информации и просто отличного ресурса !. Вот пример кода, с которого вы должны начать, если не сразу сработаете.

public void AddAltChunkPart(Stream parentStream, Stream altStream, string altChunkId)
{
    //make sure we are at the start of the stream    
    parentStream.Position = 0;
    altStream.Position = 0;
    //push the parentStream into a WordProcessing Document
    using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(parentStream, true))
    {
        //get the main document part
        MainDocumentPart mainPart = wordDoc.MainDocumentPart;
        //create an altChunk part by adding a part to the main document part
        AlternativeFormatImportPart chunk = mainPart.AddAlternativeFormatImportPart(altChunkPartType, altChunkId);
        //feed the altChunk stream into the chunk part
        chunk.FeedData(altStream);
        //create and XElement to represent the new chunk in the document
        XElement newChunk = new XElement(altChunk, new XAttribute(relId, altChunkId));
        //Add the chunk to the end of the document (search to last paragraph in body and add at the end)
        wordDoc.MainDocumentPart.GetXDocument().Root.Element(body).Elements(paragraph).Last().AddAfterSelf(newChunk);
        //Finally, save the document
        wordDoc.MainDocumentPart.PutXDocument();
    }
    //reset position of parent stream
    parentStream.Position = 0;
}
person Pete Skelly    schedule 21.01.2009
comment
Если вы используете w: altchunk, вам нужно открыть документ в чем-то (например, Word 2007), которое способно преобразовать элемент altChunk в обычное содержимое документа. - person JasonPlutext; 14.11.2010

Это сложно, поэтому код выходит за рамки сообщения на форуме, я бы написал ваше приложение для вас, но в итоге.

  • Откройте оба документа как пакеты
  • Просматривайте части второго документа в поисках изображений и встраиваемых материалов.
  • Добавьте эти части в первый пакет, запомнив новые идентификаторы отношений (это требует большой потоковой работы)
  • откройте часть document.xml во втором документе и замените все старые идентификаторы отношений новыми - добавьте все дочерние узлы, но не корневой узел, второго document.xml к первому document.xml
  • сохраните все XmlDocuments и очистите пакет
person Community    schedule 15.01.2009

Я сделал приложение на C # для объединения файлов RTF в один документ, и надеюсь, что оно должно работать и для файлов DOC и DOCX.

    Word._Application wordApp;
    Word._Document wordDoc;
    object outputFile = outputFileName;
    object missing = System.Type.Missing;
    object vk_false = false;
    object defaultTemplate = defaultWordDocumentTemplate;
    object pageBreak = Word.WdBreakType.wdPageBreak;
    string[] filesToMerge = new string[pageCounter];
    filestoDelete = new string[pageCounter];

    for (int i = 0; i < pageCounter; i++)
    {
        filesToMerge[i] = @"C:\temp\temp" + i.ToString() + ".rtf";
        filestoDelete[i] = @"C:\temp\temp" + i.ToString() + ".rtf";                
    }
    try
    {
        wordDoc = wordApp.Documents.Add(ref missing, ref missing, ref missing, ref missing);
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
    Word.Selection selection= wordApp.Selection;

    foreach (string file in filesToMerge)
    {
        selection.InsertFile(file,
            ref missing,
            ref missing,
            ref missing,
            ref missing);

        selection.InsertBreak(ref pageBreak);                                     
    }
    wordDoc.SaveAs(ref outputFile, ref missing, ref missing, ref missing, ref missing, ref missing,
           ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing,
           ref missing, ref missing);

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

person Sumit Ghosh    schedule 22.01.2009

Для тех, кто хочет работать со списком имен файлов:

void AppendToExistingFile(string existingFile, IList<string> filenames)
{
    using (WordprocessingDocument document = WordprocessingDocument.Open(existingFile, true))
    {
        MainDocumentPart mainPart = document.MainDocumentPart;

        for (int i = filenames.Count - 1; i >= 0; --i)
        {
            string altChunkId = "AltChunkId" + i;
            AlternativeFormatImportPart chunk = mainPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.WordprocessingML, altChunkId);

            using (FileStream fileStream = File.Open(filenames[i], FileMode.Open))
            {
                chunk.FeedData(fileStream);
            }

            AltChunk altChunk = new AltChunk { Id = altChunkId };
            mainPart.Document.Body.InsertAfter(altChunk, mainPart.Document.Body.Elements<Paragraph>().Last());
        }

        mainPart.Document.Save();
    }
}
person Jinjinov    schedule 05.03.2020