Műszaki cikk

PDF mellékletek Delphiben a PDFium komponens segítségével: olvasás, hozzáadás, törlés

A PDF fájlmellékletek a dokumentum beágyazott fájlfájában (embedded-file tree) tárolódnak, amelyet a legtöbb nézőke gemkapocs panelként vagy mellékletek oldalsávként jelenít meg. Delphi kódból a PDFium komponens ezt a fát a TPdf néhány indexelt tulajdonságán keresztül teszi elérhetővé: egész indexek szerint iterálhat, olvashatja a neveket és a bájtokat, új helyeket hozhat létre, és törölheti a meglévőket. Az API felülete szűk; csak néhány sorrendi korlátozást és egy elérési út tisztítási (sanitization) szabályt érdemes ismernie, mielőtt produkciós kódot írna hozzá

Mellékletek olvasása nyitott dokumentumból

A AttachmentCount megadja a dokumentumban deklarált beágyazott fájlok számát. Ezt közvetlenül a PDFium alaphívásából olvassa be, így csak azt tükrözi, amit a PDF valójában tartalmaz. Innen az AttachmentName[Index] visszaadja a megjelenített nevet WString formátumban, az Attachment[Index] pedig a nyers bájtokat TBytes tömbként. Mindkettő nulla-alapú. A dokumentumnak nyitva kell lennie (Pdf.Active = True), mielőtt bármelyik tulajdonságot lekérdezné; lezárt dokumentumon történő hívásuk nullát vagy üres eredményt ad kivétel nélkül

Egy dolgot szem előtt kell tartani: az Attachment[Index] minden olvasáskor lefoglalja és visszaadja a teljes fájlterhelést. Olyan dokumentumnál, amely nagy méretű beágyazott objektumot tartalmaz, a mellékleteken való iterálás a megjelenítési lista felépítéséhez azt jelenti, hogy minden hívásnál megfizeti ezt a lefoglalási költséget. Ha csak a nevekre van szüksége a megjelenítéshez, olvassa be először az AttachmentName tulajdonságot, és halassza el a bájtok lekérését addig, amíg a felhasználó ténylegesen nem kéri a fájlt

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 bytes)',
      [I, Pdf.AttachmentName[I], Length(Data)]));
  end;
end;

Melléklet kicsomagolása a lemezre

Nincs SaveAttachment segédfunkció. A bájtokat beolvassa, és oda írja, ahova szüksége van rájuk, így az elérési út felépítése és tisztítása teljesen az Ön kódjára hárul. Ez különösen akkor számít, ha a mellékletnevek nem megbízható dokumentumokból származnak. A PDF mellékletek nevei a fájlban tárolt karakterláncok; tartalmazhatnak elérésiút-választókat, Unicode-szerű karaktereket és más olyan karaktereket, amelyek váratlan eredményeket hoznak, ha közvetlenül a TFileStream.Create-nek adja át őket. Mindig futtassa át a nevet az ExtractFileName függvényen az output útvonal felépítése előtt, és fontolja meg a ponttal kezdődő vagy a rendszer által nem elvárt karaktereket tartalmazó nevek elutasítását

Az Attachment[Index] by visszaadott bájttömb a hívó tulajdona. Írja ki egy normál TFileStream segítségével, és tetszése szerint kezelheti, beleértve az első néhány bájt megvizsgálását a tényleges fájlformátum ellenőrzéséhez, ahelyett, hogy bízna a deklarált névben

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;

Mellékletek hozzáadása és a kétlépcsős írás

A melléklet létrehozása két hívást igényel, nem egyet. A CreateAttachment(Name) regisztrál egy új helyet a beágyazott fájl fában, és sikeres működés esetén True-t ad vissza. Ez a hely üresen indul. Ezt követően hozzárendeli a tartalmat az Attachment[AttachmentCount - 1] írásával, megcélozva a legutóbb létrehozott bejegyzést. Ha a CreateAttachment False értéket ad vissza, a hely nem jött létre, és a hozzárendelés megrongálná az utolsóként álló mellékletet

