💡 ตัวอย่างทำงานเต็มที่มีบน GitHub:
extract-annotations-from-pdf-using-groupdocs-parser-dotnet

บทนำ

PDF ที่ผ่านการตรวจสอบมักจะมีข้อมูลมากกว่าข้อความที่มองเห็นได้ – โน้ตสติ๊กกี้, การไฮไลท์, และคอมเมนต์แบบอินไลน์ที่ผู้ตรวจสอบทิ้งไว้ การเลื่อนดูทุกหน้าเพื่อค้นหาเหล่านี้ไม่สามารถทำได้เมื่อเอกสารผ่านหลายรอบของฟีดแบ็ก GroupDocs.Parser เป็นไลบรารี .NET ที่อ่าน Annotation ที่ฝังอยู่ในเอกสารแบบโปรแกรมเมติก, เปลี่ยนคอมเมนต์ที่กระ散ของผู้ตรวจสอบให้เป็นข้อมูลโครงสร้างที่โค้ดของคุณสามารถทำงานได้ บทเรียนนี้จะแสดงวิธีสกัด Annotation จาก PDF ทั้งไฟล์, แบ่งตามหน้า, ดึงพร้อมกับข้อความของเอกสาร, และส่งออกผลลัพธ์เป็น CSV หรือ JSON

ฉันเจอปัญหานี้ขณะสร้างตัวติดตามการตรวจสอบสำหรับทีมเอกสาร: โน้ตปล่อยเวอร์ชัน 40 หน้าได้ผ่านผู้ตรวจสอบสามคน, และการเปิดไฟล์เพื่อค้นหาคอมเมนต์ทุกอันใช้เวลานานกว่าการแก้ไขปัญหาที่พวกเขาแจ้งไว้ การสกัด Annotation ด้วยไม่กี่บรรทัดของโค้ดทำให้งานนี้เสร็จในสองนาที

ในส่วนต่อไปนี้คุณจะได้เรียนรู้วิธี:

  • สกัด Annotation ทุกอันจาก PDF ในหนึ่งครั้ง
  • แท็กแต่ละ Annotation ด้วยหน้าที่มันอยู่
  • ดึงข้อความของเอกสารและข้อความ Annotation มาร่วมกันในหนึ่งการอ่าน
  • แปลงผลลัพธ์เป็น CSV หรือ JSON เพื่อใช้ในเครื่องมือต่อไป

ทำไมการสกัด Annotation ของ PDF ถึงสำคัญ

การอ่าน Annotation ของ PDF แบบโปรแกรมเมติกมีประโยชน์สำหรับ:

  • กระบวนการตรวจสอบ: รวบรวมคอมเมนต์ของผู้ตรวจสอบทุกอันโดยไม่ต้องเปิดไฟล์ในโปรแกรมดู PDF
  • การทำงานร่วมกัน: แสดงส่วนที่ไฮไลท์หรือมีโน้ตโดยตรงในเครื่องมือของคุณ
  • การตรวจสอบ: เก็บบันทึกของการทำเครื่องหมายบนเอกสารตามเวลา, แม้หลังจากที่เอกสารถูกแบนหรือสรุปแล้ว

GroupDocs.Parser เพิ่มการสกัด Annotation แบบเนทีฟสำหรับเอกสาร PDF ในเวอร์ชัน 26.7 ผ่านเมธอด GetAnnotations พร้อมตัวเลือกใหม่ IncludeAnnotations บน TextOptions เพื่อดึงข้อความ Annotation เข้ากับการอ่านข้อความปกติ

ข้อกำหนดเบื้องต้น

  • .NET 6.0 หรือใหม่กว่า
  • GroupDocs.Parser for .NET 26.7+ (temporary license)
  • ไฟล์ PDF ที่มี Annotation อยู่แล้ว (เช่น document-with-annotations.pdf)

ติดตั้งผ่าน NuGet:

dotnet add package GroupDocs.Parser

