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

Γρήγορη καταγραφή ονομάτων φύλλων στο Delphi με το HotXLS GetSheetNames

Μερικές φορές το μόνο ερώτημα που χρειάζεται να απαντήσει μια ρουτίνα εισαγωγής είναι διαρθρωτικό: αυτό το βιβλίο εργασίας έχει φύλλο με όνομα «Mapping», ή πόσες καρτέλες φέρει. Η απάντηση σε αυτό καλώντας την Open είναι ο ακριβός τρόπος να το κάνετε. Ένα πλήρες άνοιγμα διογκώνει τον πίνακα κοινών συμβολοσειρών, αποκωδικοποιεί κάθε εγγραφή στυλ και διατρέχει τα κελιά κάθε φύλλου εργασίας, επειδή δεν έχει τρόπο να γνωρίζει ότι θέλατε μόνο τον πίνακα περιεχομένων. Σε ένα μεγάλο αρχείο αυτό σημαίνει εκατοντάδες megabyte δεσμεύσεων μνήμης και αρκετά δευτερόλεπτα CPU για να διαβαστεί μια λίστα που καταλαμβάνει μερικά kilobyte. Το HotXLS, η εγγενής βιβλιοθήκη υπολογιστικών φύλλων της losLab για Delphi, σας δίνει αυτή τη λίστα από μόνη της: η GetSheetNames επιστρέφει τα ονόματα των φύλλων εργασίας, με τη σειρά του βιβλίου εργασίας, χωρίς να υλοποιήσει ούτε ένα κελί

Γιατί ο κατάλογος είναι φθηνός στην ανάγνωση

Και οι δύο μορφές υπολογιστικών φύλλων τοποθετούν τον πίνακα περιεχομένων τους κοντά στην αρχή, κάτι που κάνει μια κλήση καταλόγου γρήγορη και όχι απλώς έξυπνη. Ένα πακέτο OOXML διατηρεί τον κατάλογο φύλλων στο xl/workbook.xml, ένα μέρος που παραμένει μικρό είτε το βιβλίο εργασίας έχει δέκα γραμμές είτε δέκα εκατομμύρια. Ένα BIFF8 .xls αποθηκεύει τις εγγραφές BoundSheet στην αρχή της ροής globals του βιβλίου εργασίας, πριν από οποιαδήποτε δεδομένα κελιών. Έτσι, η εργασία που αποφεύγει μια κλήση καταλόγου δεν είναι ένα αμελητέο σφάλμα στρογγυλοποίησης σε σχέση με ένα πλήρες άνοιγμα. Είναι το μεγαλύτερο μέρος του αρχείου. Η ανάγνωση του καταλόγου κοστίζει τα ίδια λίγα kilobyte ανεξάρτητα από τον αριθμό γραμμών, ενώ ένα πλήρες άνοιγμα κλιμακώνεται με τα δεδομένα, και σε ένα βιβλίο εργασίας πολλών megabyte αυτό το χάσμα φτάνει σε αρκετές τάξεις μεγέθους τόσο στα bytes που αγγίζονται όσο και στη μνήμη που δεσμεύεται

Το GetSheetNames HotXLS σε Delphi διαβάζει μόνο τον κατάλογο φύλλων ενός αρχείου XLSX ή XLS ενώ ένα πλήρες άνοιγμα διασχίζει κάθε κελί
Ο κατάλογος κάθεται στο workbook.xml ή στις εγγραφές BoundSheet, οπότε η απαρίθμηση κοστίζει λίγα kilobytes ενώ ένα πλήρες άνοιγμα κλιμακώνεται με τα δεδομένα

Αυτό το σταθερό κόστος είναι η ιδιότητα γύρω από την οποία αξίζει να σχεδιάσετε. Μια πύλη εισαγωγής χτισμένη πάνω στην GetSheetNames συμπεριφέρεται το ίδιο σε ένα αρχείο 200 γραμμών και σε ένα των 200 MB, οπότε το πιο αργό αρχείο μιας παρτίδας δεν καθορίζει πλέον τον ρυθμό για την απόφαση αν ένα αρχείο αξίζει καν να επεξεργαστεί

Μία κλήση για .xls, .xlsx και τις μορφές προτύπων

Στην πρόσοψη XLS, η TXLSWorkbook.GetSheetNames διαβάζει περισσότερα από .xls. Δέχεται επίσης τα βασισμένα σε zip .xlsx, .xlsm, .xltx και .xltm, εξάγοντας μόνο το workbook.xml από το αρχείο. Για γνήσια είσοδο .xls σαρώνει τις εγγραφές BoundSheet και σταματά στην πρώτη εγγραφή EOF της υπο-ροής globals, οπότε ένα μεγάλο δυαδικό αρχείο εξακολουθεί να κοστίζει μόνο τα αρχικά του kilobyte. Η πρόσοψη XLSX φέρει μια εγγύηση που έχει μεγαλύτερη σημασία για κώδικα υπηρεσίας που τρέχει επί μακρόν απ' όσο φαίνεται στην αρχή: η TXLSXWorkbook.GetSheetNames δεν αφήνει το instance του βιβλίου εργασίας ούτε επαναφερμένο ούτε γεμάτο, οπότε ένα instance που ήδη κρατά ανοιχτό ένα έγγραφο μπορεί να εξετάσει άλλα αρχεία χωρίς να διαταράξει αυτό που κρατά. Η GetODSSheetNames εφαρμόζει την ίδια προσέγγιση σε πακέτα OpenDocument, και καθεμία από αυτές τις κλήσεις έχει μια υπερφόρτωση με stream, η οποία σας επιτρέπει να εξετάσετε ένα upload που δεν προσγειώνεται ποτέ στον δίσκο

var
  Book: TXLSXWorkbook;
  Names: TStringList;
  I: Integer;
begin
  Names := TStringList.Create;
  Book := TXLSXWorkbook.Create;
  try
    if Book.GetSheetNames('upload-7f3a.xlsx', Names) <= 0 then
      raise Exception.Create('unreadable workbook package');
    if Names.IndexOf('Mapping') < 0 then
      raise Exception.Create('required Mapping sheet is missing');
    for I := 0 to Names.Count - 1 do
      Writeln(Format('sheet %d: %s', [I, Names[I]]));
  finally
    Book.Free;
    Names.Free;
  end;
end;

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

Τα αρχεία .xlsm με ενεργοποιημένες μακροεντολές και οι μορφές προτύπων παραθέτονται ακριβώς όπως ένα απλό .xlsx, αφού ο κατάλογος βρίσκεται στο ίδιο workbook.xml είτε υπάρχει είτε όχι ένα vbaProject.bin μέσα στο πακέτο. Ένα pipeline εισαγωγής μπορεί επομένως να απαριθμήσει τα φύλλα ενός βιβλίου εργασίας με μακροεντολές για δρομολόγηση, χωρίς ποτέ να αγγίζει το περιεχόμενο της μακροεντολής και χωρίς να κάνει ποτέ κάτι που θα την εκτελούσε, αφήνοντας την απόφαση πολιτικής μακροεντολών στο στάδιο που πραγματικά ανοίγει το αρχείο

Ανάγνωση της τιμής επιστροφής χωρίς αυταπάτες

Οι συμβάσεις τιμών επιστροφής δεν είναι ενιαίες σε όλο το HotXLS. Ορισμένες κλήσεις επιστρέφουν 1 σε επιτυχία, άλλες επιστρέφουν έναν αριθμό, οπότε για τις συναρτήσεις καταλόγου ο μόνος έλεγχος που ευσταθεί είναι να αντιμετωπίζετε κάθε τιμή μηδέν ή κάτω από το μηδέν ως αποτυχία, με τη λίστα συμβολοσειρών άδεια. Αντισταθείτε στον πειρασμό να διαβάσετε μια κενή λίστα ως «βιβλίο εργασίας χωρίς φύλλα». Τόσο το ECMA-376 όσο και η προδιαγραφή BIFF8 απαιτούν τουλάχιστον ένα φύλλο σε ένα έγκυρο βιβλίο εργασίας, οπότε μηδέν ονόματα σημαίνει πάντα ότι η ανάγνωση απέτυχε, ποτέ ότι το αρχείο είναι νόμιμα κενό

