Tehnički članak

Čitanje Excel 2.0 do 4.0 datoteka u Delphiju uz HotXLS

HotXLS izravno iz Delphija i C++Buildera otvara radne knjige napisane u Excelu 2.0, 3.0 i 4.0. Te datoteke prethode OLE spojenom dokumentnom kontejneru koji koristi svaka kasnija .xls datoteka, pa su to sirovi tokovi BIFF zapisa bez ikakvog omota za pohranu, a čitač izgrađen za BIFF8 u njima neće pronaći nijednu prepoznatljivu strukturu. Otvaranje jedne od njih koristi isti poziv Open kao i svaka druga radna knjiga; čitač otkriva format i prebacuje se na odgovarajući put

Te se datoteke i dalje pojavljuju, a to je jedini razlog zašto je bilo što od ovoga uopće važno. Inženjerski arhivi, državno čuvanje evidencije, laboratorijski podaci s instrumenata čiji je upravljački softver napisan 1993., i dugotrajni računovodstveni sustavi svi su za sobom ostavili BIFF2 i BIFF4 radne knjige. Moderni Excel neke od njih izravno odbija otvoriti, nakon što je iz sigurnosnih razloga uklonio naslijeđene pretvarače, čime nastaje skup podataka koji nitko ne može pročitati alatom koji itko ima

Po čemu se pred-OLE radna knjiga razlikuje?

Svaka .xls datoteka od Excela 5.0 nadalje OLE2 je spojena datoteka, mali datotečni sustav unutar datoteke, u kojoj radna knjiga živi u toku imenom Workbook ili Book. Raščlamba jedne od njih počinje raščlambom tog kontejnera, kako je opisano u binarnom formatu spojene datoteke u Pascalu

BIFF2 do BIFF4 nemaju kontejner. Datoteka odmah počinje BOF zapisom, a broj zapisa tog BOF-a kodira generaciju: $0009 za BIFF2, $0209 za BIFF3 i $0409 za BIFF4. HotXLS provjerava duljinu tijela BOF-a, koja iznosi između četiri i šest bajtova, i tip podtoka, $0010 za radni list, $0020 za grafikon i $0040 za makro list, prije nego što se posveti sirovom putu. Ta provjera sprječava da se oštećena ili pogrešno identificirana datoteka protumači kao vrlo stara radna knjiga

Tri generacije, tri rasporeda zapisa

Zapisi ćelija mjesto su gdje se generacije najvidljivije razilaze. BIFF2 zauzima susjedan blok niskih brojeva zapisa, $0001 do $0005 za prazne, cjelobrojne, brojčane, oznaka (label) i logičko-ili-pogreška ćelije, a svako tijelo nosi polje atributa od tri bajta ondje gdje kasnije verzije stavljaju prošireni indeks formata. BIFF3 i BIFF4 to napuštaju i ponovno koriste brojeve zapisa i rasporede BIFF5, $0201, $0203, $0204 i $0205, s dvobajtnim XF indeksom

Taj posljednji detalj uzrokuje specifičan i lako pogrešno dijagnosticiran kvar. BIFF3 ili BIFF4 LABEL zapis strukturno je identičan svom BIFF5 parnjaku, redak i stupac praćeni indeksom formata, a zatim brojem znakova. Napišite čitač koji pretpostavlja BIFF2 raspored i on pročita dva bajta premalo, zatim iskorači s kraja zapisa i pogrešno protumači sve nakon njega. Simptom nije iznimka; to je radna knjiga koja se pročita s uvjerljivim smećem u sebi

Zapisi formula zauzimaju paralelno numeriranje kroz sve tri generacije, $0006, $0206 i $0406. Kada formula proizvede rezultat u obliku niza, taj niz stiže u zasebnom sljedećem zapisu, $0007 ili $0207, a BIFF2 oblik toga koristi jednobajtni prefiks duljine umjesto dvobajtnog koji se koristi kasnije

Zašto se formule vraćaju kao vrijednosti, a ne kao tekst

HotXLS čita predmemorirani rezultat formule u ovim datotekama i ne pokušava rekonstruirati izraz formule. To je namjerna granica, a ne praznina koja čeka da se popuni

