PDFium Component PDF puslapyje aptinka lenteles ir grąžina jas kaip langelių tinklelį su eilučių ir stulpelių apimtimis (spans), antraštės eilutėmis ir pasitikėjimo verte, per ExtractTables vienam puslapiui ir ExtractDocumentTables visam dokumentui. Kiekviena lentelė vienu iškvietimu konvertuojama į CSV arba JSON, o lentelės, tęsiamos per puslapio lūžį, gali būti susietos į tęsinio grandinę
PDF neturi lentelės objekto. PDF faile lentelė yra teksto atkarpų (text runs) rinkinys, pozicionuotas taip, kad žmogus jas skaitytų kaip tinklelį, kartais su aplink nubrėžtomis linijomis, o dažnai be jų. Tinklelio atkūrimas reiškia ketinimo, kurio failas niekada neužfiksavo, atstatymą, todėl kiekvienas ištraukimo įrankis pateikia šiek tiek skirtingus rezultatus, ir todėl įrankis, praneša savo pasitikėjimo laipsnį, yra naudingesnis už tą, kuris to nedaro
Du aptikimo režimai dviem lentelių tipams
Linijomis pagrįstas aptikimas naudoja nubrėžtas linijas. Kiekvienas kontūro segmentas per puslapio objekto matricą transformuojamas į puslapio koordinates, horizontalios ir vertikalios linijos sankirtos, o sankirtos sudaro susietus komponentus. Kiekvienas komponentas tampa savo rūšiuotu X ir Y pozicijų tinkleliu, ir būtent tai neleidžia dviem atskiroms to paties puslapio lentelėms susilieti į vieną beprasmį tinklelį
Tarpais pagrįstas aptikimas apdoroja lenteles, nubraižytas lygiavimu, o ne linijomis. Žodžių dėžutės (word boxes) grupuojamos į vizualias eilutes, tarpai eilutės viduje ją padalija į kandidatinius stulpelius, o lentelė priimama tik tada, kai bent MinRows eilučių pakartoja bent MinColumns kairėje lygiuotus inkarus, esančius AlignmentTolerance ribose. Eilutės tarpo koeficientas pagal numatymą yra 3, ir tai apima maždaug 30 taškų bazinės linijos tarpą, būdingą 12 taškų dydžio tekstui, neleidžiant vienai eilutei, turinčiai kelias teksto atkarpas, apsimesti lentele
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; // numeracija nuo 1
Options := TPdfTableExtractionOptions.Default;
Options.DetectRuledTables := True;
Options.DetectWhitespaceTables := True;
Options.MinConfidence := 0.6; // numatytoji reikšmė yra 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;
Kaip atkuriami sujungti langeliai?
Tai dalis, kurioje naivūs ištraukimo įrankiai klysta. Sujungto langelio negalima nustatyti vien iš bendro tinklelio, nes tinklelis kildinamas iš visų puslapio linijų, o sujungtai sričiai tiesiog trūksta vidinės linijos, kuri ją būtų atskyrusi
Čia naudojama taisyklė yra lokali: du gretimi baziniai langeliai sujungiami, kai jokia ribinė linija neapima intervalo tarp jų. Union-find juos sujungia, gauti stačiakampiai komponentai tampa RowSpan ir ColumnSpan reikšmėmis, o tekstas priskiriamas baziniam langeliui pagal jo centro tašką ir tada seka tą langelį iki jo sujungimo šaknies. Tai atliekant tokiu būdu, sąnaudos taip pat išlieka tiesinės žodžių plius langelių atžvilgiu, o ne kvadratinės, kaip būtų testuojant kiekvieną žodį prieš kiekvieną langelį
Praktinis poveikis yra tas, kad finansinė lentelė su sujungta „Total“ antrašte, apimančia tris stulpelius, gaunama kaip vienas langelis, apimties trys, o ne kaip vienas užpildytas langelis ir du paslaptingi tušti
Tęsinys per puslapius
Ilgos lentelės lūžta per puslapius, o kiekvieno puslapio fragmento traktavimas kaip nepriklausomos lentelės verčia iškvietėją juos susiūti. ExtractDocumentTables vietoj to gali juos susieti, bet tik esant griežtoms sąlygoms: fragmentas turi būti žemiausia ankstesnio puslapio lentelė, kita turi būti aukščiausia sekančio puslapio lentelė, puslapių numeriai turi būti gretimi, o stulpelių ribos turi sutapti
Būtent visos keturios sąlygos kartu apsaugo nuo akivaizdžios klaidos — sugrandinti kiekvieną keturių stulpelių lentelę dokumente į vieną įsivaizduojamą mega-lentelę vien todėl, kad joms atsitiktinai sutampa stulpelių skaičius. Kai sąlygos tenkinamos, lentelės dalijasi tęsinio grupės identifikatoriumi ir neša tęsinio metaduomenis; kai netenkinamos, gaunate atskiras lenteles ir galite spręsti patys
Dokumento lygio ištraukimas MaxCells ir MaxTables biudžetus dalijasi tarp puslapių, o ne juos atstato kiekvienam puslapiui, ir aktyvų puslapį atkuria finally bloke, todėl ištraukimas, paleistas žiūryklėje, vartotoją palieka žiūrint puslapį, kuriame jis buvo
Eksportavimas nesugadinant duomenų
Abu eksportuotojai apgalvotai tvarko escape simbolius. CSV visada apgaubia laukus kabutėmis ir dubliuoja vidines kabutes, taip išvengiant klasikinės klaidos, kai langelis su kableliu tyliai virsta dviem stulpeliais. Sujungtiems langeliams turinys išvedamas tik viršutiniame kairiajame inkare, todėl CSV apvažiavimas (round-trip) nedubliuoja apimties antraštės per stulpelius, kuriuos ji apima
JSON išsaugo Unicode, o ne konvertuoja jį į ASCII, escape'ina valdymo simbolius ir įtraukia metaduomenis, kurių vartotojui reikia kokybei įvertinti: aptikimo režimą, pasitikėjimą, ribas, apimties reikšmes, antraštės žymes ir tęsinio informaciją. Jei ištrauktas lenteles perduodate tolesnei sistemai, rinkitės JSON, nes CSV eilutė negali pasakyti, kad lentelė, iš kurios ji atėjo, gavo 0,51 pasitikėjimo balą:
// Viso dokumento ištraukimas, paliekant tik lenteles, kuriomis verta pasitikėti
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;
Derinimas ir žinojimas, kada sustoti
Trys nustatymai svarbesni už likusius. MinConfidence yra kokybės vartai, ir 0,5 yra sąmoningai liberalus; padidinkite jį automatiniam įkėlimui ir sumažinkite peržiūros sąsajai, kur žmogus patvirtina kiekvieną rezultatą. MinColumnGap nusprendžia, kas laikoma stulpelio riba tarpų režime, o tankiai išdėstytoms lentelėms glaustose ataskaitose gali prireikti jį sumažinti nuo numatytųjų 12 taškų. MaxRowGapFactor nusprendžia, kada vertikalus atstumas baigia lentelę, ir tai svarbu lentelėms su pavienėmis tuščiomis eilutėmis
Būkite sąžiningi dėl ribų. Linijomis pagrįstos lentelės ištraukiamos patikimai. Švariai lygiuotos tarpais pagrįstos lentelės ištraukiamos gerai. Lentelėms su pasuktu tekstu, įdėtomis lentelėmis arba langeliais, kurių turinys persikelia į kitą eilutę primenančią formą, prireiks peržiūros nepriklausomai nuo to, kaip nustatyti parametrai. Joms struktūrizuoto teksto modelis suteikia žaliavą specifinio domeno skaitytuvui sukurti, aprašytą straipsnyje struktūrizuoto teksto blokai ir skaitymo tvarka
Vienas naudingas derinys: kai nuskaitytas dokumentas apskritai neturi teksto, lentelių aptikimui neturi su kuo dirbti, kol neatsiranda teksto sluoksnis. Pirmiausia jį pridėkite, kaip aprašyta straipsnyje ieškomo teksto sluoksnio pridėjimas prie nuskaitytų PDF failų, tada ištraukite. OCR tiekėjo grąžinamos žodžių dėžutės yra būtent tokia įvestis, kurios reikia tarpais pagrįstam aptikimui
Lentelių ištraukimas, struktūrizuotas tekstas ir perkomponavimas (reflow) visi skaito iš to paties puslapio modelio Delphi, C++Builder ir Lazarus aplinkoms; pilnas API aprašytas PDFium Component for Delphi puslapyje