บทความเทคนิค

ไฟล์แนบ PDF ใน Delphi ด้วย PDFium Component: การอ่าน การเพิ่ม การลบ

ไฟล์แนบ PDF จะถูกจัดเก็บไว้ในโครงสร้างแผนผังไฟล์ฝังตัวของเอกสาร ซึ่งโปรแกรมดูเอกสารส่วนใหญ่จะแสดงผลเป็นพาเนลรูปคลิปหนีบกระดาษหรือแถบข้างไฟล์แนบ จากโค้ด Delphi ตัว PDFium Component จะเปิดเผยแผนผังนั้นผ่านชุดพร็อพเพอร์ตี้ที่มีดัชนีกำกับบน 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 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 สำหรับไฟล์ไบนารีคอมพาวด์รุ่นเก่า ข้อมูลประเภทจากพจนานุกรมใช้ได้สำหรับการแสดงผลในป้ายกำกับ UI แต่ไม่ควรขับเคลื่อนการตัดสินใจประมวลผลเมื่อคุณมีไบต์จริงพร้อมใช้งาน

การลบไฟล์แนบ

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 ปรากฏในการใช้งานจริง

API ไฟล์แนบทำงานบน PDF ใด ๆ ที่ PDFium สามารถเปิดได้ แต่เอกสารที่คุณพบไฟล์แนบฝังตัวมักจะกระจุกตัวอยู่ในกรณีเฉพาะไม่กี่กรณี มาตรฐาน PDF/A-3 (ISO 19005-3) อนุญาตอย่างชัดเจนให้มีไฟล์ฝังตัวที่เป็นไปตามข้อกำหนดเป็นกลไกสำหรับการรวมข้อมูลต้นทางควบคู่ไปกับการจัดเก็บแบบถาวร; ใบแจ้งหนี้อิเล็กทรอนิกส์ ZUGFeRD และ Factur-X อ้างอิงกลไกนี้อย่างแน่นอนในการฝังเพย์โหลด XML ที่มีโครงสร้างภายในเค้าโครง PDF ที่มนุษย์อ่านได้ เอกสาร PDF ที่ได้รับจากอีเมลบางครั้งมีไฟล์แนบของข้อความต้นฉบับส่งต่อเข้ามาในแผนผังไฟล์ฝังตัว เอกสารทางเทคนิคที่มีต้นกำเนิดมาจากระบบการเขียนแบบมีโครงสร้างในบางครั้งรวมสินทรัพย์สนับสนุนในลักษณะเดียวกันนี้ด้วย

เมื่อแอปพลิเคชันของคุณประมวลผล PDF ขาเข้าจากภายนอกองค์กร การตรวจสอบ AttachmentCount ซึ่งเป็นส่วนหนึ่งของการนำเข้าเอกสารเป็นสิ่งที่มีค่าควรทำด้วยเหตุผลอิสระสองประการ ประการแรก ไฟล์ฝังตัวอาจนำข้อมูลที่คุณต้องการแยกและประมวลผลมาด้วย เช่น XML ภายใน PDF ใบแจ้งหนี้ ประการที่สอง ไฟล์ฝังตัวสามารถนำเนื้อหาที่เรียกใช้ได้ใด ๆ มาด้วย ดังนั้นการทราบว่ามีอะไรอยู่จึงมีความสำคัญ แม้ว่าคุณจะไม่เคยตั้งใจที่จะแยกมันออกมาก็ตาม ทั้งสองเหตุผลไม่ต้องการให้คุณดำเนินการใด ๆ ที่ซับซ้อน: อ่านจำนวน ตรวจสอบชื่อ และตัดสินใจว่าจะทำอย่างไรกับข้อมูลไบต์

พร็อพเพอร์ตี้ไฟล์แนบที่แสดงที่นี่เป็นส่วนหนึ่งของ PDFium Component สำหรับ Delphi และ C++Builder