Tehnični članak

Branje datotek Excel 2.0 do 4.0 v Delphiju s HotXLS

HotXLS neposredno iz Delphija in C++Builderja odpira delovne zvezke, ki jih je zapisal Excel 2.0, 3.0 in 4.0. Te datoteke so starejše od vsebnika sestavljenega dokumenta OLE, ki ga uporablja vsak poznejši .xls, zato so surovi tokovi zapisov BIFF povsem brez ovojnice za shrambo, bralnik, zgrajen za BIFF8, pa v njih ne bo našel niti ene prepoznavne strukture. Odpiranje ene od njih uporablja isti klic Open kot vsak drug delovni zvezek; bralnik zazna obliko in preklopi pot

Te datoteke se še vedno pojavljajo, kar je edini razlog, da je karkoli od tega pomembno. Inženirski arhivi, hramba državnih evidenc, laboratorijski podatki iz instrumentov, katerih krmilna programska oprema je bila napisana leta 1993, in dolgo delujoči računovodski sistemi so vsi za sabo pustili delovne zvezke BIFF2 in BIFF4. Sodobni Excel več izmed njih naravnost zavrne odpreti, saj je iz varnostnih razlogov odstranil podedovane pretvornike, kar pusti nabor podatkov, ki ga nihče ne more prebrati z orodjem, ki bi ga kdorkoli imel

Po čem se predOLE delovni zvezek razlikuje?

Vsak .xls od Excela 5.0 naprej je sestavljena datoteka OLE2, majhen datotečni sistem znotraj datoteke, pri čemer delovni zvezek živi v toku z imenom Workbook ali Book. Razčlenjevanje takšne datoteke se začne z razčlenjevanjem tega vsebnika, kot je opisano v binarni obliki sestavljene datoteke v Pascalu

BIFF2 do BIFF4 nimajo vsebnika. Datoteka se začne neposredno z zapisom BOF, številka tega zapisa BOF pa kodira generacijo: $0009 za BIFF2, $0209 za BIFF3 in $0409 za BIFF4. HotXLS pred zavezo surovi poti preveri dolžino telesa BOF, ki je med štiri in šest bajtov, in vrsto podtoka, $0010 za delovni list, $0020 za grafikon in $0040 za makro list. Prav ta preverba prepreči, da bi bila pokvarjena ali napačno prepoznana datoteka interpretirana kot zelo star delovni zvezek

Tri generacije, tri postavitve zapisov

Zapisi celic so mesto, kjer se generacije najbolj vidno razhajajo. BIFF2 zaseda strnjen blok nizkih številk zapisov, $0001 do $0005 za prazne, celoštevilske, številske, oznaka in logične ali napačne celice, vsako telo pa nosi tribajtno polje atributov, tja, kamor poznejše različice postavijo razširjen indeks oblike. BIFF3 in BIFF4 to opustita in ponovno uporabita številke in postavitve zapisov BIFF5, $0201, $0203, $0204 in $0205, z dvobajtnim indeksom XF

Ta zadnja podrobnost povzroči specifično in zlahka napačno diagnosticirano napako. Zapis LABEL BIFF3 ali BIFF4 je strukturno enak svoji ustreznici BIFF5: vrstica in stolpec, ki jima sledita indeks oblike in nato število znakov. Napišite bralnik, ki predpostavi postavitev BIFF2, in ta prebere dva bajta premalo, nato pa steče čez konec zapisa in napačno interpretira vse, kar sledi. Simptom ni izjema; je delovni zvezek, ki se prebere z verjetno videti smetmi v sebi

Zapisi formul zasedajo vzporedno oštevilčenje čez vse tri generacije, $0006, $0206 in $0406. Kadar formula da niz kot rezultat, ta niz prispe v ločenem, naslednjem zapisu, $0007 ali $0207, njegova oblika BIFF2 pa uporablja enobajtno predpono dolžine namesto dvobajtne, uporabljene pozneje

Zakaj se formule vrnejo kot vrednosti in ne kot besedilo

HotXLS v teh datotekah prebere predpomnjeni rezultat formule in ne poskuša obnoviti izraza formule. To je namerna meja, ne vrzel, ki čaka na zapolnitev

