Artikel Teknis

Ekstrak Tabel dari Halaman PDF di Delphi dengan PDFium

PDFium Component mendeteksi tabel pada halaman PDF dan mengembalikannya sebagai grid sel dengan span baris dan kolom, baris header, dan sebuah nilai confidence, melalui ExtractTables untuk satu halaman dan ExtractDocumentTables untuk seluruh dokumen. Setiap tabel dikonversi ke CSV atau JSON dengan satu panggilan, dan tabel yang bersambung melewati batas halaman dapat ditautkan menjadi rantai kelanjutan (continuation chain)

PDF tidak memiliki objek tabel. Sebuah tabel dalam PDF adalah kumpulan text run yang diposisikan sedemikian rupa sehingga manusia membacanya sebagai sebuah grid, kadang dengan garis yang digambar di sekelilingnya dan sering kali tanpa garis sama sekali. Memulihkan grid tersebut berarti merekonstruksi maksud yang tidak pernah dicatat oleh filenya, itulah sebabnya setiap tool ekstraksi menghasilkan hasil yang sedikit berbeda, dan mengapa tool yang memberi tahu Anda confidence-nya lebih berguna daripada yang tidak

Dua mode deteksi untuk dua jenis tabel

Deteksi bergaris menggunakan garis yang digambar. Setiap segmen path bergaris ditransformasikan ke koordinat halaman melalui matriks milik page object, garis horizontal dan vertikal diperpotongkan, dan perpotongan-perpotongan itu membentuk komponen yang saling terhubung. Setiap komponen menjadi grid posisi X dan Y yang terurut miliknya sendiri, dan inilah yang mencegah dua tabel terpisah pada halaman yang sama tergabung menjadi satu grid yang tidak masuk akal

Deteksi selaras-spasi menangani tabel yang digambar dengan perataan, bukan garis. Kotak kata dikelompokkan menjadi baris visual, celah dalam sebuah baris memecahnya menjadi kandidat kolom, dan sebuah tabel diterima hanya ketika setidaknya MinRows baris mengulang setidaknya MinColumns anchor rata-kiri dalam AlignmentTolerance. Faktor celah baris defaultnya 3, yang mencakup jarak baseline sekitar 30 poin yang umum untuk teks 12 poin tanpa membiarkan satu baris berisi beberapa text run menyamar sebagai tabel

Diagram pipeline deteksi tabel PDFium Component di Delphi, tempat perpotongan garis bergaris dan baris kata selaras-spasi mengalir ke satu record tabel bernilai skor dengan ekspor CSV dan JSON
Deteksi bergaris memperpotongkan garis yang digambar, sementara deteksi selaras-spasi menghitung baris kotak-kata yang selaras; kandidat yang melewati MinRows dan MinColumns muncul dengan skor confidence dan DetectionMode terlampir
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;                    // 1-based

    Options := TPdfTableExtractionOptions.Default;
    Options.DetectRuledTables := True;
    Options.DetectWhitespaceTables := True;
    Options.MinConfidence := 0.6;            // default is 0.5
    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;

Bagaimana sel gabungan dipulihkan?

Inilah bagian yang sering salah dikerjakan ekstraktor naif. Sel gabungan tidak dapat diidentifikasi hanya dari grid global, karena grid diturunkan dari semua garis pada halaman, dan sebuah region gabungan sekadar tidak memiliki garis interior yang seharusnya memisahkannya

Aturan yang dipakai di sini bersifat lokal: dua sel dasar yang bersebelahan digabungkan ketika tidak ada garis batas yang mencakup interval di antara keduanya. Union-find menggabungkannya, komponen persegi panjang hasilnya menjadi nilai RowSpan dan ColumnSpan, dan teks diberikan ke sel dasar berdasarkan titik tengahnya lalu mengikuti sel tersebut hingga ke akar gabungannya. Melakukannya dengan cara ini juga menjaga biaya tetap linear terhadap jumlah kata ditambah sel, alih-alih pemindaian kuadratik yang Anda dapatkan dari menguji setiap kata terhadap setiap sel

Diagram pemulihan sel gabungan pada ekstraksi tabel PDFium untuk Delphi, tempat union-find menggabungkan sel dasar yang bersebelahan setiap kali tidak ada garis batas yang mencakup interval di antara keduanya, menghasilkan RowSpan dan ColumnSpan
Union-find menggabungkan sel dasar bertetangga yang interval bersamanya tidak memiliki garis batas, sehingga header gabungan dikembalikan sebagai satu sel dengan ColumnSpan diatur, bukan satu sel terisi yang diapit sel-sel kosong

Efek praktisnya adalah tabel finansial dengan header "Total" gabungan yang membentang tiga kolom keluar sebagai satu sel dengan span tiga, bukan satu sel terisi dan dua sel kosong yang membingungkan

