HotXLS åpner arbeidsbøker skrevet av Excel 2.0, 3.0 og 4.0 direkte fra Delphi og C++Builder. Disse filene er eldre enn OLE-sammensatt-dokument-beholderen hver senere .xls bruker, så de er rå BIFF-postrømmer helt uten lagringsomslag, og en leser bygget for BIFF8 vil ikke finne en eneste gjenkjennelig struktur inni dem. Å åpne én bruker det samme Open-kallet som enhver annen arbeidsbok; leseren oppdager formatet og bytter bane
Filene dukker fremdeles opp, og det er den eneste grunnen til at noe av dette betyr noe. Ingeniørarkiver, offentlig journalbevaring, laboratoriedata fra instrumenter hvis kontrollprogramvare ble skrevet i 1993, og langvarige regnskapssystemer har alle etterlatt seg BIFF2- og BIFF4-arbeidsbøker. Moderne Excel nekter å åpne flere av dem rett ut, siden gamle konvertere er fjernet av sikkerhetsgrunner, noe som etterlater et datasett ingen kan lese med et verktøy noen faktisk har
Hva gjør en pre-OLE-arbeidsbok annerledes?
Hver .xls fra Excel 5.0 og fremover er en OLE2-sammensatt fil, et lite filsystem inni en fil, der arbeidsboken bor i en strøm kalt Workbook eller Book. Å analysere én starter med å analysere den beholderen, som beskrevet i det sammensatte binærfilformatet i Pascal
BIFF2 til og med BIFF4 har ingen beholder. Filen starter umiddelbart med en BOF-post, og postnummeret til den BOF-en koder generasjonen: $0009 for BIFF2, $0209 for BIFF3 og $0409 for BIFF4. HotXLS validerer BOF-kroppslengden, som er mellom fire og seks byte, og substrømtypen, $0010 for et regneark, $0020 for et diagram og $0040 for et makroark, før den forplikter seg til den rå banen. Den valideringen er det som hindrer en korrupt eller feilidentifisert fil i å bli tolket som en veldig gammel arbeidsbok
Tre generasjoner, tre postoppsett
Celleposter er der generasjonene avviker mest synlig. BIFF2 opptar en sammenhengende blokk av lave postnumre, $0001 til og med $0005 for tomme, heltall-, tall-, etikett- og boolsk-eller-feil-celler, og hver kropp bærer et attributtfelt på tre byte der senere versjoner setter en utvidet formatindeks. BIFF3 og BIFF4 forlater det og gjenbruker BIFF5-postnumrene og -oppsettene, $0201, $0203, $0204 og $0205, med en XF-indeks på to byte
Den siste detaljen forårsaker en spesifikk og lett feildiagnostisert feil. En BIFF3- eller BIFF4-LABEL-post er strukturelt identisk med sin BIFF5-motpart, rad og kolonne etterfulgt av formatindeksen og deretter tegnantallet. Skriv en leser som antar BIFF2-oppsettet, og den leser to byte for lite, går deretter ut over slutten av posten og mistolker alt etter den. Symptomet er ikke et unntak; det er en arbeidsbok som leses med plausibelt søppel inni seg
Formelposter opptar en parallell nummerering på tvers av alle tre, $0006, $0206 og $0406. Når en formel produserer et strengresultat, kommer den strengen i en separat, etterfølgende post, $0007 eller $0207, og BIFF2-formen av den bruker et lengdeprefiks på én byte i stedet for det på to byte som brukes senere
Hvorfor formler kommer tilbake som verdier, ikke som tekst
HotXLS leser det bufrede resultatet av en formel i disse filene og forsøker ikke å rekonstruere formeluttrykket. Dette er en bevisst grense, ikke et hull som venter på å bli fylt
Det analyserte uttrykket i BIFF2 til BIFF4 bruker en token-koding som skiller seg fra BIFF5 og senere på måter som går utover det kosmetiske: tokenlengder prefikses annerledes, referansetokens har andre størrelser, og funksjonsindekstabellene ble omnummerert mellom generasjonene. Å kjøre de bytene gjennom en BIFF8-uttrykksoversetter produserer ikke en feil formel, den produserer en tilfeldig en. Å lese den bufrede verdien gir deg tallet eller strengen Excel sist beregnet, som er det en arkivmigrering faktisk trenger
Den bufrede verdien bor på en generasjonsavhengig forskyvning inni posten: byte 7 for BIFF2 og byte 6 for BIFF3 og BIFF4. Spesialverdier, strenger, boolske verdier, feil og tomme celler, kodes i et markerord på $FFFF med en diskriminator, den samme konvensjonen senere BIFF-generasjoner beholdt
Åpne en
Den kallende koden er ganske ordinær, og det er poenget. Deteksjon skjer inni 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[] er 1-basert
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;
Legg merke til indeksaritmetikken i den løkken. UsedRange-grenser er nullbaserte, mens både arksamlingen og celletilgangen er enbaserte, en inkonsistens som er eldre enn dagens API og bevares for kompatibilitet. Å glemme justeringen reviderer det gale rektangelet og rapporterer ingenting uvanlig mens den gjør det. Billige forhåndssjekker som unngår å laste inn en fil i det hele tatt, er dekket i lettvekts arbeidsbokinspeksjon
Hva du ikke får, og hva du skal gjøre med det
Formatering tolkes ikke. HotXLS analyserer ikke XF- og FONT-postene til disse generasjonene, så skrifter, farger, kantlinjer og tallformater er utilgjengelige, og celler Excel en gang viste som datoer, kommer tilbake som sine rå serienumre
Den siste trenger håndtering i din egen kode snarere enn i leseren, og grunnen er ærlig: tallformater i BIFF2 til BIFF4 er ikke pålitelige nok til å styre en automatisk datobeslutning. En kolonne med femsifrede tall kan være datoer, eller kan være delenumre. Konverter bevisst, ved å bruke arbeidsbokens datosystem, hvis regler er beskrevet i dato-serienumre, 1904-systemet og tallformater:
// Avgjør per kolonne, aldri per verdi: et femsifret tall kan være en
// dato eller et delenummer, og det gamle formatet vil ikke fortelle deg det
if ColumnHoldsDates(C) then
begin
// De to datosystemene ligger 1462 dager fra hverandre, så det samme serienummeret
// angir to datoer fire år fra hverandre. Les systemet fra
// arbeidsboken i stedet for å anta ett
if Book.Date1904 then
Writeln(DateToStr(SerialToDate1904(V)))
else
Writeln(DateToStr(SerialToDate1900(V)));
end
else
Writeln(VarToStr(V));
To strukturelle merknader fullfører bildet. Passordbeskyttelse og kodesideposter opptrer inni den ene regnearkstrømmen snarere enn i en strøm på arbeidsboknivå, fordi det ikke finnes noen strøm på arbeidsboknivå å legge dem i, så de må gjenkjennes i regnearkkontekst. Og en BIFF2- til BIFF4-fil inneholder nøyaktig én arksubstrøm; flerarks-arbeidsbøker fantes ikke før formatet fikk sin beholder
Den pragmatiske migreringsbanen er derfor en totrinns en: les den gamle filen for verdiene dens, skriv deretter en moderne arbeidsbok som bærer de verdiene med formatering du påfører selv. Gammel lesing, moderne skriving og alt derimellom kjører i ett bibliotek for Delphi og C++Builder, beskrevet på HotXLS Delphi regnearkkomponentsiden