Teknik Makale

HotPDF'de Sayfalar Arası Typed Table Extraction

HotPDF, mevcut bir PDF'den tabloları ExtractLoadedTypedTables üzerinden kurtarır; bu Delphi API'si layout geçişinin ürettiği satır parçalarını birleştirir, her tablo için tek bir canonical column grid kurar, geometri desteklediğinde tabloyu sayfa sonu boyunca sürdürür ve her hücreyi sayfa provenance'ı, column span ve bounds taşıyan typed bir değer olarak döndürür. ExportLoadedTypedTables aynı sonucu doğrudan CSV veya JSON'a yazar. Bunu kurmaya değer kılan senaryo sıkıcı ve son derece yaygındır. Tek bir tablo olan kırk sayfalık fatura kayıtları, her sayfanın başında header tekrarlanarak yazdırılır. Naif bir reading-order geçişi çalıştırırsanız kırk tablo, otuz dokuz sahte header satırı ve ortadaki hücre boş kaldığında her satırda bir pozisyon sola kayan bir currency column elde edersiniz. Bunu çağıran uygulamada aşağı akışta temizlemeye çalışmak, document-import projelerinin öldüğü yerdir

PDF sayfası neden size tablo yerine parçalar verir?

Çünkü belge tagged değilse bir PDF sayfası hiç table semantics taşımaz. Content stream, text-showing operator'larını ve positioning matrix'lerini (ISO 32000-1 §9.4.3) taşır, başka hiçbir şey taşımaz; ekranda gördüğünüz çizgili kutu, hiçbir extractor'ın metinle ilişkilendirmek zorunda olmadığı bağımsız path painting'dir. Table, TR, TH ve TD structure element type'ları yalnızca tagged PDF'nin logical structure hierarchy'sinde (ISO 32000-1 §14.8.4) yaşar ve dolaşımdaki business document'ların ezici çoğunluğu tagged değildir. Aşağıda anlatılan her şey parsing değil geometric recovery'dir; herkes üzerine reconciliation report kurmadan önce bunu açıkça söylemek gerekir

HotPDF bu nedenle önce çıkarılan glyph'ler üzerinde semantic layout analysis çalıştırır; bu, yüklenmiş PDF'den structure-order text extraction ile structured HTML ve XML export'larını da besleyen geçiştir. Geçiş, hücreleri dikey hizalanan baseline'ları run'lara gruplar ve yalnızca ardışık satırlar aynı hücre sayısına sahipken bir run'ı sürdürür. Layout engine için bu kural doğru ve ucuzdur. Çağıran için yanlış biçimdir: iç değeri boş tek bir satır, görsel bir tabloyu iki source table'a böler. Typed table layer parçaları tam da bu yüzden yeniden birleştirir

Canonical column grid'ler ve ColumnTolerance düğmesi

ExtractLoadedTypedTables başka bir şey yapmadan önce aynı sayfadaki parçaları birleştirir; metin satırı yerine sütun geometrisi üzerinden birleştirir. Aynı sayfadaki iki bitişik source table, ikisinin de en az iki sütunu varsa, ilk tablonun son satırı ile ikinci tablonun ilk satırı arasındaki dikey boşluk tolerance band içinde kalıyorsa ve sütun başlangıçları hizalanıyorsa birleşir. Birbirinden ColumnTolerance kadar uzakta olan sütun başlangıçları tek bir canonical column'da toplanır ve birleşirken ortalamaları alınır. Varsayılan tolerance, normal business typography'ye uyan 12 user-space unit'tir; geniş aralıklı veya derin girintili layout'larda yükseltilmek ister

