HotPDF oporavlja tabele iz postojećeg PDF-a preko ExtractLoadedTypedTables, Delphi API-ja koji spaja fragmente redova nastale u layout prolazu, gradi jednu kanonsku mrežu kolona po tabeli, nastavlja tabelu preko preloma stranice kada geometrija to podržava i vraća svaku ćeliju kao tipizovanu vrednost sa poreklom stranice, rasponom kolona i granicama. ExportLoadedTypedTables upisuje isti rezultat direktno u CSV ili JSON. Situacija zbog koje vredi graditi ovo rešenje dosadna je i veoma česta. Registar faktura od četrdeset strana, logički jedna tabela, štampan sa ponovljenim zaglavljem na vrhu svake strane. Pokrenite naivan prolaz po redosledu čitanja i dobićete četrdeset tabela, trideset devet lažnih redova zaglavlja i kolonu valuta koja se pri svakom redu gde je srednja ćelija slučajno prazna pomera za jedno mesto ulevo. Čišćenje toga nizvodno, u pozivajućoj aplikaciji, mesto je gde projekti uvoza dokumenata odlaze da umru
Zašto vam PDF stranica daje fragmente umesto tabele?
Zato što PDF stranica uopšte nema semantiku tabele ako dokument nije tagovan. Content stream nosi operatore za prikaz teksta i matrice pozicioniranja (ISO 32000-1 §9.4.3) i ništa više; okvir koji vidite na ekranu nepovezano je crtanje putanjama koje nijedan ekstraktor nije dužan da poveže sa tekstom. Tipovi strukturnih elemenata Table, TR, TH i TD postoje samo u hijerarhiji logičke strukture tagovanog PDF-a (ISO 32000-1 §14.8.4), a ogromna većina poslovnih dokumenata u opticaju nije tagovana. Sve što sledi jeste geometrijski oporavak, a ne parsiranje, i to vredi reći naglas pre nego što neko nad time izgradi izveštaj za usklađivanje
HotPDF zato prvo izvršava semantičku analizu layout-a nad izdvojenim glifovima, isti prolaz koji podržava ekstrakciju teksta po redosledu strukture iz učitanog PDF-a i strukturisane HTML i XML izvoze. Taj prolaz grupiše baseline-e u nizove čije se ćelije vertikalno poravnavaju i nastavlja niz samo dok uzastopni redovi imaju isti broj ćelija. Za layout engine to pravilo je ispravno i jeftino. Za pozivaoca je pogrešnog oblika: jedan red sa praznom unutrašnjom ćelijom deli jednu vizuelnu tabelu na dve izvorne tabele. Tipizovani table layer postoji upravo da bi delove ponovo spojio
Kanonske mreže kolona i ColumnTolerance prekidač
ExtractLoadedTypedTables spaja fragmente iste stranice pre nego što uradi bilo šta drugo, i spaja ih prema geometriji kolona, a ne prema tekstu redova. Dve susedne izvorne tabele na jednoj stranici spajaju se kada obe imaju najmanje dve kolone, kada vertikalni razmak između poslednjeg reda prve i prvog reda druge ostaje unutar tolerancione trake i kada se njihove početne pozicije kolona poklapaju. Početci kolona unutar ColumnTolerance jedni od drugih sažimaju se u jednu kanonsku kolonu i pri spajanju se proseče. Podrazumevana tolerancija je 12 jedinica user space-a, što odgovara običnoj poslovnoj tipografiji, a traži povećanje za široko razvučene ili duboko uvučene layout-e
Šta se dešava sa redom kojem nedostaje unutrašnja vrednost jeste deo koji je važan. HotPDF snap-uje svaku ćeliju na najbliži kanonski početak kolone, a zatim postavlja ColumnSpan na udaljenost od te kolone do sledeće zauzete, umesto da preostale ćelije pomeri ulevo. Red sa tri ćelije u mreži od pet kolona zadržava vrednosti ispod pravih zaglavlja i tačno beleži gde se praznine nalaze. To je razlika između tabele koju možete da uskladite i tabele koja tiho pripisuje novac pogrešnoj stavci
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; // jedinice user space-a
Options.MinimumTableConfidence := 0.55; // ispod ovoga tabele se odbacuju
Options.DateOrder := ttdoDMY; // 03/04/2026 je 3. april
Options.DecimalSeparator := ',';
Options.ThousandsSeparator := '.';
if Pdf.ExtractLoadedTypedTables([0, 1, 2, 3], Options, Tables, Info) then
// Info.TableCount naspram Info.SourceTableCount pokazuje koliko je spojeno
ProcessTables(Tables)
else if Info.Status = ttesBudgetExceeded then
Log(string(Info.Diagnostic));
finally
Pdf.Free;
end;
end;
Šta spajanje kroz više strana zaista garantuje?
Garantuje konzervativnost i to namerno. HotPDF spaja dve tabele preko granice stranice samo kada je MergeAcrossPages uključen, kada druga tabela počinje tačno na indeksu stranice posle one na kojoj se prva završava, kada obe imaju najmanje dve kolone i kada se najmanje dva kanonska početka kolona poravnaju unutar ColumnTolerance. Uslov susednih stranica nosi najveću težinu. Pozivaoci prosleđuju PageIndices kao otvoreni niz u bilo kom redosledu, a bez te provere zahtev za stranice 3, 9 i 14 mogao bi da zavari tri nepovezane tabele u jedan rezultat koji izgleda sasvim uverljivo. Cena je da stvarni nastavak koji preskoči stranicu, umešani dodatak ili duplex sken sa praznom poleđinom dolazi kao dve tabele, bez opcije koja bi to olabavila. Ponovno spajanje takvih delova pitanje je politike koje samo pozivajuća aplikacija može da reši, pa API izlaže FirstPageIndex, LastPageIndex, SourceTableCount i PageIndex po redu, ostavljajući odluku tamo gde pripada
Ponovljena zaglavlja se označavaju, nikada ne brišu
ExtractLoadedTypedTables nikada ne uklanja ponovljeni red zaglavlja iz rezultata. Kada cross-page merge utvrdi da ulazna tabela počinje tekstom zaglavlja identičnim akumuliranoj tabeli, upoređenim nakon trimovanja i izjednačavanja case-a, označava te redove sa IsHeader i IsRepeatedHeader i ipak ih dodaje po redosledu izvora. Brisanje je gubitnički i nepovratan izbor, a različiti potrošači žele različite odgovore: CSV import želi da ponavljanja nestanu, audit trag želi da budu prisutna sa brojevima stranica, alat za diff želi da izvorni redosled bude očuvan bajt po bajt. Zato biblioteka prijavljuje, a pozivalac odlučuje
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; // zadrži samo prvi blok zaglavlja
for C := 0 to High(Row.Cells) do
if Row.Cells[C].ValueKind = ttvkCurrency then
Total := Total + Row.Cells[C].NumberValue;
end;
end;
Tipizovane vrednosti i separatori koje morate proslediti
Izvođenje tipa radi po fiksnom redosledu koji dvosmislenosti rešava u jedinom razumnom smeru: prvo boolean, zatim datum, procenat, valuta, običan broj, a sve što se ne uklopi ostaje string. Redosled sprečava da 2026 u koloni datuma odluči parser brojeva pre nego što parser datuma stigne do njega. Valuta se prepoznaje po početnom $, £, ¥ ili €, ili po trocifrenom ISO 4217 kodu za kojim sledi razmak, a kod se čuva u CurrencyCode. Presudno je da HotPDF ne nagađa vaš locale. DecimalSeparator, ThousandsSeparator i DateOrder dolaze iz opcija, jer je 1.234 ili jedan broj ili hiljadu dvesta trideset četiri, u zavisnosti od činjenice koju PDF ne sadrži. Sirovi Unicode Text čuva se u svakoj ćeliji zajedno sa tipizovanom vrednošću, pa je pogrešno nagađanje uvek moguće oporaviti bez drugog prolaza ekstrakcije
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;
Dva izvozna formata odgovaraju na različita pitanja i namerno nisu ekvivalentna. CSV upisuje nastavne kolone spojenog raspona kao prazna polja, što očekuju spreadsheet ili bulk loader. JSON čuva sve što je ekstrakcija znala: tipizovanu vrednost pod sopstvenim kind-om, columnSpan, pouzdanost po ćeliji i redu, granice ćelije i poreklo stranice i izvorne tabele. Oba formata prvo slažu ceo dokument u ograničeni memorijski bafer, pa tek onda objavljuju u vaš odredišni stream, vraćajući izvorne bajtove, dužinu i poziciju ako upis usput ne uspe, tako da neuspešan izvoz nikada ne ostavlja napola upisan fajl. Budžeti za stranice, glifove po stranici, tabele, redove, ćelije, znakove i izlazne bajtove obračunavaju se zasebno, a redovi se broje pre alokacije, jer SetLength po redu degeneriše u kvadratno kopiranje mnogo pre podrazumevanog limita od milion redova
Gde geometrijski oporavak tabele odustaje
Eksplicitno navođenje režima otkaza korisnije je od spiska funkcija, jer je svaka od ovih tačaka mesto na kojem pozivaocu treba sopstvena politika, a ne bolja vrednost opcije
- Vertikalna spajanja se ne oporavljaju. HotPDF prijavljuje
ColumnSpanza horizontalne raspone i ostavljaRowSpanna 1, pa ćelija koja u odštampanoj tabeli zauzima tri reda stiže kao jedna ćelija i dve praznine - Detekcija zaglavlja zavisi od podataka, a ne od vizuelnog izgleda. Blok zaglavlja je niz redova pre prvog reda koji sadrži tipizovanu vrednost različitu od stringa, pa tabela čije je telo u potpunosti tekst prijavljuje
HeaderRowCountkao nulu bez obzira na stil - Tabele ispod
MinimumTableConfidenceodbacuju se iz rezultata bez greške. UporediteInfo.TableCountsaInfo.SourceTableCountkada morate da znate da je nešto odbačeno - Nizu su potrebna najmanje dva reda i najmanje dve kolone pre nego što ga layout prolaz uopšte proglasi tabelom, pa jedna linija koja liči na tabelu ili layout od dve kolone sa dugom prozom ispravno, ali nekorisno, nije tabela
- Skenirane stranice nemaju tekstualne operatore, pa nema ničega što bi se geometrijski oporavilo dok na stranici ne postoji OCR tekstualni sloj
Ako vaši PDF-ovi dolaze iz sopstvenog reporting stack-a, najjeftinija popravka svega ovoga jeste upstream: emitujte tagovane tabele ili zadržite izvorne podatke i ekstrakciju tretirajte kao fallback za dokumente koje niste sami proizveli. Za sve ostalo vredi učiti pipeline ovim redom, jer se svaki sloj oslanja na prethodni: počnite običnom ekstrakcijom teksta iz učitanog PDF-a, pređite na tipizovani table API kada geometrija mora da se sačuva, pa pogledajte renderovanje tabele podataka u novi PDF kada ste na strani generisanja i sami odlučujete koliko će izlaz biti oporavljiv
ExtractLoadedTypedTables i ExportLoadedTypedTables isporučuju se u sklopu izvorne HotPDF Delphi PDF komponente za Delphi i C++Builder, bez spoljašnjeg DLL-a i runtime zavisnosti; stranica proizvoda nosi punu referencu opcija, statusa i zapisa tipizovanog table API-ja