💡 Полный рабочий пример доступен на GitHub:
extract-annotations-from-pdf-using-groupdocs-parser-dotnet

Introduction

PDF‑файл, прошедший процесс рецензирования, обычно содержит не только видимый текст – в нём могут быть стикеры, выделения и встроенные комментарии, оставленные рецензентами. Прокручивать каждую страницу в поисках этих элементов нецелесообразно, особенно после нескольких раундов обратной связи. GroupDocs.Parser — это .NET‑библиотека, которая программно читает встроенные аннотации документа, превращая разбросанные комментарии рецензентов в структурированные данные, с которыми может работать ваш код. В этом руководстве показано, как извлечь аннотации из всего PDF, разбить их по страницам, получить их вместе с текстом документа и экспортировать результаты в CSV или JSON.

Я столкнулся с этой задачей, создавая трекер отзывов для команды документации: 40‑страничная записка о выпуске прошла три рецензии, и ручной поиск каждого комментария занял больше времени, чем исправление найденных проблем. Извлечение аннотаций в несколько строк кода сократило эту работу до двух минут.

В следующих разделах вы узнаете, как:

  • Извлечь каждую аннотацию из PDF за один проход.
  • Привязать каждую аннотацию к странице, к которой она относится.
  • Получить текст документа и текст аннотации одновременно в одном чтении.
  • Сериализовать результаты в CSV или JSON для дальнейшей обработки.

Why Extracting PDF Annotations Matters

Программное чтение аннотаций PDF полезно для:

  • Рабочих процессов рецензирования: собрать все комментарии рецензентов без открытия файла в PDF‑просмотрщике.
  • Сотрудничества: выводить выделенные или помеченные фрагменты непосредственно в своих инструментах.
  • Аудита: сохранять запись разметки, оставленной в документе, даже после его «уплощения» или финализации.

GroupDocs.Parser добавил нативное извлечение аннотаций из PDF‑документов в версии 26.7 через метод GetAnnotations и новую опцию IncludeAnnotations в TextOptions для включения текста аннотаций в обычный вывод текста.

Prerequisites

  • .NET 6.0 или новее
  • GroupDocs.Parser for .NET 26.7+ (временная лицензия)
  • PDF‑файл с существующими аннотациями (например, document-with-annotations.pdf)

Установить через NuGet:

dotnet add package GroupDocs.Parser

How do I extract annotations from a PDF document?

Ответ: Загрузите файл с помощью Parser, затем вызовите GetAnnotations() для всего документа или GetAnnotations(pageIndex) для отдельной страницы. Каждый результат — это коллекция объектов AnnotationItem, у которых свойство Value содержит текст комментария. Если хотите видеть комментарии вместе с обычным содержимым документа, установите IncludeAnnotations в TextOptions и вызовите 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;

Ключевые моменты:

  • 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;

Ключевые моменты:

  • GetDocumentInfo().PageCount задаёт количество итераций; отдельного «количества страниц с аннотациями» нет.
  • GetAnnotations(pageIndex) использует нулевой индекс, как и все остальные методы уровня страниц в API.
  • Полученный список AnnotationRecord имеет именно ту форму, которая нужна для экспорта в CSV или JSON.

Extracting Text Together with Annotations

Вместо двух проходов по документу можно сразу включить текст аннотаций в обычный вывод текста.

// 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;
    }
}

Ключевые моменты:

  • IncludeAnnotations — свойство TextOptions, поэтому это работает с тем же вызовом GetText, который вы уже используете для извлечения чистого текста.
  • Удобно, когда нужен один «транскрипт», а не отдельный список комментариев.
  • Можно комбинировать с 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;
}

Ключевые моменты:

  • Features.Annotations — простой булевый флаг у экземпляра Parser.
  • Предварительная проверка делает намерения кода явными, хотя GetAnnotations уже «мягко» возвращает null, если аннотации недоступны.

Exporting the Annotations to CSV

Экспорт в CSV позволяет рецензентам открыть список комментариев сразу в Excel. Ниже показан метод, который записывает двухколоночный файл (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());

Ключевые моменты:

  • 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‑массив, чем плоский CSV.

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());

Ключевые моменты:

  • Вывод представляет собой плоский массив объектов { page, value } — легко десериализуется любой downstream‑службой.
  • 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 Быстрая проверка «есть ли комментарии вообще?» Один вызов, самый простой код Нет привязки к страницам
Per‑Page Extraction Маршрутизация отзывов к нужному разделу Результаты с указанием страницы, готовые к экспорту Один дополнительный вызов на страницу
Combined Text + Annotations Один читаемый транскрипт Нет второго прохода по документу Комментарии не отделены от основного текста
CSV Export Отслеживание в таблицах Легко открыть в Excel, человекочитаемо Ограничено плоской структурой
JSON Export Автоматизированные конвейеры, системы тикетов Структурировано, машиночитаемо Немного больший объём данных

Начните с извлечения всего документа, чтобы убедиться, что в файле есть комментарии, а затем перейдите к постраничному извлечению, когда понадобится привязать обратную связь к конкретному разделу.

Best Practices and Tips

  • Своевременно освобождайте Parser: оборачивайте его в using, чтобы освободить нативные ресурсы.
  • Различайте null и пустую коллекцию: GetAnnotations, возвращающая null, означает, что формат не поддерживается; пустая коллекция — что в документе нет комментариев.
  • Проверяйте Features.Annotations в пакетных задачах: сразу отбрасывайте неподдерживаемые файлы, а не полагайтесь на проверку null внутри цикла.
  • Повторно используйте список с привязкой к страницам: сформируйте его один раз с помощью ExtractAnnotationsByPage и передайте в экспортеры CSV и JSON, чтобы два вывода никогда не расходились.
  • Безопасность: текст аннотации — произвольный ввод рецензента; обрабатывайте его как любой другой недоверенный строковый ввод перед отображением в UI или отчёте.

Conclusion

GroupDocs.Parser предоставляет прямой программный способ извлекать комментарии рецензентов из PDF, избавляя от ручного поиска. Извлекая аннотации для всего документа, помечая их по страницам или объединяя с обычным текстовым потоком, вы можете построить рабочие процессы рецензирования, которые сразу показывают обратную связь, как только документ попадает в ваш конвейер. Экспортируйте результаты в CSV или JSON и интегрируйте их в уже используемые командой инструменты.

Следующие шаги:

  • Ознакомьтесь с GetAnnotations API reference для полного описания сигнатур и перегрузок метода.
  • Узнайте, как extract text from PDF documents вместе с аннотациями для полной конвейерной обработки контента.
  • Посмотрите дополнительные примеры проектов на GitHub для сценариев пакетной обработки (Examples Repo).

Additional Resources