💡 Ví dụ hoạt động đầy đủ có sẵn trên 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. Việc cuộn qua từng trang để tìm chúng không khả thi khi một tài liệu đã trải qua nhiều vòng phản hồi. GroupDocs.Parser là một thư viện .NET đọ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 chỉ ra cách trích xuất chú thích từ toàn bộ PDF, tách chúng ra theo từng 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 nhóm 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 cho các công cụ downstream.
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 xem xét: 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 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 trong 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 trở lên
- GroupDocs.Parser for .NET 26.7+ (giấy phép tạm thời)
- 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: Tải tệp bằng Parser, sau đó gọi GetAnnotations() cho toàn bộ tài liệu hoặc GetAnnotations(pageIndex) cho một trang duy nhất. Mỗi kết quả là một tập hợp các đối tượng AnnotationItem mà thuộc tính Value chứa nội dung bình luận. Nếu bạn muốn xem bình luận ngay trong nội dung tài liệu, hãy đặt IncludeAnnotations trên TextOptions và gọi GetText thay thế.
Whole‑Document Extraction
Đoạn mã dưới đây lấy mọi chú thích ra khỏi tệp trong một lần gọi duy nhất, đây là cách nhanh nhất để kiểm tra xem tài liệu có bất kỳ bình luận nào hay không.
// 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()trả vềnullkhi định dạng không hỗ trợ trích xuất chú thích, và trả về một tập hợp rỗng khi tài liệu không có chú thích nào.- Mỗi
AnnotationItemcung cấp văn bản qua thuộc tínhValue– đây là điểm dữ liệu duy nhất mà SDK hiện tại báo cáo. - Không có thông tin trang được bao gồm ở đây; hãy sử dụng phiên bản theo trang bên dưới nếu bạn cần.
Per‑Page Extraction
Khi vị trí của bình luận quan trọng, lặp qua các trang của tài liệu và gọi GetAnnotations(pageIndex) cho mỗi trang.
// 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().PageCountđiều khiển vòng lặp; không có “số trang chú thích” riêng.GetAnnotations(pageIndex)sử dụng chỉ mục bắt đầu từ 0, giống như mọi phương thức cấp trang khác trong API.- Danh sách
AnnotationRecordkết quả có đúng định dạng mà việc xuất CSV hoặc JSON yêu cầu.
Extracting Text Together with Annotations
Thay vì thực hiện hai lần duyệt tài liệu, bạn có thể gộp văn bản chú thích trực tiếp vào đầu ra của việc trích xuất văn bản thông thường.
// 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:
IncludeAnnotationslà một thuộc tính trênTextOptions, vì vậy cách này hoạt động với cùng một lời gọiGetTextmà bạn đã dùng để trích xuất văn bản thuần.- Hữu ích khi bạn muốn một đầu ra dạng bản ghi duy nhất thay vì danh sách bình luận riêng.
- Kết hợp với
GetText(pageIndex, options)nếu bạn chỉ cần điều này cho một trang.
Checking Annotation Support First
Không phải mọi định dạng đều hỗ trợ chú thích, vì vậy nên kiểm tra trước khi xây dựng logic quanh GetAnnotations.
// Returns true if the loaded document format supports annotation extraction
using (var parser = new Parser(path))
{
return parser.Features.Annotations;
}
Key points:
Features.Annotationslà một cờ boolean đơn giản trên đối tượngParser.- Kiểm tra nó ngay từ đầu làm cho ý định rõ ràng, mặc dù
GetAnnotationsđã trả vềnullmột cách an toàn khi không hỗ trợ.
Exporting the Annotations to CSV
Xuất CSV cho phép người đánh giá mở danh sách bình luận trực tiếp trong Excel. Phương thức dưới đây ghi một tệp hai cột (page,value) từ các bản ghi đã gắn nhãn trang được tạo ở trên.
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:
CsvEscapean toàn khi bao quanh các trường chứa dấu phẩy, dấu ngoặc kép hoặc ngắt dòng.- Tệp kết quả mở trực tiếp trong Excel hoặc có thể được chuyển tiếp vào công cụ quản lý ticket.
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
Đối với các pipeline tiêu thụ bình luận một cách lập trình, một mảng JSON thường phù hợp hơn so với CSV phẳng.
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:
- Đầu ra là một mảng phẳng các đối tượng
{ page, value }– dễ dàng cho bất kỳ dịch vụ downstream nào để giải tuần tự. Escapegiữ cho payload hợp lệ JSON mà không cần đưa vào thư viện tuần tự hoá.
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 | Bản ghi duy nhất có thể đọc | Không cần lần duyệt thứ hai | Bình luận không tách riêng khỏi nội dung |
| CSV Export | Theo dõi đánh giá dựa trên 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 xử lý, sau đó chuyển sang trích xuất theo trang khi bạn 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 nó 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 được hỗ trợ; một tập hợp rỗng nghĩa là tài liệu không có bình luận. - Kiểm tra
Features.Annotationstrong các công việc 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à cung cấp cho cả bộ xuất CSV và JSON từ cùng một dữ liệu, để hai đầu ra không bao giờ 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ý nó 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 cho bạn một 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 xem xét mà phản hồi xuất hiện ngay khi tài liệu vào pipeline của bạn. Xuất kết quả ra CSV hoặc JSON và kết nối chúng trực tiếp vào các công cụ mà nhóm của bạn đã sử dụng.
Next steps:
- Khám phá tài liệu tham khảo API GetAnnotations để biết đầy đủ chữ ký phương thức và các overload.
- Tìm hiểu cách trích xuất văn bản từ tài liệu PDF 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 (Kho ví dụ).