HotXLS, izvorna knjižnica Excel za Delphi in C++Builder, prebere vrednost, ki jo je Excel že shranil ob formuli, prek TryGetCachedFormulaValue in IXLSFormulaCacheReader. Noben od vstopnih točk ne pokliče kalkulatorja, ne dekompilira žetonov formul, ne posodobi stanja umazanosti in ne zapiše ničesar nazaj v model, zato delovni zvezek, ki ga le berete, ostane točno tak, kot ste ga odprli
Scenarij, ki to poganja, je dolgočasen in izjemno pogost. Nočno opravilo odpre nekaj sto delovnih zvezkov, ki jih je izdelal nekdo drug, iz vsakega izvleče en stolpec vsot in številke potisne v podatkovno skladišče. Vsote že ležijo v datotekah — Excel jih je izračunal in shranil. A v trenutku, ko opravilo vpraša celico s formulo po vrednosti, knjižnica, ki ima za to vprašanje samo en odgovor, zgradi graf odvisnosti in vrednoti cel list, opravilo, ki bi moralo biti vezano na I/O, pa se spremeni v merilnik izračunov
Zakaj branje celice s formulo stane poln ponovni izračun?
Ker je pridobivalnik vrednosti na celici s formulo prošnja, da se vrednost izdela, edini splošno pravilen način izdelave pa je vrednotenje formule. To je prava privzeta izbira za aplikacijo, ki ureja delovne zvezke, in napačna privzeta izbira za cevovod, ki jih izvleče. Še huje, vrednotenje ni brez stranskih učinkov: rezultate zapiše nazaj v celice, prevrne zastavice umazanosti in se lahko razreši drugače kot pri proizvajajoči aplikaciji, kadar funkcija ni podprta ali je zunanja referenca pokvarjena. Opravilo, ki ste ga svoji ekipi za obratovanje opisali kot samo za branje, tiho izdela delovni zvezek, ki se ne ujema več z tistim na disku, če ga kasneje karkoli shrani, pa se spremeni tudi datoteka na disku
Branje predpomnjenih vrednosti je druga polovica dogovora. Odgovarja na ožje vprašanje — kaj je proizvajajoča aplikacija shranila tukaj? — in zavrača odgovoriti na karkoli drugega. Ko resnično želite sveže številke, vam HotXLS vseeno ponudi inkrementalni ponovni izračun, voden z grafom odvisnosti; bistvo je, da sta izvleka in vrednotenje dva različna klica in ne en klic z dvema razpoloženjema
Trije ortogonalni podatki o eni celici
Najprej sklep: predpomnjena vrednost formule nosi tri neodvisne podatke, njihovo zloženje v en sam Variant pa izgubi informacijo, ki jo potrebujete. TXLSFormulaCacheInfo jih ločuje kot State, Kind in Value. TXLSFormulaCacheState beleži izvor čez pet primerov — xlfcsNotFormula, xlfcsMissing, xlfcsLoaded, xlfcsCalculated in xlfcsInvalidated — TXLSFormulaCacheValueKind pa razvršča tovor kot xlfcvBlank, xlfcvNumber, xlfcvDateTime, xlfcvString, xlfcvBoolean ali xlfcvError. Prav ta ločitev omogoča pošteno poročanje o prisotnosti: predpomnjena prazna celica, predpomnjen prazen niz, predpomnjen False, predpomnjena ničla in predpomnjena napaka so vse prave vrednosti, prisotnosti torej nikoli ni mogoče sklepati iz VarIsEmpty ali VarIsNull. TryGetCachedFormulaValue vrne True le za xlfcsLoaded in xlfcsCalculated, ob False pa vseeno izpolni stanje, iz katerega je razvidna diagnoza
var
Book: TXLSXWorkbook;
Info: TXLSFormulaCacheInfo;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-model.xlsx');
// SheetIndex, Row in Col so tu vsi eniško osnovani
if Book.TryGetCachedFormulaValue(1, 12, 5, Info) then
Writeln('cached value: ', VarToStr(Info.Value))
else
Writeln('no usable cache, state ordinal ', Ord(Info.State));
finally
Book.Free;
end;
end;
Zakaj predpomnjena vrednost manjka?
Ravno štirje so razlogi, da TryGetCachedFormulaValue vrne False, stanje pa vam pove, kateri velja. xlfcsNotFormula pomeni, da celica nosi literál ali sploh nič, koordinate izven območja pa se zložijo v isti odgovor. xlfcsMissing pomeni, da je celica res formula, proizvajalec pa zanjo ni shranil tovora vrednosti — pogost izid, ko generator zapiše formule in pusti Excelu, da rezultate dopolni ob prvem odpiranju. xlfcsInvalidated pomeni, da je besedilo formule po nalaganju zamenjano, vrednost, ki je bila nekdaj tam, pa opisuje izraz, ki ne obstaja več. xlfcsCalculated pa je nasprotno primer uspeha: označuje vrednost, ki jo je v tem zasedanju izdelala vaša koda ali vrednotitelj HotXLS, v nasprotju s xlfcsLoaded, ki je prišla iz datoteke
Iskrenost glede manjkajočega predpomnilnika je pomembnejša od prekrivanja luknj. HotXLS noče izmisliti vrednosti, pri shranjevanju pa je enako strog — predpomnjeno vrednost izdajata le xlfcsLoaded in xlfcsCalculated, xlfcsMissing in xlfcsInvalidated pa zapišeta samo formulo, namesto da bi zamrznila zastarel podatek v datoteko. V cevovodu vam to pusti tri smiselne odgovore: preskočite vrstico in zabeležite vrzel, namenoma ponovno izračunajte tisti en zvezek in sprejmite ceno, ali pa vrednotite in uskladite. Če izračunana številka ne ustreza temu, kar bi zapisala proizvajajoča aplikacija, je sledilnik vrednotenja formul orodje za ugotavljanje, kje se izračuna razideta, in ne ugibanje iz rezultata
En bralnik čez klasični, OOXML in ODF pogon
Cevovodu ne bi smelo biti mar, ali je bila pravkar odprta datoteka BIFF, OOXML ali ODF. IXLSFormulaCacheReader je ena sama vstopna točka, namenjena branju, za vse tri: TXLSWorkbook.CreateFormulaCacheReader in TXLSXWorkbook.CreateFormulaCacheReader vračata lahkoten vmesnik nad razpršenim iskanjem celic, ki ga vsak pogon že uporablja, z enakimi koordinatami lista, vrstice in stolpca, osnovanimi na ena. Razredi delovnih zvezkov vmesnika namenoma ne izvajajo sami — referenca vmesnika na delovni zvezek bi spremenila njegovo semantiko lastništva in klicateljem dovolila zdrsiti mimo najema življenjske dobe. Namesto tega uničenje delovnega zvezka počisti surovi kazalec znotraj tega najema, vsak bralnik, ki ga vaša koda še drži, pa ob naslednji poizvedbi sproži EXLSFormulaCacheReaderInvalidated, namesto da bi dereferenciral sproščen pomnilnik. To je hitro odpovedoče preverjanje življenjske dobe, ne zagotovilo sočasnosti
var
Reader: IXLSFormulaCacheReader;
Info: TXLSFormulaCacheInfo;
Row, Missing, Errors: Integer;
Total: Double;
begin
Reader := Book.CreateFormulaCacheReader;
Total := 0;
Missing := 0;
Errors := 0;
for Row := 2 to LastRow do
if Reader.TryGetCachedFormulaValue(1, Row, 7, Info) then
begin
case Info.Kind of
xlfcvNumber: Total := Total + Double(Info.Value);
xlfcvError: Inc(Errors);
end;
end
else if Info.State = xlfcsMissing then
Inc(Missing);
// Kalkulator ni tekel, zastavica umazanosti se ni premaknila, Book je nespremenjen
end;
Kjer predpomnjeni bajti resnično živijo
Za klasične datoteke .xls je predpomnilnik polje FormulaValue zapisa Formula, osem bajtov, opisanih v [MS-XLS] §2.5.133. Ko je zgornja beseda enaka $FFFF, tovor ni IEEE 754 double, temveč označena varianta, postavitev pa je lahko subtilno narobe: vrsta variante leži v val[0], tovor boolean ali BErr pa v val[2], val[1] pa je nedefiniran. HotXLS je tovor prej bral iz val[1], kar je tista vrsta off-by-one, ki pride na dan le na posebnih datotekah, ki predpomnijo boolean ali napako namesto številke. Bralnik in zapisovalnik skupnih formul se zdaj strinjata o istih odmikih, zato predpomnjen TRUE preživi nalaganje in shranjevanje nedotaknjen, namesto da bi razpadel v šum
Zvestoba vrst v paketnih formatih je ločen problem s svojo pastjo. V OOXML visi predpomnjena vrednost na elementu c kot <v>, atribut t pa poimenuje vrsto po ECMA-376 Part 1 §18.3.1.4. HotXLS prebere t="e" naravnost v Variant varError in ga pri shranjevanju preslika nazaj v standardno besedilo napake, zato se napake nikoli ne izdajajo za navadna cela števila — RTL Delphi pa vam tu ne pomaga, ker VarAsType(Integer, varError) sproži izjemo pretvorbe. Delujoča konstrukcija nastavi TVarData.VType in TVarData.VError neposredno. Datumi sledijo istemu redu v obratni smeri: t="d" in vrsta vrednosti datuma ODF sta izrecni izjavi vrste in postaneta varDate, številski predpomnilnik BIFF pa ne nosi nobene zastavice datuma in zato ostane Double. HotXLS nikoli ne uganjuje datuma iz oblike števila celice, ker je oblika števila predstavitev, predpomnilnik pa podatki. ODF doda še en primer, vreden poznavanja — office:value-type="void" izraža predpomnilnik, ki je prisoten, a ne nosi vrednosti, ker ODF nima vrste vrednosti za napako, pa se besedilo, ki spominja na napako, ohrani kot besedilo in ne povzdigni v napako
function DescribeCache(const Info: TXLSFormulaCacheInfo): string;
begin
case Info.State of
xlfcsNotFormula: Result := 'not a formula cell';
xlfcsMissing: Result := 'formula stored with no cached value';
xlfcsInvalidated: Result := 'formula replaced since load';
else
case Info.Kind of
xlfcvError: Result := 'error code ' + IntToStr(TVarData(Info.Value).VError);
xlfcvDateTime: Result := DateTimeToStr(VarToDateTime(Info.Value));
xlfcvBoolean: Result := BoolToStr(Info.Value, True);
xlfcvNumber: Result := FloatToStr(Double(Info.Value));
xlfcvString: Result := VarToStr(Info.Value);
else
Result := 'present but blank';
end;
end;
end;
Ali skupne formule delijo svoje predpomnjene vrednosti?
Ne, in domneva nasprotnega je način, kako pretres na koncu poroča isto številko za cel stolpec. Skupna formula OOXML deli le izraz formule in optimizacijo shranjevanja; vsaka članska celica še vedno lasti svoj <v>. HotXLS zato nikoli ne razširi predpomnilnika korenskega člana na sledilca, ki je prispel brez vrednosti; sledilec, naložen kot xlfcsMissing, pa še po shranjevanju in ponovnem odpiranju poroča xlfcsMissing. Če razglabljate, kako je skupina sploh shranjena in razširjena, so mehanika atributa si skupne formule in njena razširitev obravnavana ločeno; za branje predpomnilnika se pravilo skrči na eno vrstico — vprašajte vsako celico, ne zaupajte ničemer, česar niste vprašali
Branje predpomnjenih vrednosti, poenoteni bralnik med pogoni in pogon ponovnega izračuna, ki ga lahko izberete, da ga ne pokličete, vsi pridejo v standardni HotXLS Delphi Spreadsheet Component za Delphi in C++Builder, brez odvisnosti od Excela ali kateregakoli strežnika avtomatizacije OLE; stran izdelka nosi celotno referenco API za vstopni točki delovnega zvezka in bralnika, prikazana tukaj