HotXLS atidaro darbaknyges, kurias parašė Excel 2.0, 3.0 ir 4.0, tiesiai iš Delphi ir C++Builder. Šie failai atsirado anksčiau nei OLE sudėtinio dokumento konteineris, kurį naudoja kiekvienas vėlesnis .xls failas, todėl jie yra žali BIFF įrašų srautai be jokio saugyklos apvalkalo, ir BIFF8 formatui sukurtas skaitytuvas jų viduje nerastų nė vienos atpažįstamos struktūros. Tokio failo atidarymas naudoja tą patį Open iškvietimą kaip ir bet kuri kita darbaknygė; skaitytuvas aptinka formatą ir persijungia į kitą kelią
Šie failai vis dar pasitaiko, ir tai vienintelė priežastis, kodėl visa tai svarbu. Inžinerijos archyvai, valstybinis dokumentų saugojimas, laboratorijų duomenys iš prietaisų, kurių valdymo programinė įranga buvo parašyta 1993-iaisiais, ir ilgai veikiančios apskaitos sistemos – visi jie paliko BIFF2 ir BIFF4 darbaknyges. Šiuolaikinis Excel kai kurias iš jų atsisako atidaryti visiškai, dėl saugumo priežasčių pašalinęs senuosius konverterius, todėl lieka duomenų rinkinys, kurio niekas negali perskaityti su jokiu turimu įrankiu
Kuo skiriasi darbaknygė, atsiradusi anksčiau nei OLE?
Kiekvienas .xls failas nuo Excel 5.0 yra OLE2 sudėtinis failas – maža failų sistema failo viduje, kur darbaknygė gyvena sraute pavadinimu Workbook arba Book. Tokio failo analizė prasideda šio konteinerio analize, kaip aprašyta straipsnyje sudėtinio failo dvejetainis formatas Pascal kalba
BIFF2 iki BIFF4 konteinerio neturi. Failas prasideda iškart BOF įrašu, o šio BOF įrašo numeris koduoja kartą: $0009 BIFF2, $0209 BIFF3 ir $0409 BIFF4. HotXLS patikrina BOF turinio ilgį, kuris yra nuo keturių iki šešių baitų, ir posrauto tipą – $0010 darbalapiui, $0020 diagramai ir $0040 makrolapiui – prieš pereidama prie žalio kelio. Būtent ši patikra saugo, kad sugadintas ar neteisingai identifikuotas failas nebūtų interpretuojamas kaip labai sena darbaknygė
Trys kartos, trys įrašų išdėstymai
Langelių įrašai yra ta vieta, kur kartos labiausiai matomai išsiskiria. BIFF2 užima gretimą žemų įrašų numerių bloką, nuo $0001 iki $0005 tuščių, sveikų skaičių, skaičių, žymos ir loginio arba klaidos langelių, ir kiekvienas turinys neša trijų baitų atributo lauką, kur vėlesnės versijos deda išplėstinio formato indeksą. BIFF3 ir BIFF4 to atsisako ir pakartotinai naudoja BIFF5 įrašų numerius ir išdėstymus – $0201, $0203, $0204 ir $0205 – su dviejų baitų XF indeksu
Ši paskutinė detalė sukelia konkretų ir lengvai neteisingai diagnozuojamą gedimą. BIFF3 arba BIFF4 LABEL įrašas struktūriškai identiškas savo BIFF5 atitikmeniui – eilutė ir stulpelis, po to formato indeksas ir simbolių skaičius. Parašykite skaitytuvą, kuris prielaidauja BIFF2 išdėstymą, ir jis perskaitys dviem baitais per mažai, tada nueis už įrašo ribos ir neteisingai interpretuos viską, kas eina po to. Simptomas nėra išimtis – tai darbaknygė, kuri perskaitoma su tikėtinu šiukšlių turiniu joje
Formulių įrašai visose trijose kartose užima lygiagretų numeravimą – $0006, $0206 ir $0406. Kai formulė duoda eilutės rezultatą, ta eilutė atkeliauja atskirame kitame įraše, $0007 arba $0207, o BIFF2 forma naudoja vieno baito ilgio priešdėlį, o ne vėliau naudojamą dviejų baitų priešdėlį
Kodėl formulės grįžta kaip reikšmės, o ne kaip tekstas
HotXLS šiuose failuose skaito talpykloje saugomą formulės rezultatą ir nebando atkurti formulės išraiškos. Tai sąmoninga riba, o ne dar neužpildyta spraga
Analizuota išraiška BIFF2 iki BIFF4 naudoja žymenų kodavimą, kuris skiriasi nuo BIFF5 ir vėlesnių būdais, viršijančiais kosmetiką: žymenų ilgiai turi kitokį priešdėlį, nuorodų žymenys turi kitokius dydžius, o funkcijų indeksų lentelės buvo pernumeruotos tarp kartų. Praleidus šiuos baitus per BIFF8 išraiškos vertėją, gaunama ne neteisinga formulė, o atsitiktinė. Talpykloje saugomos reikšmės skaitymas duoda skaičių arba eilutę, kurią Excel paskutinį kartą apskaičiavo – būtent to ir reikia archyvo migracijai
Talpykloje saugoma reikšmė gyvena nuo kartos priklausančiame poslinkyje įraše: 7 baitas BIFF2 ir 6 baitas BIFF3 bei BIFF4. Specialios reikšmės – eilutės, loginės reikšmės, klaidos ir tuščios reikšmės – koduojamos žymens žodžiu $FFFF su skiriamuoju požymiu, ta pačia konvencija, kurią išlaikė ir vėlesnės BIFF kartos
Failo atidarymas
Kviečiantis kodas nepastebimas, ir tai yra esmė. Aptikimas vyksta Open viduje:
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[] skaičiuojamas nuo 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;
Atkreipkite dėmesį į indeksų aritmetiką tame cikle. UsedRange ribos skaičiuojamos nuo nulio, o tiek lapų kolekcija, tiek langelių prieiga skaičiuojama nuo vieneto – šis nenuoseklumas atsirado anksčiau nei dabartinis API ir paliktas suderinamumui palaikyti. Pamiršus koregavimą, patikrinamas neteisingas stačiakampis, ir tai vyksta nepranešant nieko neįprasto. Pigios išankstinės patikros, leidžiančios apskritai neįkelti failo, aprašytos straipsnyje lengvasvorė darbaknygės apžiūra
Ko negaunate ir ką su tuo daryti
Formatavimas neinterpretuojamas. HotXLS neanalizuoja šių kartų XF ir FONT įrašų, todėl šriftai, spalvos, kraštinės ir skaičių formatai nepasiekiami, o langeliai, kuriuos Excel kadaise rodė kaip datas, grįžta kaip savo žalūs serijos numeriai
Šiuo paskutiniu atveju reikia pasirūpinti savo pačių kode, o ne skaitytuve, ir priežastis yra sąžininga: skaičių formatai BIFF2 iki BIFF4 nėra pakankamai patikimi, kad galėtų valdyti automatinį sprendimą dėl datos. Penkiaženklių skaičių stulpelis gali būti datos, o gali būti ir dalies numeriai. Konvertuokite sąmoningai, naudodami darbaknygės datos sistemą, kurios taisyklės aprašytos straipsnyje datos serijos numeriai, 1904 sistema ir skaičių formatai:
// Nuspręskite pagal stulpelį, niekada pagal reikšmę: penkiaženklis
// skaičius gali būti data arba dalies numeris, o senasis formatas to nepasakys
if ColumnHoldsDates(C) then
begin
// Dvi datos sistemos skiriasi 1462 dienomis, todėl ta pati serijos
// reikšmė žymi dvi datas, nutolusias ketveriais metais. Skaitykite
// sistemą iš darbaknygės, o ne darykite prielaidą
if Book.Date1904 then
Writeln(DateToStr(SerialToDate1904(V)))
else
Writeln(DateToStr(SerialToDate1900(V)));
end
else
Writeln(VarToStr(V));
Dvi struktūrinės pastabos užbaigia vaizdą. Slaptažodžio apsaugos ir koduočių lentelės (code page) įrašai pasirodo viename darbalapio sraute, o ne darbaknygės lygio sraute, nes tokio srauto tiesiog nėra, kur juos padėti, todėl juos reikia atpažinti darbalapio kontekste. Ir BIFF2 iki BIFF4 failas turi lygiai vieną lapo posrautą; kelių lapų darbaknygės neegzistavo, kol formatas neįgavo savo konteinerio
Todėl pragmatiškas migracijos kelias yra dviejų žingsnių: perskaityti seną failą dėl jo reikšmių, tada parašyti šiuolaikinę darbaknygę, kuri neša tas reikšmes su formatavimu, kurį pritaikote patys. Senų failų skaitymas, šiuolaikinis rašymas ir viskas tarp jų veikia vienoje bibliotekoje Delphi ir C++Builder aplinkoms, aprašytoje HotXLS Delphi skaičiuoklės komponento puslapyje