C++ で開発したアプリケーションにおいて、Word 文書からテキストや画像を抽出したいという要件に直面することがあります。例えば、文書の内容をデータベースに格納する、既存の資産から画像だけを集めて別の資料に流用する、あるいは文書内の情報を分析対象として取り出すといったケースです。
手動でコピー&ペーストを繰り返すのは、文書の数が増えるほど手間がかかり、画像の場合は一枚ずつ保存する必要があるため非常に時間を要します。本記事では、C++ から Word 文書内のテキストと画像をプログラムで抽出する方法について解説します。
Word 文書からのデータ抽出の技術的課題
Word 文書からテキストや画像を抽出する際には、以下のような技術的課題があります。
DOCX 形式の構造理解
現在主流の DOCX 形式は XML ファイル群を ZIP で圧縮した複合構造を持っています。テキストは文書本体の XML に格納されていますが、書式情報と分離されていたり、複数の要素に分割されていたりするため、単純な文字列検索では正確な抽出が難しい場合があります。
画像の所在と形式
画像は文書内に埋め込まれており、複数のセクションや段落、表の中など、さまざまな場所に分散して存在します。文書全体を走査して画像オブジェクトを特定し、元の形式を保持したまま取り出す仕組みが必要です。
実装アプローチの選択肢
| 手法 | メリット | デメリット |
|---|---|---|
| サードパーティライブラリ | Office 不要、組み込みが容易 | ライセンスコストが発生する場合あり |
| Open XML SDK の直接操作 | 無償、細かい制御が可能 | DOCX のみ対応、XML 構造の深い理解が必要 |
| COM オートメーション | Office と同等の精度 | Windows 限定、Office 要インストール |
本記事では、DOC / DOCX 両形式に対応し、C++ アプリケーションに直接組み込めるサードパーティライブラリを用いる手法について、Spire.Doc for C++ を例に実装方法を示します。
環境構築
入手方法
Spire.Doc for C++ は以下のいずれかの方法でプロジェクトに導入できます。
-
NuGet パッケージマネージャー経由
Visual Studio の「NuGet パッケージの管理」からSpire.Doc.Cppを検索してインストール -
手動での組み込み
配布パッケージを入手し、インクルードディレクトリとライブラリファイルをプロジェクトに追加
評価目的であれば、一時ライセンスを申請することで機能制限なく試用できます。
必要なインクルード
#include "Spire.Doc.o.h"
#include <deque>
#include <fstream>
#include <vector>
using namespace Spire::Doc;
文書からテキストを抽出する
全テキストを取得する
Spire.Doc for C++ では Document->GetText() メソッドを使用して、Word 文書全体のテキストを一度に取得できます。抽出したテキストは TXT ファイルとして保存できます。
#include "Spire.Doc.o.h"
#include <iostream>
#include <fstream>
using namespace Spire::Doc;
using namespace std;
int main()
{
// 入力ファイルと出力ファイルのパスを指定
wstring inputFile = L"C:\\documents\\sample.docx";
wstring outputFile = L"C:\\output\\extracted_text.txt";
// Document オブジェクトを生成
intrusive_ptr<Document> document = new Document();
// Word 文書を読み込み
document->LoadFromFile(inputFile.c_str());
// 文書全体のテキストを取得
wstring text = document->GetText();
// TXT ファイルとして保存
wofstream write(outputFile);
write << text;
write.close();
document->Close();
wcout << L"テキストの抽出が完了しました。" << endl;
return 0;
}
上記のコードでは以下の処理を行っています。
-
Documentオブジェクトを生成し、文書を読み込み -
GetText()メソッドで文書内の全テキストを文字列として取得 -
wofstreamで TXT ファイルに書き出し
GetText() は文書の構造に関わらず、すべてのテキストを返します。書式情報は失われますが、テキスト内容のみが必要な場合には十分です。
文書から画像を抽出する
すべての画像を取り出す
画像の抽出は、文書のオブジェクトツリーを走査し、画像タイプのオブジェクトを見つける方法で行います。文書内のどこに画像が埋め込まれていても、再帰的に探索することで取り出すことができます。
#include "Spire.Doc.o.h"
#include <deque>
#include <fstream>
#include <vector>
using namespace Spire::Doc;
using namespace std;
int main()
{
// 入力ファイルのパスを指定
wstring inputFile = L"C:\\documents\\sample.docx";
wstring outputDir = L"C:\\output\\images\\";
// Document オブジェクトを生成
intrusive_ptr<Document> document = new Document();
document->LoadFromFile(inputFile.c_str());
// 文書をキューに追加(幅優先探索のため)
deque<intrusive_ptr<ICompositeObject>> nodes;
nodes.push_back(document);
// 抽出した画像を格納するベクター
vector<vector<byte>> images;
// 文書の全オブジェクトを走査
while (nodes.size() > 0)
{
intrusive_ptr<ICompositeObject> node = nodes.front();
nodes.pop_front();
for (int i = 0; i < node->GetChildObjects()->GetCount(); i++)
{
intrusive_ptr<IDocumentObject> child = node->GetChildObjects()->GetItem(i);
// 画像オブジェクトかどうかを判定
if (child->GetDocumentObjectType() == DocumentObjectType::Picture)
{
// DocPicture にキャストして画像バイト列を取得
intrusive_ptr<DocPicture> picture = Object::Dynamic_cast<DocPicture>(child);
vector<byte> imageBytes = picture->GetImageBytes();
images.push_back(imageBytes);
}
// 複合オブジェクトの場合は子ノードを探索対象に追加
else if (Object::CheckType<ICompositeObject>(child))
{
nodes.push_back(boost::dynamic_pointer_cast<ICompositeObject>(child));
}
}
}
// 画像をファイルとして保存
for (size_t i = 0; i < images.size(); i++)
{
wstring fileName = outputDir + L"Image-" + to_wstring(i) + L".png";
ofstream outFile(fileName, ios::binary);
if (outFile.is_open())
{
outFile.write(reinterpret_cast<const char*>(images[i].data()), images[i].size());
outFile.close();
}
}
document->Close();
wcout << L"全 " << images.size() << L" 枚の画像を抽出しました。" << endl;
return 0;
}
上記のコードでは以下の処理を行っています。
- 文書をキューに入れ、幅優先探索でオブジェクトツリーを走査
-
GetDocumentObjectType()がPictureと一致するオブジェクトを特定 -
DocPictureにキャストし、GetImageBytes()で画像のバイト列を取得 - 取得したバイト列を PNG ファイルとして出力
この方法により、段落内の画像、表のセル内の画像、ヘッダーやフッターに埋め込まれた画像も含め、文書内のすべての画像を抽出できます。ICompositeObject をインターフェースとして使用しているため、セクション・段落・表・セルなど、子要素を持つあらゆるオブジェクトを統一的に探索できます。また、GetImageBytes() で画像の生バイトデータを取得しているため、元の画質を保ったまま保存できます。
注意点と制限事項
動作環境
- プラットフォーム: Windows、Linux に対応
- アーキテクチャ: 64 ビットアプリケーション向け
- Microsoft Office: 不要(ライブラリ単体で動作)
メモリ管理
このライブラリでは intrusive_ptr という参照カウント方式のスマートポインタが使用されています。new で生成したオブジェクトは自動的に管理されるため、明示的な delete は不要です。処理完了時には Close() を呼び出してリソースを解放します。
テキスト抽出時の注意
GetText() メソッドは文書内のテキストを抽出しますが、テキストボックス内の文字列は取得できない場合があります。また、抽出されたテキストに改行や空白が期待通りに含まれないケースもあるため、用途に応じて後処理が必要になることがあります。
評価版の制限
評価版では、抽出結果に評価版であることを示す透かしが付加される場合があります。本番環境で使用する場合はライセンスの適用が必要です。
おわりに
本記事では、C++ 環境において Word 文書からテキストと画像を抽出する手法として、Spire.Doc for C++ を用いた実装例を示した。テキスト抽出は GetText() メソッドでシンプルに実現でき、画像抽出はオブジェクトツリーの幅優先探索によって文書内の任意の場所から取り出せる。
文書からのデータ抽出は、コンテンツ管理やデータ分析の前処理として活用できる。実際の開発においては、対象文書の構造や抽出したい情報の種類に応じて、適切な手法を選択されたい。