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