Kelanjutan antar halaman

Tabel panjang terputus antar halaman, dan memperlakukan fragmen setiap halaman sebagai tabel independen memaksa pemanggil untuk menyambungkannya sendiri. ExtractDocumentTables dapat menautkannya sebagai gantinya, tetapi hanya dalam kondisi ketat: fragmen harus menjadi tabel paling bawah pada halaman sebelumnya, tabel berikutnya harus menjadi tabel paling atas pada halaman berikutnya, nomor halamannya harus berdekatan, dan batas kolomnya harus cocok

Keempat syarat itu bersama-samalah yang mencegah kesalahan yang jelas, yaitu merangkai setiap tabel empat kolom dalam sebuah dokumen menjadi satu mega-tabel khayalan hanya karena kebetulan memiliki jumlah kolom yang sama. Ketika syaratnya terpenuhi, tabel-tabel tersebut berbagi identifier grup kelanjutan dan membawa metadata kelanjutan; ketika tidak, Anda mendapatkan tabel terpisah dan bisa memutuskan sendiri

Diagram kelanjutan tabel antar halaman PDF di Delphi, tempat empat gerbang ketat menentukan apakah fragmen paling bawah pada satu halaman bergabung dengan fragmen paling atas pada halaman berikutnya
Ekstraksi tingkat dokumen hanya menautkan fragmen ketika keempat gerbang terpenuhi, mencegah tabel empat-kolom yang tidak berhubungan menyatu menjadi satu mega-tabel khayalan

Ekstraksi tingkat dokumen membagi budget MaxCells dan MaxTables lintas halaman alih-alih mengaturnya ulang per halaman, dan ia memulihkan halaman aktif dalam blok finally, sehingga proses ekstraksi yang dijalankan di dalam sebuah viewer meninggalkan pengguna tetap melihat halaman yang sedang mereka buka

Mengekspor tanpa merusak data

Kedua exporter cermat soal escaping. CSV selalu mengutip field dan menggandakan tanda kutip internal, yang menghindari kegagalan klasik di mana sel berisi koma diam-diam menjadi dua kolom. Untuk sel gabungan, konten hanya dikeluarkan pada anchor kiri-atas, sehingga round-trip CSV tidak menduplikasi header yang membentang di kolom-kolom yang dicakupnya

JSON mempertahankan Unicode alih-alih meng-escape-nya menjadi ASCII, meng-escape karakter kontrol, dan menyertakan metadata yang dibutuhkan konsumen untuk menilai kualitas: mode deteksi, confidence, batas, nilai span, flag header, dan informasi kelanjutan. Jika Anda memasukkan tabel hasil ekstraksi ke sistem hilir, utamakan JSON, karena satu baris CSV tidak bisa memberi tahu Anda bahwa tabel asalnya hanya mendapat skor confidence 0,51:

// Ekstraksi seluruh dokumen, hanya menyisakan tabel yang layak dipercaya
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;

Penyetelan, dan mengetahui kapan berhenti

Tiga pengaturan lebih penting daripada yang lain. MinConfidence adalah gerbang kualitas, dan 0,5 secara sengaja bersifat permisif; naikkan untuk ingest otomatis dan turunkan untuk UI review di mana manusia mengonfirmasi setiap hasil. MinColumnGap menentukan apa yang dihitung sebagai batas kolom dalam mode selaras-spasi, dan tabel yang diatur ketat dalam laporan padat mungkin perlu menurunkannya dari default 12 poin. MaxRowGapFactor menentukan kapan jarak vertikal mengakhiri sebuah tabel, yang penting untuk tabel dengan baris kosong sesekali

Bersikap jujur soal batasannya. Tabel bergaris terekstraksi dengan andal. Tabel selaras-spasi yang rapi terekstraksi dengan baik. Tabel dengan teks yang diputar, tabel bersarang, atau sel yang isinya membungkus hingga terlihat seperti baris lain akan tetap butuh peninjauan berapa pun parameternya diatur. Untuk kasus-kasus itu, model teks terstruktur memberi Anda bahan mentah untuk membangun pembaca khusus domain, dijelaskan dalam blok teks terstruktur dan urutan baca

Satu pasangan yang berguna: ketika dokumen hasil pindai sama sekali tidak memiliki teks, deteksi tabel tidak punya apa pun untuk diolah sampai sebuah layer teks ada. Tambahkan dulu, seperti dijelaskan dalam menambahkan layer teks yang dapat dicari ke PDF hasil pindai, lalu ekstraksi. Kotak kata yang dikembalikan provider OCR persis merupakan input yang dibutuhkan deteksi selaras-spasi

Ekstraksi tabel, teks terstruktur, dan reflow semuanya membaca dari model halaman yang sama di Delphi, C++Builder, dan Lazarus; API lengkapnya dijelaskan di halaman PDFium Component untuk Delphi