HotXLS otvára zošity zapísané programami Excel 2.0, 3.0 a 4.0 priamo z Delphi a C++Builder. Tieto súbory predchádzajú kontajneru zloženého dokumentu OLE, ktorý používa každé neskoršie .xls, takže ide o surové streamy záznamov BIFF úplne bez obalu úložiska, a čítačka postavená pre BIFF8 v nich nenájde ani jednu rozpoznateľnú štruktúru. Otvorenie jedného z nich používa rovnaké volanie Open ako pri ktoromkoľvek inom zošite; čítačka rozpozná formát a prepne cestu
Tieto súbory sa stále objavujú, a to je jediný dôvod, prečo na tomto vôbec záleží. Inžinierske archívy, štátne uchovávanie záznamov, laboratórne dáta z prístrojov, ktorých riadiaci softvér bol napísaný v roku 1993, a dlhobežiace účtovné systémy — všetky za sebou zanechali zošity BIFF2 a BIFF4. Moderný Excel viaceré z nich rovno odmieta otvoriť, keďže z bezpečnostných dôvodov odstránil staršie konvertory, čím zostane dátová sada, ktorú nedokáže prečítať nikto žiadnym bežne dostupným nástrojom
Čím sa líši zošit spred éry OLE?
Každé .xls od Excelu 5.0 vyššie je zložený súbor OLE2, malý súborový systém vo vnútri súboru, pričom zošit žije v streame s názvom Workbook alebo Book. Parsovanie takého súboru začína parsovaním tohto kontajnera, ako opisuje časť binárny formát zloženého súboru v Pascale
BIFF2 až BIFF4 nemajú žiadny kontajner. Súbor začína rovno záznamom BOF a číslo tohto záznamu kóduje generáciu: $0009 pre BIFF2, $0209 pre BIFF3 a $0409 pre BIFF4. HotXLS pred prechodom na surovú cestu overí dĺžku tela BOF, ktorá je medzi štyrmi a šiestimi bajtmi, a typ podstreamu — $0010 pre hárok, $0020 pre graf a $0040 pre hárok makier. Práve táto validácia zabraňuje tomu, aby sa poškodený alebo nesprávne identifikovaný súbor interpretoval ako veľmi starý zošit
Tri generácie, tri rozloženia záznamov
Práve pri záznamoch buniek sa generácie líšia najviditeľnejšie. BIFF2 zaberá súvislý blok nízkych čísel záznamov, $0001 až $0005 pre prázdne, celočíselné, číselné, textové a boolovsko-chybové bunky, a každé telo nesie trojbajtové pole atribútov tam, kde neskoršie verzie umiestňujú rozšírený index formátu. BIFF3 a BIFF4 to opúšťajú a znovu používajú čísla a rozloženia záznamov BIFF5, $0201, $0203, $0204 a $0205, s dvojbajtovým indexom XF
Tento posledný detail spôsobuje konkrétne a ľahko zle diagnostikovateľné zlyhanie. Záznam LABEL v BIFF3 alebo BIFF4 je štruktúrne identický so svojím náprotivkom BIFF5: riadok a stĺpec, po ktorých nasleduje index formátu a následne počet znakov. Napíšte čítačku, ktorá predpokladá rozloženie BIFF2, a prečíta o dva bajty menej, potom prejde za koniec záznamu a nesprávne interpretuje všetko, čo za ním nasleduje. Príznakom nie je výnimka; je ním zošit, ktorý sa načíta s vierohodne vyzerajúcim, no nezmyselným obsahom
Záznamy vzorcov majú paralelné číslovanie naprieč všetkými tromi generáciami: $0006, $0206 a $0406. Keď vzorec produkuje textový výsledok, tento text príde v samostatnom nasledujúcom zázname, $0007 alebo $0207, pričom forma BIFF2 používa jednobajtový prefix dĺžky namiesto dvojbajtového, ktorý sa používa neskôr
Prečo sa vzorce vracajú ako hodnoty, nie ako text
HotXLS v týchto súboroch číta vyrovnávaný výsledok vzorca a nepokúša sa rekonštruovať samotný výraz vzorca. Ide o zámernú hranicu, nie o medzeru čakajúcu na zaplnenie
Parsovaný výraz v BIFF2 až BIFF4 používa kódovanie tokenov, ktoré sa od BIFF5 a novších líši spôsobmi presahujúcimi kozmetiku: dĺžky tokenov majú iný prefix, referenčné tokeny majú iné veľkosti a tabuľky indexov funkcií boli medzi generáciami prečíslované. Prehnanie týchto bajtov cez prekladač výrazov BIFF8 neprodukuje nesprávny vzorec, produkuje náhodný vzorec. Čítanie vyrovnávanej hodnoty vám dá číslo alebo reťazec, ktorý Excel naposledy vypočítal, a to je presne to, čo archívna migrácia skutočne potrebuje
Vyrovnávaná hodnota sa nachádza na posune vo vnútri záznamu, ktorý závisí od generácie: bajt 7 pre BIFF2 a bajt 6 pre BIFF3 a BIFF4. Špeciálne hodnoty — reťazce, boolovské hodnoty, chyby a prázdne bunky — sú kódované v značkovom slove $FFFF s rozlišovačom, teda rovnakou konvenciou, akú si zachovali aj neskoršie generácie BIFF
Otvorenie súboru
Volajúci kód je nenápadný, a to je práve podstata. Detekcia prebieha vo vnútri 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 indexované od jedna
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;
Všimnite si aritmetiku indexov v tejto slučke. Hranice UsedRange sú indexované od nuly, zatiaľ čo kolekcia hárkov aj prístup k bunkám sú indexované od jedna — nezrovnalosť, ktorá predchádza súčasné API a je zachovaná kvôli kompatibilite. Zabudnutie na túto úpravu skontroluje nesprávny obdĺžnik a pritom nenahlási nič nezvyčajné. Lacné predbežné kontroly, ktoré sa vyhnú načítaniu súboru úplne, opisuje časť ľahká inšpekcia zošita
Čo nedostanete a čo s tým robiť
Formátovanie sa neinterpretuje. HotXLS neparsuje záznamy XF a FONT týchto generácií, takže písma, farby, okraje a formáty čísel nie sú k dispozícii, a bunky, ktoré Excel kedysi zobrazoval ako dátumy, sa vrátia ako svoje surové poradové čísla
Poslednú z týchto vecí treba ošetriť vo vlastnom kóde, nie v čítačke, a dôvod je úprimný: formáty čísel v BIFF2 až BIFF4 nie sú dostatočne spoľahlivé na to, aby riadili automatické rozhodnutie o dátume. Stĺpec päťciferných čísel môžu byť dátumy, alebo môžu byť čísla dielov. Konvertujte zámerne, s použitím dátumového systému zošita, ktorého pravidlá opisuje časť poradové čísla dátumov, systém 1904 a formáty čísel:
// Rozhodujte podľa stĺpca, nikdy podľa hodnoty: päťciferné číslo môže
// byť dátum alebo číslo dielu, a starší formát vám to nepovie
if ColumnHoldsDates(C) then
begin
// Oba dátumové systémy sú od seba vzdialené o 1462 dní, takže to isté
// poradové číslo označuje dva dátumy vzdialené štyri roky. Systém
// prečítajte zo zošita, nepredpokladajte ho
if Book.Date1904 then
Writeln(DateToStr(SerialToDate1904(V)))
else
Writeln(DateToStr(SerialToDate1900(V)));
end
else
Writeln(VarToStr(V));
Obraz dopĺňajú dve štruktúrne poznámky. Záznamy ochrany heslom a znakovej stránky sa objavujú vo vnútri jediného streamu hárka namiesto streamu na úrovni zošita, pretože žiadny stream na úrovni zošita, do ktorého by sa dali umiestniť, neexistuje, takže ich treba rozpoznať v kontexte hárka. A súbor BIFF2 až BIFF4 obsahuje presne jeden podstream hárka; viachárkové zošity neexistovali, kým formát nezískal svoj kontajner
Pragmatická cesta migrácie je preto dvojkroková: prečítajte starší súbor kvôli jeho hodnotám a potom zapíšte moderný zošit, ktorý tieto hodnoty nesie s formátovaním, ktoré aplikujete sami. Čítanie starších formátov, moderný zápis a všetko medzi tým beží v jednej knižnici pre Delphi a C++Builder, opísanej na stránke HotXLS Delphi spreadsheet component