İç değeri eksik bir satıra ne olduğu asıl önemli kısımdır. HotPDF her hücreyi en yakın canonical column başlangıcına snap eder ve sonra ColumnSpan'i bu sütundan bir sonraki dolu sütuna olan mesafe olarak ayarlar; kalan hücreleri sola kaydırmaz. Beş sütunlu bir grid'deki üç hücreli satır değerlerini doğru başlıkların altında tutar ve boşlukların tam olarak nerede olduğunu kaydeder. Bu, uzlaştırabileceğiniz bir tablo ile parayı sessizce yanlış ilişkilendiren bir tablo arasındaki farktır

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 birimleri
    Options.MinimumTableConfidence := 0.55;  // bunun altında tablolar atılır
    Options.DateOrder := ttdoDMY;            // 03/04/2026, 3 Nisan'dır
    Options.DecimalSeparator := ',';
    Options.ThousandsSeparator := '.';
    if Pdf.ExtractLoadedTypedTables([0, 1, 2, 3], Options, Tables, Info) then
      // Info.TableCount ve Info.SourceTableCount ne kadar birleştirildiğini gösterir
      ProcessTables(Tables)
    else if Info.Status = ttesBudgetExceeded then
      Log(string(Info.Diagnostic));
  finally
    Pdf.Free;
  end;
end;

Sayfalar arası birleştirme gerçekte neyi garanti eder?

Bilerek muhafazakârlığı garanti eder. HotPDF iki tabloyu sayfa sınırı üzerinden yalnızca MergeAcrossPages etkinse, ikinci tablo birincinin bittiği sayfa index'inin tam sonraki sayfasında başlıyorsa, ikisi de en az iki sütun taşıyorsa ve en az iki canonical column başlangıcı ColumnTolerance içinde hizalanıyorsa birleştirir. Yük taşıyan koşul ardışık sayfa şartıdır. Çağıranlar PageIndices'i istedikleri sıradaki open array olarak verir ve bu kontrol olmazsa 3, 9 ve 14. sayfalar için istek, birbiriyle ilgisiz üç tabloyu bütünüyle makul görünen tek bir sonuca kaynaklayabilir. Bedeli, sayfa atlayan gerçek bir devamın, araya giren bir appendix'in veya boş verso'su olan duplex scan'in iki tablo olarak dönmesidir; hiçbir seçenek bu sınırı gevşetmez. Bunları yeniden birleştirmek yalnızca çağıran uygulamanın verebileceği bir policy kararıdır; bu nedenle API FirstPageIndex, LastPageIndex, SourceTableCount ve satır başına PageIndex sunar, kararı ait olduğu yerde bırakır

Tekrarlanan header'lar etiketlenir, hiç silinmez

ExtractLoadedTypedTables tekrarlanan header satırını sonuçtan asla kaldırmaz. Sayfalar arası birleştirme gelen tablonun accumulated table ile aynı header metniyle açıldığını, trim ve case folding sonrasında karşılaştırarak bulursa o satırları IsHeader ve IsRepeatedHeader olarak işaretler ve yine source order içinde ekler. Silme kayıplı ve geri döndürülemez bir tercihtir; farklı consumer'lar farklı cevaplar ister: CSV import tekrarları kaldırmak ister, audit trail sayfa numaralarıyla birlikte görmek ister, diffing tool source order'ı byte byte korumak ister. Bu yüzden kütüphane raporlar, kararı çağıran verir

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;                    // yalnızca ilk header bloğunu tut
      for C := 0 to High(Row.Cells) do
        if Row.Cells[C].ValueKind = ttvkCurrency then
          Total := Total + Row.Cells[C].NumberValue;
    end;
end;

Typed değerler ve sizin sağlamanız gereken ayraçlar

Type inference, belirsizlikleri makul olan tek yönde çözen sabit bir sırada çalışır: önce boolean, sonra date, sonra percentage, sonra currency, sonra plain number; hiçbirine uymayan string olarak kalır. Sıra, date column içindeki 2026 değerinin date parser onu görmeden önce number parser tarafından kararlaştırılmasını önler. Currency, baştaki $, £, ¥ veya işaretinden ya da ardından boşluk gelen üç harfli ISO 4217 kodundan tanınır ve kod CurrencyCode içinde korunur. Kritik nokta, HotPDF'in locale tahmin etmemesidir. DecimalSeparator, ThousandsSeparator ve DateOrder options'tan gelir; çünkü 1.234, PDF'nin taşımadığı bir bilgiye göre tek sayı da olabilir bin iki yüz otuz dört de. Ham Unicode Text her hücrede typed değerle birlikte tutulur; bu nedenle yanlış bir tahmin her zaman ikinci extraction pass olmadan geri alınabilir

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;