ฉันจะสกัด Annotation จากเอกสาร PDF อย่างไร?

คำตอบ: โหลดไฟล์ด้วย Parser, แล้วเรียก GetAnnotations() สำหรับทั้งเอกสารหรือ GetAnnotations(pageIndex) สำหรับหน้าเดียว แต่ละผลลัพธ์เป็นคอลเลกชันของอ็อบเจ็กต์ AnnotationItem ที่มีคุณสมบัติ Value เก็บข้อความคอมเมนต์ หากคุณต้องการเห็นคอมเมนต์แบบอินไลน์กับเนื้อหาเอกสารปกติ ให้ตั้งค่า IncludeAnnotations บน TextOptions แล้วเรียก GetText แทน

การสกัดแบบทั้งเอกสาร

โค้ดสั้น ๆ ด้านล่างจะดึง Annotation ทุกอันออกจากไฟล์ในหนึ่งการเรียก, ซึ่งเป็นวิธีที่เร็วที่สุดในการตรวจสอบว่าเอกสารมีคอมเมนต์เปิดอยู่หรือไม่

// 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 เมื่อฟอร์แมตไม่รองรับการสกัด Annotation, และคืนคอลเลกชันว่างเมื่อเอกสารไม่มีคอมเมนต์เลย
  • แต่ละ AnnotationItem เปิดเผยข้อความผ่านคุณสมบัติ Value – นี่คือข้อมูลจุดเดียวที่ SDK รายงานในขณะนี้
  • ไม่มีการระบุหน้าที่แนบมาที่นี่; ใช้ overload per‑page ด้านล่างหากต้องการ

การสกัดแบบต่อหน้า

เมื่อตำแหน่งของคอมเมนต์สำคัญ, ให้วนลูปผ่านหน้าของเอกสารและเรียก 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 เป็นตัวขับลูป; ไม่มี “จำนวนหน้าที่มี Annotation” แยกออกมา
  • GetAnnotations(pageIndex) ใช้ดัชนีเริ่มจากศูนย์, ตรงกับเมธอดระดับหน้าทุกตัวใน API
  • รายการ AnnotationRecord ที่ได้มีรูปแบบที่เหมาะกับการส่งออกเป็น CSV หรือ JSON

การสกัดข้อความพร้อมกับ Annotation

แทนที่จะทำสองรอบบนเอกสาร, คุณสามารถผสานข้อความ Annotation เข้าไปในผลลัพธ์การสกัดข้อความปกติได้โดยตรง

// 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) หากต้องการเฉพาะหน้าเดียว

ตรวจสอบการสนับสนุน Annotation ก่อน

ไม่ใช่ทุกฟอร์แมตที่รองรับ Annotation, ดังนั้นควรตรวจสอบก่อนที่จะเขียนตรรกะรอบ 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 อยู่แล้ว

การส่งออก Annotation ไปยัง 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 หรือสามารถส่งต่อเข้าเครื่องมือจัดการตั๋วได้

Helper: CsvEscape

if (string.IsNullOrEmpty(s)) return string.Empty;
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
{
    return "\"" + s.Replace("\"", "\"\"") + "\"";
}
return s;

การส่งออก Annotation ไปยัง JSON

สำหรับ pipeline ที่ต้องการคอมเมนต์แบบโปรแกรมเมติก, อาร์เรย์ 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 } – ง่ายต่อการ deserialize โดยบริการ downstream ใด ๆ
  • Escape ทำให้ payload เป็น JSON ที่ถูกต้องโดยไม่ต้องดึงไลบรารีการแปลงข้อมูลเข้ามา

Helper: Escape

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

การเปรียบเทียบวิธีการ: เมื่อใช้แต่ละวิธี

