مقاله فنی

استخراج جدول از صفحات PDF در دلفی با PDFium

PDFium Component جدول‌ها را روی یک صفحه PDF تشخیص می‌دهد و آن‌ها را به‌صورت یک شبکه از سلول‌ها با span سطر و ستون، سطرهای سربرگ و یک مقدار اطمینان برمی‌گرداند، از طریق ExtractTables برای یک صفحه و ExtractDocumentTables برای کل یک سند. هر جدول با یک فراخوانی به CSV یا JSON تبدیل می‌شود، و جدول‌هایی که در شکست صفحه ادامه می‌یابند می‌توانند در یک زنجیره ادامه به هم پیوند بخورند

PDF هیچ شیء جدولی ندارد. یک جدول در یک PDF مجموعه‌ای از اجراهای متنی است که به‌گونه‌ای قرار داده شده که یک انسان آن‌ها را به‌صورت یک شبکه می‌خواند، گاهی با خطوطی که دورشان رسم شده و اغلب بدون آن. بازیابی شبکه یعنی بازسازی یک نیتی که فایل هرگز ثبت نکرده، به همین دلیل هر ابزار استخراج نتایج کمی متفاوت تولید می‌کند و به همین دلیل ابزاری که اطمینان خودش را به شما می‌گوید مفیدتر از ابزاری است که نمی‌گوید

دو حالت تشخیص برای دو نوع جدول

تشخیص خط‌کشی‌شده از خطوط رسم‌شده استفاده می‌کند. هر قطعه مسیر رنگ‌شده از طریق ماتریس شیء صفحه به مختصات صفحه تبدیل می‌شود، خطوط افقی و عمودی متقاطع می‌شوند، و تقاطع‌ها مؤلفه‌های متصل تشکیل می‌دهند. هر مؤلفه شبکه مرتب‌شده خودش از موقعیت‌های X و Y می‌شود، که همان چیزی است که مانع می‌شود دو جدول جداگانه روی یک صفحه در یک شبکه بی‌معنا ادغام شوند

تشخیص فاصله سفید جدول‌هایی را مدیریت می‌کند که با تراز به‌جای خط رسم شده‌اند. جعبه‌های کلمه به سطرهای بصری گروه‌بندی می‌شوند، شکاف‌های درون یک سطر آن را به ستون‌های نامزد تقسیم می‌کنند، و یک جدول فقط زمانی پذیرفته می‌شود که دست‌کم MinRows سطر دست‌کم MinColumns نقطه لنگر تراز-چپ را درون AlignmentTolerance تکرار کنند. ضریب شکاف سطر به‌طور پیش‌فرض ۳ است، که فاصله خط پایه تقریباً ۳۰ نقطه‌ای معمولِ متن ۱۲ نقطه‌ای را پوشش می‌دهد بدون اینکه اجازه دهد یک خط منفرد حاوی چندین اجرای متنی وانمود کند یک جدول است

uses
  PDFium;

var
  Pdf: TPdf;
  Options: TPdfTableExtractionOptions;
  Tables: TPdfTables;
  I: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'annual-report.pdf';
    Pdf.LoadDocument;
    Pdf.PageNumber := 12;                    // مبتنی بر ۱

    Options := TPdfTableExtractionOptions.Default;
    Options.DetectRuledTables := True;
    Options.DetectWhitespaceTables := True;
    Options.MinConfidence := 0.6;            // پیش‌فرض ۰٫۵ است
    Options.HeaderRowCount := 1;

    Tables := Pdf.ExtractTables(Options);
    for I := 0 to High(Tables) do
      Writeln(Format('table %d: %dx%d cells, confidence %.2f, mode %d',
        [I, Tables[I].RowCount, Tables[I].ColumnCount,
         Tables[I].Confidence, Ord(Tables[I].DetectionMode)]));

    if Length(Tables) > 0 then
      SaveText('page12-table0.csv', Tables[0].ToCsv);
  finally
    Pdf.Free;
  end;
end;
نموداری از خط لوله تشخیص جدول PDFium Component در دلفی، که در آن تقاطع خطوط رسم‌شده و سطرهای کلمه تراز‌شده با فاصله سفید یک رکورد جدول امتیازدهی‌شده واحد را با صادرات CSV و JSON تغذیه می‌کنند
تشخیص خط‌کشی‌شده ضربه‌های رسم‌شده را قطع می‌کند در حالی که تشخیص فاصله سفید سطرهای جعبه‌کلمه تراز‌شده را می‌شمارد؛ نامزدهایی که از MinRows و MinColumns عبور می‌کنند با یک امتیاز اطمینان و DetectionMode ضمیمه پدیدار می‌شوند

