HotXLS öppnar arbetsböcker skrivna av Excel 2.0, 3.0 och 4.0 direkt från Delphi och C++Builder. Dessa filer föregår den OLE-sammansatta dokumentbehållare som varje senare .xls använder, så de är råa BIFF-postströmmar utan någon lagringsomslutning alls, och en läsare byggd för BIFF8 hittar inte en enda igenkännbar struktur i dem. Att öppna en använder samma Open-anrop som vilken annan arbetsbok som helst; läsaren upptäcker formatet och byter väg
Filerna dyker fortfarande upp, vilket är det enda skälet till att något av detta spelar roll. Ingenjörsarkiv, myndigheters registerbevarande, laboratoriedata från instrument vars styrprogramvara skrevs 1993, och långvariga redovisningssystem har alla lämnat BIFF2- och BIFF4-arbetsböcker efter sig. Modern Excel vägrar helt att öppna flera av dem, efter att ha tagit bort äldre konverterare av säkerhetsskäl, vilket lämnar en datamängd som ingen kan läsa med ett verktyg någon faktiskt har
Vad gör en pre-OLE-arbetsbok annorlunda?
Varje .xls från Excel 5.0 och framåt är en OLE2-sammansatt fil, ett litet filsystem inuti en fil, med arbetsboken liggande i en ström vid namn Workbook eller Book. Att tolka en sådan börjar med att tolka den behållaren, vilket beskrivs i det sammansatta filbinärformatet i Pascal
BIFF2 till BIFF4 har ingen behållare. Filen börjar omedelbart med en BOF-post, och postnumret för den BOF:en kodar generationen: $0009 för BIFF2, $0209 för BIFF3 och $0409 för BIFF4. HotXLS validerar BOF-kroppens längd, som är mellan fyra och sex byte, och undertypen, $0010 för ett kalkylblad, $0020 för ett diagram och $0040 för ett makroblad, innan den binder sig till den råa vägen. Den valideringen är det som hindrar en korrupt eller feltolkad fil från att tolkas som en mycket gammal arbetsbok
Tre generationer, tre postlayouter
Cellposterna är där generationerna skiljer sig mest synligt. BIFF2 upptar ett sammanhängande block av låga postnummer, $0001 till $0005 för tom, heltal, tal, etikett och boolesk-eller-fel-celler, och varje kropp bär ett trebyte attributfält där senare versioner placerar ett utökat formatindex. BIFF3 och BIFF4 överger det och återanvänder BIFF5-postnumren och layouterna, $0201, $0203, $0204 och $0205, med ett tvåbyte XF-index
Den sista detaljen orsakar ett specifikt och lätt feldiagnostiserat fel. En BIFF3- eller BIFF4-LABEL-post är strukturellt identisk med sin BIFF5-motsvarighet, rad och kolumn följt av formatindexet och sedan teckenantalet. Skriv en läsare som antar BIFF2-layouten och den läser två byte för lite, och går sedan förbi postens slut och feltolkar allt efter det. Symptomet är inte ett undantag; det är en arbetsbok som läses in med rimligt utseende skräp i sig
Formelposter upptar en parallell numrering över alla tre, $0006, $0206 och $0406. När en formel producerar ett strängresultat anländer den strängen i en separat efterföljande post, $0007 eller $0207, och BIFF2-formen av den använder ett enbyte längdprefix snarare än det tvåbyte som används senare
Varför kommer formler tillbaka som värden, inte som text?
HotXLS läser det cachade resultatet av en formel i dessa filer och försöker inte rekonstruera formeluttrycket. Detta är en medveten gräns, inte en lucka som väntar på att fyllas
Det tolkade uttrycket i BIFF2 till BIFF4 använder en tokenkodning som skiljer sig från BIFF5 och senare på sätt som går utöver det kosmetiska: tokenlängder prefixas annorlunda, referenstokens har olika storlekar, och funktionsindextabellerna numrerades om mellan generationerna. Att köra dessa byte genom en BIFF8-uttrycksöversättare producerar inte en felaktig formel, den producerar en slumpmässig. Att läsa det cachade värdet ger dig det tal eller den sträng Excel senast beräknade, vilket är vad en arkivmigrering faktiskt behöver
Det cachade värdet ligger på en generationsberoende offset inuti posten: byte 7 för BIFF2 och byte 6 för BIFF3 och BIFF4. Specialvärden, strängar, booleaner, fel och tomma celler, kodas i ett markörord på $FFFF med en särskiljare, samma konvention som senare BIFF-generationer behöll
Att öppna en
Den anropande koden är oanmärkningsvärd, vilket är poängen. Detektering sker inuti 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[] är ettbaserat
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;
Notera indexaritmetiken i den loopen. UsedRange-gränserna är nollbaserade medan både bladsamlingen och cellåtkomsten är ettbaserade, en inkonsekvens som föregår det nuvarande API:et och bevaras för kompatibilitet. Att glömma justeringen granskar fel rektangel och rapporterar inget ovanligt medan den gör det. Billiga förkontroller som undviker att läsa in en fil alls täcks i lättviktig arbetsboksinspektion
Vad du inte får, och vad du ska göra åt det
Formatering tolkas inte. HotXLS tolkar inte XF- och FONT-posterna för dessa generationer, så typsnitt, färger, kantlinjer och talformat är otillgängliga, och celler som Excel en gång visade som datum kommer tillbaka som sina råa serienummer
Den sistnämnda måste hanteras i din egen kod snarare än i läsaren, och skälet är ärligt: talformat i BIFF2 till BIFF4 är inte tillförlitliga nog för att styra ett automatiskt datumbeslut. En kolumn med femsiffriga tal kan vara datum, eller kan vara artikelnummer. Konvertera medvetet, med arbetsbokens datumsystem, vars regler beskrivs i datumserienummer, 1904-systemet och talformat:
// Avgör per kolumn, aldrig per värde: ett femsiffrigt tal kan vara ett
// datum eller ett artikelnummer, och det äldre formatet talar inte om det
if ColumnHoldsDates(C) then
begin
// De två datumsystemen ligger 1462 dagar isär, så samma serienummer
// betecknar två datum fyra år ifrån varandra. Läs systemet från
// arbetsboken istället för att anta ett
if Book.Date1904 then
Writeln(DateToStr(SerialToDate1904(V)))
else
Writeln(DateToStr(SerialToDate1900(V)));
end
else
Writeln(VarToStr(V));
Två strukturella noteringar fullbordar bilden. Lösenordsskydd och kodsidesposter förekommer inuti den enda kalkylbladsströmmen snarare än i en ström på arbetsboksnivå, eftersom det inte finns någon ström på arbetsboksnivå att lägga dem i, så de måste kännas igen i kalkylbladskontext. Och en BIFF2- till BIFF4-fil innehåller exakt en bladunderström; arbetsböcker med flera blad existerade inte förrän formatet fick sin behållare
Den pragmatiska migreringsvägen är därför en tvåstegsväg: läs den äldre filen för dess värden, skriv sedan en modern arbetsbok som bär dessa värden med formatering du själv applicerar. Äldre läsning, modern skrivning och allt däremellan körs i ett bibliotek för Delphi och C++Builder, beskrivet på sidan för HotXLS Delphi-kalkylbladskomponent