วิธี เหมาะสำหรับ ข้อได้เปรียบหลัก ข้อจำกัด
การสกัดแบบทั้งเอกสาร ตรวจสอบอย่างรวดเร็วว่า “มีคอมเมนต์ใด ๆ หรือไม่?” เรียกครั้งเดียว, โค้ดง่ายที่สุด ไม่มีการระบุหน้า
การสกัดแบบต่อหน้า ส่งฟีดแบ็กไปยังส่วนที่ถูกต้อง ผลลัพธ์มีการแท็กหน้า, พร้อมส่งออก ต้องเรียกเพิ่มหนึ่งครั้งต่อหน้า
ข้อความรวมกับ Annotation สร้างทรานสคริปต์อ่านได้เดียว ไม่ต้องทำรอบสองบนเอกสาร คอมเมนต์ไม่ได้แยกจากข้อความหลัก
ส่งออก CSV การติดตามการตรวจสอบแบบสเปรดชีต เปิดง่ายใน Excel, อ่านได้โดยมนุษย์ โครงสร้างแบนจำกัด
ส่งออก JSON Pipeline อัตโนมัติ, ระบบตั๋ว โครงสร้าง, อ่านได้โดยเครื่อง ขนาด payload มากกว่านิดหน่อย

เริ่มต้นด้วยการสกัดแบบทั้งเอกสารเพื่อยืนยันว่าไฟล์มีคอมเมนต์ที่ต้องดำเนินการ, จากนั้นสลับไปใช้การสกัดแบบต่อหน้าเมื่อจำเป็นต้องระบุตำแหน่งฟีดแบ็ก

แนวทางปฏิบัติที่ดีที่สุดและเคล็ดลับ

  • Dispose Parser ทันที: ห่อไว้ในบล็อก using เพื่อปล่อยทรัพยากรเนทีฟ
  • แยกความแตกต่างระหว่าง null กับค่าว่าง: GetAnnotations คืนค่า null หมายถึงฟอร์แมตไม่รองรับ; คอลเลกชันว่างหมายถึงเอกสารไม่มีคอมเมนต์
  • ตรวจสอบ Features.Annotations ในงานแบตช์: ข้ามไฟล์ที่ไม่รองรับตั้งแต่ต้นแทนการตรวจสอบ null ภายในลูป
  • ใช้รายการที่มีการแท็กหน้าเดียวกัน: สร้างรายการครั้งเดียวด้วย ExtractAnnotationsByPage แล้วส่งต่อให้ทั้งตัวส่งออก CSV และ JSON จากข้อมูลเดียวกัน เพื่อให้ผลลัพธ์สองแบบไม่แตกต่างกัน
  • ความปลอดภัย: ข้อความ Annotation เป็นข้อมูลที่ผู้ตรวจสอบใส่เอง – ควรปฏิบัติเช่นเดียวกับสตริงที่ไม่เชื่อถือได้ก่อนแสดงใน UI หรือรายงาน

สรุป

GroupDocs.Parser ให้วิธีโดยตรงและโปรแกรมเมติกในการดึงคอมเมนต์ของผู้ตรวจสอบออกจาก PDF แทนการค้นหาด้วยตนเอง โดยการสกัด Annotation ทั้งเอกสาร, แท็กตามหน้า, หรือผสานเข้ากับสตรีมข้อความปกติ คุณสามารถสร้างกระบวนการตรวจสอบที่แสดงฟีดแบ็กทันทีเมื่อเอกสารเข้าสู่ pipeline ของคุณ ส่งออกผลลัพธ์เป็น CSV หรือ JSON แล้วเชื่อมต่อเข้ากับเครื่องมือที่ทีมของคุณใช้แล้ว

ขั้นตอนต่อไป:

  • สำรวจ GetAnnotations API reference เพื่อดูลายเซ็นเมธอดเต็มและ overloads
  • เรียนรู้วิธี extract text from PDF documents ควบคู่กับ Annotation เพื่อสร้าง pipeline เนื้อหาที่ครบถ้วน
  • ดูตัวอย่างโปรเจกต์เพิ่มเติมบน GitHub สำหรับสถานการณ์การประมวลผลแบบแบตช์ (Examples Repo)

แหล่งข้อมูลเพิ่มเติม