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

Порівняння методів: коли який використовувати

Метод Краще підходить для Ключові переваги Обмеження
Витяг всього документа Швидка перевірка «чи є коментарі взагалі?» Один виклик, найпростіший код Без атрибуції до сторінки
Витяг за сторінками Маршрутизація зворотного зв’язку до потрібного розділу Результати з позначенням сторінок, готові до експорту Додатковий виклик на кожну сторінку
Текст + анотації разом Єдиний читабельний транскрипт Не потрібен другий прохід по документу Коментарі не розділені від основного тексту
CSV‑експорт Відстеження у електронних таблицях Легко відкрити в Excel, зрозумілий людям Плоска структура
JSON‑експорт Автоматизовані конвеєри, системи тикетів Структурований, машинно‑читабельний Трохи більший обсяг даних

Почніть з витягу всього документа, щоб переконатися, що файл містить коментарі, які варто обробляти, а потім переходьте до витягу за сторінками, коли потрібно прив’язати зворотний зв’язок до конкретного розділу.

Кращі практики та поради

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

Висновок

GroupDocs.Parser надає прямий, програмний спосіб витягнути коментарі рецензентів з PDF, замість їх ручного пошуку. Витягуючи анотації для всього документа, позначаючи їх сторінками або включаючи їх у звичайний текстовий потік, ви можете будувати робочі процеси ревізії, які одразу показують зворотний зв’язок, коли документ потрапляє у ваш конвеєр. Експортуйте результати у CSV або JSON і підключайте їх безпосередньо до інструментів, якими вже користується ваша команда.

Наступні кроки:

Додаткові ресурси