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

Ανάγνωση αρχείων Excel 2.0 έως 4.0 στη Delphi με το HotXLS

Το HotXLS ανοίγει βιβλία εργασίας γραμμένα από Excel 2.0, 3.0 και 4.0 απευθείας από τη Delphi και το C++Builder. Αυτά τα αρχεία προηγούνται του σύνθετου εγγράφου OLE που χρησιμοποιεί κάθε μεταγενέστερο .xls, οπότε είναι ακατέργαστες ροές εγγραφών BIFF χωρίς κανένα περιτύλιγμα αποθήκευσης, και ένας αναγνώστης χτισμένος για BIFF8 δεν θα βρει ούτε μία αναγνωρίσιμη δομή μέσα τους. Το άνοιγμα ενός τέτοιου αρχείου χρησιμοποιεί την ίδια κλήση Open με κάθε άλλο βιβλίο εργασίας· ο αναγνώστης εντοπίζει τη μορφή και αλλάζει διαδρομή

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

Τι κάνει ένα βιβλίο εργασίας πριν το OLE διαφορετικό;

Κάθε .xls από το Excel 5.0 και μετά είναι ένα σύνθετο αρχείο OLE2, ένα μικρό σύστημα αρχείων μέσα σε ένα αρχείο, με το βιβλίο εργασίας να ζει σε μια ροή με όνομα Workbook ή Book. Η ανάλυση ενός τέτοιου αρχείου ξεκινά με την ανάλυση εκείνου του περιέκτη, όπως περιγράφεται στη δυαδική μορφή σύνθετου αρχείου σε Pascal

Τα BIFF2 έως BIFF4 δεν έχουν περιέκτη. Το αρχείο ξεκινά αμέσως με μια εγγραφή BOF, και ο αριθμός εγγραφής εκείνου του BOF κωδικοποιεί τη γενιά: $0009 για BIFF2, $0209 για BIFF3 και $0409 για BIFF4. Το HotXLS επικυρώνει το μήκος σώματος BOF, που είναι μεταξύ τεσσάρων και έξι byte, και τον τύπο υπορροής, $0010 για φύλλο εργασίας, $0020 για γράφημα και $0040 για φύλλο μακροεντολών, πριν δεσμευτεί στην ακατέργαστη διαδρομή. Αυτή η επικύρωση είναι αυτό που εμποδίζει ένα κατεστραμμένο ή λανθασμένα αναγνωρισμένο αρχείο από το να ερμηνευτεί ως πολύ παλιό βιβλίο εργασίας

Τρεις γενιές, τρεις διατάξεις εγγραφών

Οι εγγραφές κελιών είναι όπου οι γενιές αποκλίνουν πιο ορατά. Το BIFF2 καταλαμβάνει ένα συνεχόμενο μπλοκ χαμηλών αριθμών εγγραφών, $0001 έως $0005 για κενά, ακέραιους, αριθμητικά, ετικέτες και κελιά boolean-ή-σφάλμα, και κάθε σώμα φέρει ένα πεδίο χαρακτηριστικού τριών byte όπου μεταγενέστερες εκδόσεις βάζουν εκτεταμένο ευρετήριο μορφής. Τα BIFF3 και BIFF4 εγκαταλείπουν αυτό και επαναχρησιμοποιούν τους αριθμούς εγγραφών και τις διατάξεις του BIFF5, $0201, $0203, $0204 και $0205, με ευρετήριο XF δύο byte

Αυτή η τελευταία λεπτομέρεια προκαλεί μια συγκεκριμένη και εύκολα λανθασμένα διαγνώσιμη αποτυχία. Μια εγγραφή LABEL BIFF3 ή BIFF4 είναι δομικά ταυτόσημη με το αντίστοιχό της BIFF5, γραμμή και στήλη ακολουθούμενες από το ευρετήριο μορφής και έπειτα τον αριθμό χαρακτήρων. Γράψτε έναν αναγνώστη που υποθέτει τη διάταξη BIFF2 και διαβάζει δύο byte λιγότερα απ' όσο πρέπει, έπειτα περπατά πέρα από το τέλος της εγγραφής και παρερμηνεύει οτιδήποτε ακολουθεί. Το σύμπτωμα δεν είναι εξαίρεση· είναι ένα βιβλίο εργασίας που διαβάζεται με εύλογα σκουπίδια μέσα του

Οι εγγραφές τύπων καταλαμβάνουν παράλληλη αρίθμηση και στις τρεις γενιές, $0006, $0206 και $0406. Όταν ένας τύπος παράγει αποτέλεσμα συμβολοσειράς, εκείνη η συμβολοσειρά φτάνει σε ξεχωριστή επόμενη εγγραφή, $0007 ή $0207, και η μορφή BIFF2 της χρησιμοποιεί πρόθεμα μήκους ενός byte αντί για το δύο byte που χρησιμοποιείται αργότερα

Γιατί οι τύποι επιστρέφουν ως τιμές, όχι ως κείμενο

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

