Τεχνικό Άρθρο

Συνημμένα PDF στο Delphi με το PDFium Component: Ανάγνωση, Προσθήκη, Διαγραφή

Τα συνημμένα αρχεία PDF αποθηκεύονται στο δέντρο ενσωματωμένων αρχείων του εγγράφου, μια δομή που τα περισσότερα προγράμματα προβολής εμφανίζουν ως πάνελ συνδετήρα ή ως πλευρική εργαλειοθήκη συνημμένων. Από τον κώδικα Delphi, το PDFium Component εκθέτει αυτό το δέντρο μέσω ενός μικρού συνόλου ιδιοτήτων με δείκτη στο TPdf: κάνετε επανάληψη με ακέραιο δείκτη, διαβάζετε ονόματα και ωφέλιμα φορτία byte, δημιουργείτε νέες θέσεις και διαγράφετε υπάρχουσες. Η επιφάνεια του API είναι στενή· υπάρχουν μόνο μερικοί περιορισμοί σειράς και ένας κανόνας εξυγίανσης (sanitization) που αξίζει να γνωρίζετε πριν γράψετε κώδικα παραγωγής γύρω από αυτό

Ανάγνωση συνημμένων από ανοιχτό έγγραφο

Το AttachmentCount δίνει τον αριθμό των ενσωματωμένων αρχείων που δηλώνει το έγγραφο. Διαβάζει απευθείας από την υποκείμενη κλήση του PDFium, επομένως αντικατοπτρίζει μόνο ό,τι πραγματικά περιέχει το PDF. Από εκεί, το AttachmentName[Index] επιστρέφει το όνομα εμφάνισης ως WString, και το Attachment[Index] παραδίδει τα ακατέργαστα byte ως πίνακα TBytes. Και τα δύο βασίζονται σε μηδενικό δείκτη (zero-based). Το έγγραφο πρέπει να είναι ανοιχτό (Pdf.Active = True) πριν υποβάλετε ερώτημα για οποιαδήποτε ιδιότητα· η κλήση τους σε ένα κλειστό έγγραφο σας δίνει μηδενικό ή κενό αποτέλεσμα χωρίς καμία εξαίρεση

Ένα πράγμα που πρέπει να έχετε κατά νου: το Attachment[Index] εκχωρεί και επιστρέφει το πλήρες ωφέλιμο φορτίο του αρχείου σε κάθε ανάγνωση. Για ένα έγγραφο που φέρει ένα μεγάλο ενσωματωμένο στοιχείο, η επανάληψη σε όλα τα συνημμένα για τη δημιουργία μιας λίστας εμφάνισης σημαίνει ότι πληρώνετε αυτό το κόστος εκχώρησης μνήμης σε κάθε κλήση. Εάν χρειάζεστε μόνο ονόματα για σκοπούς εμφάνισης, διαβάστε πρώτα το AttachmentName και αναβάλετε τη λήψη των byte μέχρι ο χρήστης να ζητήσει πραγματικά το αρχείο

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. Διαβάζετε τα byte και τα γράφετε όπου χρειάζεστε, γεγονός που θέτει την κατασκευή και την εξυγίανση της διαδρομής εξ ολοκλήρου στον κώδικά σας. Αυτό έχει σημασία όταν τα ονόματα των συνημμένων προέρχονται από μη αξιόπιστα έγγραφα. Τα ονόματα των συνημμένων PDF είναι συμβολοσειρές που αποθηκεύονται μέσα στο αρχείο· μπορούν να περιέχουν διαχωριστικά διαδρομής, οπτικά παρόμοιους χαρακτήρες Unicode (lookalikes) και άλλους χαρακτήρες που θα παράγουν απροσδόκητα αποτελέσματα εάν τους περάσετε απευθείας στο TFileStream.Create. Πάντα να περνάτε το όνομα από το ExtractFileName πριν δημιουργήσετε οποιαδήποτε διαδρομή εξόδου, και σκεφτείτε να απορρίψετε ονόματα που ξεκινούν με τελεία ή περιέχουν χαρακτήρες εκτός αυτών που αναμένει το σύστημά σας

Ο πίνακας byte που επιστρέφεται από το Attachment[Index] ανήκει στον καλούντα. Γράψτε τον με ένα κανονικό TFileStream και είναι δικός σας για να τον κάνετε ό,τι θέλετε, συμπεριλαμβανομένης της επιθεώρησης των πρώτων byte για να επαληθεύσετε την πραγματική μορφή του αρχείου αντί να εμπιστευτείτε το δηλωμένο όνομα

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;

Πληροφορίες τύπου συνημμένου

Πέρα από το όνομα και το ωφέλιμο φορτίο byte, το AttachmentType[Index] επιστρέφει τη συμβολοσειρά τύπου MIME που είναι αποθηκευμένη στο λεξικό ενσωματωμένου αρχείου του PDF, εάν είχε καταγραφεί όταν το αρχείο επισυνάφθηκε αρχικά. Πολλές γεννήτριες αφήνουν αυτό το πεδίο κενό ή το ορίζουν σε μια γενική τιμή όπως application/octet-stream, επομένως δεν μπορείτε να βασιστείτε σε αυτό για την ανίχνευση μορφής σε μια ροή παραγωγής. Για αξιόπιστη αναγνώριση, διαβάστε τα πρώτα byte του ωφέλιμου φορτίου και ελέγξτε για γνωστές υπογραφές αρχείων: %PDF για ένα ένθετο PDF, την τοπική κεφαλίδα αρχείου ZIP PK\x03\x04 για έγγραφα Office Open XML, \xD0\xCF\x11\xE0 για παλαιού τύπου δυαδικά αρχεία σύνθετων αρχείων. Οι πληροφορίες τύπου από το λεξικό είναι κατάλληλες για εμφάνιση σε μια ετικέτα UI, αλλά δεν θα πρέπει να καθοδηγούν τις αποφάσεις επεξεργασίας όταν έχετε διαθέσιμα τα πραγματικά byte

Διαγραφή συνημμένων

Η μέθοδος 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. Τα PDFs που προέρχονται από email μερικές φορές μεταφέρουν τα αρχικά συνημμένα μηνυμάτων τους προωθημένα στο δέντρο ενσωματωμένων αρχείων. Η τεχνική τεκμηρίωση που προέρχεται από δομημένα συστήματα συγγραφής μερικές φορές συγκεντρώνει υποστηρικτικά στοιχεία με τον ίδιο τρόπο

Όταν η εφαρμογή σας επεξεργάζεται εισερχόμενα PDFs από το εξωτερικό του οργανισμού σας, ο έλεγχος του AttachmentCount ως μέρος της εισαγωγής εγγράφων αξίζει να γίνει για δύο ανεξάρτητους λόγους. Πρώτον, τα ενσωματωμένα αρχεία ενδέχεται να μεταφέρουν δεδομένα που θέλετε να εξαγάγετε και να επεξεργαστείτε, όπως το XML μέσα σε ένα PDF τιμολογίου. Δεύτερον, τα ενσωματωμένα αρχεία μπορούν να μεταφέρουν αυθαίρετο εκτελέσιμο περιεχόμενο, επομένως το να γνωρίζετε τι υπάρχει έχει σημασία ακόμα κι αν δεν σκοπεύετε ποτέ να το εξαγάγετε. Κανένας από τους δύο λόγους δεν απαιτεί να κάνετε κάτι περίπλοκο: διαβάστε τον αριθμό, ελέγξτε τα ονόματα και αποφασίστε τι θα κάνετε με τα byte

Οι ιδιότητες συνημμένων που εμφανίζονται εδώ αποτελούν μέρος του PDFium Component για Delphi και C++Builder