Μια αποτυχημένη λίστα είναι από μόνη της ένα σήμα που αξίζει να διατηρηθεί. Ένα αρχείο .xlsx που αποτυγχάνει στην κλήση είναι ένα από λίγα συγκεκριμένα πράγματα: περικομμένο, όχι πραγματικά πακέτο OOXML (εξαγωγές CSV λανθασμένα επισημασμένες από άλλα συστήματα εμφανίζονται εδώ συνεχώς), ή ένας κρυπτογραφημένος περιέκτης. Η διάκριση μεταξύ αυτών είναι δουλειά του επόμενου ελέγχου. Η καταγραφή των πρώτων bytes του απορριφθέντος αρχείου μαζί με την αποτυχία συνήθως μετατρέπει ένα νήμα υποστήριξης σε ένα και μόνο μήνυμα

Ανίχνευση κρυπτογραφημένων περιεκτών πριν από τη δρομολόγηση

Ένα κρυπτογραφημένο .xlsx δεν είναι zip. Είναι ένα σύνθετο αρχείο OLE που τυλίγει ροές EncryptionInfo και EncryptedPackage, οπότε η GetSheetNames δεν μπορεί να δει μέσα του και επιστρέφει αποτυχία όπως κάθε άλλο μη αναγνώσιμο αρχείο. Η CanReadEncrypted ελέγχει για αυτό το σχήμα περιέκτη, κάτι που επιτρέπει στην εισαγωγή να δρομολογήσει σκόπιμα ένα κρυπτογραφημένο αρχείο αντί να καταπίνει ένα γενικό σφάλμα ανάγνωσης από κάπου βαθιά μέσα σε έναν worker:

Ροή ταξινόμησης εισερχόμενων Delphi με CanReadEncrypted και GetSheetNames HotXLS που δρομολογεί uploads σε needs-password, δυσανάγνωστο ή κανονικό
Η CanReadEncrypted τρέχει πρώτη επειδή ένα κρυπτογραφημένο αρχείο OOXML είναι περιέκτης OLE που οι κλήσεις απαρίθμησης δεν μπορούν να δουν μέσα
type
  TIntakeRoute = (irNormal, irNeedsPassword, irUnreadable);

function ClassifyUpload(const FileName: string; Names: TStrings): TIntakeRoute;
var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    // Το κρυπτογραφημένο OOXML είναι περιέκτης OLE, όχι zip: ελέγξτε πρώτα,
    // επειδή οι κλήσεις καταλόγου δεν μπορούν να δουν μέσα του.
    if Book.CanReadEncrypted(FileName) then
      Exit(irNeedsPassword);
    if SameText(ExtractFileExt(FileName), '.ods') then
    begin
      if Book.GetODSSheetNames(FileName, Names) <= 0 then
        Exit(irUnreadable);
    end
    else if Book.GetSheetNames(FileName, Names) <= 0 then
      Exit(irUnreadable);
    Result := irNormal;
  finally
    Book.Free;
  end;
end;

