💡 Full working example available on GitHub:
extract-annotations-from-pdf-using-groupdocs-parser-dotnet

Introduction

PDF تم مراجعته عادةً يحمل أكثر من النص الظاهر – ملاحظات لاصقة، تعليقات مميزة، وتعليقات مدمجة تركها المراجعون. التمرير عبر كل صفحة للعثور عليها لا يُجدي نفعًا بمجرد أن يمر المستند بعدة جولات من الملاحظات. GroupDocs.Parser هي مكتبة .NET تقرأ التعليقات المدمجة في المستند برمجيًا، محوّلةً تعليقات المراجعين المتناثرة إلى بيانات مُهيكلة يمكن لكودك التعامل معها. يوضح هذا الدرس كيفية استخراج التعليقات من ملف PDF كامل، تفصيلها صفحةً بصفحة، سحبها جنبًا إلى جنب مع نص المستند، وتصدير النتائج إلى CSV أو JSON.

واجهت هذه المشكلة أثناء بناء متعقّب مراجعات لفريق توثيق: ملاحظة إصدار مكوّنة من 40 صفحة مرت بثلاث مراجعين، وكان فتح الملف يدويًا للعثور على كل تعليق يستغرق وقتًا أطول من إصلاح المشكلات التي أشاروا إليها. استخراج التعليقات ببضع أسطر من الكود حول ذلك إلى مهمة تستغرق دقيقتين.

في الأقسام التالية ستتعلم كيف:

  • تستخرج كل تعليق من PDF في عملية واحدة.
  • تُعلّم كل تعليق بالصفحة التي ينتمي إليها.
  • تسحب نص المستند ونص التعليق معًا في قراءة واحدة.
  • تُسلسل النتائج إلى CSV أو JSON لأدوات المعالجة اللاحقة.

Why Extracting PDF Annotations Matters

قراءة تعليقات PDF برمجيًا مفيدة لـ:

  • سير عمل المراجعة: جمع كل تعليق من المراجعين دون فتح الملف في عارض PDF.
  • التعاون: إظهار الأقسام المميزة أو الملاحظة مباشرة داخل أدواتك.
  • التدقيق: الحفاظ على سجل للعلامات المضافة إلى المستند بمرور الوقت، حتى بعد تسطيحه أو إكماله.

أضاف GroupDocs.Parser استخراج التعليقات الأصلي لمستندات PDF في الإصدار 26.7 عبر طريقة GetAnnotations، إلى جانب خيار جديد IncludeAnnotations في TextOptions لسحب نص التعليق إلى قراءة النص العادية.

Prerequisites

  • .NET 6.0 أو أحدث
  • GroupDocs.Parser for .NET 26.7+ (temporary license)
  • ملف PDF يحتوي على تعليقات موجودة (مثال: document-with-annotations.pdf)

التثبيت عبر 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() returns null when annotation extraction isn’t supported for the document, and an empty collection when the document simply has none.
  • Each AnnotationItem exposes its text through the Value property – 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().PageCount drives 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 AnnotationRecord list 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:

  • IncludeAnnotations is a property on TextOptions, so this works with the same GetText call 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.Annotations is a simple boolean flag on the Parser instance.
  • Checking it upfront makes intent explicit, even though GetAnnotations already fails gracefully by returning null.

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:

  • CsvEscape safely 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.
  • Escape keeps the payload valid JSON without pulling in a serialization library.

Helper: Escape

return s?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? string.Empty;

Comparing Methods: When to Use Each

Method Best For Key Advantages Limitations
Whole‑Document Extraction فحص سريع لمعرفة ما إذا كان هناك أي تعليقات استدعاء واحد، أبسط كود لا يتضمن إسناد الصفحة
Per‑Page Extraction توجيه الملاحظات إلى القسم الصحيح نتائج مع إشارة للصفحة، جاهزة للتصدير استدعاء إضافي لكل صفحة
Combined Text + Annotations الحصول على نص موحد قابل للقراءة لا حاجة لمرور ثاني على المستند التعليقات غير منفصلة عن نص الجسم
CSV Export تتبع المراجعات باستخدام جداول البيانات سهل الفتح في Excel، قابل للقراءة البشرية بنية مسطحة فقط
JSON Export خطوط الأنابيب الآلية، أنظمة التذاكر مُهيكل، قابل للقراءة الآلية حجم حمولة أكبر قليلًا

ابدأ باستخراج المستند الكامل لتأكيد وجود تعليقات تستحق المعالجة، ثم انتقل إلى استخراج الصفحات عندما تحتاج لتوجيه الملاحظات إلى قسم محدد.

Best Practices and Tips

  • Dispose Parser promptly: wrap it in a using block to free native resources.
  • Distinguish null from empty: GetAnnotations returning null means the format isn’t supported; an empty collection means the document has no comments.
  • Check Features.Annotations in batch jobs: skip unsupported files early instead of relying on a null check deep inside your loop.
  • Reuse the page‑tagged list: build it once with ExtractAnnotationsByPage and 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 يمنحك طريقة مباشرة وبرمجية لسحب تعليقات المراجعين من PDF بدلاً من البحث عنها يدويًا. من خلال استخراج التعليقات للمستند كاملًا، أو وسمها بالصفحة، أو دمجها مع تدفق النص العادي، يمكنك بناء سير عمل مراجعة يعرض الملاحظات فور وصول المستند إلى خط أنابيبك. صدّر النتائج إلى CSV أو JSON وربطها مباشرة بالأدوات التي يستخدمها فريقك بالفعل.

Next steps:

  • استكشف مرجع API لـ GetAnnotations للحصول على التوقيع الكامل للطريقة وتحميلاتها.
  • تعلّم كيفية استخراج النص من مستندات PDF جنبًا إلى جنب مع التعليقات للحصول على خط أنابيب محتوى كامل.
  • اطلع على مشاريع عينات إضافية على GitHub لسيناريوهات المعالجة الدفعة (Examples Repo).

Additional Resources