web-dev-qa-db-ja.com

複数のDOCXファイルを一緒に追加する

プログラムでC#を使用して、いくつかの既存のdocxファイルを単一の長いdocxファイルに追加する必要があります。これには、箇条書きや画像などの特別なマークアップが含まれます。ヘッダーとフッターの情報は削除されるため、問題が発生することはありません。

個々のdocxファイルを.NETFramework 3で操作する方法についてはたくさんの情報を見つけることができますが、ファイルをマージする方法について簡単で明白なことは何もありません。それを実行するサードパーティのプログラム(Acronis.Words)もありますが、非常に高価です。

更新:

Wordを介した自動化が提案されていますが、私のコードはIIS Webサーバー上のASP.NETで実行されるため、Wordにアクセスすることはできません。言及しないで申し訳ありません。そもそもそれ。

26
Shinobi

提出されたすべての良い提案と解決策にもかかわらず、私は代替案を開発しました。私の意見では、サーバーアプリケーションでWordを使用することは完全に避けるべきです。そのため、OpenXMLを使用しましたが、AltChunkでは機能しませんでした。元の本文にテキストを追加しました。ファイル名のリストではなくbyte []のリストを受け取りますが、必要に応じてコードを簡単に変更できます。

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

これがお役に立てば幸いです。

24
GRGodoi

自動化を使用する必要はありません。 DOCXファイルはOpenXML形式に基づいています。それらは、XMLとバイナリ部分(ファイルを考えてください)が内部に含まれている単なるZipファイルです。それらをPackagingAPI(WindowsBase.dllのSystem.IO.Packaging)で開き、フレームワークの任意のXMLクラスで操作できます。

詳細については、 OpenXMLDeveloper.org を確認してください。

7
Rob Windsor

これは元の質問に非常に遅れており、かなりの変更がありますが、マージロジックの記述方法を共有したいと思いました。これは 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;
}
5
Mike B

AltChunksとOpenXmlSDK 1.0を使用する必要があります(可能な場合は少なくとも2.0)。詳細については、Eric Whiteのブログをチェックしてください。これは、すばらしいリソースです。すぐに機能しない場合でも、開始する必要のあるコードサンプルを次に示します。

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;
}
2
Pete Skelly

これを行うために、少し前に小さなテストアプリを作成しました。私のテストアプリは.docxではなくWord2003ドキュメント(.doc)で動作しましたが、プロセスは同じだと思います。変更する必要があるのは、新しいバージョンのプライマリ相互運用機能アセンブリを使用することだけだと思います。このコードは、新しい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;
    }
}
2
Terence Lewis

非常に複雑なので、コードはフォーラムの投稿の範囲外です。私はあなたのためにあなたのアプリを書いていますが、要約すると。

  • 両方のドキュメントをパッケージとして開きます
  • 画像や埋め込みのものを探して、2番目のドキュメントのパーツをループします
  • これらのパーツを最初のパッケージに追加して、新しい関係IDを記憶します(これには多くのストリーム作業が含まれます)
  • 2番目のドキュメントのdocument.xml部分を開き、古い関係IDをすべて新しいものに置き換えます-2番目のdocument.xmlのすべての子ノード(ルートノードではない)を最初のdocument.xmlに追加します
  • すべてのXmlDocumentsを保存し、パッケージをフラッシュします
0
Trevor

RTFファイルを1つのドキュメントにマージするために、C#でアプリケーションを作成しました。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);

お役に立てれば!

0
Sumit Ghosh