Η κρυπτογράφηση είναι το σημείο όπου το HotXLS είναι σκόπιμα ασύμμετρο, οπότε η δρομολόγηση πρέπει να το σέβεται. Η παλαιού τύπου κρυπτογράφηση .xls (RC4, RC4 CryptoAPI, XOR) είναι αναγνώσιμη: η TXLSWorkbook.Open(FileName, Password) αποκρυπτογραφεί με έναν αποθηκευμένο κωδικό πρόσβασης, και αυτά τα αρχεία μπορούν να παραμείνουν στην αυτοματοποιημένη διαδρομή. Τα κρυπτογραφημένα πακέτα OOXML πηγαίνουν προς την αντίθετη κατεύθυνση. Το HotXLS μπορεί να γράψει ένα με την SaveAsEncrypted, αλλά δεν μπορεί να το διαβάσει ξανά. Η OpenEncrypted εγείρει την EXlsxEncryptionNotImplemented όταν της δοθεί ένα κρυπτογραφημένο πακέτο, γι' αυτό και ένας ειλικρινής σχεδιασμός εισαγωγής στέλνει τα κρυπτογραφημένα .xlsx σε έναν άνθρωπο με Excel και κρατά το .xls με κωδικό πρόσβασης στον κώδικα

Για δουλειές παρτίδας αυτός ο ταξινομητής κερδίζει τη θέση του τρέχοντας πάνω σε έναν ολόκληρο εισερχόμενο φάκελο πριν κάποιος worker ξεκινήσει την πραγματική επεξεργασία, αφού κάθε δοκιμή κοστίζει περίπου ένα άνοιγμα αρχείου και μερικά kilobyte αναγνώσεων. Το να τον φέρετε στην αρχή αλλάζει τον τρόπο αποτυχίας που πραγματικά ενδιαφέρει τις λειτουργίες. Αντί για μια δουλειά στις 3 το πρωί που πεθαίνει στο αρχείο 412 από τα 600, παίρνετε 412 αρχεία σε ουρά και 5 απορριφθέντα κατά την εισαγωγή, με έναν λόγο συνδεδεμένο στο καθένα. Ίδιες κλήσεις βιβλιοθήκης, πολύ καλύτερη λειτουργική εικόνα

Τα ερωτήματα που μια κλήση καταλόγου δεν μπορεί να απαντήσει

Τα ονόματα και η σειρά είναι το σύνολο αυτού που παίρνετε. Οι κλήσεις καταλόγου δεν λένε τίποτα για την ορατότητα, οπότε τα κρυμμένα και τα πολύ κρυμμένα φύλλα φτάνουν στη λίστα σαν όλα τα άλλα. Δεν αναφέρουν διαστάσεις used-range, ούτε πλήθος κελιών, ούτε ιδιότητες εγγράφου. Το τμήμα docProps/core.xml είναι επίσης μικρό, αλλά δεν υπάρχει σήμερα δοκιμή μόνο για ιδιότητες, οπότε τα μεταδεδομένα συγγραφέα και τίτλου εξακολουθούν να κοστίζουν ένα πλήρες Open. Ο καθαρός τρόπος να ζήσετε με αυτό είναι να αφήσετε τα φθηνά γεγονότα να δρομολογούν κάθε αρχείο και να επιφυλάξετε τα ακριβά για αρχεία που επιβιώνουν από τη δρομολόγηση. Για τα αρχεία που πράγματι προχωρούν σε βαθιά ανάγνωση, μια σάρωση μόνο για ανάγνωση ενός μεγάλου .xls τρέχει αισθητά πιο γρήγορα με _DisableGraphics := True, η οποία παρακάμπτει την ανάλυση OfficeArt. Απλώς μην αποθηκεύσετε ποτέ από αυτό το instance: το επίπεδο σχεδίασης που παρέκαμψε έχει χαθεί από το μοντέλο, και η αποθήκευση θα το αφαιρούσε από το αρχείο

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

Το HotXLS είναι μια εγγενής βιβλιοθήκη υπολογιστικών φύλλων Object Pascal για Delphi και C++Builder· η πλήρης επιφάνεια API, συμπεριλαμβανομένων των κλήσεων επιθεώρησης που παρουσιάζονται εδώ, τεκμηριώνεται στη σελίδα προϊόντος HotXLS Delphi Component