PDFium Component detecteert tabellen op een PDF-pagina en geeft ze terug als een raster van cellen met rij- en kolomspans, headerrijen en een betrouwbaarheidswaarde, via ExtractTables voor één pagina en ExtractDocumentTables voor een heel document. Elke tabel wordt met één aanroep omgezet naar CSV of JSON, en tabellen die doorlopen over een paginabreek kunnen worden gekoppeld tot een vervolgketen
PDF heeft geen tabelobject. Een tabel in een PDF is een verzameling tekstruns die zo gepositioneerd is dat een mens ze als een raster leest, soms met lijnen eromheen getekend en vaak zonder. Het raster herstellen betekent een intentie reconstrueren die het bestand nooit heeft vastgelegd, wat de reden is waarom elke extractietool net iets andere resultaten oplevert en waarom een tool die u zijn betrouwbaarheid vertelt, nuttiger is dan een die dat niet doet
Twee detectiemodi voor twee soorten tabellen
Detectie op basis van lijnen gebruikt de getekende lijnen. Elk gestreken padsegment wordt via de matrix van het pagina-object omgezet naar paginacoördinaten, horizontale en verticale lijnen worden gesneden, en de snijpunten vormen samenhangende componenten. Elke component wordt zijn eigen gesorteerde raster van X- en Y-posities, wat voorkomt dat twee afzonderlijke tabellen op dezelfde pagina worden samengevoegd tot één onzinnig raster
Witruimtedetectie behandelt tabellen die met uitlijning zijn getekend in plaats van met lijnen. Woordkaders worden gegroepeerd in visuele rijen, hiaten binnen een rij splitsen die op in kandidaat-kolommen, en een tabel wordt alleen geaccepteerd wanneer ten minste MinRows rijen ten minste MinColumns links uitgelijnde ankers herhalen binnen AlignmentTolerance. De rijhiaatfactor staat standaard op 3, wat de ongeveer 30-punts baseline-afstand dekt die typisch is voor tekst van 12 punt, zonder dat één regel met meerdere tekstruns zich kan voordoen als een 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; // gebaseerd op 1
Options := TPdfTableExtractionOptions.Default;
Options.DetectRuledTables := True;
Options.DetectWhitespaceTables := True;
Options.MinConfidence := 0.6; // standaard 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;
Hoe worden samengevoegde cellen hersteld?
Dit is het onderdeel dat naïeve extractors fout doen. Een samengevoegde cel kan niet alleen uit het globale raster worden herkend, omdat het raster is afgeleid van alle lijnen op de pagina en een samengevoegde regio simpelweg de interne lijn mist die haar zou hebben gescheiden
De regel die hier wordt gebruikt is lokaal: twee aangrenzende basiscellen worden samengevoegd wanneer geen grenslijn het interval ertussen dekt. Union-find voegt ze samen, de resulterende rechthoekige componenten worden RowSpan- en ColumnSpan-waarden, en tekst wordt op basis van zijn middelpunt toegewezen aan een basiscel en volgt vervolgens die cel naar haar samenvoegingswortel. Dit op deze manier doen houdt de kosten ook lineair in woorden plus cellen, in plaats van de kwadratische scan die u krijgt door elk woord tegen elke cel te testen
Het praktische effect is dat een financiële tabel met een samengevoegde "Totaal"-header over drie kolommen naar voren komt als één cel met span drie, in plaats van één gevulde cel en twee mysterieuze lege cellen
Doorloop over pagina's heen
Lange tabellen breken over pagina's heen, en elk paginafragment als een onafhankelijke tabel behandelen dwingt de aanroeper ze aan elkaar te naaien. ExtractDocumentTables kan ze in plaats daarvan koppelen, maar alleen onder strikte voorwaarden: het fragment moet de onderste tabel op de eerdere pagina zijn, de volgende moet de bovenste tabel op de daaropvolgende pagina zijn, de paginanummers moeten aangrenzend zijn, en de kolomgrenzen moeten overeenkomen
Alle vier de voorwaarden samen voorkomen de voor de hand liggende fout, namelijk elke tabel met vier kolommen in een document aaneenschakelen tot één denkbeeldige megatabel omdat ze toevallig hetzelfde aantal kolommen delen. Wanneer de voorwaarden gelden, delen de tabellen een vervolggroep-identifier en dragen ze vervolgmetadata; wanneer dat niet zo is, krijgt u afzonderlijke tabellen en kunt u zelf beslissen
Extractie op documentniveau deelt de budgetten MaxCells en MaxTables over pagina's in plaats van ze per pagina te resetten, en herstelt de actieve pagina in een finally-blok, zodat een extractierun in een viewer de gebruiker laat kijken naar de pagina waar hij was
Exporteren zonder de data te beschadigen
Beide exporters zijn bewust in hun escaping. CSV zet velden altijd tussen aanhalingstekens en verdubbelt interne aanhalingstekens, wat de klassieke fout voorkomt waarbij een cel met een komma stilzwijgend twee kolommen wordt. Bij samengevoegde cellen wordt de inhoud alleen bij het linkerboven-anker uitgevoerd, zodat een CSV-rondgang een overspannende header niet dupliceert over de kolommen die ze dekt
JSON behoudt Unicode in plaats van het naar ASCII te escapen, escapet controletekens, en bevat de metadata die een afnemer nodig heeft om de kwaliteit te beoordelen: detectiemodus, betrouwbaarheid, grenzen, spanwaarden, headervlaggen en vervolginformatie. Als u geëxtraheerde tabellen doorvoert naar een downstream-systeem, geef dan de voorkeur aan JSON, want een CSV-rij kan u niet vertellen dat de tabel waar ze vandaan komt 0,51 betrouwbaarheid scoorde:
// Documentbrede extractie, waarbij alleen tabellen worden bewaard die het vertrouwen waard zijn
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;
Afstemmen, en weten wanneer te stoppen
Drie instellingen zijn belangrijker dan de rest. MinConfidence is de kwaliteitspoort, en 0,5 is bewust permissief; verhoog deze voor geautomatiseerde inname en verlaag deze voor een review-UI waarin een mens elk resultaat bevestigt. MinColumnGap bepaalt wat telt als een kolomgrens in witruimtemodus, en strak gezette tabellen in dichte rapporten hebben deze mogelijk verlaagd nodig ten opzichte van de standaard van 12 punt. MaxRowGapFactor bepaalt wanneer verticale afstand een tabel beëindigt, wat van belang is voor tabellen met af en toe een lege rij
Wees eerlijk over de grenzen. Tabellen met lijnen worden betrouwbaar geëxtraheerd. Netjes uitgelijnde witruimtetabellen worden goed geëxtraheerd. Tabellen met gedraaide tekst, geneste tabellen, of cellen waarvan de inhoud doorloopt naar wat eruitziet als nog een rij, hebben altijd review nodig, ongeacht hoe de parameters zijn ingesteld. Voor die gevallen geeft het gestructureerde-tekstmodel u het ruwe materiaal om een domeinspecifieke lezer te bouwen, beschreven in gestructureerde tekstblokken en leesvolgorde
Eén nuttige combinatie: wanneer een gescand document helemaal geen tekst heeft, heeft tabeldetectie niets om mee te werken totdat er een tekstlaag bestaat. Voeg er eerst één toe, zoals beschreven in het toevoegen van een doorzoekbare tekstlaag aan gescande PDF's, en extraheer dan pas. De woordkaders die een OCR-provider teruggeeft, zijn precies de input die witruimtedetectie nodig heeft
Tabelextractie, gestructureerde tekst en reflow lezen allemaal uit hetzelfde paginamodel in Delphi, C++Builder en Lazarus; de volledige API wordt beschreven op de PDFium Component voor Delphi-pagina