سلول‌های ادغام‌شده چگونه بازیابی می‌شوند؟

این بخشی است که استخراج‌کننده‌های ساده‌لوحانه در آن اشتباه می‌کنند. یک سلول ادغام‌شده نمی‌تواند فقط از شبکه سراسری شناسایی شود، زیرا شبکه از همه خطوط روی صفحه مشتق شده و یک ناحیه ادغام‌شده به‌سادگی فاقد خط داخلی‌ای است که آن را جدا می‌کرد

قاعده‌ای که اینجا استفاده می‌شود محلی است: دو سلول پایه مجاور وقتی ادغام می‌شوند که هیچ خط مرزی فاصله میان آن‌ها را پوشش ندهد. union-find آن‌ها را می‌پیوندد، مؤلفه‌های مستطیلی حاصل به مقادیر RowSpan و ColumnSpan تبدیل می‌شوند، و متن به یک سلول پایه با نقطه مرکزی‌اش اختصاص می‌یابد و سپس آن سلول را تا ریشه ادغامش دنبال می‌کند. انجام آن به این شکل همچنین هزینه را در کلمات به‌علاوه سلول‌ها خطی نگه می‌دارد، به‌جای اسکن درجه‌دومی که از آزمایش هر کلمه در برابر هر سلول به دست می‌آورید

اثر عملی این است که یک جدول مالی با یک سربرگ «مجموع» ادغام‌شده که سه ستون را در بر می‌گیرد، با یک سلول با span سه بیرون می‌آید، نه یک سلول پرشده و دو سلول خالی مرموز

نموداری از بازیابی سلول ادغام‌شده در استخراج جدول PDFium برای دلفی، که در آن union-find سلول‌های پایه مجاور را هرگاه هیچ خط مرزی بازه میان آن‌ها را نپوشاند به هم می‌پیوندد و RowSpan و ColumnSpan تولید می‌کند
union-find سلول‌های پایه همسایه‌ای را که بازه مشترکشان هیچ مرز رسم‌شده‌ای ندارد ادغام می‌کند، بنابراین یک سربرگ ادغام‌شده به‌صورت یک سلول واحد با ColumnSpan تنظیم‌شده برمی‌گردد نه یک سلول پرشده در کنار سلول‌های خالی

ادامه در سراسر صفحات

جدول‌های طولانی در سراسر صفحات می‌شکنند، و رفتار با قطعه هر صفحه به‌عنوان یک جدول مستقل، فراخوان را مجبور می‌کند آن‌ها را به هم بدوزد. ExtractDocumentTables می‌تواند در عوض آن‌ها را پیوند بزند، اما فقط تحت شرایط سخت‌گیرانه: قطعه باید پایین‌ترین جدول روی صفحه قبلی باشد، بعدی باید بالاترین جدول روی صفحه بعدی باشد، شماره صفحات باید مجاور باشند، و مرزهای ستون باید مطابقت داشته باشند

هر چهار شرط با هم چیزی است که مانع خطای واضح می‌شود، که زنجیره کردن هر جدول چهار-ستونی در یک سند در یک مگا-جدول خیالی است چون به‌طور تصادفی یک شمار ستون مشترک دارند. وقتی شرایط برقرارند، جدول‌ها یک شناسه گروه ادامه مشترک را به اشتراک می‌گذارند و متادیتای ادامه حمل می‌کنند؛ وقتی برقرار نیستند، جدول‌های جداگانه به دست می‌آورید و می‌توانید خودتان تصمیم بگیرید

استخراج در سطح سند بودجه‌های MaxCells و MaxTables را در سراسر صفحات به اشتراک می‌گذارد نه اینکه آن‌ها را به ازای هر صفحه بازنشانی کند، و صفحه فعال را در یک بلوک finally بازمی‌گرداند، بنابراین یک اجرای استخراج در یک نمایشگر کاربر را با نگاه به همان صفحه‌ای که روی آن بود رها می‌کند

نموداری از ادامه جدول در سراسر صفحات PDF در دلفی، که در آن چهار دروازه سخت‌گیرانه تصمیم می‌گیرند آیا پایین‌ترین قطعه روی یک صفحه به بالاترین قطعه روی صفحه بعدی می‌پیوندد
استخراج در سطح سند قطعات را فقط زمانی پیوند می‌دهد که هر چهار دروازه برقرار باشند، و مانع از ادغام جدول‌های چهارستونیِ نامرتبط در یک مگاجدول خیالی می‌شود

صادرات بدون خراب کردن داده