A mellékletlista módosítása után a változások csak a memóriában élnek. Hívja meg a SaveAs metódust egy új fájl kiírásához a frissített beágyazott fájl fával. A PDFium komponens jelenleg nem támogatja a megnyitott fájlba történő közvetlen visszamentést, mivel a motor olvasási fogantyút (read handle) tart fenn a forráshoz. A helyben történő frissítés szokásos mintája az, hogy elmenti egy ideiglenes útvonalra, bezárja a dokumentumot, törli vagy átnevezi az eredetit, majd a helyére nevezi át az ideiglenes fájlt, és újra megnyitja

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;

Melléklet típusinformációi

A nézőn és a bájtokon kívül az AttachmentType[Index] visszaadja a PDF beágyazott fájl szótárában tárolt MIME-típus karakterláncot, ha a fájl eredeti csatolásakor rögzítették azt. Sok generáló program üresen hagyja ezt a mezőt, vagy olyan általános értére állítja, mint az application/octet-stream, így egy éles feldolgozóban nem hagyatkozhat rá a formátum észleléséhez. A megbízható azonosításhoz olvassa be a tartalom első néhány bájtját, és ellenőrizze az ismert fájl-aláírásokat (file signatures): %PDF beágyazott PDF-hez, a ZIP helyi fájlfejléce PK\x03\x04 az Office Open XML dokumentumokhoz, és \xD0\xCF\x11\xE0 a régi compound-file binárisokhoz. A szótárból származó típusinformáció jó a felhasználói felületen való megjelenítésre, de nem szabad, hogy feldolgozási döntéseket vezéreljen, amikor a tényleges bájtok is rendelkezésre állnak

Mellékletek törlése

A DeleteAttachment(Index) eltávolítja a bejegyzést az adott pozícióból, és sikeres működés esetén True-t ad vissza. Törlés után a megmaradt bejegyzések lefelé tolódnak el, így ha egy ciklusban több mellékletet töröl, az utolsó indextől lefelé kell haladnia, nem pedig előre, hogy elkerülje a bejegyzések kihagyását az eltolódás után. A változás a memóriában marad a SaveAs meghívásáig

A dokumentumfeldolgozási folyamatok gyakori forgatókönyve az összes melléklet eltávolítása a beérkező PDF-ből biztonsági vagy méretbeli okokból, mielőtt azt továbbítanák. Számolja meg őket egyszer a ciklus előtt, és iteráljon visszafelé:

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

Hol fordulnak elő PDF mellékletek a gyakorlatban

A melléklet API minden olyan PDF-en működik, amelyet a PDFium meg tud nyitni, de azok a dokumentumok, amelyekben ténylegesen találkozhat beágyazott fájlokkal, néhány konkrét esetre korlátozódnak. A PDF/A-3 (ISO 19005-3) kifejezetten engedélyezi a megfelelő beágyazott fájlok használatát forrásadatok kötegelésére az archivált változat mellett; a ZUGFeRD és Factur-X elektronikus számlák pontosan erre támaszkodnak a strukturált XML tartalom beágyazásához a humán-olvasható PDF elrendezésbe. Az e-mailből származó PDF-ek néha az eredeti üzenetmellékleteket hordozzák továbbítva a beágyazott fájlok fájában. A strukturált szerzői rendszerekből származó műszaki dokumentációk alkalmanként ugyanígy kötegelik a támogató elemeket

Ha az alkalmazása a szervezeten kívülről érkező PDF-eket dolgoz fel, az AttachmentCount ellenőrzését érdemes elvégezni a dokumentum befogadásakor két független okból is. Először is, a beágyazott fájlok olyan adatokat tartalmazhatnak, amelyeket ki szeretne nyerni és fel szeretne dolgozni, például egy számla PDF-ben lévő XML-t. Másodszor, a beágyazott fájlok tetszőleges futtatható tartalmat hordozhatnak, így annak ismerete, hogy mi van jelen, akkor is számít, ha soha nem áll szándékában kinyerni azt. Egyik ok sem követel meg bonyolult dolgokat: olvassa le a darabszámot, ellenőrizze a neveket, és döntse el, mit tesz a bájtokgal

Az itt bemutatott melléklet-tulajdonságok a Delphihez és C++Builderhez készült PDFium komponens részét képezik