PDFium Component detekuje tabulky na stránce PDF a vrací je jako mřížku buněk s rozpětím řádků a sloupců, záhlavními řádky a hodnotou spolehlivosti, přes ExtractTables pro jednu stránku a ExtractDocumentTables pro celý dokument. Každá tabulka se jedním voláním převede na CSV nebo JSON a tabulky pokračující přes zalomení stránky lze propojit do řetězce pokračování
PDF nemá objekt tabulky. Tabulka v PDF je sada textových běhů umístěných tak, aby je člověk vnímal jako mřížku, někdy s liniemi nakreslenými kolem nich a často bez nich. Rekonstrukce mřížky znamená znovu vytvořit záměr, který soubor nikdy nezaznamenal, což je důvod, proč každý extrakční nástroj vyprodukuje mírně odlišné výsledky a proč je nástroj, který vám řekne svou spolehlivost, užitečnější než ten, který to neudělá
Dva režimy detekce pro dva druhy tabulek
Detekce podle linek používá nakreslené čáry. Každý natažený segment cesty se transformuje do souřadnic stránky přes matici objektu stránky, vodorovné a svislé čáry se protnou a průsečíky vytvoří spojené komponenty. Každá komponenta se stane vlastní seřazenou mřížkou pozic X a Y, což udržuje dvě samostatné tabulky na stejné stránce od sloučení do jedné nesmyslné mřížky
Detekce podle mezer zpracovává tabulky nakreslené zarovnáním místo liniemi. Rámečky slov se seskupí do vizuálních řádků, mezery uvnitř řádku ho rozdělí na kandidátní sloupce a tabulka se přijme jen tehdy, když se alespoň MinRows řádků opakuje alespoň s MinColumns zleva zarovnanými kotvami v rámci AlignmentTolerance. Faktor mezery mezi řádky má výchozí hodnotu 3, což pokrývá zhruba 30bodové rozestupy základní čáry typické pro 12bodový text, aniž by dovolil jednomu řádku s několika textovými běhy vydávat se za tabulku
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; // číslováno od 1
Options := TPdfTableExtractionOptions.Default;
Options.DetectRuledTables := True;
Options.DetectWhitespaceTables := True;
Options.MinConfidence := 0.6; // výchozí je 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;
Jak se rozpoznávají sloučené buňky?
Toto je část, kterou naivní extraktory dělají špatně. Sloučenou buňku nelze identifikovat jen z globální mřížky, protože mřížka je odvozena ze všech čar na stránce a sloučená oblast prostě postrádá vnitřní čáru, která by je oddělila
Zde použité pravidlo je lokální: dvě sousední základní buňky se sloučí, když žádná ohraničující čára nepokrývá interval mezi nimi. Union-find je spojí, výsledné obdélníkové komponenty se stanou hodnotami RowSpan a ColumnSpan a text se přiřadí základní buňce podle jejího středu a pak sleduje tuto buňku až ke kořeni sloučení. Tento postup také udržuje náklady lineární vzhledem ke slovům plus buňkám, místo kvadratického skenu, který dostanete testováním každého slova proti každé buňce
Praktickým efektem je, že finanční tabulka se sloučeným záhlavím "Celkem" napříč třemi sloupci vyjde jako jedna buňka s rozpětím tři, ne jako jedna vyplněná buňka a dvě záhadně prázdné
Pokračování přes stránky
Dlouhé tabulky se zalamují přes stránky a zacházení s fragmentem každé stránky jako se samostatnou tabulkou nutí volajícího je sešívat. ExtractDocumentTables je místo toho dokáže propojit, ale jen za přísných podmínek: fragment musí být nejnižší tabulka na dřívější stránce, další musí být nejvyšší tabulka na následující stránce, čísla stránek musí být sousední a hranice sloupců se musí shodovat
Všechny čtyři podmínky dohromady zabraňují zjevné chybě, tedy zřetězení každé čtyřsloupcové tabulky v dokumentu do jedné imaginární megatabulky jen proto, že mají shodou okolností stejný počet sloupců. Když podmínky platí, tabulky sdílejí identifikátor skupiny pokračování a nesou metadata pokračování; když neplatí, dostanete samostatné tabulky a rozhodnete se sami
Extrakce na úrovni dokumentu sdílí rozpočty MaxCells a MaxTables napříč stránkami místo jejich resetování na stránku a obnoví aktivní stránku v bloku finally, takže extrakce spuštěná v prohlížeči zanechá uživatele koukajícího na stránku, na které byl
Export bez poškození dat
Oba exportéry jsou v otázce escapování promyšlené. CSV vždy uzavře pole do uvozovek a zdvojí vnitřní uvozovky, čímž se vyhne klasickému selhání, kdy se buňka s čárkou tiše promění ve dva sloupce. U sloučených buněk se obsah emituje jen na kotvě vlevo nahoře, takže zpáteční cyklus CSV nezdvojuje záhlaví přesahující přes sloupce, které pokrývá
JSON zachovává Unicode místo jeho escapování do ASCII, escapuje řídicí znaky a obsahuje metadata, která spotřebitel potřebuje k posouzení kvality: režim detekce, spolehlivost, hranice, hodnoty rozpětí, příznaky záhlaví a informace o pokračování. Pokud extrahované tabulky posíláte do navazujícího systému, upřednostněte JSON, protože řádek CSV vám nemůže říct, že tabulka, ze které pochází, měla spolehlivost 0,51:
// Extrakce napříč celým dokumentem, přičemž se ponechají jen důvěryhodné tabulky
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;
Ladění, a kdy přestat
Tři nastavení mají větší význam než zbytek. MinConfidence je brána kvality a 0,5 je záměrně shovívavá; zvyšte ji pro automatizované zpracování a snižte pro UI pro kontrolu, kde člověk potvrzuje každý výsledek. MinColumnGap rozhoduje, co se počítá jako hranice sloupce v režimu mezer, a hustě sázené tabulky v nabitých reportech mohou potřebovat snížit ji z výchozích 12 bodů. MaxRowGapFactor rozhoduje, kdy svislá vzdálenost ukončí tabulku, což má význam pro tabulky s občasnými prázdnými řádky
Buďte upřímní ohledně limitů. Tabulky s linkami se extrahují spolehlivě. Čistě zarovnané tabulky s mezerami se extrahují dobře. Tabulky s otočeným textem, vnořené tabulky nebo buňky, jejichž obsah se zalamuje do podoby, která vypadá jako další řádek, budou potřebovat kontrolu bez ohledu na to, jak nastavíte parametry. Pro ně vám model strukturovaného textu dává surový materiál pro stavbu doménově specifického čtenáře, popsaný v článku bloky strukturovaného textu a pořadí čtení
Jedno užitečné spárování: když skenovaný dokument nemá vůbec žádný text, detekce tabulek nemá s čím pracovat, dokud neexistuje textová vrstva. Přidejte ji nejdřív, jak popisuje článek přidání prohledávatelné textové vrstvy do skenovaných PDF, a pak extrahujte. Rámečky slov, které vrátí poskytovatel OCR, jsou přesně ten vstup, který detekce podle mezer potřebuje
Extrakce tabulek, strukturovaný text a reflow čtou v Delphi, C++Builder a Lazarus ze stejného modelu stránky; úplné API popisuje stránka PDFium Component pro Delphi