Raščlanjeni izraz u BIFF2 do BIFF4 koristi kodiranje tokena koje se razlikuje od BIFF5 i kasnijih na način koji nadilazi kozmetiku: duljine tokena imaju drugačiji prefiks, referentni tokeni imaju drugačije veličine, a tablice indeksa funkcija su prenumerirane između generacija. Provođenje tih bajtova kroz prevoditelj izraza za BIFF8 ne proizvodi pogrešnu formulu, proizvodi nasumičnu. Čitanje predmemorirane vrijednosti daje vam broj ili niz koji je Excel posljednji put izračunao, a upravo to je ono što je arhivskoj migraciji zapravo potrebno

Predmemorirana vrijednost živi na pomaku unutar zapisa koji ovisi o generaciji: bajt 7 za BIFF2 i bajt 6 za BIFF3 i BIFF4. Posebne vrijednosti, nizovi, logičke vrijednosti, pogreške i prazne ćelije, kodirane su u markerskoj riječi $FFFF s razlikovnim znakom, istoj konvenciji koju su kasnije BIFF generacije zadržale

Otvaranje jedne

Pozivajući kod je neupadljiv, i to je poanta. Otkrivanje se odvija unutar 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[] ima bazu 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;

Uočite aritmetiku indeksa u toj petlji. Granice UsedRange imaju bazu 0, dok i kolekcija listova i pristup ćelijama imaju bazu 1, nedosljednost koja prethodi trenutnom API-ju i čuva se radi kompatibilnosti. Zaboravljanje te prilagodbe pregledava pogrešan pravokutnik i pritom ne prijavljuje ništa neobično. Jeftine predprovjere koje u potpunosti izbjegavaju učitavanje datoteke opisane su u lakoj inspekciji radne knjige

Što ne dobivate i što s time učiniti

Oblikovanje se ne tumači. HotXLS ne raščlanjuje XF i FONT zapise ovih generacija, pa fontovi, boje, obrubi i formati brojeva nisu dostupni, a ćelije koje je Excel nekoć prikazivao kao datume vraćaju se kao svoji sirovi serijski brojevi

Ovo posljednje treba obraditi u vlastitom kodu, a ne u čitaču, a razlog je pošten: formati brojeva u BIFF2 do BIFF4 nisu dovoljno pouzdani da bi pokretali automatsku odluku o datumu. Stupac peteroznamenkastih brojeva mogao bi biti datumi, ili bi mogao biti brojevi dijelova. Pretvarajte namjerno, koristeći sustav datuma radne knjige, čija su pravila opisana u serijskim brojevima datuma, sustavu 1904 i formatima brojeva:

// Odlučujte po stupcu, nikada po vrijednosti: peteroznamenkasti broj može biti
// datum ili broj dijela, a naslijeđeni format vam to neće reći
if ColumnHoldsDates(C) then
begin
  // Dva sustava datuma razlikuju se za 1462 dana, pa isti serijski broj
  // označava dva datuma udaljena četiri godine. Pročitajte sustav iz
  // radne knjige, umjesto da ga pretpostavljate
  if Book.Date1904 then
    Writeln(DateToStr(SerialToDate1904(V)))
  else
    Writeln(DateToStr(SerialToDate1900(V)));
end
else
  Writeln(VarToStr(V));

Dvije strukturne napomene upotpunjuju sliku. Zapisi zaštite lozinkom i kodne stranice pojavljuju se unutar jedinog toka radnog lista, a ne u toku na razini radne knjige, jer ne postoji tok na razini radne knjige u koji bi ih se stavilo, pa ih se mora prepoznati u kontekstu radnog lista. A datoteka BIFF2 do BIFF4 sadrži točno jedan podtok lista; radne knjige s više listova nisu postojale sve dok format nije dobio svoj kontejner

Pragmatičan put migracije stoga je dvokoračan: pročitajte naslijeđenu datoteku radi njezinih vrijednosti, zatim zapišite modernu radnu knjigu koja nosi te vrijednosti s oblikovanjem koje sami primijenite. Naslijeđeno čitanje, moderno pisanje i sve između izvode se u jednoj knjižnici za Delphi i C++Builder, opisanoj na stranici HotXLS Delphi komponente proračunske tablice