A HotXLS, a natív Delphi és C++Builder Excel-könyvtár, a TryGetCachedFormulaValue és az IXLSFormulaCacheReader útján olvassa azt az értéket, amelyet az Excel már a képlet mellett tárolt. Egyik belépési pont sem hívja a számítót, nem dekompilál képlettokéneket, nem frissíti a dirty állapotot, és nem ír semmit vissza a modellbe, így egy csak olvasott munkafüzet pontosan úgy marad, ahogy megnyitotta
A mögötte álló forgatókönyv unalmas és rendkívül gyakori. Egy éjszakai feladat néhány száz, mások által készített munkafüzetet nyit meg, mindegyikből egy összesítő oszlopot húz ki, és a számokat adattárházba nyomja. Az összesítők már a fájlokban ülnek — az Excel kiszámolta és elmentette őket. Mégis abban a pillanatban, amikor a feladat egy képletcellától az értékét kéri, egy olyan könyvtár, amely erre a kérdésre csak egy választ tud, függőséggrafikont épít és kiértékeli az egész munkalapot, és egy I/O-kötöttnek szánt feladat számítási teljesítménytesztté válik
Miért kerül egy képletcella olvasása teljes újraszámolásba?
Mert egy érték-lekérdező egy képletcellán kérés egy érték előállítására, és az egyetlen univerzálisan helyes módja az előállításnak a képlet kiértékelése. Ez a helyes alapértelmezés egy munkafüzeteket szerkesztő alkalmazás számára, és a rossz alapértelmezés egy kinyerő pipeline számára. Rosszabb, hogy a kiértékelés nem mentes a mellékhatásoktól: eredményeket ír a cellákba, dirty jelzőket billent át, és eltérően oldhat fel a készítő alkalmazáshoz képest, amikor egy függvény nem támogatott vagy egy külső hivatkozás sérült. Egy feladat, amelyet az üzemeltetési csapatának csak olvasóként írt le, csendben olyan munkafüzetet állít elő, amely már nem egyezik a lemezen lévővel, és ha bármi később menti, a lemezen lévő fájl is megváltozik
A gyorsítótárazott érték olvasása a szerződés másik fele. Szűkebb kérdésre felel — mit tárolt ide a készítő alkalmazás? —, és minden mást megtagad. Amikor valóban friss számokat akar, a HotXLS továbbra is ad függőséggráfon hajtott növekményes újraszámolást; a lényeg az, hogy a kinyerés és a kiértékelés két különböző hívás legyen, nem egy hívás két hangulattal
Három ortogonális tény egyetlen celláról
Először a következtetés: egy gyorsítótárazott képletérték három független tényt hordoz, és egyetlen Variant-be összecsukva elvész a szükséges információ. A TXLSFormulaCacheInfo külön tartja őket State, Kind és Value néven. A TXLSFormulaCacheState a származást rögzíti öt esetben — xlfcsNotFormula, xlfcsMissing, xlfcsLoaded, xlfcsCalculated és xlfcsInvalidated —, a TXLSFormulaCacheValueKind pedig a terhelést osztályozza xlfcvBlank, xlfcvNumber, xlfcvDateTime, xlfcvString, xlfcvBoolean vagy xlfcvError néven. Ez az elkülönítés az, amely lehetővé teszi a jelenlét őszinte jelentését: egy gyorsítótárazott üres cella, egy gyorsítótárazott üres sztring, egy gyorsítótárazott False, egy gyorsítótárazott nulla és egy gyorsítótárazott hiba mind valós értékek, így a jelenlét sosem következtethető a VarIsEmpty-ből vagy a VarIsNull-ből. A TryGetCachedFormulaValue csak xlfcsLoaded és xlfcsCalculated esetén ad True-t, és akkor is diagnosztizálható állapotot tölt ki, amikor False-ot ad
var
Book: TXLSXWorkbook;
Info: TXLSFormulaCacheInfo;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-model.xlsx');
// a SheetIndex, Row és Col itt mind egy alapú
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;
Miért hiányzik a gyorsítótárazott érték?
Pontosan négy oka van annak, hogy a TryGetCachedFormulaValue False-ot ad vissza, és az állapot megmondja, melyik érvényes. A xlfcsNotFormula azt jelenti, hogy a cella literált vagy semmit sem tartalmaz, és a tartományon kívüli koordináták ugyanabba a válaszba csuklanak össze. A xlfcsMissing azt jelenti, hogy a cella valóban képlet, de a készítő nem tárolt értékterhelést hozzá — gyakori kimenet, amikor egy generátor képleteket ír, és az Excel első megnyitáskor tölti ki az eredményeket. A xlfcsInvalidated azt jelenti, hogy a képletszöveg betöltés után cserélve lett, így a korábban ott lévő érték egy már nem létező kifejezést ír le. A xlfcsCalculated ezzel szemben sikeres eset: azt jelöli, amit a saját kódja vagy a HotXLS kiértékelője ebben a szekcióban állított elő, szemben a xlfcsLoaded-dal, amely a fájlból jött
Az őszinteség a hiányzó gyorsítótárral kapcsolatban fontosabb, mint azt elfedni. A HotXLS visszautasítja egy érték kitalálását, és mentéskor ugyanilyen szigorú — csak a xlfcsLoaded és a xlfcsCalculated bocsát ki gyorsítótárazott értéket, a xlfcsMissing és a xlfcsInvalidated pedig egyedül a képletet írja, elavult szám fájlba fagyasztása helyett. Ez három épelméjű választ hagy egy pipeline-ban: kihagyni a sort és feljegyezni a rést, szándékosan újraszámolni azt az egy munkafüzetet és vállalni a költséget, vagy kiértékelni és egyeztetni. Ha a kiértékelt szám nem egyezik azzal, amit a készítő alkalmazás írt volna, a képlet-kiértékelés nyomkövető az eszköz annak kiderítésére, hol válnak szét a két számítás, az eredményből találgatás helyett
Egyetlen olvasó a klasszikus, OOXML és ODF motorokon át
Egy pipeline nem foglalkozhat azzal, hogy az éppen megnyitott fájl BIFF, OOXML vagy ODF volt. Az IXLSFormulaCacheReader az egyetlen csak olvasó belépési pont mindháromhoz: a TXLSWorkbook.CreateFormulaCacheReader és a TXLSXWorkbook.CreateFormulaCacheReader egyaránt könnyűsúlyú adaptert ad vissza az egyes motorok által már használt ritka cellakeresésre, azonos egy alapú munkalap-, sor- és oszlopkoordinátákkal. A munkafüzet-osztályok szándékosan nem implementálják maguk az interfészt — egy munkafüzetre mutató interfészreferencia megváltoztatná annak tulajdonlási szemantikáját, és lehetővé tenné a hívóknak, hogy kicsússzanak az élettartami bérleményen. Ehelyett a munkafüzet megsemmisítése törli a nyers pointert a bérleményen belül, és a kódja által még tartott bármely olvasó EXLSFormulaCacheReaderInvalidated-t dob a következő lekérdezésén, felszabadított memória dereferálása helyett. Ez gyors hibázású élettartam-ellenőrzés, nem párhuzamosítási garancia
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);
// Nem futott számító, nem mozdult dirty jelző, a Book változatlan
end;
Hol élnek valójában a gyorsítótárazott bájtok
Klasszikus .xls fájlok esetén a gyorsítótár a Formula rekord FormulaValue mezője, a [MS-XLS] §2.5.133 által leírt nyolc bájt. Amikor a magas szó $FFFF-fel egyezik, a terhelés nem IEEE 754 double, hanem címkézett variáns, és az elrendezést könnyű finoman elrontani: a variánstípus a val[0]-ban ül, a boolean vagy BErr terhelés a val[2]-ben, a val[1] definiálatlan. A HotXLS korábban a val[1]-ből olvasta a terhelést, ami az az off-by-one fajta, amely csak azokon a konkrét fájlokon kerül felszínre, amelyek szám helyett booleant vagy hibát gyorsítótáraznak. Az olvasó és a megosztott képlet író most azonos eltolásokon állapodik meg, így egy gyorsítótárazott TRUE sértetlenül él túl egy betöltésen és mentésen, zajmá romlás helyett
A típushűség a csomagformátumokban külön probléma, saját csapdával. OOXML-ben a gyorsítótárazott érték a c elemen lóg <v>-ként, a t attribútum nevezi meg a típust az ECMA-376 Part 1 §18.3.1.4 szerint. A HotXLS a t="e"-t egyből varError Variant-be olvassa, és mentéskor vissza képezi a standard hibaszövegre, így a hibák sosem maszkírozzák magukat közönséges egész számoknak — de a Delphi RTL itt nem segít, mert a VarAsType(Integer, varError) konverziós kivételt dob. A működő konstrukció közvetlenül állítja a TVarData.VType-ot és a TVarData.VError-t. A dátumok ellentétes irányban követik ugyanezt a fegyelmet: a t="d" és az ODF dátum értéktípus explicit típusdeklarációk, és varDate-dé válnak, míg a BIFF numerikus gyorsítótár egyáltalán nem hordoz dátumjelzőt, és ezért Double marad. A HotXLS sosem találgat dátumot cellaszámformátumból, mert a számformátum prezentáció, a gyorsítótár pedig adat. Az ODF még egy, a tudásra érdemes esetet ad — az office:value-type="void" olyan gyorsítótárat fejez ki, amely jelen van, de nem hordoz értéket, és mivel az ODF-nek nincs hiba értéktípusa, a hibának tűnő szöveg szövegként őrződik meg, hibává léptetés helyett
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;
Megosztják-e a megosztott képletek a gyorsítótárazott értékeiket?
Nem, és a másik feltételezése az, ahogy egy átfogó olvasás egy teljes oszlopra ugyanazt a számot jelenti. Egy OOXML megosztott képlet csak a képlet kifejezést és a tárolási optimalizálást osztja meg; minden tagcella továbbra is a saját <v>-jét birtokolja. A HotXLS ezért sosem propagálja a gyökértag gyorsítótárát egy érték nélkül érkező követőre, és egy xlfcsMissing-ként betöltött követő mentés és újranyitás után is xlfcsMissing-et jelent. Ha azon dolgozik, hogy a csoport hogyan tárolódik és bontódik ki elsőként, a megosztott képlet si attribútumának és kibontásának mechanikája külön tárgyalt; a gyorsítótár olvasásához a szabály egy sorra zsugorodik — kérdezzen meg minden cellát, és semmiben ne bízzon, amit nem kért meg
A gyorsítótárazott érték olvasása, az egységes motorokon átívelő olvasó és a kiértékelőmotor, amelyet úgy dönthet el, hogy nem hív meg, mind a standard HotXLS Delphi Spreadsheet Component részét képezi Delphi és C++Builder számára, Excel- vagy OLE automatizálási kiszolgálófüggés nélkül; a termékoldal hordozza a teljes API referenciát az itt bemutatott munkafüzet- és olvasó belépési pontokhoz