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

Введение

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

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

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

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

Почему важно извлекать аннотации из PDF

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

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

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

Предварительные требования

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

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

dotnet add package GroupDocs.Parser

Как извлечь аннотации из PDF‑документа?

Ответ: Загрузите файл с помощью Parser, затем вызовите GetAnnotations() для всего документа или GetAnnotations(pageIndex) для отдельной страницы. Каждый результат – это коллекция объектов AnnotationItem, у которых свойство Value содержит текст комментария. Если хотите видеть комментарии вместе с обычным содержимым документа, установите IncludeAnnotations в TextOptions и вызовите GetText.

Извлечение аннотаций из всего документа

Следующий фрагмент получает все аннотации из файла одним вызовом – это самый быстрый способ проверить, есть ли в документе открытые комментарии.

// 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 предоставляет.
  • Здесь нет привязки к страницам; используйте перегрузку для отдельной страницы, если она нужна.

Извлечение аннотаций по страницам

Когда важна позиция комментария, пройдитесь по страницам документа и вызовите 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.

Извлечение текста вместе с аннотациями

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

// 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), если нужен такой вывод только для одной страницы.

Проверка поддержки аннотаций заранее

Не каждый формат поддерживает аннотации, поэтому имеет смысл проверить это до написания логики вокруг 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 при отсутствии поддержки.

Экспорт аннотаций в 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 или может быть передан в систему тикетов.

Вспомогательная функция: CsvEscape

if (string.IsNullOrEmpty(s)) return string.Empty;
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
{
    return "\"" + s.Replace("\"", "\"\"") + "\"";
}
return s;

Экспорт аннотаций в 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 без подключения библиотеки сериализации.

Вспомогательная функция: Escape

return s?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? string.Empty;

Сравнение методов: когда использовать каждый

Метод Лучшее применение Ключевые преимущества Ограничения
Whole‑Document Extraction Быстрая проверка «есть ли вообще комментарии?» Один вызов, самый простой код Нет привязки к страницам
Per‑Page Extraction Маршрутизация отзывов к нужному разделу Результаты с указанием страницы, готово к экспорту Один дополнительный вызов на страницу
Combined Text + Annotations Один читаемый транскрипт Нет второго прохода по документу Комментарии не отделены от основного текста
CSV Export Отслеживание отзывов в таблицах Легко открыть в Excel, человекочитаемо Ограничено плоской структурой
JSON Export Автоматизированные конвейеры, системы тикетов Структурировано, машиночитаемо Немного больший объём данных

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

Лучшие практики и советы

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

Заключение

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

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

Дополнительные ресурсы