Technical Article

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

Прикачените файлове в PDF се съхраняват в йерархичната структура за вградени файлове на документа, структура, която повечето програми за преглед показват като панел с кламер или странична лента за прикачени файлове. В Delphi код PDFium VCL разкрива тази структура чрез малък набор от индексирани свойства на TPdf: обхождате по целочислен индекс, четете имена и съдържание в байтове, създавате нови позиции и изтривате съществуващи. Интерфейсът на API е тесен; има само няколко ограничения в последователността и едно правило за пречистване на пътища, които си струва да знаете, преди да пишете софтуер за реална употреба.

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

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 VCL не поддържа запазване обратно в същия файл, който е отворен в момента, тъй като ядрото държи дескриптор за четене към източника. Стандартният шаблон за актуализация на място е запис във временен път, затваряне на документа, изтриване или преименуване на оригинала, последвано от преименуване на временния файл на правилното място и повторно отваряне.

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 файлове на практика

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

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

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