简体   繁体   English

将多个 DOCX 文件附加在一起

[英]Append multiple DOCX files together

I need to use C# programatically to append several preexisting docx files into a single, long docx file - including special markups like bullets and images.我需要以编程方式使用 C# 将几个预先存在的docx文件附加到一个长docx文件中 - 包括特殊标记,如项目符号和图像。 Header and footer information will be stripped out, so those won't be around to cause any problems.页眉和页脚信息将被删除,因此不会造成任何问题。

I can find plenty of information about manipulating an individual docx file with .NET Framework 3, but nothing easy or obvious about how you would merge files.我可以找到大量有关使用 .NET Framework 3 操作单个docx文件的信息,但关于如何合并文件没有任何简单或明显的信息。 There is also a third-party program (Acronis.Words) that will do it, but it is prohibitively expensive.还有一个第三方程序 (Acronis.Words) 可以做到这一点,但它非常昂贵。

Update:更新:

Automating through Word has been suggested, but my code is going to be running on ASP.NET on an IIS web server, so going out to Word is not an option for me.已建议通过 Word 进行自动化,但我的代码将在 IIS Web 服务器上的 ASP.NET 上运行,因此使用 Word 对我来说不是一个选择。 Sorry for not mentioning that in the first place.很抱歉没有首先提到这一点。

In spite of all good suggestions and solutions submitted, I developed an alternative.尽管提交了所有好的建议和解决方案,我还是开发了一个替代方案。 In my opinion you should avoid using Word in server applications entirely.在我看来,您应该完全避免在服务器应用程序中使用 Word。 So I worked with OpenXML, but it did not work with AltChunk.所以我使用 OpenXML,但它不适用于 AltChunk。 I added text to original body, I receive a List of byte[] instead a List of file names but you can easily change the code to your needs.我在原始正文中添加了文本,我收到了一个字节列表 [] 而不是文件名列表,但您可以根据需要轻松更改代码。

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);
        }
    }
}

I hope this helps you.我希望这可以帮助你。

You don't need to use automation.您不需要使用自动化。 DOCX files are based on the OpenXML Formats. DOCX 文件基于 OpenXML 格式。 They are just zip files with a bunch of XML and binary parts (think files) inside.它们只是带有一堆 XML 和二进制部分(想想文件)的 zip 文件。 You can open them with the Packaging API (System.IO.Packaging in WindowsBase.dll) and manipulate them with any of the XML classes in the Framework.您可以使用打包 API(WindowsBase.dll 中的 System.IO.Packaging)打开它们,并使用框架中的任何 XML 类操作它们。

Check out OpenXMLDeveloper.org for details.查看OpenXMLDeveloper.org了解详细信息。

This is a very late to the original question and quite a bit has change but thought I would share the way I have written my merge logic.这是原始问题的一个很晚的问题,并且发生了很多变化,但我想我会分享我编写合并逻辑的方式。 This makes use of the Open XML Power Tools这利用了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;
}

Currently this is working very well in our application.目前,这在我们的应用程序中运行良好。 I have changed the code a little because my requirements is that each document that needs to be processed first.我对代码做了一点改动,因为我的要求是每个需要先处理的文档。 So what gets passed in is a DTO object with the template byte array and the various values that need to be replaced.所以传入的是一个带有模板字节数组和需要替换的各种值的 DTO 对象。 Here is how my code currently looks.这是我的代码目前的样子。 Which takes the code a little bit further.这使代码更进一步。

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;
}

I wrote a little test app a while ago to do this.不久前我写了一个小测试应用程序来做到这一点。 My test app worked with Word 2003 documents (.doc) not .docx, but I imagine the process is the same - I should think all you'd have to change is to use a newer version of the Primary Interop Assembly.我的测试应用程序使用 Word 2003 文档 (.doc) 而不是 .docx,但我认为过程是相同的 - 我认为您需要更改的只是使用更新版本的主互操作程序集。 This code would look a lot neater with the new C# 4.0 features...有了新的 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;
    }
}

You want to use AltChunks and the OpenXml SDK 1.0 (at a minimum, 2.0 if you can).您想使用 AltChunks 和 OpenXml SDK 1.0(如果可以,至少为 2.0)。 Check out Eric White's blog for more details and just as a great resource!.查看 Eric White 的博客以了解更多详细信息,这是一个很好的资源! Here is a code sample that should get you started, if not work immediately.这是一个代码示例,如果不能立即工作,它应该可以帮助您入门。

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;
}

Its quit complex so the code is outside the scope of a forum post, I'd be writing your App for you, but to sum up.它退出复杂所以代码不在论坛帖子的范围内,我会为你编写你的应用程序,但总结一下。

  • Open both documents as Packages将两个文档作为包打开
  • Loop through the second docuemnt's parts looking for images and embbed stuff循环遍历第二个文档的部分以查找图像并嵌入内容
  • Add these parts to the first package remembering the new relationship IDs(this involves alot of stream work)将这些部分添加到记住新关系 ID 的第一个包中(这涉及大量流工作)
  • open the document.xml part in the second document and replace all the old relationship IDs with the new ones- Append all the child nodes, but not the root node, of the second document.xml to the first document.xml打开第二个文档中的 document.xml 部分并将所有旧的关系 ID 替换为新的 - 将第二个 document.xml 的所有子节点(但不是根节点)附加到第一个 document.xml
  • save all the XmlDocuments and Flush the Package保存所有 XmlDocuments 并刷新包

I had made an application in C# to merge RTF files into one doc,Iam hopeful it should work for DOC and DOCX files as well.我在 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);

Hope this helps!希望这可以帮助!

For anyone who wants to work with a list of file names:对于想要使用文件名列表的任何人:

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();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM