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

Διατήρηση μακροεντολών VBA και εξωτερικών συνδέσμων κατά την επανεγγραφή βιβλίου εργασίας με Delphi

Σκεφτείτε μια εργασία που δεν κάνει σχεδόν τίποτα: ανοίγει ένα μηνιαίο βιβλίο εργασίας, γράφει τη σημερινή ημερομηνία σε ένα κελί, το αποθηκεύει ξανά. Τρέξτε το αρκετά συχνά μέσα από μια υπηρεσία και κάποιο παράπονο θα φτάσει έτσι κι αλλιώς. Οι μακροεντολές έχουν εξαφανιστεί, ή οι συνδεδεμένες συναλλαγματικές ισοτιμίες τώρα δείχνουν #REF!, και η ομάδα λειτουργιών είναι πεπεισμένη ότι ο κώδικάς σας τις διέγραψε. Δεν διέγραψε τίποτα. Αυτό που συνήθως συνέβη είναι ότι ένα βιβλίο εργασίας με ενεργοποιημένες μακροεντολές βγήκε με απλή ονομασία .xlsx, και το Excel υπάκουσε στους κανόνες τύπου περιεχομένου του ECMA-376: ένα πακέτο του οποίου ο τύπος περιεχομένου δηλώνει καμία VBA δεν μπορεί να φορτώσει ένα έργο VBA, ανεξάρτητα από το αν τα bytes βρίσκονται ακριβώς εκεί. Το αρχείο δεν χάλασε. Μετονομάστηκε σε μια κατάσταση όπου το Excel υποχρεούται να αγνοήσει μέρος του

Οι μακροεντολές και οι εξωτερικοί σύνδεσμοι βιβλίων εργασίας είναι τα δύο πράγματα που ο αυτοματισμός χάνει πιο αξιόπιστα, για τον ίδιο υποκείμενο λόγο. Και τα δύο ζουν έξω από το πλέγμα κελιών που πραγματικά αγγίζει ο κώδικας επεξεργασίας, οπότε ένας κώδικας που σκέφτεται σε όρους γραμμών και στηλών θα τα αποβάλει χωρίς ποτέ να εκδώσει διαγραφή. Το HotXLS είναι μια εγγενής βιβλιοθήκη για Delphi και C++Builder που διαβάζει και γράφει XLS και XLSX χωρίς εγκατεστημένο Excel, και αντιμετωπίζει και τα δύο περιουσιακά στοιχεία ως φορτία που μεταφέρει σκόπιμα και όχι ως δεδομένα που τυχαίνει να αντιγράφει. Ακολουθεί τι χρειάζεται το καθένα από τη διαδρομή αποθήκευσής σας, και πού σταματούν οι εγγυήσεις

Γιατί αυτά τα δύο περιουσιακά στοιχεία συμπεριφέρονται διαφορετικά σε μια επανεγγραφή

Ένα έργο VBA είναι ένα αδιαφανές δυαδικό αρχείο. Σε ένα πακέτο OOXML είναι το αρχείο vbaProject.bin· σε ένα παλαιού τύπου αρχείο BIFF είναι μια αποθήκευση OLE. Υπάρχουν ακριβώς δύο τρόποι να το χάσετε: ο writer ποτέ δεν το αντιγράφει στην έξοδο, ή η έξοδος παίρνει έναν τύπο αρχείου που το απαγορεύει. Και οι δύο αποτυχίες είναι ολικές και σιωπηλές. Το έργο είτε υπάρχει είτε όχι

Ένας εξωτερικός σύνδεσμος δεν είναι καθόλου blob. Είναι ένας μικρός γράφος σχέσεων: μια διαδρομή προορισμού ή URL που δείχνει σε ένα άλλο βιβλίο εργασίας, η λίστα ονομάτων φύλλων που εκθέτει αυτός ο προορισμός, και μια προαιρετική cache με τις τιμές που είδε τελευταία σε αυτά τα φύλλα ώστε το Excel να μπορεί να δείξει κάτι όταν ο προορισμός είναι εκτός σύνδεσης. Αυτά τα τρία μέρη έχουν διαφορετικούς κύκλους ζωής σε μια επανεγγραφή, και μια βιβλιοθήκη μπορεί να διατηρήσει πιστά κάποια ενώ αθόρυβα αποβάλλει άλλα. Αυτή η ασυμμετρία είναι το σημείο που αξίζει να γίνει ακριβές, επειδή τίποτα στον κώδικα επεξεργασίας κελιών δεν θα το φέρει στην επιφάνεια

Διάγραμμα σύγκρισης blob project VBA έναντι των τριών μερών ενός εξωτερικού συνδέσμου βιβλίου εργασίας που μεταφέρει το HotXLS μέσα από επανεγγραφή Delphi
Ένα έργο VBA επιβιώνει επανεγγραφής ως όλα-ή-τίποτα δυαδικό φορτίο, ενώ ένας εξωτερικός σύνδεσμος είναι μικρός γράφος του οποίου ο στόχος, τα ονόματα φύλλων και οι cached τιμές μπορούν να διατηρηθούν ή να ριφθούν ανεξάρτητα

Μεταφορά ενός έργου VBA μέσα από μια επανεγγραφή XLSX

Στην πλευρά XLSX, η TXLSXWorkbook κρατά το φορτίο μακροεντολών αυτούσιο. Η ιδιότητα VbaProject κρατά τα ακατέργαστα bytes του vbaProject.bin μέσα σε ένα AnsiString, και μια κενή συμβολοσειρά είναι ο τρόπος με τον οποίο το μοντέλο λέει ότι δεν υπάρχουν μακροεντολές. Γύρω της βρίσκονται τρεις λειτουργίες: η HasVbaProject απαντά αν υπάρχει ένα έργο, η ClearVbaProject το αφαιρεί σκόπιμα, και η LoadVbaProjectFromFile εισάγει ένα εξαγμένο από ένα πρότυπο. Αυτή η τελευταία κλήση αξίζει περισσότερο απ' όσο φαίνεται. Επιτρέπει σε παραγόμενα βιβλία εργασίας να αποκτήσουν ένα τυπικό έργο μακροεντολών χωρίς να σέρνουν ένα πλήρες αρχείο προτύπου μέσα από το pipeline

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Data');
    Sheet.Cells[1, 1].Value := 'Refreshed ' + DateTimeToStr(Now);

    Book.LoadVbaProjectFromFile('macros\vbaProject.bin');
    if not Book.HasVbaProject then
      raise Exception.Create('VBA payload failed to load');

    // Η επέκταση .xlsm δεν είναι διακοσμητική: επιλέγει τον
    // τύπο περιεχομένου με ενεργοποιημένες μακροεντολές μέσα στο πακέτο.
    Book.SaveAs('monthly-report.xlsm');
  finally
    Book.Free;
  end;
end;

Η γραμμή αποθήκευσης είναι εκεί που στρέφεται όλο το πρόβλημα. Ένα βιβλίο εργασίας που κρατά ένα έργο VBA πρέπει να γραφτεί με σημασιολογία ενεργοποιημένων μακροεντολών, και το HotXLS την εφαρμόζει όταν το όνομα προορισμού τελειώνει σε .xlsm. Δώστε του .xlsx αντ' αυτού και το Excel αρνείται τις μακροεντολές, παρόλο που τα bytes είναι φυσικά παρόντα στο πακέτο και θα αποσειριοποιούνταν κανονικά. Η επέκταση δεν είναι διακόσμηση· επιλέγει τον τύπο περιεχομένου που λέει στο Excel ότι επιτρέπεται να υπάρχει ένα έργο VBA. Τις περισσότερες φορές χρειάζεται απλώς να μεταφέρετε το φορτίο. Όταν χρειάζεται να διαβάσετε μέσα του, ας πούμε για να παραθέσετε ονόματα modules για μια αναφορά ελέγχου, η ParsedVBAProject εκθέτει ένα αναλυμένο μοντέλο modules ενώ η VbaProject παραμένει τα αρχικά ανέγγιχτα bytes

Επαναχρησιμοποίηση μακροεντολών από παλαιού τύπου βιβλία εργασίας XLS

Η πρόσοψη BIFF αντικατοπτρίζει αυτό το σύνολο εργαλείων με ένα επιπλέον βήμα. Η HasVBAProject εξετάζει ένα φορτωμένο αρχείο, η SaveVBAProjectToFile γράφει την αποθήκευση του έργου στον δίσκο, και η LoadVBAProjectFromFile τη διαβάζει ξανά μέσα σε ένα άλλο βιβλίο εργασίας. Η παράκαμψη μέσω ενός αρχείου κάνει μια συνηθισμένη δουλειά εκσυγχρονισμού απλή: σηκώστε τις μακροεντολές από ένα μοντέλο εποχής 2003 και φυτέψτε τις μέσα σε φρέσκια παραγόμενη έξοδο XLS, χωρίς να απαιτείται το αρχικό πρότυπο κατά την εκτέλεση

var
  Src, Dst: IXLSWorkbook;   // αναφορές interface: χωρίς χειροκίνητο Free
begin
  Src := TXLSWorkbook.Create;
  if Src.Open('legacy-model.xls') <= 0 then
    raise Exception.Create('Cannot open legacy model');
  if Src.HasVBAProject then
    Src.SaveVBAProjectToFile('extracted-vba.bin');

  Dst := TXLSWorkbook.Create;
  Dst.Sheets.Add.Name := 'Report2026';
  Dst.LoadVBAProjectFromFile('extracted-vba.bin');
  Dst.SaveAs('report-with-macros.xls');
end;

Το μοντέλο μνήμης είναι η παγίδα εδώ, και λειτουργεί αντίθετα από την κλάση XLSX. Η TXLSWorkbook κρατιέται μέσω του interface IXLSWorkbook που μετρά αναφορές, οπότε ποτέ δεν την ελευθερώνετε με το χέρι· η XLSX TXLSXWorkbook είναι ένα απλό αντικείμενο που πρέπει να τυλίξετε σε try..finally και να ελευθερώσετε. Ανακατέψτε τις δύο συμβάσεις σε μία μονάδα και ακολουθούν κρασαρίσματα double-free. Ένα ακόμα όριο που αξίζει να σεβαστείτε: κρατήστε την εξαγωγή και την εισαγωγή μέσα σε μία μόνο μορφή αρχείου. Η αποθήκευση έργου BIFF και το OOXML vbaProject.bin είναι ξαδέρφια, όχι το ίδιο container, και ένα pipeline που πρέπει να εκδίδει μακροεντολές και στις δύο μορφές θα πρέπει να κρατά ξεχωριστό πρότυπο μακροεντολών για κάθε μία

Εξωτερικοί σύνδεσμοι: ο χάρτης επιβιώνει, οι αποθηκευμένες τιμές όχι

Για βιβλία εργασίας XLSX, το HotXLS εκθέτει εξωτερικούς συνδέσμους μέσω της συλλογής ExternalLinks. Κάθε TXLSXExternalLink φέρει ένα Target, τη διαδρομή ή το URL του απομακρυσμένου βιβλίου εργασίας, καθώς και μια λίστα SheetNames που ονομάζει τα φύλλα στα οποία αναφέρεται. Και τα δύο επιβιώνουν άθικτα σε έναν κύκλο ανοίγματος-αποθήκευσης, και μπορείτε επίσης να χτίσετε έναν σύνδεσμο από την αρχή:

var
  Link: TXLSXExternalLink;
begin
  Link := Book.ExternalLinks.Add('\\fileserver\finance\fx-rates-2026.xlsx');
  Link.SheetNames.Add('FX');

  if Book.ExternalLinks.Count > 0 then
    Writeln(Format('%d external link(s): delivery requires reachable targets',
      [Book.ExternalLinks.Count]));
end;

Το όριο βρίσκεται ένα επίπεδο πιο βαθιά από τη λίστα προορισμών. Το HotXLS κάνει round-trip τον χάρτη συνδέσμων, δηλαδή τον προορισμό και τα ονόματα φύλλων, αλλά δεν αναλύει ούτε ξαναγράφει τις αποθηκευμένες τιμές κελιών που το OOXML κρατά στο στοιχείο sheetDataSet του συνδέσμου. Αυτή η cache είναι αυτό που επιτρέπει στο Excel να δείχνει τον τελευταίο γνωστό αριθμό όταν το αρχείο πηγής είναι εκτός σύνδεσης, και ένα παραγόμενο βιβλίο εργασίας αποστέλλεται χωρίς αυτήν. Η συνέπεια πέφτει στον παραλήπτη, όχι σε εσάς. Ανοίξτε ένα τέτοιο αρχείο όπου ο προορισμός είναι απρόσιτος, ένα laptop εκτός VPN ή ένα share που μετονομάστηκε, και οι τύποι που εξαρτώνται από τον σύνδεσμο επιλύονται σε #REF! ή κολλάνε πίσω από μια προτροπή ενημέρωσης. Έτσι προκύπτουν δύο κανόνες από αυτό. Μην υπόσχεστε ότι ένα παραγόμενο βιβλίο εργασίας θα εμφανίζει τις εξωτερικά συνδεδεμένες τιμές του εκτός σύνδεσης. Και διαβάστε ένα μη μηδενικό ExternalLinks.Count ως προϋπόθεση παράδοσης παρά ως χαρακτηριστικό: κάθε προορισμός πρέπει να είναι προσβάσιμος από όπου θα ανοιχτεί πραγματικά το αρχείο

Διάγραμμα ροής κλήσης αποθήκευσης Delphi όπου η επέκταση .xlsm διαλέγει τον τύπο περιεχομένου με υποστήριξη μακροεντολών και το .xlsx κάνει το Excel να αρνείται σιωπηλά τις μακροεντολές
Το HotXLS μεταφέρει τα ωμά byte vbaProject.bin μέσα από την αποθήκευση, και η επέκταση .xlsm είναι ό,τι διαλέγει τον τύπο περιεχομένου με ενεργοποιημένες μακροεντολές που απαιτεί το Excel

Τι διατηρεί ο αναγνώστης XLS byte προς byte

Για δομές που δεν μοντελοποιεί, η πλευρά BIFF έχει διαφορετική απάντηση: αφήστε τις ακριβώς όπως βρέθηκαν. Οι caches και οι προβολές pivot (η οικογένεια εγγραφών SX*), οι ορισμοί QueryTable, οι εξωτερικές συνδέσεις δεδομένων, οι προσαρμοσμένες προβολές, οι εικόνες κεφαλίδας και οι εγγραφές θέματος περνούν όλες μέσα από έναν κύκλο ανοίγματος-αποθήκευσης ως ακατέργαστα blocks εγγραφών, χωρίς ανάλυση και χωρίς τροποποίηση. Οι ίδιες οι εξωτερικές αναφορές κάνουν round-trip μέσω των υποκείμενων εγγραφών EXTERNSHEET και SupBook. Δεν υπάρχει typed API δημιουργίας γι' αυτές στην πλευρά XLS, αλλά ένας υπάρχων σύνδεσμος επιβιώνει ανέγγιχτος στην επεξεργασία

Διάγραμμα των μερών ενός εξωτερικού συνδέσμου βιβλίου εργασίας HotXLS που επιβιώνουν από επανεγγραφή και του τι συμβαίνει όταν οι cached τιμές πίσω από το sheetDataSet απουσιάζουν εκτός σύνδεσης
Το HotXLS κάνει μετ' επιστροφής τον στόχο συνδέσμου και τα ονόματα φύλλων του, αλλά οι cached τιμές κελιών πίσω από το sheetDataSet δεν μεταφέρονται στο παραγόμενο αρχείο

Η διατήρηση byte προς byte είναι μια γνήσια εγγύηση με μια αιχμηρή λεπτομέρεια. Επειδή τίποτα δεν διαβάζει μια διατηρημένη δομή, οι επεξεργασίες σας δεν μπορούν να τη διαφθείρουν. Για τον ίδιο λόγο, τίποτα δεν την ενημερώνει επίσης. Εισάγετε γραμμές μέσα σε μια περιοχή στην οποία δείχνει μια διατηρημένη cache pivot ή ένας πίνακας ερωτημάτων, και η δομή κρατά τις αρχικές συντεταγμένες της ενώ τα δεδομένα από κάτω μετατοπίζονται. Το αρχείο παραμένει έγκυρο XML ή BIFF· το νόημα έχει αθόρυβα παρεκκλίνει από την ευθυγράμμιση, και κανένα σφάλμα δεν πυροδοτείται για να σας ενημερώσει. Η υπερασπίσιμη διάταξη είναι να κρατάτε τις παραγόμενες επεξεργασίες σε φύλλα που δεν περιέχουν διατηρημένες δομές, κάτι που είναι η ίδια πειθαρχία που προστατεύει τα κλειδωμένα και ρυθμισμένα για εκτύπωση φύλλα στο άρθρο μας για την προστασία φύλλων εργασίας και τη διαμόρφωση σελίδας

Επαλήθευση του αρχείου που πραγματικά γράψατε

