0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C++ で Word 文書に改ページを挿入・削除する実装手法

0
Posted at

C++ で開発したアプリケーションにおいて、Word 文書内の任意の位置に改ページを挿入したり、不要な改ページを削除したりしたいという要件に直面することがあります。例えば、帳票出力の際に特定のセクションを新しいページから開始する、レポートの章区切りを調整する、あるいは誤って挿入された改ページを一括削除して文書構造を整理するといったケースです。

手動で改ページを調整するのは、文書が長くなるほど手間がかかり、見落としも発生しやすくなります。プログラムによる自動化で、これらの操作を効率的に行うことができます。本記事では、C++ から Word 文書の改ページを操作する実装手法について解説します。

改ページ操作の技術的課題

Word 文書内の改ページをプログラムで操作する際には、以下のような技術的課題があります。

改ページの種類と配置

Word には手動で挿入する改ページのほかに、ページ設定に基づいて自動的に挿入される自動改ページがあります。プログラムで操作する際は、これらを区別して扱う必要があります。また、改ページは段落の子オブジェクトとして扱われるため、文書のオブジェクト構造を理解した上で操作する必要があります 。

文書構造の維持

改ページを挿入または削除した後も、段落間の関係やセクション区切り、ページレイアウトが意図した通りに保たれる必要があります。特に、改ページを削除する際は、前後の段落が適切に結合されることを確認する必要があります 。

実装アプローチの選択肢

C++ から Word 文書の改ページを操作する方法としては、以下のような選択肢があります。

手法 メリット デメリット
サードパーティライブラリ 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;

基本的な操作

指定した段落の後に改ページを挿入する

Paragraph->AppendBreak(BreakType::PageBreak) メソッドを使用すると、特定の段落の末尾に改ページを挿入できます。挿入後は、その位置に改ページ記号が表示されます 。

#include "Spire.Doc.o.h"

using namespace Spire::Doc;

int main()
{
    // 入出力ファイルのパスを指定
    std::wstring inputFile = L"C:\\documents\\sample.docx";
    std::wstring outputFile = L"C:\\output\\insert_pagebreak.docx";

    // Document オブジェクトを生成
    intrusive_ptr<Document> document = new Document();

    // Word 文書を読み込み
    document->LoadFromFile(inputFile.c_str());

    // 最初のセクションを取得
    intrusive_ptr<Section> section = document->GetSections()->GetItemInSectionCollection(0);

    // 2 番目の段落を取得(インデックスは 1)
    intrusive_ptr<Paragraph> paragraph = section->GetParagraphs()->GetItemInParagraphCollection(1);

    // 段落の末尾に改ページを挿入
    paragraph->AppendBreak(BreakType::PageBreak);

    // 文書を保存
    document->SaveToFile(outputFile.c_str(), FileFormat::Docx2013);
    document->Close();

    return 0;
}

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

  1. Document オブジェクトを生成し、文書を読み込み
  2. セクションと段落をインデックスで取得
  3. AppendBreak(BreakType::PageBreak) で段落末尾に改ページを追加
  4. SaveToFile で結果を保存

指定したテキストの後に改ページを挿入する

文書内の特定のテキストを検索し、その直後に改ページを挿入することも可能です 。

#include "Spire.Doc.o.h"

using namespace Spire::Doc;

int main()
{
    // 入出力ファイルのパスを指定
    std::wstring inputFile = L"C:\\documents\\sample.docx";
    std::wstring outputFile = L"C:\\output\\insert_pagebreak_after_text.docx";

    // Document オブジェクトを生成
    intrusive_ptr<Document> document = new Document();

    // Word 文書を読み込み
    document->LoadFromFile(inputFile.c_str());

    // 指定テキストを検索(大文字小文字を区別、単語全体で検索)
    intrusive_ptr<TextSelection> selection = document->FindString(L"特定のテキスト", true, true);

    // 検索結果のテキスト範囲を取得
    intrusive_ptr<TextRange> range = selection->GetAsOneRange();

    // テキストが属する段落を取得
    intrusive_ptr<Paragraph> paragraph = range->GetOwnerParagraph();

    // 段落内でのテキストの位置インデックスを取得
    int index = paragraph->GetChildObjects()->IndexOf(range);

    // 改ページオブジェクトを生成
    intrusive_ptr<Break> pageBreak = new Break(document, BreakType::PageBreak);

    // テキストの直後に改ページを挿入
    paragraph->GetChildObjects()->Insert(index + 1, pageBreak);

    // 文書を保存
    document->SaveToFile(outputFile.c_str(), FileFormat::Docx2013);
    document->Close();

    return 0;
}

