💡 完全な動作例は 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 メソッドと、アノテーションテキストを通常のテキスト取得に組み込むための TextOptionsIncludeAnnotations オプションが利用可能です。

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 プロパティにコメントテキストが格納されています。コメントを文書本文と同時に取得したい場合は、TextOptionsIncludeAnnotations を設定して 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:

  • IncludeAnnotationsTextOptions のプロパティで、通常の 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.AnnotationsParser インスタンス上のシンプルなブールフラグです。
  • 事前にチェックすることで意図が明確になり、GetAnnotationsnull を返すケースを予め除外できます。

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 と空コレクションを区別GetAnnotationsnull を返すのは形式未対応、空コレクションはコメントが無いことを示します。
  • バッチ処理では 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 をチェックしてください。

Additional Resources