هر دو صادرکننده درباره فرار عمدی هستند. CSV همیشه فیلدها را نقل‌قول می‌کند و نشانه‌های نقل‌قول داخلی را دوبل می‌کند، که از شکست کلاسیکی جلوگیری می‌کند که در آن یک سلول حاوی یک ویرگول بی‌سروصدا به دو ستون تبدیل می‌شود. برای سلول‌های ادغام‌شده، محتوا فقط در نقطه لنگر بالا-چپ منتشر می‌شود، بنابراین یک رفت‌وبرگشت CSV یک سربرگ گسترده را در سراسر ستون‌هایی که پوشش می‌دهد تکرار نمی‌کند

JSON یونیکد را حفظ می‌کند به‌جای فرار دادنش به ASCII، کاراکترهای کنترلی را فرار می‌دهد، و متادیتایی را می‌گنجاند که یک مصرف‌کننده برای قضاوت کیفیت به آن نیاز دارد: حالت تشخیص، اطمینان، مرزها، مقادیر span، پرچم‌های سربرگ و اطلاعات ادامه. اگر جدول‌های استخراج‌شده را به یک سیستم پایین‌دستی تغذیه می‌کنید، JSON را ترجیح دهید، زیرا یک سطر CSV نمی‌تواند به شما بگوید جدولی که از آن آمده امتیاز اطمینان ۰٫۵۱ کسب کرده:

// استخراج سراسر سند، فقط نگه‌داشتن جدول‌های ارزش‌اعتماد
Tables := Pdf.ExtractDocumentTables(Options);
for I := 0 to High(Tables) do
begin
  if Tables[I].Confidence < 0.75 then
  begin
    Log(Format('page %d table needs review (%.2f)',
      [Tables[I].PageNumber, Tables[I].Confidence]));
    Continue;
  end;
  if Tables[I].ContinuationGroup > 0 then
    AppendToGroup(Tables[I].ContinuationGroup, Tables[I].ToJson)
  else
    EmitStandalone(Tables[I].ToJson);
end;

تنظیم دقیق، و دانستن اینکه چه زمانی متوقف شوید

سه تنظیم بیشتر از بقیه اهمیت دارند. MinConfidence دروازه کیفیت است، و ۰٫۵ عمداً اغماض‌کننده است؛ آن را برای دریافت خودکار بالا ببرید و برای یک رابط کاربری بازبینی که در آن یک انسان هر نتیجه را تأیید می‌کند پایین بیاورید. MinColumnGap تصمیم می‌گیرد چه چیزی در حالت فاصله سفید به‌عنوان یک مرز ستون شمرده می‌شود، و جدول‌های به‌طور تنگ تنظیم‌شده در گزارش‌های فشرده ممکن است نیاز داشته باشند آن از پیش‌فرض ۱۲ نقطه‌ای کاهش یابد. MaxRowGapFactor تصمیم می‌گیرد چه زمانی فاصله عمودی یک جدول را پایان می‌دهد، که برای جدول‌هایی با سطرهای خالی گاه‌به‌گاه اهمیت دارد

درباره محدودیت‌ها صادق باشید. جدول‌های خط‌کشی‌شده به‌طور قابل‌اعتماد استخراج می‌شوند. جدول‌های فاصله سفید به‌تمیزی ترازشده به‌خوبی استخراج می‌شوند. جدول‌هایی با متن چرخانده‌شده، جدول‌های تودرتو، یا سلول‌هایی که محتوایشان به چیزی شبیه یک سطر دیگر بسته‌بندی می‌شود، صرف‌نظر از نحوه تنظیم پارامترها به بازبینی نیاز خواهند داشت. برای آن‌ها، مدل متن ساختاریافته مواد خام را برای ساختن یک خواننده حوزه‌محور به شما می‌دهد، که در بلوک‌های متن ساختاریافته و ترتیب خواندن شرح داده شده

یک همراهی مفید: وقتی یک سند اسکن‌شده اصلاً متنی ندارد، تشخیص جدول تا وقتی یک لایه متن وجود نداشته باشد، چیزی برای کار کردن ندارد. ابتدا یکی اضافه کنید، همان‌طور که در افزودن لایه متن قابل‌جستجو به PDFهای اسکن‌شده شرح داده شده، سپس استخراج کنید. جعبه‌های کلمه‌ای که یک ارائه‌دهنده OCR برمی‌گرداند دقیقاً همان ورودی‌ای هستند که تشخیص فاصله سفید به آن نیاز دارد

استخراج جدول، متن ساختاریافته و بازچیدمان همگی از همان مدل صفحه در Delphi، C++Builder و Lazarus می‌خوانند؛ API کامل در صفحه PDFium Component برای Delphi شرح داده شده است