Razčlenjeni izraz v BIFF2 do BIFF4 uporablja kodiranje žetonov, ki se od BIFF5 in poznejših razlikuje na načine, ki presegajo kozmetiko: dolžine žetonov so predponsko označene drugače, referenčni žetoni imajo drugačne velikosti, tabele indeksov funkcij pa so bile med generacijami preštevilčene. Speljati te bajte skozi prevajalnik izrazov BIFF8 ne da napačne formule, ampak naključno. Branje predpomnjene vrednosti vam da število ali niz, ki ga je Excel nazadnje izračunal, kar je natanko to, kar migracija arhiva dejansko potrebuje

Predpomnjena vrednost živi na od generacije odvisnem odmiku znotraj zapisa: bajt 7 za BIFF2 in bajt 6 za BIFF3 in BIFF4. Posebne vrednosti — nizi, logične vrednosti, napake in prazne vrednosti — so kodirane v oznaki besede $FFFF z razlikovalnikom, isti konvenciji, ki so jo ohranile poznejše generacije BIFF

Odpiranje ene

Klicna koda je nič posebnega, in prav to je bistvo. Zaznavanje poteka znotraj 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[] je od ena štet
    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;

Bodite pozorni na indeksno aritmetiko v tej zanki. Meje UsedRange so od nič štete, medtem ko sta tako zbirka listov kot dostop do celic od ena šteta, kar je neskladje, ki je starejše od trenutnega API in je ohranjeno zaradi združljivosti. Če prilagoditev pozabite, revidirate napačen pravokotnik in pri tem ne javite ničesar nenavadnega. Poceni predhodne preverbe, ki se izognejo nalaganju datoteke, so opisane v lahki inšpekciji delovnega zvezka

Kaj ne dobite, in kaj storiti glede tega

Oblikovanje ni interpretirano. HotXLS ne razčlenjuje zapisov XF in FONT teh generacij, zato pisave, barve, obrobe in številske oblike niso na voljo, celice, ki jih je Excel nekoč prikazoval kot datume, pa se vrnejo kot svoja surova zaporedna števila

To zadnje je treba obravnavati v vaši lastni kodi in ne v bralniku, razlog pa je pošten: številske oblike v BIFF2 do BIFF4 niso dovolj zanesljive, da bi vodile samodejno odločitev o datumu. Stolpec petmestnih števil so lahko datumi, lahko pa so številke delov. Pretvarjajte namensko, z uporabo datumskega sistema delovnega zvezka, čigar pravila so opisana v zaporednih številkah datumov, sistemu 1904 in številskih oblikah:

// Odločajte po stolpcu, nikoli po vrednosti: petmestno število je lahko
// datum ali številka dela, podedovana oblika pa vam tega ne pove
if ColumnHoldsDates(C) then
begin
  // Datumska sistema sta razmaknjena za 1462 dni, zato isto zaporedno
  // število označuje dva datuma, oddaljena štiri leta. Sistem preberite
  // iz delovnega zvezka, namesto da bi ga predpostavili
  if Book.Date1904 then
    Writeln(DateToStr(SerialToDate1904(V)))
  else
    Writeln(DateToStr(SerialToDate1900(V)));
end
else
  Writeln(VarToStr(V));

Sliko dopolnita dve strukturni opombi. Zapisi zaščite z geslom in kodne strani se pojavijo znotraj enega samega toka delovnega lista in ne v toku na ravni delovnega zvezka, ker ni toka na ravni delovnega zvezka, v katerega bi jih dali, zato jih je treba prepoznati v kontekstu delovnega lista. Datoteka BIFF2 do BIFF4 vsebuje natanko en podtok lista; večlistni delovni zvezki niso obstajali, dokler oblika ni dobila svojega vsebnika

Pragmatična pot migracije je zato dvostopenjska: podedovano datoteko preberite zaradi njenih vrednosti, nato pa napišite sodoben delovni zvezek, ki nosi te vrednosti z oblikovanjem, ki ga uveljavite sami. Podedovano branje, sodobno pisanje in vse vmes teče v eni knjižnici za Delphi in C++Builder, opisani na strani komponente HotXLS Excel za Delphi