İki export formatı farklı sorulara cevap verir ve bilerek eşdeğer değildir. CSV, birleştirilmiş span'in continuation column'larını boş field olarak yazar; spreadsheet veya bulk loader'ın beklediği budur. JSON, extraction'ın bildiği her şeyi korur: typed değer kendi kind'ı altında, columnSpan, hücre ve satır başına confidence, hücre bounds'ları, page ve source-table provenance. Her iki format da bütün belgeyi bounded in-memory buffer'a alır ve ancak sonra hedef stream'e yayımlar; write yarıda başarısız olursa özgün baytları, uzunluğu ve konumu geri yükler, bu yüzden başarısız export yarım yazılmış bir dosya bırakmaz. Page, page başına glyph, table, row, cell, character ve output byte budget'ları ayrı ayrı hesaplanır; bir milyon satırlık varsayılan tavana çok daha önce per-row SetLength quadratic copying'e dönüşeceği için satırlar allocation'dan önce sayılır

Geometrik table recovery nerede vazgeçer?

Başarısızlık kiplerini açıkça belirtmek bir feature list'ten daha yararlıdır; çünkü bunların her biri çağıranın daha iyi bir option value yerine kendi policy'sine ihtiyaç duyduğu bir yerdir

  • Dikey birleşmeler kurtarılmaz. HotPDF yatay span'ler için ColumnSpan bildirir ve RowSpan'i 1'de bırakır; yazdırılmış tabloda üç satıra yayılan bir hücre bir hücre ve iki boşluk olarak gelir
  • Header detection görsel değil data-driven'dır. Header block, typed olmayan ilk non-string değeri taşıyan satırdan önceki satır run'ıdır; gövdesi bütünüyle metin olan bir tablo nasıl stillenmiş olursa olsun HeaderRowCount değerini sıfır bildirir
  • MinimumTableConfidence altındaki tablolar sonuçtan hatasız biçimde atılır. Bir şeyin atıldığını bilmek istediğinizde Info.TableCount ile Info.SourceTableCount değerlerini karşılaştırın
  • Bir run, layout pass'in ona tablo diyebilmesi için en az iki satıra ve en az iki sütuna ihtiyaç duyar; bu yüzden tek satırlık pseudo-table veya uzun prose içeren iki sütunlu bir layout doğru ama yararsız biçimde tablo değildir
  • Taranmış sayfalarda text operator yoktur; sayfada bir OCR text layer bulunana kadar geometrik olarak kurtarılacak hiçbir şey yoktur

PDF'leriniz kendi reporting stack'inizden çıkıyorsa bunların hepsinin en ucuz çözümü upstream'dedir: tagged table üretin veya kaynak veriyi tutun ve extraction'ı üretmediğiniz belgeler için fallback kabul edin. Geri kalan her şey için pipeline'ı şu sırayla öğrenmek değerlidir, çünkü her katman altındakinin üzerine kurulur: yüklenmiş PDF'den düz metin çıkarma ile başlayın, geometrinin korunması gerektiğinde typed table API'ye geçin ve üretim tarafındaysanız, çıktının ne kadar kurtarılabilir olacağına karar verebildiğiniz yerde veri tablosunu yeni PDF'e render etmeye bakın

ExtractLoadedTypedTables ve ExportLoadedTypedTables, Delphi ve C++Builder için native HotPDF Delphi PDF Component'in parçasıdır; harici DLL ve runtime dependency yoktur, ürün sayfası typed table API için option, status ve record referansının tamamını taşır