💡 Full working example available on GitHub:
extract-annotations-from-pdf-using-groupdocs-parser-dotnet
Introduction
Một tệp PDF đã được xem xét thường chứa nhiều hơn chỉ văn bản hiển thị – các ghi chú dính, các đoạn được tô sáng và các bình luận nội dòng do người đánh giá để lại. Cuộn qua từng trang để tìm chúng không khả thi khi tài liệu đã trải qua nhiều vòng phản hồi. GroupDocs.Parser là một thư viện .NET cho phép đọc các chú thích nhúng trong tài liệu một cách lập trình, biến các bình luận rải rác của người đánh giá thành dữ liệu có cấu trúc mà mã của bạn có thể xử lý. Hướng dẫn này sẽ chỉ cách trích xuất chú thích từ toàn bộ PDF, phân tách chúng theo trang, lấy chúng cùng với văn bản của tài liệu, và xuất kết quả ra CSV hoặc JSON.
Tôi gặp vấn đề này khi xây dựng một công cụ theo dõi đánh giá cho đội ngũ tài liệu: một bản ghi chú phát hành 40 trang đã được ba người đánh giá, và việc mở tệp để tìm mọi bình luận mất nhiều thời gian hơn việc thực sự sửa các vấn đề họ đã chỉ ra. Việc trích xuất chú thích chỉ trong vài dòng mã đã biến công việc thành một nhiệm vụ kéo dài hai phút.
Trong các phần sau, bạn sẽ học cách:
- Trích xuất mọi chú thích từ một PDF trong một lần gọi.
- Gắn nhãn mỗi chú thích với trang mà nó thuộc về.
- Lấy văn bản tài liệu và văn bản chú thích cùng nhau trong một lần đọc.
- Chuẩn hóa kết quả ra CSV hoặc JSON để các công cụ downstream sử dụng.
Why Extracting PDF Annotations Matters
Đọc chú thích PDF một cách lập trình hữu ích cho:
- Quy trình đánh giá: Thu thập mọi bình luận của người đánh giá mà không cần mở tệp trong trình xem PDF.
- Hợp tác: Hiển thị các đoạn được tô sáng hoặc có ghi chú trực tiếp trong công cụ của bạn.
- Kiểm toán: Giữ hồ sơ các đánh dấu trên tài liệu theo thời gian, ngay cả khi tài liệu đã được làm phẳng hoặc hoàn thiện.
GroupDocs.Parser đã thêm khả năng trích xuất chú thích gốc cho tài liệu PDF từ phiên bản 26.7 thông qua phương thức GetAnnotations, cùng với tùy chọn mới IncludeAnnotations trên TextOptions để kéo văn bản chú thích vào kết quả đọc văn bản thông thường.
Prerequisites
- .NET 6.0 hoặc mới hơn
- GroupDocs.Parser for .NET 26.7+ (temporary license)
- Một tệp PDF có sẵn các chú thích (ví dụ:
document-with-annotations.pdf)
Cài đặt qua 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
| Phương pháp | Thích hợp cho | Ưu điểm chính | Hạn chế |
|---|---|---|---|
| Whole‑Document Extraction | Kiểm tra nhanh “có bình luận nào không?” | Gọi một lần, mã đơn giản nhất | Không có thông tin trang |
| Per‑Page Extraction | Gửi phản hồi tới đúng phần | Kết quả có nhãn trang, sẵn sàng xuất | Gọi thêm một lần cho mỗi trang |
| Combined Text + Annotations | Tạo một bản ghi chép duy nhất | Không cần lần đọc thứ hai | Bình luận không tách riêng khỏi nội dung |
| CSV Export | Theo dõi đánh giá bằng bảng tính | Dễ mở trong Excel, người đọc được | Cấu trúc phẳng, hạn chế |
| JSON Export | Pipeline tự động, hệ thống ticket | Cấu trúc, máy đọc được | Kích thước payload hơi lớn hơn |
Bắt đầu với việc trích xuất toàn tài liệu để xác nhận tệp có bình luận đáng chú ý, sau đó chuyển sang trích xuất theo trang khi cần định vị phản hồi vào phần cụ thể.
Best Practices and Tips
- Giải phóng
Parserkịp thời: bao bọc trong khốiusingđể giải phóng tài nguyên gốc. - Phân biệt
nullvà rỗng:GetAnnotationstrả vềnullnghĩa là định dạng không hỗ trợ; một collection rỗng nghĩa là tài liệu không có bình luận. - Kiểm tra
Features.Annotationstrong các job batch: bỏ qua các tệp không hỗ trợ ngay từ đầu thay vì dựa vào kiểm tranullsâu trong vòng lặp. - Tái sử dụng danh sách đã gắn nhãn trang: tạo một lần bằng
ExtractAnnotationsByPagevà dùng chung cho cả exporter CSV và JSON, để hai đầu ra không bị lệch nhau. - Bảo mật: văn bản chú thích là đầu vào tự do của người đánh giá – hãy xử lý chúng như bất kỳ chuỗi không tin cậy nào trước khi hiển thị trong UI hoặc báo cáo.
Conclusion
GroupDocs.Parser cung cấp cách trực tiếp, lập trình để lấy các bình luận của người đánh giá ra khỏi PDF thay vì phải tìm kiếm thủ công. Bằng cách trích xuất chú thích cho toàn bộ tài liệu, gắn nhãn chúng theo trang, hoặc gộp chúng vào luồng văn bản thông thường, bạn có thể xây dựng quy trình đánh giá mà ngay khi tài liệu vào pipeline đã hiện ra phản hồi. Xuất kết quả ra CSV hoặc JSON và tích hợp ngay vào các công cụ mà đội ngũ của bạn đã sử dụng.
Next steps:
- Khám phá GetAnnotations API reference để xem đầy đủ chữ ký phương thức và các overload.
- Tìm hiểu cách extract text from PDF documents cùng với chú thích để có một pipeline nội dung hoàn chỉnh.
- Xem thêm các dự án mẫu trên GitHub cho các kịch bản xử lý batch (Examples Repo).