💡 Pełny działający przykład dostępny na GitHub: extract-annotations-from-pdf-using-groupdocs-parser-dotnet
Introduction
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 uwag. GroupDocs.Parser to biblioteka .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 na poszczególne strony, pobrać je razem z tekstem dokumentu oraz wyeksportować wyniki do CSV lub JSON.
Natrafiłem na ten problem, tworzą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. Wyciągnięcie adnotacji w kilku linijkach kodu zamieniło to w dwuminutowe zadanie.
W kolejnych sekcjach dowiesz się, jak:
- Wyciągnąć 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 jednocześnie w jednym odczycie.
- Zserializować wyniki do CSV lub JSON dla dalszych narzędzi.
Why Extracting PDF Annotations Matters
Programatyczne odczytywanie adnotacji PDF jest przydatne do:
- Workflow recenzji: Zbierz wszystkie komentarze recenzentów bez otwierania pliku w przeglądarce PDF.
- Współpraca: Udostępnij podświetlone lub notowane fragmenty bezpośrednio w własnych narzędziach.
- Audyt: Zachowaj zapis oznaczeń pozostawionych na 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.
Prerequisites
- .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
How do I extract annotations from a PDF document?
Answer: Load the file with Parser, then call GetAnnotations() for the whole document or GetAnnotations(pageIndex) for a single page. Each result is a collection of AnnotationItem objects whose Value property holds the comment text. If you’d rather see comments inline with the document’s regular content, set IncludeAnnotations on TextOptions and call GetText instead.
Whole‑Document Extraction
The following snippet pulls every annotation out of the file in a single call, which is the fastest way to check whether a document has any open comments at all.
// 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;
Key points:
GetAnnotations()returnsnullwhen annotation extraction isn’t supported for the document, and an empty collection when the document simply has none.- Each
AnnotationItemexposes its text through theValueproperty – that’s the only data point the SDK currently reports. - No page attribution is included here; use the per‑page overload below if you need it.
Per‑Page Extraction
When a comment’s location matters, loop over the document’s pages and call GetAnnotations(pageIndex) for each one.
// 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;
Key points:
GetDocumentInfo().PageCountdrives the loop; there’s no separate “annotation page count”.GetAnnotations(pageIndex)uses a zero‑based index, matching every other page‑level method in the API.- The resulting
AnnotationRecordlist is exactly the shape a CSV or JSON export needs.
Extracting Text Together with Annotations
Instead of two passes over the document, you can fold annotation text directly into the regular text extraction output.
// 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;
}
}
Key points:
IncludeAnnotationsis a property onTextOptions, so this works with the sameGetTextcall you’d already use for plain text extraction.- Useful when you want a single transcript‑style output rather than a separate comment list.
- Combine it with
GetText(pageIndex, options)if you only need this for one page.
Checking Annotation Support First
Not every format supports annotations, so it’s worth checking before you build logic around GetAnnotations.
// Returns true if the loaded document format supports annotation extraction
using (var parser = new Parser(path))
{
return parser.Features.Annotations;
}
Key points:
Features.Annotationsis a simple boolean flag on theParserinstance.- Checking it upfront makes intent explicit, even though
GetAnnotationsalready fails gracefully by returningnull.
Exporting the Annotations to CSV
A CSV export lets reviewers open the comment list directly in Excel. The method below writes a two‑column file (page,value) from the page‑tagged records built earlier.
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());
Key points:
CsvEscapesafely quotes fields containing commas, quotes, or line breaks.- The resulting file opens directly in Excel or can be piped into a ticketing tool.
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
For pipelines that consume comments programmatically, a JSON array is usually a better fit than a flat 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());
Key points:
- The output is a flat array of
{ page, value }objects – easy for any downstream service to deserialize. Escapekeeps the payload valid JSON without pulling in a serialization library.
Helper: Escape
return s?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? string.Empty;
Comparing Methods: When to Use Each
| Metoda | Najlepsze zastosowanie | Kluczowe zalety | Ograniczenia |
|---|---|---|---|
| Whole‑Document Extraction | Szybkie sprawdzenie, czy istnieją jakiekolwiek komentarze | Jeden wywołanie, najprostszy kod | Brak informacji o stronie |
| Per‑Page Extraction | Kierowanie uwag do odpowiedniej sekcji | Wyniki oznaczone stroną, gotowe do eksportu | Dodatkowe wywołanie dla każdej strony |
| Combined Text + Annotations | Jeden czytelny transkrypt | Brak drugiego przebiegu po dokumencie | Komentarze nie są oddzielone od treści |
| CSV Export | Śledzenie recenzji w arkuszu kalkulacyjnym | Łatwe otwarcie w Excelu, czytelny dla człowieka | Ograniczone do płaskiej struktury |
| JSON Export | Automatyczne pipeline’y, systemy zgłoszeń | Strukturalny, łatwy do odczytu maszynowego | Nieco większy rozmiar payloadu |
Zacznij od ekstrakcji całego dokumentu, aby potwierdzić, że plik zawiera komentarze warte przetworzenia, a następnie przejdź do ekstrakcji per‑page, gdy będziesz potrzebować przypisać uwagi do konkretnych sekcji.
Best Practices and Tips
- Dispose
Parserpromptly: wrap it in ausingblock to free native resources. - Distinguish
nullfrom empty:GetAnnotationsreturningnullmeans the format isn’t supported; an empty collection means the document has no comments. - Check
Features.Annotationsin batch jobs: skip unsupported files early instead of relying on anullcheck deep inside your loop. - Reuse the page‑tagged list: build it once with
ExtractAnnotationsByPageand feed both the CSV and JSON exporters from the same data, so the two outputs never drift apart. - Security: annotation text is free‑form reviewer input – treat it the same as any other untrusted string before rendering it in a UI or report.
Conclusion
GroupDocs.Parser gives you a direct, programmatic way to pull reviewer comments out of a PDF instead of hunting for them by hand. By extracting annotations for the whole document, tagging them by page, or folding them into the regular text stream, you can build review workflows that surface feedback the moment a document lands in your pipeline. Export the results to CSV or JSON and wire them straight into the tools your team already uses.
Next steps:
- Explore the GetAnnotations API reference for the full method signature and overloads.
- Learn how to extract text from PDF documents alongside annotations for a complete content pipeline.
- Check out additional sample projects on GitHub for batch‑processing scenarios (Examples Repo).