💡 Полный рабочий пример доступен на 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 и интегрируйте их в инструменты, уже используемые вашей командой.
Следующие шаги:
- Изучите справку API GetAnnotations для полного описания сигнатур и перегрузок.
- Узнайте, как извлекать текст из PDF‑документов вместе с аннотациями для полной конвейерной обработки контента.
- Посмотрите дополнительные примеры проектов на GitHub для сценариев пакетной обработки (Examples Repo).