😸

C++ で Word 文書を結合する実装手法

に公開

C++ で開発したアプリケーションにおいて、複数の Word 文書を 1 つに結合したいという要件に直面することがあります。例えば、各部門から提出された月次レポートを統合する、複数章に分かれた文書を一つの完成版にまとめる、契約書に別紙や付属文書を追加するなどが典型的なユースケースです。

手動で文書をコピー&ペーストして結合するのは、ファイル数が多い場合に時間がかかるうえ、書式の崩れやセクション区切りの不整合といった問題が発生しやすくなります。プログラムによる自動化で、これらの課題を解決できます。本記事では、C++ から複数の Word 文書を結合する実装手法について解説します。

Word 文書結合の技術的課題

Word 文書をプログラムで結合する際には、以下のような技術的課題があります。

書式とスタイルの整合性

結合元の文書ごとに異なるスタイル定義やページ設定が適用されている場合、結合後の文書でレイアウトが崩れることがあります。特に、見出しスタイルの名前が重複している場合や、ページ余白の設定が異なる場合に注意が必要です。

セクション区切りの扱い

Word 文書はセクション単位でページレイアウトを管理しています。文書を結合する際、単純に内容を追加すると、元のセクション区切りが保持されず、意図しないページ構成になることがあります。

結合方法の選択

結合方法には主に 2 つのアプローチがあります。1 つは文書を新しいページから開始する方法(InsertTextFromFile メソッドを使用)、もう 1 つは元の文書の末尾に内容を直接追加する方法(内容のクローン)です。要件に応じて適切な方法を選択する必要があります 。

実装アプローチの選択肢

C++ から Word 文書を結合する方法としては、以下のような選択肢があります。

手法 メリット デメリット
サードパーティライブラリ(例:Spire.Doc) Office 不要、組み込みが容易 ライセンスコストが発生する場合あり
COM オートメーション Office と同等の精度 Windows 限定、Office 要インストール
オープンソースライブラリ 無償で利用可能 機能が限定的、複雑な書式に対応できない場合あり

本記事では、外部依存が少なく C++ アプリケーションに直接組み込めるサードパーティライブラリを用いる手法について、Spire.Doc for C++ を具体例として実装方法を示します。

環境構築

入手方法

Spire.Doc for C++ は、以下のいずれかの方法でプロジェクトに導入できます 。

  1. NuGet パッケージマネージャー経由
    Visual Studio の「NuGet パッケージの管理」から Spire.Doc.Cpp を検索してインストール

  2. 手動での組み込み
    配布パッケージを入手し、インクルードディレクトリとライブラリファイルをプロジェクトに追加

評価目的であれば、一時ライセンスを申請することで機能制限なく試用できます。

必要なインクルード

#include "Spire.Doc.o.h"

using namespace Spire::Doc;
using namespace std;

基本的な結合処理

ファイル挿入による結合(改ページあり)

InsertTextFromFile メソッドを使用すると、別の Word 文書の内容を現在の文書に挿入できます。この方法では、挿入される内容は新しいページから開始されます 。

#include "Spire.Doc.o.h"

using namespace Spire::Doc;
using namespace std;

int main()
{
    // 入力ファイルのパスを指定
    wstring inputFile_1 = L"Sample1.docx";
    wstring inputFile_2 = L"Sample2.docx";
    wstring outputFile = L"Merged_InsertFile.docx";

    // Document オブジェクトを初期化
    intrusive_ptr<Document> doc = new Document();
    
    // 1 つ目の Word 文書を読み込み
    doc->LoadFromFile(inputFile_1.c_str());

    // 2 つ目の文書を 1 つ目の文書に挿入
    doc->InsertTextFromFile(inputFile_2.c_str(), FileFormat::Auto);

    // 結果を保存
    doc->SaveToFile(outputFile.c_str(), FileFormat::Docx2013);
    doc->Close();

    return 0;
}

上記のコードでは以下の処理を行っています 。

  1. Document オブジェクトを生成し、1 つ目の文書を読み込み
  2. InsertTextFromFile メソッドで 2 つ目の文書を挿入
  3. SaveToFile メソッドで結合結果を保存

内容のクローンによる結合(改ページなし)

改ページを挿入せずに文書を結合したい場合は、内容をクローンして追加する方法を使用します。この方法では、元のセクション区切りが保持され、より柔軟な結合が可能です 。

#include "Spire.Doc.o.h"

using namespace Spire::Doc;
using namespace std;