この方法では、FindString メソッドで検索したテキストの位置を特定し、その直後に改ページオブジェクトを挿入しています。文書内に同名のテキストが複数ある場合、最初に見つかった箇所が対象となります。

改ページを削除する

文書内の不要な改ページを検出して削除するには、各段落の子オブジェクトを走査し、改ページタイプのオブジェクトを特定して削除します 。

#include "Spire.Doc.o.h"

using namespace Spire::Doc;

int main()
{
    // 入出力ファイルのパスを指定
    std::wstring inputFile = L"C:\\output\\insert_pagebreak.docx";
    std::wstring outputFile = L"C:\\output\\remove_pagebreaks.docx";

    // Document オブジェクトを生成
    intrusive_ptr<Document> document = new Document();

    // Word 文書を読み込み
    document->LoadFromFile(inputFile.c_str());

    // 最初のセクションの全段落を走査
    for (int j = 0; j < document->GetSections()->GetItemInSectionCollection(0)->GetParagraphs()->GetCount(); j++)
    {
        intrusive_ptr<Paragraph> para = document->GetSections()->GetItemInSectionCollection(0)->GetParagraphs()->GetItemInParagraphCollection(j);

        // 段落内の全子オブジェクトを走査
        for (int i = 0; i < para->GetChildObjects()->GetCount(); i++)
        {
            intrusive_ptr<DocumentObject> obj = para->GetChildObjects()->GetItem(i);

            // 子オブジェクトが改ページかどうかを判定
            if (Object::CheckType<Break>(obj) && 
                (Object::Dynamic_cast<Break>(obj))->GetBreakType() == BreakType::PageBreak)
            {
                // 改ページを削除
                para->GetChildObjects()->Remove(obj);
            }
        }
    }

    // 文書を保存
    document->SaveToFile(outputFile.c_str(), FileFormat::Docx2013);
    document->Close();

    return 0;
}

上記のコードでは、Object::CheckType<Break> で子オブジェクトが改ページかどうかを判定し、GetBreakType() == BreakType::PageBreak でページ区切りであることを確認した上で削除しています 。

応用的な操作

すべてのセクションから改ページを削除する

文書全体から改ページを一括削除したい場合は、すべてのセクションを対象にループ処理を行います。

// 全セクションを走査
int sectionCount = document->GetSections()->GetCount();
for (int s = 0; s < sectionCount; s++)
{
    intrusive_ptr<Section> section = document->GetSections()->GetItemInSectionCollection(s);
    
    // セクション内の全段落を走査
    for (int j = 0; j < section->GetParagraphs()->GetCount(); j++)
    {
        intrusive_ptr<Paragraph> para = section->GetParagraphs()->GetItemInParagraphCollection(j);

        // 段落内の全子オブジェクトを走査
        for (int i = 0; i < para->GetChildObjects()->GetCount(); i++)
        {
            intrusive_ptr<DocumentObject> obj = para->GetChildObjects()->GetItem(i);

            // 改ページ判定と削除
            if (Object::CheckType<Break>(obj) && 
                (Object::Dynamic_cast<Break>(obj))->GetBreakType() == BreakType::PageBreak)
            {
                para->GetChildObjects()->Remove(obj);
            }
        }
    }
}

特定の段落にのみ改ページがあるか確認する

特定の段落に改ページが含まれているかどうかを確認するには、以下のような判定関数を作成します。

bool HasPageBreak(intrusive_ptr<Paragraph> paragraph)
{
    for (int i = 0; i < paragraph->GetChildObjects()->GetCount(); i++)
    {
        intrusive_ptr<DocumentObject> obj = paragraph->GetChildObjects()->GetItem(i);
        if (Object::CheckType<Break>(obj) && 
            (Object::Dynamic_cast<Break>(obj))->GetBreakType() == BreakType::PageBreak)
        {
            return true;
        }
    }
    return false;
}

注意点と制限事項

動作環境

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

改ページ削除時の注意

削除処理では、手動で挿入された改ページのみが対象となります。ページ設定や段落設定(「段落前で改ページする」など)によって自動的に発生する改ページは、異なるプロパティで制御されているため、本記事の方法では削除されません 。

メモリ管理

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

評価版の制限

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

おわりに

本記事では、C++ 環境において Word 文書に改ページを挿入・削除する手法として、Spire.Doc for C++ を用いた実装例を示した。段落への直接的な改ページ挿入、特定テキストの後への挿入、文書全体からの改ページ削除といった操作が、いずれも比較的少ないコード量で実現できる。

改ページの操作は、文書のレイアウト調整において基本的かつ頻繁に必要となる処理である。実際の開発においては、対象文書の構造を十分に理解した上で、プロジェクトの要件に適した手法を選択されたい。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?