💡 Pełny działający przykład dostępny na GitHubie:
extract-annotations-from-pdf-using-groupdocs-parser-dotnet

Wprowadzenie

PDF, który przeszedł proces recenzji, zazwyczaj zawiera więcej niż tylko widoczny tekst – notatki samoprzylepne, podświetlone uwagi i komentarze wstawione przez recenzentów. Przeglądanie każdej strony w poszukiwaniu ich nie skaluje się, gdy dokument przechodzi przez kilka rund feedbacku. GroupDocs.Parser jest biblioteką .NET, która programowo odczytuje osadzone w dokumencie adnotacje, zamieniając rozproszone komentarze recenzentów w ustrukturyzowane dane, które można przetwarzać w kodzie. Ten samouczek pokazuje, jak wyodrębnić adnotacje z całego PDF‑a, podzielić je strona po stronie, pobrać je razem z tekstem dokumentu oraz wyeksportować wyniki do CSV lub JSON.

Natrafiłem na ten problem, budując tracker recenzji dla zespołu dokumentacji: 40‑stronicowa notatka wydania przeszła przez trzech recenzentów, a ręczne otwieranie pliku w celu znalezienia każdego komentarza zajęło więcej czasu niż faktyczne naprawianie zgłoszonych problemów. Wyodrębnienie adnotacji w kilku linijkach kodu zamieniło to w dwuminutowe zadanie.

W kolejnych sekcjach dowiesz się, jak:

  • Wyodrębnić każdą adnotację z PDF‑a w jednym przebiegu.
  • Oznaczyć każdą adnotację numerem strony, do której należy.
  • Pobrać tekst dokumentu i tekst adnotacji razem w jednym odczycie.
  • Zserializować wyniki do CSV lub JSON dla dalszych narzędzi.

Dlaczego wyodrębnianie adnotacji PDF ma znaczenie

Programowe odczytywanie adnotacji PDF jest przydatne w:

  • Workflow recenzji: Zbierz wszystkie komentarze recenzentów bez otwierania pliku w przeglądarce PDF.
  • Współpraca: Wyświetl podświetlone lub notowane fragmenty bezpośrednio w własnych narzędziach.
  • Audyt: Zachowaj zapis oznaczeń pozostawionych w dokumencie w czasie, nawet po spłaszczeniu lub finalizacji.

GroupDocs.Parser dodał natywną ekstrakcję adnotacji dla dokumentów PDF w wersji 26.7 poprzez metodę GetAnnotations, wraz z nową opcją IncludeAnnotations w TextOptions, umożliwiającą włączenie tekstu adnotacji do zwykłego odczytu tekstu.

Wymagania wstępne

  • .NET 6.0 lub nowszy
  • GroupDocs.Parser for .NET 26.7+ (temporary license)
  • Plik PDF z istniejącymi adnotacjami (np. document-with-annotations.pdf)

Instalacja przez NuGet:

dotnet add package GroupDocs.Parser

Jak wyodrębnić adnotacje z dokumentu PDF?

Odpowiedź: Załaduj plik przy pomocy Parser, a następnie wywołaj GetAnnotations() dla całego dokumentu lub GetAnnotations(pageIndex) dla pojedynczej strony. Każdy wynik to kolekcja obiektów AnnotationItem, których właściwość Value zawiera tekst komentarza. Jeśli wolisz zobaczyć komentarze w linii z regularną treścią dokumentu, ustaw IncludeAnnotations w TextOptions i wywołaj GetText.

Wyodrębnianie całego dokumentu

Poniższy fragment pobiera wszystkie adnotacje z pliku w jednym wywołaniu, co jest najszybszym sposobem sprawdzenia, czy dokument zawiera jakiekolwiek otwarte komentarze.

// 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;

Kluczowe punkty:

  • GetAnnotations() zwraca null, gdy ekstrakcja adnotacji nie jest obsługiwana dla danego formatu, oraz pustą kolekcję, gdy dokument po prostu ich nie posiada.
  • Każdy AnnotationItem udostępnia swój tekst poprzez właściwość Value – to jedyny punkt danych, który obecnie zwraca SDK.
  • Brak tutaj przypisania do stron; użyj przeciążenia per‑strona poniżej, jeśli jest to potrzebne.

Wyodrębnianie po stronie

Gdy lokalizacja komentarza ma znaczenie, przeiteruj strony dokumentu i wywołaj GetAnnotations(pageIndex) dla każdej z nich.

// 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;

Kluczowe punkty:

  • GetDocumentInfo().PageCount steruje pętlą; nie istnieje osobna „liczba stron z adnotacjami”.
  • GetAnnotations(pageIndex) używa indeksu zerowego, tak jak wszystkie inne metody poziomu strony w API.
  • Lista AnnotationRecord ma dokładnie taką strukturę, jakiej potrzebuje eksport do CSV lub JSON.

Wyodrębnianie tekstu razem z adnotacjami

Zamiast dwóch przebiegów po dokumencie, możesz włączyć tekst adnotacji bezpośrednio do wyniku zwykłego wyodrębniania tekstu.

// 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;
    }
}

