Teknik Makale

Delphi'de PDFium Bileşeni ile PDF Ekleri: Okuma, Ekleme, Silme

PDF dosya ekleri, çoğu görüntüleyicinin bir ataş paneli veya ekler kenar çubuğu olarak yüzeye çıkardığı bir yapı olan belgenin gömülü dosya ağacında (embedded-file tree) saklanır. Delphi kodundan PDFium Bileşeni, bu ağacı TPdf üzerindeki küçük bir dizinli özellikler kümesi aracılığıyla sunar: Tamsayı dizinine göre yineler, adları ve bayt yüklerini okur, yeni yuvalar (slots) oluşturur ve mevcut olanları silersiniz. API yüzeyi dardır; etrafında üretim kodu yazmadan önce bilinmesi gereken yalnızca birkaç sıralama kısıtlaması ve bir temizleme (sanitization) kuralı vardır

Açık bir belgeden ekleri okuma

AttachmentCount, belgenin beyan ettiği gömülü dosyaların sayısını verir. Doğrudan PDFium'un alttaki çağrısından okur, bu nedenle yalnızca PDF'in gerçekte ne içerdiğini yansıtır. Oradan, AttachmentName[Index] görüntü adını bir WString olarak döndürür ve Attachment[Index] ham baytları bir TBytes dizisi olarak sunar. Her ikisi de sıfır tabanlıdır. Her iki özelliği de sorgulamadan önce belgenin açık olması gerekir (Pdf.Active = True); bunları kapalı bir belgede çağırmak, istisna olmaksızın size sıfır veya boş bir sonuç verir

Akılda tutulması gereken bir şey: Attachment[Index] her okumada dosya yükünün tamamını tahsis eder ve döndürür. Büyük bir gömülü varlık taşıyan bir belge için, bir görüntüleme listesi oluşturmak üzere tüm ekleri yinelemek, her çağrıda bu tahsis maliyetini ödemek anlamına gelir. Yalnızca görüntüleme amacıyla adlara ihtiyacınız varsa, önce AttachmentName özelliğini okuyun ve bayt alımını kullanıcı dosyayı gerçekten talep edene kadar erteleyin

procedure ListAttachments(Pdf: TPdf);
var
  I: Integer;
  Data: TBytes;
begin
  if not Pdf.Active then
    Exit;

  for I := 0 to Pdf.AttachmentCount - 1 do
  begin
    Data := Pdf.Attachment[I];
    Writeln(Format('%d: %s (%d bayt)',
      [I, Pdf.AttachmentName[I], Length(Data)]));
  end;
end;

Bir eki diske çıkarma

SaveAttachment yardımcısı yoktur. Baytları okur ve ihtiyacınız olan her yere yazarsınız; bu da yol oluşturmayı ve temizlemeyi tamamen sizin kodunuza bırakır. Bu durum, ek adları güvenilmeyen belgelerden geldiğinde önem taşır. PDF ek adları dosya içinde saklanan dizelerdir; doğrudan TFileStream.Create'e iletirseniz beklenmeyen sonuçlar üretecek yol ayırıcıları, Unicode benzerleri ve diğer karakterleri içerebilirler. Herhangi bir çıktı yolu oluşturmadan önce adı her zaman ExtractFileName'den geçirin ve nokta ile başlayan veya sisteminizin beklediğinin dışındaki karakterleri içeren adları reddetmeyi düşünün

Attachment[Index] tarafından döndürülen bayt dizisi çağırana aittir. Normal bir TFileStream ile yazın; beyan edilen ada güvenmek yerine gerçek dosya biçimini doğrulamak için ilk birkaç baytı incelemek de dahil olmak üzere dilediğinizi yapmak size kalmıştır

procedure ExtractAttachment(Pdf: TPdf; Index: Integer; const OutputDir: string);
var
  SafeName: string;
  OutPath: string;
  Data: TBytes;
  FS: TFileStream;
begin
  SafeName := ExtractFileName(Pdf.AttachmentName[Index]);
  if SafeName = '' then
    SafeName := Format('attachment_%d', [Index]);

  OutPath := IncludeTrailingPathDelimiter(OutputDir) + SafeName;
  Data := Pdf.Attachment[Index];

  FS := TFileStream.Create(OutPath, fmCreate);
  try
    if Length(Data) > 0 then
      FS.WriteBuffer(Data[0], Length(Data));
  finally
    FS.Free;
  end;
end;

Ek ekleme ve iki adımlı yazma

Bir ek oluşturmak tek değil, iki çağrı gerektirir. CreateAttachment(Name) gömülü dosya ağacında yeni bir yuva (slot) kaydeder ve başarı durumunda True döndürür. Bu yuva boş başlar. Daha sonra en son oluşturulan girişi hedefleyerek Attachment[AttachmentCount - 1]'e yazarak yükü atarsınız. CreateAttachment False döndürürse yuva oluşturulmamış demektir ve atama işlemi son sırada hangi dizin varsa oradaki eki bozacaktır