int main()
{
    // 入力ファイルのパスを指定
    wstring inputFile_1 = L"Sample1.docx";
    wstring inputFile_2 = L"Sample2.docx";
    wstring outputFile = L"Merged_CloneContents.docx";

    // 1 つ目の文書を読み込み
    intrusive_ptr<Document> document1 = new Document();
    document1->LoadFromFile(inputFile_1.c_str(), FileFormat::Auto);
    
    // 1 つ目の文書の最後のセクションを取得
    intrusive_ptr<Section> lastSection = document1->GetLastSection();

    // 2 つ目の文書を読み込み
    intrusive_ptr<Document> document2 = new Document();
    document2->LoadFromFile(inputFile_2.c_str(), FileFormat::Auto);

    // 2 つ目の文書のセクション数を取得
    int sectionCount = document2->GetSections()->GetCount();

    // 2 つ目の文書の各セクションを反復処理
    for (int i = 0; i < sectionCount; i++)
    {
        intrusive_ptr<Section> section = document2->GetSections()->GetItemInSectionCollection(i);
        int childObjectsCount = section->GetBody()->GetChildObjects()->GetCount();
        
        // セクション内の各子オブジェクトを反復処理
        for (int j = 0; j < childObjectsCount; j++)
        {
            intrusive_ptr<DocumentObject> documentObject = 
                section->GetBody()->GetChildObjects()->GetItem(j);
            
            // オブジェクトをクローン
            intrusive_ptr<DocumentObject> clonedObject = documentObject->Clone();
            
            // 1 つ目の文書の最後のセクションに追加
            lastSection->GetBody()->GetChildObjects()->Add(clonedObject);
        }
    }

    // 結果を保存
    document1->SaveToFile(outputFile.c_str(), FileFormat::Docx);
    document1->Close();
    document2->Close();

    return 0;
}

この方法では、2 つ目の文書のすべてのセクションとその子オブジェクトを反復処理し、各要素をクローンして 1 つ目の文書の末尾に追加しています 。

応用的な結合処理

複数の文書を順次結合する

3 つ以上の文書を結合する場合は、ループ処理で順次結合していきます。

#include "Spire.Doc.o.h"
#include <vector>

using namespace Spire::Doc;
using namespace std;

int main()
{
    // 結合するファイルのリスト
    vector<wstring> files = {
        L"Chapter1.docx",
        L"Chapter2.docx",
        L"Chapter3.docx",
        L"Appendix.docx"
    };
    
    wstring outputFile = L"Complete_Document.docx";

    // 最初の文書を読み込み
    intrusive_ptr<Document> doc = new Document();
    doc->LoadFromFile(files[0].c_str());

    // 2 つ目以降の文書を順次結合
    for (size_t i = 1; i < files.size(); i++)
    {
        doc->InsertTextFromFile(files[i].c_str(), FileFormat::Auto);
    }

    // 結果を保存
    doc->SaveToFile(outputFile.c_str(), FileFormat::Docx2013);
    doc->Close();

    return 0;
}

結合時のオプション設定

結合時に書式の保持方法を制御したい場合は、InsertTextFromFile メソッドのオプションを活用します。FileFormat::Auto を指定することで、ファイル形式を自動判別できます。

// 形式を自動判別して挿入
doc->InsertTextFromFile(L"source.docx", FileFormat::Auto);

// DOC 形式として明示的に指定
doc->InsertTextFromFile(L"source.doc", FileFormat::Doc);

// DOCX 形式として明示的に指定
doc->InsertTextFromFile(L"source.docx", FileFormat::Docx2013);

注意点と制限事項

動作環境

  • プラットフォーム: Windows、Linux に対応
  • アーキテクチャ: 64 ビットアプリケーション向け
  • Microsoft Office: 不要(ライブラリ単体で動作)

メモリ管理

このライブラリでは intrusive_ptr という参照カウント方式のスマートポインタが使用されています。new で生成したオブジェクトは自動的に管理されるため、明示的な delete は不要です。処理完了時には Close() を呼び出してリソースを解放します。

スタイルの競合

複数の文書を結合する際、同じ名前のスタイルが存在する場合、後から挿入された文書のスタイルが優先されることがあります。意図しない書式変更を避けるため、事前にスタイル名の統一を検討してください。

評価版の制限

評価版では、生成される文書に評価版であることを示す透かしが挿入されます。また、段落数や表の数に制限がある場合があります。本番環境で使用する場合はライセンスの適用が必要です。

おわりに

本記事では、C++ 環境において複数の Word 文書を結合する手法として、Spire.Doc for C++ を用いた実装例を示した。InsertTextFromFile メソッドによる改ページありの結合と、内容のクローンによる改ページなしの結合という 2 通りのアプローチを紹介した。いずれも比較的少ないコード量で実現できる。

文書結合の自動化は、レポート統合や資料作成の効率化に有効である。実際の開発においては、結合後の書式整合性やセクション区切りの扱いを検証した上で、プロジェクトの要件に適した手法を選択されたい。

Discussion