💡 Полный рабочий пример доступен на 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).