💡 مثال کامل قابل اجرا در گیتهاب موجود است:
extract-annotations-from-pdf-using-groupdocs-parser-dotnet
مقدمه
یک PDF که تحت بازبینی قرار گرفته است معمولاً بیش از متن قابل مشاهده خود دارد – یادداشتهای چسبان، نکات برجسته و نظرات درونمتنی که توسط بازبینها گذاشته شدهاند. پیمایش هر صفحه برای یافتن آنها وقتی یک سند چندین بار بازبینی شده باشد، مقیاسپذیر نیست. GroupDocs.Parser یک کتابخانه .NET است که حاشیهنویسیهای توکار یک سند را بهصورت برنامهنویسی میخواند و نظرات پراکنده بازبینها را به دادههای ساختاریافتهای تبدیل میکند که کد شما میتواند بر روی آنها عمل کند. این آموزش نشان میدهد چگونه حاشیهنویسیها را از یک PDF کامل استخراج کنیم، صفحه به صفحه تجزیه کنیم، آنها را همراه با متن سند بیرون بکشیم و نتایج را به CSV یا JSON صادر کنیم.
من این مشکل را هنگام ساخت یک ردیاب بازبینی برای تیم مستندسازی مواجه شدم: یک یادداشت انتشار 40‑صفحهای از سه بازبین عبور کرده بود و باز کردن دستی فایل برای یافتن هر نظر، طولانیتر از رفع خود مشکلاتی بود که آنها علامتگذاری کرده بودند. استخراج حاشیهنویسیها در چند خط کد، این کار را به دو دقیقه تبدیل کرد.
در بخشهای زیر خواهید آموخت چگونه:
- تمام حاشیهنویسیها را از یک PDF در یک عبور استخراج کنید.
- هر حاشیهنویسی را با صفحهای که به آن تعلق دارد برچسبگذاری کنید.
- متن سند و متن حاشیهنویسی را در یک خواندن واحد ترکیب کنید.
- نتایج را به CSV یا JSON برای ابزارهای بعدی سریالسازی کنید.
چرا استخراج حاشیهنویسیهای PDF مهم است
خواندن حاشیهنویسیهای PDF بهصورت برنامهنویسی برای موارد زیر مفید است:
- گردشهای بازبینی: جمعآوری هر نظر بازبین بدون باز کردن فایل در یک نمایشگر PDF.
- همکاری: نمایش بخشهای برجسته یا یادداشتدار بهصورت مستقیم در ابزارهای خودتان.
- حسابرسی: نگهداری رکوردی از علامتگذاریهای باقیمانده بر روی یک سند در طول زمان، حتی پس از صافسازی یا نهاییسازی آن.
GroupDocs.Parser استخراج بومی حاشیهنویسی برای اسناد PDF را در نسخه 26.7 از طریق متد GetAnnotations اضافه کرد، بههمراه گزینه جدید IncludeAnnotations در TextOptions برای کشیدن متن حاشیهنویسیها به یک خواندن متن معمولی.
پیشنیازها
- .NET 6.0 یا بالاتر
- GroupDocs.Parser برای .NET 26.7+ (مجوز موقت)
- یک فایل PDF با حاشیهنویسیهای موجود (مثلاً
document-with-annotations.pdf)
نصب از طریق NuGet:
dotnet add package GroupDocs.Parser
چگونه حاشیهنویسیها را از یک سند PDF استخراج کنم؟
پاسخ: فایل را با Parser بارگذاری کنید، سپس GetAnnotations() را برای کل سند یا GetAnnotations(pageIndex) برای یک صفحهٔ واحد فراخوانی کنید. هر نتیجه مجموعهای از اشیای AnnotationItem است که ویژگی Value متن نظر را در خود دارد. اگر ترجیح میدهید نظرات را بهصورت درونمتنی همراه با محتوای معمولی سند ببینید، IncludeAnnotations را در TextOptions تنظیم کنید و بهجای آن GetText را فراخوانی کنید.
استخراج کل‑سند
قطعه کد زیر تمام حاشیهنویسیها را در یک فراخوانی از فایل بیرون میکشد، که سریعترین راه برای بررسی اینکه آیا سند حاوی نظرات باز است یا خیر، است.
// 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;
نکات کلیدی:
GetAnnotations()وقتی استخراج حاشیهنویسی برای سند پشتیبانی نمیشودnullبرمیگرداند و وقتی سند هیچ حاشیهنویسی ندارد، یک مجموعهٔ خالی برمیگرداند.- هر
AnnotationItemمتن خود را از طریق ویژگیValueارائه میدهد – این تنها نقطه دادهای است که SDK در حال حاضر گزارش میکند. - هیچ انتساب صفحهای در اینجا گنجانده نشده؛ اگر به آن نیاز دارید، از overload صفحهای زیر استفاده کنید.
استخراج صفحه‑به‑صفحه
وقتی مکان یک نظر مهم است، بر روی صفحات سند حلقه بزنید و برای هر یک GetAnnotations(pageIndex) را فراخوانی کنید.
// 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;
نکات کلیدی:
GetDocumentInfo().PageCountحلقه را هدایت میکند؛ هیچ «تعداد صفحهٔ حاشیهنویسی» جداگانهای وجود ندارد.GetAnnotations(pageIndex)از ایندکس صفر‑مبنا استفاده میکند، همانند تمام متدهای سطح‑صفحهٔ دیگر در API.- لیست
AnnotationRecordحاصل دقیقاً همان شکلی است که برای خروجی CSV یا JSON نیاز دارید.
استخراج متن بههمراه حاشیهنویسیها
بهجای دو عبور بر روی سند، میتوانید متن حاشیهنویسی را مستقیماً به خروجی استخراج متن معمولی اضافه کنید.
// 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;
}
}
نکات کلیدی:
IncludeAnnotationsیک ویژگی درTextOptionsاست، بنابراین این کار با همان فراخوانیGetTextکه برای استخراج متن ساده استفاده میکنید، انجام میشود.- زمانی مفید است که میخواهید خروجی بهصورت یک متن پیوسته (مانند رونوشت) داشته باشید نه یک فهرست جداگانهٔ نظرات.
- اگر فقط برای یک صفحه نیاز دارید، میتوانید آن را با
GetText(pageIndex, options)ترکیب کنید.
ابتدا بررسی پشتیبانی از حاشیهنویسی
هر فرمت همه حاشیهنویسیها را پشتیبانی نمیکند، بنابراین قبل از نوشتن منطق حول GetAnnotations، بررسی آن ارزش دارد.
// Returns true if the loaded document format supports annotation extraction
using (var parser = new Parser(path))
{
return parser.Features.Annotations;
}
نکات کلیدی:
Features.Annotationsیک پرچم بولی ساده بر روی نمونهٔParserاست.- بررسی آن از پیش، نیت شما را واضح میکند، حتی اگر
GetAnnotationsبهصورت Graceful با برگرداندنnullشکست بخورد.
صادر کردن حاشیهنویسیها به CSV
یک خروجی CSV به بازبینها اجازه میدهد فهرست نظرات را مستقیماً در Excel باز کنند. متد زیر یک فایل دو ستونی (page,value) را از رکوردهای برچسبگذاریشدهٔ صفحهای که قبلاً ساخته شدهاند، مینویسد.
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());
نکات کلیدی:
CsvEscapeبهصورت ایمن فیلدهایی که شامل کاما، نقلقول یا شکست خط هستند، نقلقول میکند.- فایل حاصل مستقیماً در Excel باز میشود یا میتواند به یک ابزار تیکتگذاری منتقل شود.
کمکی: CsvEscape
if (string.IsNullOrEmpty(s)) return string.Empty;
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
{
return "\"" + s.Replace("\"", "\"\"") + "\"";
}
return s;
صادر کردن حاشیهنویسیها به JSON
برای خطوط لولهای که نظرات را بهصورت برنامهنویسی مصرف میکنند، یک آرایهٔ JSON معمولاً مناسبتر از یک 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());
نکات کلیدی:
- خروجی یک آرایهٔ مسطح از اشیای
{ page, value }است – برای هر سرویس downstreamی آسان برای دیسریالایز کردن. Escapeمحتوای JSON را بدون نیاز به کتابخانهٔ سریالسازی معتبر نگه میدارد.
کمکی: Escape
return s?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? string.Empty;
مقایسهٔ روشها: چه زمانی از کدام استفاده کنیم
| روش | بهترین کاربرد | مزایای کلیدی | محدودیتها |
|---|---|---|---|
| استخراج کل‑سند | بررسی سریع «آیا نظراتی وجود دارد؟» | یک فراخوانی، سادهترین کد | بدون انتساب صفحه |
| استخراج صفحه‑به‑صفحه | مسیردهی بازخورد به بخش صحیح | نتایج برچسبگذاریشدهٔ صفحه، آمادهٔ خروجی | یک فراخوانی اضافی برای هر صفحه |
| متن ترکیبی + حاشیهنویسی | رونوشت خواندنی واحد | بدون عبور دوم بر روی سند | نظرات از متن اصلی جدا نیستند |
| صادر کردن CSV | ردیابی بازبینی مبتنی بر جدولمحور | باز کردن آسان در Excel، قابل خواندن برای انسان | ساختار مسطح |
| صادر کردن JSON | خطوط لولهای خودکار، سیستمهای تیکت | ساختارمند، قابل خواندن برای ماشین | حجم کمی بزرگتر |
با استخراج کل‑سند شروع کنید تا تأیید کنید فایل حاوی نظراتی است که ارزش پردازش دارند، سپس برای مسیردهی بازخورد به بخش خاص، به استخراج صفحه‑به‑صفحه بروید.
بهترین روشها و نکات
Parserرا بهسرعت آزاد کنید: آن را در یک بلوکusingبپیچید تا منابع بومی آزاد شوند.- تمایز
nullاز خالی:GetAnnotationsکهnullبرمیگرداند به این معنی است که فرمت پشتیبانی نمیشود؛ یک مجموعهٔ خالی به این معنی است که سند هیچ نظری ندارد. - در کارهای دستهای
Features.Annotationsرا بررسی کنید: فایلهای پشتیبانینشده را زودتر رد کنید تا نیازی به بررسیnullعمیق در حلقهتان نباشد. - لیست برچسبگذاریشدهٔ صفحه را دوباره استفاده کنید: آن را یکبار با
ExtractAnnotationsByPageبسازید و هم برای صادرکنندهٔ CSV و هم JSON از همان دادهها استفاده کنید، تا خروجیها هرگز از هم جدا نشوند. - امنیت: متن حاشیهنویسی ورودی آزاد بازبین است – قبل از رندر در UI یا گزارش، همانند هر رشتهٔ غیرقابل اعتماد دیگر آن را پردازش کنید.
نتیجهگیری
GroupDocs.Parser راهی مستقیم و برنامهنویسی برای استخراج نظرات بازبینها از یک PDF فراهم میکند، بهجای اینکه بهدست خود به دنبال آنها بگردید. با استخراج حاشیهنویسیها برای کل سند، برچسبگذاری آنها بر حسب صفحه، یا ترکیب آنها با جریان متن معمولی، میتوانید گردشهای بازبینی بسازید که بازخورد را بهمحض ورود سند به خط لولهتان نشان میدهند. نتایج را به CSV یا JSON صادر کنید و مستقیماً به ابزارهایی که تیمتان قبلاً استفاده میکند، متصل کنید.
گامهای بعدی:
- مرجع API GetAnnotations را برای امضای کامل متد و overloadها بررسی کنید.
- نحوهٔ استخراج متن از اسناد PDF را همراه با حاشیهنویسیها برای یک خط لولهٔ محتوای کامل بیاموزید.
- پروژههای نمونهٔ بیشتر در گیتهاب برای سناریوهای پردازش دستهای را ببینید (Examples Repo).