💡 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()zwracanull, gdy ekstrakcja adnotacji nie jest obsługiwana dla danego formatu, oraz pustą kolekcję, gdy dokument po prostu ich nie posiada.- Każdy
AnnotationItemudostę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().PageCountsteruje 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
AnnotationRecordma 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:
IncludeAnnotationsjest właściwością wTextOptions, więc działa z tym samym wywołaniemGetText, 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.Annotationsto prosty flag boolean na instancjiParser.- Sprawdzenie tego z góry czyni intencję wyraźną, mimo że
GetAnnotationsi tak zwracanullw 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:
CsvEscapebezpiecznie 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. Escapeutrzymuje 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 blokiemusing, aby zwolnić zasoby natywne. - Rozróżniaj
nullod pustego:GetAnnotationszwracającenulloznacza brak wsparcia formatu; pusta kolekcja oznacza brak komentarzy w dokumencie. - Sprawdzaj
Features.Annotationsw zadaniach wsadowych: pomiń nieobsługiwane pliki wcześnie, zamiast polegać na sprawdzaniunullw głębi pętli. - Wykorzystuj listę oznaczoną stroną: zbuduj ją raz metodą
ExtractAnnotationsByPagei 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:
- Zapoznaj się z GetAnnotations API reference w celu poznania pełnej sygnatury metody i jej przeciążeń.
- Dowiedz się, jak extract text from PDF documents razem z adnotacjami, aby uzyskać kompletny przepływ treści.
- Przejrzyj dodatkowe projekty przykładowe na GitHubie pod kątem scenariuszy batch‑processing (Examples Repo).