Műszaki cikk

Excel 2.0-4.0 fájlok olvasása Delphiben a HotXLS-szel

A HotXLS közvetlenül Delphiből és C++Builderből nyitja meg az Excel 2.0, 3.0 és 4.0 által írt munkafüzeteket. Ezek a fájlok megelőzik az OLE összetett dokumentumkonténert, amelyet minden későbbi .xls használ, így nyers BIFF rekordfolyamok, egyáltalán tárolóburkolat nélkül, egy BIFF8-hoz épített olvasó pedig egyetlen felismerhető struktúrát sem talál bennük. Egy ilyen megnyitása ugyanazt az Open hívást használja, mint bármely más munkafüzet; az olvasó felismeri a formátumot, és útvonalat vált

Ezek a fájlok még mindig felbukkannak, és ez az egyetlen ok, amiért ez egyáltalán számít. Mérnöki archívumok, kormányzati iratmegőrzés, olyan műszerek laboratóriumi adatai, amelyek vezérlőszoftverét 1993-ban írták, és régóta futó könyvelési rendszerek egyaránt hagytak maguk után BIFF2 és BIFF4 munkafüzeteket. A modern Excel egyenesen elutasítja néhányuk megnyitását, mivel biztonsági okokból eltávolította a régi konvertereket, ami olyan adathalmazt hagy hátra, amelyet senki nem tud elolvasni olyan eszközzel, amilyen bárkinél van

Mitől más egy OLE előtti munkafüzet?

Minden .xls az Excel 5.0-tól kezdve egy OLE2 összetett fájl, egy kis fájlrendszer egy fájlon belül, ahol a munkafüzet egy Workbook vagy Book nevű streamben él. Az elemzés ennek a konténernek az elemzésével kezdődik, amelyet az összetett fájl bináris formátuma Pascalban ismertet

A BIFF2-től BIFF4-ig nincs konténer. A fájl azonnal egy BOF rekorddal kezdődik, és ennek a BOF-nak a rekordszáma kódolja a generációt: $0009 a BIFF2-nél, $0209 a BIFF3-nál és $0409 a BIFF4-nél. A HotXLS ellenőrzi a BOF-törzs hosszát, amely négy és hat bájt között van, valamint az alfolyam típusát, $0010 egy munkalapnál, $0020 egy diagramnál és $0040 egy makrólapnál, mielőtt elköteleződne a nyers útvonal mellett. Ez az ellenőrzés akadályozza meg, hogy egy sérült vagy tévesen azonosított fájlt egy nagyon régi munkafüzetként értelmezzen a rendszer

Három generáció, három rekordelrendezés

A cellarekordoknál térnek el a generációk a legláthatóbban. A BIFF2 a rekordszámok egy összefüggő, alacsony tartományát foglalja el, $0001-től $0005-ig üres, egész szám, szám, felirat és logikai-vagy-hiba cellákhoz, minden törzs pedig egy hárombájtos attribútummezőt hordoz ott, ahol a későbbi verziók egy kiterjesztett formátumindexet tesznek. A BIFF3 és a BIFF4 elhagyja ezt, és újrahasznosítja a BIFF5 rekordszámait és elrendezéseit, $0201, $0203, $0204 és $0205, egy kétbájtos XF-indexszel

Ez az utolsó részlet egy konkrét és könnyen félrediagnosztizálható hibát okoz. Egy BIFF3 vagy BIFF4 LABEL rekord strukturálisan azonos a BIFF5-beli megfelelőjével, sor és oszlop, majd a formátumindex, majd a karakterszám. Ha egy olvasót a BIFF2 elrendezését feltételezve írsz, az két bájttal kevesebbet olvas, majd túllép a rekord végén, és mindent félreértelmez, ami utána következik. A tünet nem egy kivétel; egy olyan munkafüzet, amely hihető szemétadatokkal olvasódik be

A képletrekordok mindhárom generációban párhuzamos számozást foglalnak el, $0006, $0206 és $0406. Amikor egy képlet stringeredményt ad, az a string egy külön, azt követő rekordban érkezik, $0007 vagy $0207, a BIFF2-beli formája pedig egybájtos hosszúságelőtagot használ a később alkalmazott kétbájtos helyett

Miért érkeznek vissza a képletek értékként, nem szövegként?

A HotXLS ezekben a fájlokban egy képlet gyorsítótárazott eredményét olvassa, és nem próbálja rekonstruálni a képletkifejezést. Ez egy szándékos határ, nem egy betöltésre váró hiányosság