Και οι δύο τρόποι αποτυχίας είναι σιωπηλοί κατά τη στιγμή της εγγραφής, οπότε η επιβεβαίωση που έχει σημασία γίνεται ανοίγοντας ξανά την έξοδο αντί να εμπιστεύεστε τον κώδικα που την παρήγαγε. Τρεις έλεγχοι καλύπτουν σχεδόν τα πάντα. Ανοίξτε ξανά το αρχείο και επιβεβαιώστε ότι η HasVbaProject εξακολουθεί να επιστρέφει true όποτε αναμένονταν μακροεντολές, κάτι που πιάνει ένα χαμένο φορτίο και μια λάθος επέκταση σε ένα μόνο test. Διαβάστε το ExternalLinks.Count και συγκρίνετέ το με τον αριθμό από πριν την επανεγγραφή. Στη συνέχεια ανοίξτε το αρχείο μία φορά στο Excel με απενεργοποιημένες τις μακροεντολές, επειδή η επικύρωση τύπου περιεχομένου του Excel είναι πιο αυστηρή από οποιασδήποτε βιβλιοθήκης, και το Excel είναι το πρόγραμμα με βάση το οποίο οι πελάτες σας θα κρίνουν το αρχείο

Τίποτα από αυτά δεν απαιτεί πλήρη ανάλυση κατά την είσοδο. Όταν βιβλία εργασίας φτάνουν σε όγκο και χρειάζεται μόνο να διαλέξετε ποια φέρουν διεπόμενο περιεχόμενο, η ελαφριά εξέταση στο άρθρο μας για τον κατάλογο φύλλων και την ελαφριά επιθεώρηση βιβλίων εργασίας σάς επιτρέπει να δρομολογήσετε αρχεία με μακροεντολές και συνδέσμους σε ένα αυστηρότερο pipeline προτού τρέξει ποτέ η πρώτη επανεγγραφή

Μερικές ερωτήσεις προκύπτουν αρκετά συχνά ώστε να αξίζει να απαντηθούν απευθείας. Το HotXLS ποτέ δεν εκτελεί τις μακροεντολές που διατηρεί: δεν υπάρχει VBA runtime στη βιβλιοθήκη, μόνο ο μηχανισμός για να αποθηκεύει, αντιγράφει, εξάγει και εισάγει το έργο ως δεδομένα. Σε έναν server αυτό είναι μια ιδιότητα ασφάλειας που αξίζει να δηλωθεί, αφού μια εχθρική μακροεντολή που περνά μέσα από το pipeline παραμένει αδρανής μέχρι ένα desktop Excel να ανοίξει το αρχείο και ένας χρήστης να ενεργοποιήσει το περιεχόμενο. Η μετατροπή ενός .xlsm σε .xlsx διατηρώντας τις μακροεντολές δεν είναι δυνατή, και αυτός είναι κανόνας της μορφής και όχι περιορισμός της βιβλιοθήκης: ο τύπος περιεχομένου .xlsx δηλώνει ένα βιβλίο εργασίας χωρίς μακροεντολές, οπότε τα μόνα έντιμα αποτελέσματα είναι να παραμείνει .xlsm ή να καλέσετε την ClearVbaProject και να παραδώσετε ένα αρχείο που γνήσια δεν έχει καμία. Η σιωπηλή μετονομασία είναι η μία επιλογή που δεν ικανοποιεί κανέναν. Και όταν συνδεδεμένα κελιά δείχνουν #REF! μετά από μια επανεγγραφή, η αιτία είναι η λείπουσα cache τιμών που συζητήθηκε παραπάνω: το νέο αρχείο φέρει τον προορισμό αλλά όχι τους αποθηκευμένους αριθμούς, οπότε το Excel πρέπει να επιλύσει την πηγή κατά το άνοιγμα, και μια απρόσιτη ή σχετική με το περιβάλλον διαδρομή το ματαιώνει. Είτε εγγυηθείτε ότι ο προορισμός είναι προσβάσιμος είτε γράψτε υπολογισμένες τιμές μέσα στα κελιά πριν από την παράδοση και αφαιρέστε εντελώς την εξάρτηση

Η επεξεργασία βιβλίων εργασίας άλλων ανθρώπων είναι κυρίως η δουλειά της διατήρησης πραγμάτων που δεν γράψατε εσείς και δεν κατανοείτε πλήρως. Οι δυνατότητες round-trip για VBA και εξωτερικούς συνδέσμους που περιγράφονται εδώ συνοδεύουν το HotXLS Delphi Component για Delphi και C++Builder, μαζί με τις ιδιότητες ελέγχου που σας επιτρέπουν να εντοπίζετε διεπόμενο περιεχόμενο τη στιγμή που φτάνει ένα αρχείο