Kluczowe punkty:

  • IncludeAnnotations jest właściwością w TextOptions, więc działa z tym samym wywołaniem GetText, którego używasz już do wyodrębniania czystego tekstu.
  • Przydatne, gdy potrzebny jest jeden, spójny transkrypt zamiast oddzielnej listy komentarzy.
  • Połącz z GetText(pageIndex, options), jeśli potrzebujesz tego tylko dla jednej strony.

Sprawdzanie wsparcia adnotacji najpierw

Nie każdy format obsługuje adnotacje, więc warto to sprawdzić przed budowaniem logiki wokół GetAnnotations.

// Returns true if the loaded document format supports annotation extraction
using (var parser = new Parser(path))
{
    return parser.Features.Annotations;
}

Kluczowe punkty:

  • Features.Annotations to prosty flag boolean na instancji Parser.
  • Sprawdzenie tego z góry czyni intencję wyraźną, mimo że GetAnnotations i tak zwraca null w przypadku braku wsparcia.

Eksportowanie adnotacji do CSV

Eksport CSV pozwala recenzentom otworzyć listę komentarzy bezpośrednio w Excelu. Poniższa metoda zapisuje dwukolumnowy plik (page,value) z rekordów oznaczonych stroną.

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());

Kluczowe punkty:

  • CsvEscape bezpiecznie cytuje pola zawierające przecinki, cudzysłowy lub znaki nowej linii.
  • Powstały plik otwiera się od razu w Excelu lub może być przekierowany do systemu zgłoszeń.

Helper: CsvEscape

if (string.IsNullOrEmpty(s)) return string.Empty;
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
{
    return "\"" + s.Replace("\"", "\"\"") + "\"";
}
return s;

Eksportowanie adnotacji do JSON

Dla pipeline’ów, które konsumują komentarze programowo, tablica JSON jest zazwyczaj lepszym wyborem niż płaski 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());

Kluczowe punkty:

  • Wynik to płaska tablica obiektów { page, value } – łatwa do deserializacji w dowolnym serwisie downstream.
  • Escape utrzymuje poprawny format JSON bez konieczności używania biblioteki serializacyjnej.

Helper: Escape

return s?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? string.Empty;

Porównanie metod: Kiedy używać której

Metoda Najlepsze zastosowanie Kluczowe zalety Ograniczenia
Wyodrębnianie całego dokumentu Szybkie sprawdzenie, czy istnieją jakiekolwiek komentarze Jedno wywołanie, najprostszy kod Brak przypisania do stron
Wyodrębnianie po stronie Przekierowywanie uwag do właściwej sekcji Wyniki oznaczone stroną, gotowe do eksportu Jedno dodatkowe wywołanie na stronę
Połączony tekst + adnotacje Jedno czytelne sprawozdanie Brak drugiego przejścia po dokumencie Komentarze nie są oddzielone od tekstu głównego
Eksport CSV Śledzenie recenzji w arkuszach kalkulacyjnych Łatwe otwarcie w Excelu, czytelne dla człowieka Ograniczone do płaskiej struktury
Eksport JSON Zautomatyzowane pipeline’y, systemy zgłoszeń Strukturalny, czytelny dla maszyn Nieco większy ładunek

Zacznij od wyodrębniania całego dokumentu, aby potwierdzić, że plik zawiera komentarze warte dalszej obróbki, a potem przejdź do wyodrębniania po stronie, gdy będziesz musiał skierować feedback do konkretnej sekcji.

Najlepsze praktyki i wskazówki

  • Szybko zwalniaj Parser: otaczaj go blokiem using, aby zwolnić zasoby natywne.
  • Rozróżniaj null od pustego: GetAnnotations zwracające null oznacza brak wsparcia formatu; pusta kolekcja oznacza brak komentarzy w dokumencie.
  • Sprawdzaj Features.Annotations w zadaniach wsadowych: pomiń nieobsługiwane pliki wcześnie, zamiast polegać na sprawdzaniu null w głębi pętli.
  • Wykorzystuj listę oznaczoną stroną: zbuduj ją raz metodą ExtractAnnotationsByPage i użyj zarówno w eksporcie CSV, jak i JSON, aby oba wyjścia nigdy nie rozeszły się.
  • Bezpieczeństwo: tekst adnotacji to wolny tekst wprowadzony przez recenzenta – traktuj go tak samo jak każdy inny niezweryfikowany ciąg przed wyświetleniem w UI lub raporcie.

Zakończenie

GroupDocs.Parser zapewnia bezpośredni, programowy sposób wyciągania komentarzy recenzentów z PDF‑a, zamiast ręcznego ich poszukiwania. Dzięki wyodrębnianiu adnotacji dla całego dokumentu, ich oznaczaniu stroną lub włączaniu ich do zwykłego strumienia tekstu, możesz budować workflow recenzji, które prezentują feedback natychmiast po pojawieniu się dokumentu w Twoim pipeline. Eksportuj wyniki do CSV lub JSON i podłącz je bezpośrednio do narzędzi, z których już korzysta Twój zespół.

Kolejne kroki:

Dodatkowe zasoby