A BIFF2-től BIFF4-ig elemzett kifejezés olyan tokenkódolást használ, amely kozmetikainál is jelentősebben tér el a BIFF5-től és a későbbiektől: a tokenhosszak eltérően vannak előtaggal ellátva, a hivatkozás-tokenek mérete eltérő, és a függvényindex-táblákat generációk között átszámozták. Ha ezeket a bájtokat egy BIFF8 kifejezésfordítón futtatjuk át, az nem egy rossz képletet állít elő, hanem egy véletlenszerűt. A gyorsítótárazott érték olvasása azt a számot vagy stringet adja, amelyet az Excel utoljára kiszámított, és pontosan erre van szüksége egy archívummigrációnak

A gyorsítótárazott érték a rekordon belül egy generációtól függő eltolásnál él: a 7. bájtnál BIFF2-nél, a 6. bájtnál BIFF3-nál és BIFF4-nél. A speciális értékek, stringek, logikai értékek, hibák és üres cellák egy $FFFF jelölőszóban vannak kódolva egy megkülönböztetővel, ugyanazzal a konvencióval, amelyet a későbbi BIFF-generációk is megtartottak

Egy fájl megnyitása

A hívó kód semmi különös, és pontosan ez a lényeg. A felismerés az Open-en belül történik:

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];          // A Sheets[] egytől indexelt
    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;

Figyeld meg az indexaritmetikát abban a ciklusban. Az UsedRange határai nullától indexeltek, míg mind a munkalapgyűjtemény, mind a cellaelérés egytől indexelt, egy olyan következetlenség, amely megelőzi a jelenlegi API-t, és kompatibilitás miatt maradt meg. Az igazítás elfelejtése rossz téglalapot ellenőriz, és eközben semmi szokatlant nem jelent. Az olyan olcsó előellenőrzéseket, amelyek elkerülik egy fájl teljes betöltését, a könnyűsúlyú munkafüzet-vizsgálat ismerteti

Mit nem kapsz meg, és mit tegyél vele?

A formázást nem értelmezi a rendszer. A HotXLS nem elemzi ezeknek a generációknak az XF és FONT rekordjait, így a betűtípusok, színek, keretek és számformátumok nem elérhetők, azok a cellák pedig, amelyeket az Excel egykor dátumként jelenített meg, nyers sorszámukként érkeznek vissza

Ezt az utolsót a saját kódodban kell kezelned, nem az olvasóban, és az ok őszinte: a BIFF2-től BIFF4-ig terjedő számformátumok nem elég megbízhatók egy automatikus dátumdöntés vezérléséhez. Egy ötjegyű számokból álló oszlop lehet dátum, vagy lehet cikkszám. Konvertálj tudatosan, a munkafüzet dátumrendszerét használva, amelynek szabályait a dátum-sorszámok, az 1904-es rendszer és a számformátumok ismerteti:

// Oszloponként dönts, sosem értékenként: egy ötjegyű szám lehet
// dátum vagy cikkszám, a régi formátum pedig nem árulja el
if ColumnHoldsDates(C) then
begin
  // A két dátumrendszer 1462 nap távolságra van egymástól, így ugyanaz a
  // sorszám két, négy évvel eltérő dátumot jelöl. A rendszert a
  // munkafüzetből olvasd ki, ne feltételezz egyet
  if Book.Date1904 then
    Writeln(DateToStr(SerialToDate1904(V)))
  else
    Writeln(DateToStr(SerialToDate1900(V)));
end
else
  Writeln(VarToStr(V));

Két strukturális megjegyzés egészíti ki a képet. A jelszóvédelem és a kódlaprekordok az egyetlen munkalap-streamen belül jelennek meg, nem egy munkafüzet-szintű streamben, mert nincs olyan munkafüzet-szintű stream, amelybe elhelyezhetők lennének, így munkalap-kontextusban kell felismerni őket. Egy BIFF2-től BIFF4-ig terjedő fájl pedig pontosan egy munkalap-alfolyamot tartalmaz; a többmunkalapos munkafüzetek nem léteztek addig, amíg a formátum meg nem kapta a konténerét

A pragmatikus migrációs útvonal ezért kétlépéses: olvasd be a régi fájlt az értékeiért, majd írj egy modern munkafüzetet, amely ezeket az értékeket a saját magad által alkalmazott formázással hordozza. A régi fájlok olvasása, a modern írás, és minden, ami a kettő között van, egyetlen könyvtárban fut Delphihez és C++Builderhez, amelyet a HotXLS Delphi táblázatkezelő-komponens oldala ismertet