Техническа статия

Прикачени файлове (Attachments) в PDF с PDFium Component в Delphi: четене, добавяне, изтриване

Прикачените файлове в PDF се съхраняват в структурата от вградени файлове в документа (embedded-file tree) – структура, която повечето четци показват като панел за прикачени файлове. В Delphi код компонентът PDFium Component разкрива тази структура чрез набор от индексни свойства на TPdf: можете да ги обхождате по индекс, да четете имена и съдържание, да създавате нови или да изтривате съществуващи прикачени файлове. Програмният интерфейс е съкратен; има само няколко ограничения за подредба и едно правило за сигурност, които е добре да се знаят преди писането на код

Четене на прикачени файлове от отворен документ

Свойството AttachmentCount показва броя на вградените файлове в документа. То чете данни директно от вградената библиотека PDFium, затова отразява реалното съдържание на PDF файла. Оттам свойството AttachmentName[Index] връща името за показване като WString, а Attachment[Index] предоставя самото съдържание под формата на масив TBytes. Индексацията започва от нула. Документът трябва да бъде отворен (Pdf.Active = True) преди заявка към тези свойства; извикването им при затворен документ връща нула или празен резултат без генериране на грешка

Едно нещо трябва да се има предвид: Attachment[Index] заделя памет и връща цялото съдържание на файла при всяко извикване. За документ с големи прикачени файлове, обхождането на всички прикачени файлове за генериране на обикновен списък за показване ще заделя излишна памет на всяка стъпка. Ако имате нужда само от имената за показване, четете първо AttachmentName и отложете извличането на съдържанието, докато потребителят не го поиска

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;

Записване на прикачен файл на диска

Няма вградена помощна функция SaveAttachment. Вие четете байтовете и ги записвате на диска, което означава, че генерирането на пътища и проверката за сигурност се поемат от вашия код. Това е важно, когато имената на прикачените файлове идват от външни източници. Имената на прикачени файлове в PDF са низове, записани във файла; те могат да съдържат разделители за пътища, сходни Unicode символи и други знаци, които биха довели до неочаквани резултати, ако се подадат директно към TFileStream.Create. Винаги обработвайте името с ExtractFileName преди генериране на изходния път и обмислете отхвърляне на имена, които започват с точка или съдържат невалидни символи

Байтовият масив, върнат от Attachment[Index], се управлява от извикващия код. Запишете го със стандартен TFileStream, след което можете да работите с него по ваше усмотрение, включително да проверите първите няколко байта за потвърждаване на реалния файлов формат, вместо да се доверявате сляпо на разширението в името

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;

Добавяне на прикачени файлове и двустепенен запис

Добавянето на прикачен файл изисква две стъпки, а не една. Методът CreateAttachment(Name) регистрира нова позиция в дървото на вградените файлове и връща True при успех. Този слот в началото е празен. След това присвоявате съдържанието, като записвате в Attachment[AttachmentCount - 1] – таргетирайки най-новия елемент. Ако CreateAttachment върне False, позицията не е била създадена и записът ще повреди предишния последен елемент

След промяна на списъка с прикачени файлове, промените остават само в паметта. Извикайте SaveAs, за да запишете нов файл с обновеното дърво. PDFium Component не поддържа презаписване на текущо отворения файл, тъй като ядрото държи системен дескриптор за четене към източника. Стандартният подход за обновяване на текущия файл е записване във временен файл, затваряне на документа, изтриване или преименуване на оригиналния, преименуване на временния файл и повторно отваряне

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;

Информация за типа на прикачения файл

Отвъд името и съдържанието, свойството AttachmentType[Index] връща низа с MIME типа, записан в речника на прикачения файл в PDF, ако такъв е бил посочен при прикачването. Много инструменти оставят това поле празно или го попълват с общи стойности като application/octet-stream, затова не можете да разчитате на него за разпознаване на формата в реални процеси. За сигурна идентификация прочетете първите няколко байта на съдържанието и проверете за известни файлови сигнатури: %PDF за вложен PDF, ZIP локален хедър PK\x03\x04 за Office Open XML документи, или \xD0\xCF\x11\xE0 за стари двоични формати. Информацията за типа от речника е подходяща за показване в интерфейса, но не трябва да определя логиката на работа при наличие на самите байтове

Изтриване на прикачени файлове

Методът DeleteAttachment(Index) премахва елемента на съответната позиция и връща True при успех. След изтриването останалите елементи се изместват нагоре, така че ако изтривате няколко елемента в цикъл, трябва да го правите отзад напред (от последния индекс към първия), за да не пропуснете елементи при изместването. Промяната остава само в паметта, докато не извикате SaveAs

Често срещан сценарий в системите за обработка на документи е премахването на всички прикачени файлове от входящ PDF с цел сигурност или намаляване на размера. Проверете броя веднъж преди цикъла и обходете елементите в обратен ред:

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

Къде се срещат прикачените файлове в практиката

Възможностите за прикачени файлове работят за всеки PDF, който PDFium може да отвори, но документите, при които реално се срещат такива файлове, се ограничават до няколко конкретни случая. Стандартът PDF/A-3 (ISO 19005-3) изрично позволява вграждане на файлове като механизъм за пакетно съхранение на изходни данни в архиви; електронните фактури по ZUGFeRD и Factur-X разчитат именно на това, за да вградят структуриран XML файл с данни в рамките на четимия за хора PDF документ. Генерирани от имейли PDF документи понякога съдържат оригиналните си прикачени файлове във вграденото дърво. Техническа документация, създадена със структурирани системи, понякога включва съпътстващи активи по същия начин

Когато вашето приложение обработва входящи PDF файлове, проверката на AttachmentCount при приемането на документа е важна по две причини. Първо, вградените файлове могат да съдържат данни, които искате да обработите, като например XML фактура в PDF файл. Второ, вградените файлове могат да пренасят изпълним код, така че е добре да знаете какво съдържат те от съображения за сигурност. Нито една от причините не изисква сложна логика: проверете бройката, проверете имената и вземете решение как да процедирате с байтовете

Програмните свойства за прикачени файлове, показани тук, са част от PDFium Component за Delphi и C++Builder