Odprite star xls, shranite ga znova, in formula dodatka, ki je klicala v registrirano analitično knjižnico, zdaj kaže na prazno referenco znotraj delovnega zvezka samega. HotXLS to tiho okvaro izsledi do ene same napačne predpostavke: da je zapis BIFF SupBook bodisi self bodisi zunanja datoteka. [MS-XLS] pozna sedem vrst, ne dveh
Zakaj shranjen delovni zvezek izgubi povezave dodatkov?
Ker je bil preizkus razvrščanja strukturni, ne pa tipski. Tradicionalna bližnjica prebere zapis SupBook ($01AE), preveri, ali nosi oznako self, če je ne, pa naslednji niz obravnava kot URL dokumenta. Vsak zapis, ki ni nič od teh dveh stvari, uide v privzeto vejo, privzeta veja pa je skoraj vedno »to je delovni zvezek sam«. Podporna povezava dodatka, povezava na isti list, neuporabljeno rezervirano mesto in odrezan zapis vsi končajo z isto napačno oznako. Medtem ko se to dogaja, nič ne sproži izjeme: zapis se razčleni, formula se ponovno prevede, datoteka se shrani brez opozorila, napaka pa pride na dan tri tedne pozneje, ko nekdo opazi stolpec ničel tam, kjer je bila nekdaj pretvorba valut. [MS-XLS] §2.4.271 opisuje zapis, ki je lahko sklic na samega sebe, sklic na isti list, vsebnik funkcij dodatka, zunanji delovni zvezek z virtualno potjo in tabelo imen listov, podatkovna povezava DDE ali OLE ali neuporabljeno rezervirano mesto — in sedmo stanje, ki ga specifikacija ne opisuje, na pravih diskih pa vseeno obstaja: zapis, ki se ne razčleni. Popravek ni boljša hevristika; je odklonitev hevristike kot take
Sedem vrst, ki jih zapis SupBook lahko nosi
HotXLS taksonomijo podpornih povezav izjavi kot zaprto naštetje v lxExternSheet.pas, vsaka poznejša odločitev pa se vključi nanjo. Devet vrednosti naštetja pokrije sedem kategorij, ker primer DDE in OLE potrebuje začasno stanje, preden ga je mogoče razrešiti:
type
TXLSSupportingLinkKind = (
slkUnknown, // razčlenjevanje spodletelo ali pa so ostali sledeči bajti
slkSelf, // ta delovni zvezek
slkSameSheet, // oznaka U+0000
slkAddIn, // vsebnik funkcij dodatka
slkExternalWorkbook, // virtualna pot + tabela imen listov
slkDde, // razrešeno iz zastavic ExternName
slkOle, // razrešeno iz zastavic ExternName
slkDdeOrOle, // eno od obeh, še ni znano katero
slkUnused); // rezervirano mesto iz enega presledka
TXLSFormulaReferenceClass = (
frcInternal,
frcExternalWorkbook,
frcExternalOther,
frcUnknownOrMalformed);
TXLSXtiInfo = record
XtiIndex : Integer; // ničelno osnovano, tako kot v ExternSheet.rgXTI
ExternID : Integer; // eniško osnovano, notranja konvencija
SupBookIndex: Integer;
Sheet1Index : Integer;
Sheet2Index : Integer;
LinkKind : TXLSSupportingLinkKind;
end;
Razvrščanje vodijo čuvaji, ne nizi. Vrednost polja $0401 označuje zapis self. Število listov ena skupaj z $3A01 označuje vsebnik dodatka. Le vrednost v območju 1 do $00FF pomeni, da sledi kodirana virtualna pot, in šele takrat HotXLS sploh dekodira niz. Vse zunaj teh treh oblik ostane slkUnknown, zapis, čigar tabela imen listov ne porabi telesa zapisa točno do konca, pa se ponovno uvrsti v slkUnknown, tudi ko je glava videla verodostojno
Zakaj se oznaka istega lista dekodira kot prazen niz?
Ker splošni bralnik BIFF nizov uniči bajt, od katerega je odvisno razvrščanje. Podporna povezava na isti list je enoznakovni niz, katerega edini znak je U+0000, TXLSBlob.GetBiffString pa ga vrne kot prazen WideString, nerazločljiv od res prazne poti — točno tak vhod pa hevristika samosklica odgovori z »self«. HotXLS zato iz telesa zapisa prebere surovo prvo kodo znaka, namesto da bi zaupala dekodirani vrednosti:
StringOffset := offset;
FDocUrl := Data.GetBiffString(offset, False, True);
FirstChar := $FFFF;
if val = 1 then
begin
StringOptions := Data.GetByte(StringOffset + 2);
if (StringOptions and $01) = 0 then
FirstChar := Data.GetByte(StringOffset + 3) // stisnjeno, en bajt
else
FirstChar := Data.GetWord(StringOffset + 3); // široko, dva bajta
end;
if FirstChar = 0 then
FKind := slkSameSheet
else if (Length(FDocUrl) = 1) and (FDocUrl[1] = WideChar(#32)) then
FKind := slkUnused
else if Pos(WideChar(#3), FDocUrl) > 0 then
FKind := slkDdeOrOle
else if FDocUrl <> '' then
FKind := slkExternalWorkbook;
Upoštevajte vejo stisnjeno-proti-široko. Opcijski bajt leži na fiksnem odmiku od glave niza, prva koda znaka pa je en bajt ali dva, odvisno od bita 0, zato branje kot bajt brez pogojev deluje na večini datotek in odpove na tistih, ki jih zapišejo lokalizirane različice — kar je za hrošča čim slabša porazdelitev. Neuporabljeno rezervirano mesto ujame enako, po dobesednem tovoru iz enega samega presledka, primer DDE ali OLE pa po ločilu U+0003, vgrajenem v kodirano pot
Zakaj se DDE in OLE ne data ločiti že ob zapisu SupBook?
Ker zapis SupBook ne nosi ločevalnih bitov. Pove vam le, da je povezava ena od obeh; zastavici fOle in fOleLink, ki odločata katero, živita v zapisu ExternName ($0023), ki v tok prispe kasneje. HotXLS ob razčlenjevanju zabeleži slkDdeOrOle in ga zoži v ParseExternalName; če ExternName nikoli ne prispe, vrsta za vedno ostane začasna — kar je pravilno, ker datoteka resnično ne pove. Vsak porabnik naprej po toku obravnava to začasno vrednost kot pravo vrednost in ne kot manjkajočo, zato noben klicatelj ne rabi izmišljati odločitvenega kriterija. Ugibanje »verjetno DDE« bi tu kupilo bolj urejeno naštetje in razred napačnih odgovorov, ki jim ne bi znal slediti nihče:
if FKind = slkDdeOrOle then
begin
if Data.DataLength < 2 then
Exit;
Flags := Data.GetWord(0);
if (Flags and $0010) <> 0 then
FKind := slkOle
else if (Flags and $0008) <> 0 then
FKind := slkDde;
end;
Indeksi XTI so na disku ničelno osnovani, znotraj pa eniško
HotXLS pretvorbo off-by-one izvede točno enkrat, v trenutku, ko žeton vstopi v notranje sintaksno drevo, in nikjer drugje. PtgNameX.ixti ([MS-XLS] §2.5.198.85) je ničelno osnovan indeks v polje rgXTI zapisa ExternSheet ($0017, §2.4.106), notranja konvencija ExternID knjižnice pa je eniško osnovana, z ničlo rezervirano za »ni zunanjega lista«. Bralna pot BIFF8 ob dekodiranju žetona tNameX izvede FExternID := wValue + 1, pisalna pa izda StoreExternID - 1, surovi žetonjski pogled in semantika na disku pa ostaneta nedotaknjena. To narediti narobe je nenavadno težko ujeti: zunanja definirana imena se razrešijo na sosednji vnos, v datoteki z enim samim vnosom XTI pa indeks 0 postane indeks 1, zgreši in ime tiho degradira. Regresija, ki izvaja le ponovno prevedeno besedilo formul, je nikoli ne vidi, ker ponovno prevajanje indeksa na disku sploh ne dotakne — ista past, zaradi katere je definirana imena čez liste in delovne zvezke vredno preizkušati proti pravim bajtnim tokovom. Razreševanje je omejeno na obeh koncih: TlxExternSheetSheet.TryResolveXti vrne False za negativen indeks ali manjkajoč vnos, TXLSSupBook.TryGetKind vrne False za indeks SupBook izven polja, ClassifyXti pa nato preslika slkSelf in slkSameSheet v frcInternal, slkExternalWorkbook v frcExternalWorkbook, slkAddIn, slkDde, slkOle in slkDdeOrOle pa v frcExternalOther. Vse ostalo, vsaka pot izven območja vključno, pristane v frcUnknownOrMalformed
Razvrščanje formule, preden se zamrzne
TXLSCompiledFormula.ClassifyReferences ohranjeni žetonjski tok BIFF preišče neposredno, namesto da bi formulo dekompiliral in iskal oglate oklepaje. Lovljenje oklepajev v besedilu formul je besedilna hevristika v plašču razčlenjevalnika: zadene nizovne literale, zadene strukturirane reference in povsem spregleda zunanja definirana imena, saj ta v dekompilirani obliki ne nosijo oklepajev. Žetonjski pregled gleda le na PtgNameX, PtgRef3d, PtgArea3d, PtgRefErr3d in PtgAreaErr3d, ko žetonjskega toka ni več, pa se vrne k obhodu sintaksnega drevesa. Zlivanje je namenoma pesimistično — fiksna prednost je frcUnknownOrMalformed, nato frcExternalWorkbook, nato frcExternalOther, nato frcInternal — tako en sam neberljiv žeton zastrupi celotno formulo. Za zunanje definirano ime se preveri tudi indeks imena: eniško osnovan, znotraj območja in podprt z ohranjenim zapisom ExternName
var
Wb : TXLSWorkbook;
Sheet: TXLSWorksheet;
i : Integer;
begin
Wb := TXLSWorkbook.Create;
try
Wb.Open('quarterly.xls');
for i := 1 to Wb.Sheets.Count do // Sheets je eniško osnovan
begin
Sheet := Wb.Sheets[i];
// zamrzne LE formule, razvrščene kot frcExternalWorkbook;
// notranje reference, dodatki, DDE/OLE in okvarjene ostanejo formule
Sheet.ConvertFormulasToValues(True);
end;
Wb.SaveAs('quarterly-detached.xls');
finally
Wb.Free;
end;
end;
Parameter OnlyExternal je mesto, kjer se taksonomija odplača. Zamrznitev formule je nepovratna, zato mora operacija dokazati, da je referenca zunanji delovni zvezek, in ne zgolj slutiti. Klici dodatkov preživijo, povezave DDE in OLE preživijo, preživi vse, česar razčlenjevalnik ni mogel popolnoma razumeti, ker je varen izid negotovosti, da se ne spremeni nič. Isti red vlada pri ponovnem vezanju formul, kopiranih med delovnimi zvezki, kjer se napačno razvrščena referenca zveže na napačen zvezek, namesto da bi glasno odpovedala
Zapisi, ki se ne razčlenijo, se zapišejo nazaj nedotaknjeni
HotXLS ohrani izvirni tovor SupBook in ga, kadar zapis ni bil nikoli urejen, ponovno izda bajt za bajtom. Neuspeh razčlenjevanja nastavi slkUnknown in počisti izpeljano stanje, zajeto telo pa ostane v FRawData; shranjevalna pot mu daje prednost pred vsako rekonstrukcijo, dokler vnos ni umazan in ni zapis self. Alternativa — normalizacija nerazčlenjenega zapisa v samosklic, da ima zapisovalnik kaj dobro oblikovanega za izdati — spremeni zapis, ki ga niste razumeli, v zapis, ki je nedvoumno narobe. To načelo je isti dogovor, ki velja za projekte VBA in njihove zunanje reference čez cikel nalaganja in shranjevanja, in je razlika med knjižnico, ki realne datoteke prenaša naprej in nazaj, in knjižnico, ki prenaša datoteke, ki jih njena preizkusna zbirka slučajno vsebuje. Delovni zvezek, ki je prešel skozi petnajst let različic Excela, generator poročil in dve orodji za selitev, bo vseboval zapise, ki jih ni zasnoval nihče živ. Zapišite jih nazaj, kot ste jih našli
Tipsko razvrščanje zapisov SupBook in XTI je prišlo s HotXLS 2.361.2 do 2.361.4, skupaj z omejenim razreševanjem XTI in varnejšo potjo ConvertFormulasToValues, opisano v tem članku. Če vzdržujete kodo Delphi ali C++Builder, ki bere stare datoteke xls s klici dodatkov, povezavami DDE ali OLE ali zunanjimi definiranami imeni, komponenta preglednic HotXLS za Delphi celotno taksonomijo obravnava izvorno, brez namestitve Excela in brez avtomatizacije OLE na stroju, ki delo opravlja