Η αναλυμένη έκφραση στο BIFF2 έως BIFF4 χρησιμοποιεί κωδικοποίηση διακριτικών που διαφέρει από το BIFF5 και μεταγενέστερα με τρόπους που ξεπερνούν το αισθητικό: τα μήκη διακριτικών έχουν διαφορετικό πρόθεμα, τα διακριτικά αναφοράς έχουν διαφορετικά μεγέθη, και οι πίνακες ευρετηρίου συναρτήσεων αναριθμήθηκαν ανάμεσα στις γενιές. Το πέρασμα εκείνων των byte μέσα από έναν μεταφραστή εκφράσεων BIFF8 δεν παράγει λανθασμένο τύπο, παράγει τυχαίο. Η ανάγνωση της προσωρινά αποθηκευμένης τιμής σας δίνει τον αριθμό ή τη συμβολοσειρά που το Excel υπολόγισε τελευταία, που είναι αυτό που πραγματικά χρειάζεται μια μετάπτωση αρχείου

Η προσωρινά αποθηκευμένη τιμή ζει σε μια μετατόπιση εξαρτώμενη από τη γενιά μέσα στην εγγραφή: byte 7 για BIFF2 και byte 6 για BIFF3 και BIFF4. Ειδικές τιμές, συμβολοσειρές, boolean, σφάλματα και κενά, κωδικοποιούνται σε μια λέξη-δείκτη $FFFF με διακριτικό, την ίδια σύμβαση που κράτησαν μεταγενέστερες γενιές BIFF

Άνοιγμα ενός τέτοιου αρχείου

Ο κώδικας κλήσης είναι κοινότυπος, που είναι το ζητούμενο. Ο εντοπισμός συμβαίνει μέσα στην Open:

uses
  lxHandle;

var
  Book: TXLSWorkbook;
  Sheet: TXLSWorksheet;
  R, C: Integer;
  V: Variant;
begin
  Book := TXLSWorkbook.Create;
  try
    if Book.Open('archive\1993-inventory.xls') <> 1 then
    begin
      Writeln('unreadable - quarantine for manual review');
      Exit;
    end;
    Sheet := Book.Sheets[1];          // Το Sheets[] ξεκινά από το 1
    for R := Sheet.UsedRange.FirstRow + 1 to Sheet.UsedRange.LastRow + 1 do
      for C := Sheet.UsedRange.FirstCol + 1 to Sheet.UsedRange.LastCol + 1 do
      begin
        V := Sheet.Cells[R, C].Value;
        if not VarIsEmpty(V) then
          Writeln(Format('R%dC%d = %s', [R, C, VarToStr(V)]));
      end;
  finally
    Book.Free;
  end;
end;

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

Τι δεν λαμβάνετε, και τι να κάνετε γι' αυτό

Η μορφοποίηση δεν ερμηνεύεται. Το HotXLS δεν αναλύει τις εγγραφές XF και FONT αυτών των γενιών, οπότε γραμματοσειρές, χρώματα, περιγράμματα και μορφές αριθμών δεν είναι διαθέσιμα, και κελιά που το Excel κάποτε εμφάνιζε ως ημερομηνίες επιστρέφουν ως οι ακατέργαστοι σειριακοί αριθμοί τους

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

// Αποφασίστε ανά στήλη, ποτέ ανά τιμή: ένας πενταψήφιος αριθμός
// μπορεί να είναι ημερομηνία ή κωδικός ανταλλακτικού, και η
// παλαιότερη μορφή δεν θα σας το πει
if ColumnHoldsDates(C) then
begin
  // Τα δύο συστήματα ημερομηνίας απέχουν 1462 ημέρες, οπότε ο ίδιος
  // σειριακός αριθμός δηλώνει δύο ημερομηνίες τέσσερα χρόνια
  // απόσταση. Διαβάστε το σύστημα από το βιβλίο εργασίας αντί να
  // υποθέσετε ένα
  if Book.Date1904 then
    Writeln(DateToStr(SerialToDate1904(V)))
  else
    Writeln(DateToStr(SerialToDate1900(V)));
end
else
  Writeln(VarToStr(V));

Δύο δομικές σημειώσεις ολοκληρώνουν την εικόνα. Οι εγγραφές προστασίας κωδικού πρόσβασης και σελίδας κώδικα εμφανίζονται μέσα στη μοναδική ροή φύλλου εργασίας αντί σε ροή επιπέδου βιβλίου εργασίας, επειδή δεν υπάρχει ροή επιπέδου βιβλίου εργασίας για να μπουν, οπότε πρέπει να αναγνωριστούν σε πλαίσιο φύλλου εργασίας. Και ένα αρχείο BIFF2 έως BIFF4 περιέχει ακριβώς μία υπορροή φύλλου· βιβλία εργασίας με πολλά φύλλα δεν υπήρχαν μέχρι η μορφή να αποκτήσει τον περιέκτη της

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