HotPDF gendanner tabeller fra en eksisterende PDF gennem ExtractLoadedTypedTables, en Delphi-API, der sammenfletter de række-fragmenter, layout-passet producerer, bygger ét kanonisk kolonnenet pr. tabel, fortsætter tabellen over et sideskift, når geometrien understøtter det, og returnerer hver celle som en typed value med sideproveniens, kolonnspan og bounds. ExportLoadedTypedTables skriver det samme resultat direkte til CSV eller JSON. Scenariet, der gør dette værd at bygge, er kedeligt og ekstremt almindeligt. Et fakturaregister på fyrre sider, én tabel logisk set, printet med headeren gentaget øverst på hver side. Kør en naiv reading-order-pass over det, og du får fyrre tabeller, 39 overflødige headerrækker og en valutakolonne, der flytter én plads til venstre på hver række, hvor den midterste celle tilfældigvis var tom. At rydde op i det downstream i den kaldende applikation er dér, dokumentimportprojekter dør
Hvorfor giver en PDF-side dig fragmenter i stedet for en tabel?
Fordi en PDF-side slet ikke bærer tabelsemantik, medmindre dokumentet er tagget. Content-streamen indeholder text-showing-operatorer og positioneringsmatricer (ISO 32000-1 §9.4.3) og intet mere; den indrammede boks, du ser på skærmen, er separat path-painting, som ingen extractor er forpligtet til at koble sammen med teksten. Structure-elementtyperne Table, TR, TH og TD findes kun i den logiske strukturhierarki i en tagget PDF (ISO 32000-1 §14.8.4), og langt de fleste forretningsdokumenter i omløb er ikke tagget. Alt det, der beskrives nedenfor, er geometrisk gendannelse og ikke parsing, og det er værd at sige højt, før nogen bygger en afstemningsrapport oven på det
HotPDF kører derfor først en semantisk layoutanalyse over de ekstraherede glyffer, det samme pass, der ligger bag strukturordnet tekstekstraktion fra en indlæst PDF og de strukturerede HTML- og XML-eksporter. Det pass grupperer baselines i runs, hvis celler flugter lodret, og fortsætter kun et run, så længe på hinanden følgende rækker har samme antal celler. For en layoutmotor er reglen korrekt og billig. For en caller har den den forkerte form: En enkelt række med en tom indre celle deler én visuel tabel i to source tables. Det typede tabellag ligger præcis oven på dette pass for at sætte delene sammen igen
Kanoniske kolonnenet og ColumnTolerance-knappen
ExtractLoadedTypedTables sammenfletter fragments på samme side, før den gør noget andet, og den sammenfletter efter kolonnegeometri og ikke efter rækketekst. To tilstødende source tables på én side forbindes, når begge har mindst to kolonner, når den lodrette afstand mellem den første tabels sidste række og den anden tabels første række bliver inden for tolerancebåndet, og når deres kolonnestartpositioner flugter. Kolonnestartpositioner inden for ColumnTolerance af hinanden kollapser til én kanonisk kolonne og midles, efterhånden som de flettes. Standardtolerancen er 12 user-space-enheder, hvilket passer til almindelig forretningstypografi og bør hæves for bredt tracked eller dybt indrykkede layouts
Det, der sker med en række, der mangler en indre værdi, er den del, der betyder noget. HotPDF snapper hver celle til den nærmeste kanoniske kolonnestart og sætter derefter ColumnSpan til afstanden fra denne kolonne til den næste besatte, i stedet for at flytte de resterende celler til venstre. En række med tre celler i et net med fem kolonner beholder sine værdier under de rigtige overskrifter og registrerer præcis, hvor hullerne er. Det er forskellen på en tabel, du kan afstemme, og en, der lydløst tilskriver penge forkert
var
Pdf: THotPDF;
Options: THPDFTypedTableExtractionOptions;
Tables: THPDFTypedTables;
Info: THPDFTypedTableExtractionInfo;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('register.pdf', '') <= 0 then
Exit;
Options := THPDFTypedTableExtractionOptions.Default;
Options.ColumnTolerance := 12; // user-space-enheder
Options.MinimumTableConfidence := 0.55; // under dette droppes tabeller
Options.DateOrder := ttdoDMY; // 03/04/2026 er 3. april
Options.DecimalSeparator := ',';
Options.ThousandsSeparator := '.';
if Pdf.ExtractLoadedTypedTables([0, 1, 2, 3], Options, Tables, Info) then
// Info.TableCount kontra Info.SourceTableCount viser, hvor meget der blev flettet
ProcessTables(Tables)
else if Info.Status = ttesBudgetExceeded then
Log(string(Info.Diagnostic));
finally
Pdf.Free;
end;
end;
Hvad garanterer cross-page-merging faktisk?
Det garanterer konservatisme, med vilje. HotPDF sammenføjer kun to tabeller over en sidegrænse, når MergeAcrossPages er aktiveret, når den anden tabel starter præcis på det sideindeks, der følger efter den første tabels slutning, når begge har mindst to kolonner, og når mindst to kanoniske kolonnestarter flugter inden for ColumnTolerance. Betingelsen om fortløbende sider er den bærende. Kaldere sender PageIndices som et open array i den rækkefølge, de ønsker, og uden kontrollen kunne en anmodning om side 3, 9 og 14 svejse tre uvedkommende tabeller sammen til ét helt plausibelt resultat. Prisen er, at en ægte fortsættelse, der springer en side over, et indskudt appendiks eller en duplex-scanning med en blank bagside, kommer tilbage som to tabeller, og ingen option løsner reglen. At sætte dem sammen igen er kun en policy-beslutning, den kaldende applikation kan træffe, så APIet eksponerer FirstPageIndex, LastPageIndex, SourceTableCount og et PageIndex pr. række og lader beslutningen blive, hvor den hører hjemme
Gentagne headers mærkes, de slettes aldrig
ExtractLoadedTypedTables fjerner aldrig en gentaget headerrække fra resultatet. Når et cross-page-merge finder, at den indgående tabel starter med headertekst, der er identisk med den akkumulerede tabel efter trimning og case folding, markerer den disse rækker med IsHeader og IsRepeatedHeader og tilføjer dem alligevel i kildeorden. Sletning er et tabsgivende og irreversibelt valg, og forskellige forbrugere ønsker forskellige svar: En CSV-import vil have gentagelserne væk, et audit trail vil have dem med deres sidetal, og et diff-værktøj vil have kildeordenen bevaret byte for byte. Derfor rapporterer biblioteket og lader kalderen beslutte
var
T, R, C: Integer;
Row: THPDFTypedTableRow;
Total: Double;
begin
Total := 0;
for T := 0 to High(Tables) do
for R := 0 to High(Tables[T].Rows) do
begin
Row := Tables[T].Rows[R];
if Row.IsRepeatedHeader then
Continue; // behold kun den første headerblok
for C := 0 to High(Row.Cells) do
if Row.Cells[C].ValueKind = ttvkCurrency then
Total := Total + Row.Cells[C].NumberValue;
end;
end;
Typed values og de separatorer, du skal levere
Typeinferens kører i en fast rækkefølge, der løser tvetydighederne i den eneste fornuftige retning: først boolean, derefter dato, procent, valuta og almindeligt tal, mens alt, der ikke matcher, forbliver en string. Rækkefølgen er det, der forhindrer, at 2026 i en datokolonne afgøres af en talparser, før datoparseren ser det. Valuta genkendes fra et indledende $, £, ¥ eller € eller fra en trebogstavs ISO 4217-kode efterfulgt af et mellemrum, og koden bevares i CurrencyCode. Det afgørende er, at HotPDF ikke gætter din locale. DecimalSeparator, ThousandsSeparator og DateOrder kommer fra options, fordi 1.234 enten er ét tal eller et tusind to hundrede og fireogtredive afhængigt af en oplysning, PDF-filen ikke indeholder. Den rå Unicode-Text bevares i hver celle ved siden af den typede værdi, så et forkert gæt altid kan gendannes uden en ny ekstraktionspassage
var
Stream: TFileStream;
Info: THPDFTypedTableExtractionInfo;
begin
Stream := TFileStream.Create('tables.json', fmCreate);
try
if not Pdf.ExportLoadedTypedTables([0, 1, 2], ttefJSON,
Stream, Options, Info) then
case Info.Status of
ttesInvalidOptions: ReportBadConfiguration;
ttesBudgetExceeded: ReportOversizedDocument;
ttesCancelled: ReportUserCancelled;
ttesWriteFailed: ReportDestinationProblem;
else
ReportExtractionFailure;
end;
finally
Stream.Free;
end;
end;
De to eksportformater besvarer forskellige spørgsmål og er med vilje ikke ækvivalente. CSV skriver fortsættelseskolonnerne i en flettet span som tomme felter, hvilket er det, et regneark eller en bulk-loader forventer. JSON beholder alt, som ekstraktionen vidste: den typede værdi under sin egen kind, columnSpan, confidence pr. celle og række, cellegrænserne samt side- og source-table-proveniens. Begge formater samler hele dokumentet i en afgrænset in-memory-buffer og publicerer først derefter til din destinationsstream, idet de gendanner de oprindelige bytes, længde og position, hvis skrivningen fejler undervejs, så en fejlet eksport aldrig efterlader en halvskrevet fil. Budgetter for sider, glyffer pr. side, tabeller, rækker, celler, tegn og outputbytes bogføres alle separat, og rækker tælles før allokering, fordi et per-række-SetLength degenererer til kvadratisk kopiering længe før standardloftet på en million rækker
Hvor giver geometrisk tabelgendannelse op?
Det er mere nyttigt at være eksplicit om fejltilfældene end at have en featureliste, fordi hvert af disse steder kræver sin egen politik hos kalderen og ikke en bedre optionværdi
- Lodrette merges gendannes ikke. HotPDF rapporterer
ColumnSpanfor vandrette spans og laderRowSpanstå på 1, så en celle, der spænder over tre rækker i den printede tabel, kommer frem som én celle plus to huller - Headerdetektion er datadrevet og ikke visuel. Headerblokken er det run af rækker før den første række, der indeholder en typed value, som ikke er en string, så en tabel, hvis body udelukkende er tekst, rapporterer
HeaderRowCountsom nul, uanset hvordan den er stylet - Tabeller under
MinimumTableConfidencedroppes fra resultatet uden en fejl. SammenlignInfo.TableCountmedInfo.SourceTableCount, når du har brug for at vide, at noget blev kasseret - Et run skal have mindst to rækker og mindst to kolonner, før layout-passet overhovedet kalder det en tabel, så en pseudo-tabel på én linje eller et layout med to kolonner af lang prosa bliver korrekt og uhjælpsomt ikke en tabel
- Scannede sider indeholder ingen tekstoperatorer, så der er intet at gendanne geometrisk, før der findes et OCR-tekstlag på siden
Hvis dine PDF-filer kommer fra din egen rapporteringsstack, er den billigste løsning på alt dette upstream: Udsend taggede tabeller, eller behold kildedataene, og behandl ekstraktion som en fallback for dokumenter, du ikke selv producerede. For alt andet er pipelinen værd at lære i denne rækkefølge, eftersom hvert lag bygger på det underliggende: Start med almindelig tekstekstraktion fra en indlæst PDF, gå videre til den typede tabel-API, når geometrien skal bevares, og se på rendering af en datatabel til en ny PDF, når du er på generatorsiden og selv kan bestemme, hvor gendanneligt outputtet bliver
ExtractLoadedTypedTables og ExportLoadedTypedTables leveres som en del af den native HotPDF Delphi PDF Component til Delphi og C++Builder uden ekstern DLL og uden runtime-afhængighed; produktsiden indeholder den fulde reference for options, statusser og records til den typede tabel-API