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
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
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
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