PDFium Component oppdager tabeller på en PDF-side og returnerer dem som et rutenett av celler med rad- og kolonnespenn, topptekstrader og en tillitsverdi, gjennom ExtractTables for én side og ExtractDocumentTables for et helt dokument. Hver tabell konverteres til CSV eller JSON med ett kall, og tabeller som fortsetter over et sideskift kan lenkes inn i en fortsettelseskjede
PDF har ikke noe tabellobjekt. En tabell i en PDF er et sett med tekst-runninger posisjonert slik at et menneske leser dem som et rutenett, noen ganger med linjer tegnet rundt dem og ofte uten. Å gjenopprette rutenettet betyr å rekonstruere en hensikt filen aldri registrerte, som er grunnen til at hvert uttrekksverktøy produserer litt forskjellige resultater, og hvorfor et verktøy som forteller deg tilliten sin er mer nyttig enn ett som ikke gjør det
To deteksjonsmodi for to typer tabeller
Linjert deteksjon bruker de tegnede linjene. Hvert strøkne banesegment transformeres til sidekoordinater gjennom sideobjektets matrise, horisontale og vertikale linjer skjæres, og skjæringspunktene danner sammenhengende komponenter. Hver komponent blir sitt eget sorterte rutenett av X- og Y-posisjoner, som er det som hindrer to separate tabeller på samme side fra å bli slått sammen til ett meningsløst rutenett
Mellomrom-deteksjon håndterer tabeller tegnet med justering i stedet for linjer. Ordbokser grupperes inn i visuelle rader, mellomrom innenfor en rad splitter den inn i kandidatkolonner, og en tabell godtas bare når minst MinRows rader gjentar minst MinColumns venstrejusterte ankere innenfor AlignmentTolerance. Radmellomrom-faktoren er som standard 3, som dekker den omtrent 30-punkts grunnlinjeavstanden typisk for 12-punkts tekst uten å la en enkelt linje med flere tekst-runninger utgi seg for en tabell
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;
Hvordan gjenopprettes sammenslåtte celler?
Dette er delen naive uttrekkere gjør feil. En sammenslått celle kan ikke identifiseres fra det globale rutenettet alene, fordi rutenettet er avledet fra alle linjene på siden, og et sammenslått område mangler rett og slett den interne linjen som ville ha skilt det
Regelen brukt her er lokal: to tilstøtende basisceller slås sammen når ingen grenselinje dekker intervallet mellom dem. Union-find slår dem sammen, de resulterende rektangulære komponentene blir RowSpan- og ColumnSpan-verdier, og tekst tildeles en basiscelle etter dens senterpunkt og følger deretter den cellen til sin sammenslåingsrot. Å gjøre det på denne måten holder også kostnaden lineær i ord pluss celler, i stedet for den kvadratiske skanningen du får av å teste hvert ord mot hver celle
Den praktiske effekten er at en finansiell tabell med en sammenslått «Total»-header som spenner over tre kolonner, kommer ut med én celle med spenn tre, heller enn én utfylt celle og to mystisk tomme
Fortsettelse over sider
Lange tabeller brytes over sider, og å behandle hver sides fragment som en uavhengig tabell tvinger den kallende parten til å sy dem sammen. ExtractDocumentTables kan lenke dem i stedet, men bare under strenge betingelser: fragmentet må være den nederste tabellen på den tidligere siden, den neste må være den øverste tabellen på den følgende siden, sidetallene må være tilstøtende, og kolonnegrensene må matche
Alle fire betingelsene sammen er det som forhindrer den åpenbare feilen, som er å kjede hver fire-kolonners tabell i et dokument sammen til én innbilt mega-tabell fordi de tilfeldigvis deler et kolonneantall. Når betingelsene holder, deler tabellene en fortsettelsesgruppe-identifikator og bærer fortsettelsesmetadata; når de ikke gjør det, får du separate tabeller og kan bestemme selv
Uttrekk på dokumentnivå deler budsjettene MaxCells og MaxTables på tvers av sider heller enn å nullstille dem per side, og det gjenoppretter den aktive siden i en finally-blokk, slik at en uttrekkskjøring i en visning etterlater brukeren på siden de var på
Eksport uten å korrumpere dataene
Begge eksportørene er bevisste om escaping. CSV siterer alltid felt og dobler interne anførselstegn, som unngår den klassiske svikten der en celle som inneholder et komma stille blir to kolonner. For sammenslåtte celler gis innhold kun ut ved øverste-venstre-ankeret, slik at en CSV-rundtur ikke dupliserer en spennende header på tvers av kolonnene den dekker
JSON bevarer Unicode heller enn å escape det til ASCII, escaper kontrolltegn, og inkluderer metadataen en konsument trenger for å vurdere kvalitet: deteksjonsmodus, tillit, grenser, spennverdier, header-flagg og fortsettelsesinformasjon. Hvis du mater uttrukne tabeller inn i et nedstrøms system, foretrekk JSON, fordi en CSV-rad ikke kan fortelle deg at tabellen den kom fra scoret 0,51 i tillit:
// Dokumentomfattende uttrekk, som kun beholder tabeller verdt å stole på
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;
Finjustering, og å vite når man skal stoppe
Tre innstillinger betyr mer enn resten. MinConfidence er kvalitetsporten, og 0,5 er bevisst tillatende; øk den for automatisert inntak og senk den for et gjennomgangs-UI der et menneske bekrefter hvert resultat. MinColumnGap bestemmer hva som teller som en kolonnegrense i mellomrom-modus, og tett satte tabeller i tette rapporter kan trenge den redusert fra 12-punkts standarden. MaxRowGapFactor bestemmer når vertikal avstand avslutter en tabell, som betyr noe for tabeller med sporadiske tomme rader
Vær ærlig om grensene. Linjerte tabeller trekkes ut pålitelig. Rent justerte mellomrom-tabeller trekkes ut godt. Tabeller med rotert tekst, nestede tabeller, eller celler hvis innhold brytes inn i noe som ser ut som en annen rad, vil trenge gjennomgang uansett hvordan parameterne er satt. For disse gir den strukturerte tekstmodellen deg råmaterialet til å bygge en domenespesifikk leser, beskrevet i strukturerte tekstblokker og leserekkefølge
Én nyttig sammenkobling: når et skannet dokument ikke har noen tekst i det hele tatt, har tabelldeteksjon ingenting å jobbe med før et tekstlag eksisterer. Legg til ett først, som beskrevet i å legge til et søkbart tekstlag i skannede PDF-er, og trekk deretter ut. Ordboksene en OCR-leverandør returnerer er nøyaktig inndataen mellomrom-deteksjon trenger
Tabelluttrekk, strukturert tekst og omflyt leser alle fra den samme sidemodellen i Delphi, C++Builder og Lazarus; det fullstendige API-et er beskrevet på PDFium Component for Delphi-siden