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