Ek listesini değiştirdikten sonra, değişiklikler yalnızca bellekte yaşar. Güncellenmiş gömülü dosya ağacıyla yeni bir dosya yazmak için SaveAs işlevini çağırın. PDFium Bileşeni, motor kaynak üzerinde bir okuma tanıtıcısı (read handle) tuttuğundan, şu anda açık olan aynı dosyaya geri kaydetmeyi desteklemez. Yerinde güncelleme için standart kalıp, geçici bir yola kaydetmek, belgeyi kapatmak, orijinali silmek veya yeniden adlandırmak, ardından geçici dosyayı yerine yeniden adlandırmak ve yeniden açmaktır

procedure AddFileAttachment(Pdf: TPdf; const FilePath: string);
var
  FS: TFileStream;
  Data: TBytes;
  AttachName: string;
begin
  if not Pdf.Active then
    Exit;

  FS := TFileStream.Create(FilePath, fmOpenRead or fmShareDenyWrite);
  try
    SetLength(Data, FS.Size);
    if FS.Size > 0 then
      FS.ReadBuffer(Data[0], FS.Size);
  finally
    FS.Free;
  end;

  AttachName := ExtractFileName(FilePath);
  if Pdf.CreateAttachment(AttachName) then
    Pdf.Attachment[Pdf.AttachmentCount - 1] := Data;
end;

Ek türü bilgisi

Ad ve bayt yükünün ötesinde, dosya ilk eklendiğinde kaydedilmişse AttachmentType[Index] PDF'in gömülü dosya sözlüğünde saklanan MIME türü dizesini döndürür. Birçok üretici bu alanı boş bırakır veya application/octet-stream gibi genel bir değere ayarlar, bu nedenle bir üretim hattında biçim algılama için buna güvenemezsiniz. Güvenilir tanımlama için, yükün ilk birkaç baytını okuyun ve bilinen dosya imzalarını kontrol edin: İç içe geçmiş bir PDF için %PDF, Office Open XML belgeleri için ZIP yerel dosya başlığı PK\x03\x04, eski bileşik dosya ikilileri için \xD0\xCF\x11\xE0. Sözlükten gelen tür bilgisi bir kullanıcı arayüzü etiketinde gösterilmek için uygundur, ancak gerçek baytlar elinizdeyken işleme kararlarını yönlendirmemelidir

Ekleri silme

DeleteAttachment(Index) o konumdaki girişi kaldırır ve başarı durumunda True döndürür. Silme işleminden sonra, kalan girişler aşağı kayar; bu nedenle bir döngüde birden fazla eki siliyorsanız, her kaydırmadan sonra girişlerin atlanmasını önlemek için ileri doğru değil, son dizinden geriye doğru yinelemelisiniz. Değişiklik, siz SaveAs çağırana kadar bellek içindedir

Belge işleme hatlarında yaygın bir senaryo, güvenlik veya boyut nedenleriyle gelen bir PDF'teki tüm ekleri sonraki aşamalara aktarmadan önce temizlemektir. Döngüden önce bir kez sayın ve tersine yineleyin:

procedure StripAllAttachments(Pdf: TPdf);
var
  I: Integer;
begin
  for I := Pdf.AttachmentCount - 1 downto 0 do
    Pdf.DeleteAttachment(I);
end;

PDF eklerinin pratikte göründüğü yerler

Ek API'si PDFium'un açabileceği her PDF'te çalışır, ancak gerçekte gömülü dosyalarla karşılaştığınız belgeler birkaç özel durumda kümelenir. PDF/A-3 (ISO 19005-3), arşiv sunumunun yanı sıra kaynak verileri de paketlemek için bir mekanizma olarak kurallara uygun gömülü dosyalara açıkça izin verir; ZUGFeRD ve Factur-X elektronik faturaları, insan tarafından okunabilen PDF düzeninin içine yapılandırılmış bir XML yükü yerleştirmek için tam olarak buna dayanır. E-postadan türetilen PDF'ler bazen gömülü dosya ağacına iletilen orijinal mesaj eklerini taşır. Yapılandırılmış yazma sistemlerinden kaynaklanan teknik belgeler bazen destekleyici varlıkları aynı şekilde paketler

Uygulamanız kuruluşunuz dışından gelen PDF'leri işlediğinde, belge alımının bir parçası olarak AttachmentCount değerini kontrol etmek iki bağımsız nedenden dolayı değerlidir. İlk olarak, gömülü dosyalar, fatura PDF'inin içindeki XML gibi ayıklamak ve işlemek istediğiniz verileri taşıyabilir. İkinci olarak, gömülü dosyalar rastgele yürütülebilir içerik taşıyabilir, bu nedenle neyin mevcut olduğunu bilmek, onu ayıklamayı asla düşünmeseniz bile önemlidir. Her iki neden de karmaşık bir şey yapmanızı gerektirmez: Sayıyı okuyun, adları kontrol edin ve baytlarla ne yapacağınıza karar verin

Burada gösterilen ek özellikleri Delphi ve C++Builder için PDFium Bileşeni'nin bir parçasıdır