💡 完全な動作例は GitHub にあります:
extract-annotations-from-pdf-using-groupdocs-parser-dotnet
Introduction
レビューを経た PDF には、目に見えるテキスト以外にも付箋、ハイライトされたコメント、インラインコメントなどが多数埋め込まれています。ページをすべてスクロールして探すのは、何度もフィードバックが行われた文書になると現実的ではありません。GroupDocs.Parser は、埋め込まれたアノテーションをプログラムから読み取ることができる .NET ライブラリで、散在するレビュアーのコメントを構造化データに変換します。 本チュートリアルでは、PDF 全体からアノテーションを抽出し、ページ単位に分割し、文書テキストと一緒に取得し、結果を CSV または JSON にエクスポートする方法を示します。
ドキュメントチーム向けのレビュー追跡ツールを作成しているときにこの問題に直面しました。40 ページのリリースノートが 3 人のレビュアーを通過し、手作業でファイルを開いてすべてのコメントを探すのに、指摘された問題を修正する時間よりも長くかかっていました。数行のコードでアノテーションを抽出すれば、2 分で完了しました。
以下のセクションで学べること:
- PDF からすべてのアノテーションを一括で抽出する方法
- 各アノテーションに所属ページをタグ付けする方法
- 文書テキストとアノテーションテキストを同時に取得する方法
- 結果を CSV または JSON にシリアライズして下流ツールで利用する方法
Why Extracting PDF Annotations Matters
プログラムから PDF アノテーションを読むことは次のようなシーンで有用です。
- レビュー ワークフロー:PDF ビューアを開かずにすべてのレビュアーコメントを収集
- コラボレーション:ハイライトや付箋情報を自分のツール内に直接表示
- 監査:文書がフラット化または最終化された後でも、マークアップの履歴を保持
GroupDocs.Parser はバージョン 26.7 で PDF ドキュメント向けにネイティブなアノテーション抽出機能を追加しました。GetAnnotations メソッドと、アノテーションテキストを通常のテキスト取得に組み込むための TextOptions の IncludeAnnotations オプションが利用可能です。
Prerequisites
- .NET 6.0 以降
- GroupDocs.Parser for .NET 26.7+(temporary license)
- アノテーションが付与された PDF ファイル(例:
document-with-annotations.pdf)
NuGet でインストール:
dotnet add package GroupDocs.Parser
How do I extract annotations from a PDF document?
Answer: Parser でファイルをロードし、ドキュメント全体の場合は GetAnnotations()、単一ページの場合は GetAnnotations(pageIndex) を呼び出します。戻り値は AnnotationItem オブジェクトのコレクションで、Value プロパティにコメントテキストが格納されています。コメントを文書本文と同時に取得したい場合は、TextOptions の IncludeAnnotations を設定して GetText を使用します。
Whole‑Document Extraction
以下のスニペットは、ファイル全体からすべてのアノテーションを一度の呼び出しで取得します。コメントがあるかどうかを最速で確認できます。
// Extract every annotation from the whole document
var result = new List<string>();
using (var parser = new Parser(path))
{
IEnumerable<AnnotationItem> annotations = parser.GetAnnotations();
if (annotations == null)
{
return result; // format doesn't support annotations
}
foreach (var item in annotations)
{
result.Add(item.Value); // annotation text
}
}
return result;
Key points:
GetAnnotations()は、ドキュメント形式がアノテーション抽出に対応していない場合にnullを返し、コメントが全くない場合は空コレクションを返します。- 各
AnnotationItemはテキストをValueプロパティで公開します。これは現在 SDK が提供する唯一のデータです。 - ページ情報は含まれません。ページ単位が必要な場合は下記のオーバーロードを使用してください。
Per‑Page Extraction
コメントの位置が重要な場合は、ページごとにループし GetAnnotations(pageIndex) を呼び出します。
// Tag each annotation with its zero-based page index
var result = new List<AnnotationRecord>();
using (var parser = new Parser(path))
{
if (!parser.Features.Annotations)
{
return result;
}
var info = parser.GetDocumentInfo();
if (info == null || info.PageCount == 0)
{
return result;
}
for (int pageIndex = 0; pageIndex < info.PageCount; pageIndex++)
{
IEnumerable<AnnotationItem> pageAnnotations = parser.GetAnnotations(pageIndex);
if (pageAnnotations == null)
{
continue;
}
foreach (var item in pageAnnotations)
{
result.Add(new AnnotationRecord { PageIndex = pageIndex, Value = item.Value });
}
}
}
return result;
Key points:
GetDocumentInfo().PageCountがループの上限となります。アノテーション専用のページ数はありません。GetAnnotations(pageIndex)は 0 ベースのインデックスを使用し、API の他のページレベルメソッドと同様です。- 生成された
AnnotationRecordリストは CSV や JSON へのエクスポートにそのまま利用できます。
Extracting Text Together with Annotations
2 回のパスを取らずに、アノテーションテキストを通常のテキスト抽出結果に直接組み込むことができます。
// Read document text with annotation text included
using (var parser = new Parser(path))
{
var options = new TextOptions
{
IncludeAnnotations = true
};
using (TextReader reader = parser.GetText(options))
{
return reader?.ReadToEnd() ?? string.Empty;
}
}
Key points:
IncludeAnnotationsはTextOptionsのプロパティで、通常のGetText呼び出しに同じように指定できます。- コメントリストとは別に、1 つのトランスクリプト形式の出力が欲しいときに便利です。
- 1 ページだけが対象の場合は
GetText(pageIndex, options)を併用してください。
Checking Annotation Support First
すべてのフォーマットがアノテーションに対応しているわけではないため、GetAnnotations を呼び出す前にチェックしておくと安全です。
// Returns true if the loaded document format supports annotation extraction
using (var parser = new Parser(path))
{
return parser.Features.Annotations;
}
Key points:
Features.AnnotationsはParserインスタンス上のシンプルなブールフラグです。- 事前にチェックすることで意図が明確になり、
GetAnnotationsがnullを返すケースを予め除外できます。
Exporting the Annotations to CSV
CSV エクスポートにより、レビュー担当者はコメントリストを直接 Excel で開くことができます。以下のメソッドは、先ほど作成したページタグ付きレコードから 2 列(page,value)のファイルを書き出します。
var sb = new StringBuilder();
sb.AppendLine("page,value");
foreach (var record in records)
{
sb.AppendLine($"{record.PageIndex},{CsvEscape(record.Value)}");
}
File.WriteAllText(outputPath, sb.ToString());
Key points:
CsvEscapeはカンマ、引用符、改行を含むフィールドを安全にクオートします。- 生成されたファイルは Excel で直接開くか、チケットツールにパイプできます。
Helper: CsvEscape
if (string.IsNullOrEmpty(s)) return string.Empty;
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
{
return "\"" + s.Replace("\"", "\"\"") + "\"";
}
return s;
Exporting the Annotations to JSON
プログラムからコメントを消費するパイプライン向けには、JSON 配列の方が適しています。
var sb = new StringBuilder();
sb.AppendLine("[");
for (int i = 0; i < records.Count; i++)
{
var comma = i < records.Count - 1 ? "," : string.Empty;
sb.AppendLine($" {{ \"page\": {records[i].PageIndex}, \"value\": \"{Escape(records[i].Value)}\" }}{comma}");
}
sb.AppendLine("]");
File.WriteAllText(outputPath, sb.ToString());
Key points:
- 出力は
{ page, value }オブジェクトのフラット配列です。下流サービスが簡単にデシリアライズできます。 Escapeはシリアライズライブラリを使用せずに有効な JSON を保ちます。
Helper: Escape
return s?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? string.Empty;
Comparing Methods: When to Use Each
| Method | Best For | Key Advantages | Limitations |
|---|---|---|---|
| Whole‑Document Extraction | コメントが全くあるかどうかの高速チェック | 呼び出しが 1 回だけ、コードが最もシンプル | ページ情報が付与されない |
| Per‑Page Extraction | フィードバックを正確なセクションへ振り分けたい場合 | ページタグ付き結果、エクスポートがすぐに可能 | ページごとに 1 回余分な呼び出しが必要 |
| Combined Text + Annotations | 1 つの可読テキストとして出力したい場合 | 文書全体を 2 回読む必要がない | コメントが本文テキストと分離されない |
| CSV Export | スプレッドシートベースのレビュー管理 | Excel で簡単に開け、人が読める形式 | フラット構造に限られる |
| JSON Export | 自動化パイプラインやチケットシステム | 構造化データで機械が容易に処理可能 | ペイロードがやや大きくなる |
まずは Whole‑Document Extraction でコメントの有無を確認し、必要に応じて Per‑Page Extraction に切り替えてフィードバックを特定セクションへ割り当てましょう。
Best Practices and Tips
Parserは速やかに破棄:usingブロックでラップし、ネイティブリソースを解放します。nullと空コレクションを区別:GetAnnotationsがnullを返すのは形式未対応、空コレクションはコメントが無いことを示します。- バッチ処理では
Features.Annotationsを先にチェック:ループ内部でnullを確認するよりも早期に除外できます。 - ページタグ付きリストを再利用:
ExtractAnnotationsByPageで一度作成したリストを CSV と JSON の両方のエクスポートに流用すれば、出力が食い違うことはありません。 - セキュリティ:アノテーションテキストはフリー形式のレビュアー入力です。UI やレポートに表示する前に、他の未信頼文字列と同様にサニタイズしてください。
Conclusion
GroupDocs.Parser を使えば、PDF からレビュアーのコメントを手作業で探すことなく、プログラムから直接取得できます。ドキュメント全体のアノテーション抽出、ページ単位のタグ付け、またはテキストストリームへの統合といった手法を組み合わせることで、文書がパイプラインに入った瞬間にフィードバックを可視化できます。結果を CSV や JSON にエクスポートすれば、チームがすでに利用しているツールへシームレスに連携できます。
Next steps:
- 完全なシグネチャとオーバーロードを確認するには、GetAnnotations API reference を参照してください。
- アノテーションと併せて PDF からテキストを抽出する方法は、extract text from PDF documents をご覧ください。
- バッチ処理シナリオ向けのサンプルプロジェクトは、GitHub の Examples Repo をチェックしてください。