💡 דוגמה מלאה עובדת זמינה ב‑GitHub:
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 for .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 מדווח עליו כיום.
  • כאן לא נכללת שיוך לדף; השתמש בגרסת העומס לדף למטה אם אתה צריך זאת.

חילוץ לפי דף

כאשר מיקום ההערה חשוב, חזור על דפי המסמך וקרא 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 כבר מחזירה null בצורה אלגנטית.

ייצוא ההערות ל‑CSV

ייצוא ל‑CSV מאפשר לסוקרים לפתוח את רשימת ההערות ישירות באקסל. המתודה שלהלן כותבת קובץ דו‑עמודי (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 מציטט באופן בטוח שדות המכילים פסיקים, מרכאות או שורות חדשות.
  • הקובץ המתקבל נפתח ישירות באקסל או ניתן להעבירו לכלי ניהול תקלות.

עזר: 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 } – קל לדסיריאליזציה על ידי כל שירות משני.
  • Escape שומר על תקינות ה‑JSON מבלי להוסיף ספריית סריאליזציה.

עזר: Escape

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

השוואת שיטות: מתי להשתמש בכל אחת

שיטה מתאים ל‑ יתרונות מרכזיים מגבלות
חילוץ לכל המסמך בדיקה מהירה של “האם יש תגובות בכלל?” קריאה אחת, קוד פשוט אין שיוך לדף
חילוץ לפי דף ניתוב משוב לחלק הנכון תוצאות מתויגות בדף, מוכנות לייצוא קריאה נוספת לכל דף
טקסט משולב + הערות תמליל קריא יחיד אין צורך במעבר שני על המסמך ההערות אינן מופרדות מטקסט הגוף
ייצוא CSV מעקב ביקורות מבוסס גיליון פתיחה קלה באקסל, קריא לבני אדם מוגבל למבנה שטוח
ייצוא JSON צינורות אוטומטיים, מערכות ניהול תקלות מובנה, קריא למכונה משקל פayload מעט גדול יותר

התחל עם חילוץ לכל המסמך כדי לאשר שלקובץ יש תגובות שמצריכות טיפול, ואז עבור לחילוץ לפי דף כאשר אתה צריך לנתב משוב לחלק ספציפי.

שיטות עבודה מומלצות וטיפים

  • שחרור Parser מיידית: עטוף אותו ב‑using כדי לשחרר משאבים מקומיים.
  • הבחנה בין null לריק: GetAnnotations המחזירה null משמעותה שהפורמט אינו נתמך; אוסף ריק משמעותו שאין למסמך הערות.
  • בדוק Features.Annotations בעבודות אצווה: דלג על קבצים לא נתמכים מוקדם במקום להסתמך על בדיקת null עמוקה בלולאה.
  • השתמש ברשימת הדפים המתויגת פעם אחת: בנה אותה עם ExtractAnnotationsByPage והזין את שני המייצאים (CSV ו‑JSON) מאותו מקור, כך שהפלטים לעולם לא יתפצלו.
  • אבטחה: טקסט ההערה הוא קלט חופשי של הסוקר – התייחס אליו כמו לכל מחרוזת לא מהימנה לפני הצגתו בממשק משתמש או בדוח.

סיכום

GroupDocs.Parser מספקת דרך ישירה ותכנותית לחלץ תגובות סוקרים מתוך PDF במקום לחפש אותן ידנית. על‑ידי חילוץ הערות לכל המסמך, תיוגן לפי דף, או שילובן עם זרם הטקסט הרגיל, ניתן לבנות תהליכי ביקורת שמציגים משוב ברגע שהמסמך נכנס לצינור העבודה שלכם. ייצא את התוצאות ל‑CSV או JSON וחבר אותן ישירות לכלים שהצוות שלכם כבר משתמש בהם.

צעדים הבאים:

משאבים נוספים