HotXLS, natiivi Delphi- ja C++Builder-Excel-kirjasto, lukee arvon, jonka Excel jo tallensi kaavan viereen, TryGetCachedFormulaValue- ja IXLSFormulaCacheReader-rajapinnan kautta. Kumpikaan sisääntulopiste ei kutsu laskinta, dekompiloi kaavatokeneja, päivitä likatilaa eikä kirjoita mitään takaisin malliin, joten työkirja, jota vain luet, pysyy täsmälleen sellaisena kuin avasit sen
Tilanne, joka ajaa tätä, on tylsä ja erittäin yleinen. Yöajo avaa muutaman sadan muualta tuotetun työkirjan, vetää jokaisesta yhden summasarakkeen ja työntää luvut varastoon. Summat istuvat jo tiedostoissa — Excel laski ne ja tallensi ne. Silti sillä hetkellä, kun ajo kysyy kaavasolulta sen arvoa, kirjasto, jolla on vain yksi vastaus kyseiseen kysymykseen, rakentaa riippuvuusgraafin ja evaluoi koko arkin, ja työ, jonka pitäisi olla I/O-sidottua, muuttuu laskentabenchmarkiksi
Miksi kaavasolun lukeminen maksaa koko uudelleenlaskennan?
Koska arvogetteri kaavasolulla on pyyntö tuottaa arvo, ja ainoa yleisesti oikea tapa tuottaa se on evaluoida kaava. Se on oikea oletus sovellukselle, joka muokkaa työkirjoja, ja väärä oletus putkelle, joka poimii niitä. Pahempaa, evaluointi ei ole vapaa sivuvaikutuksista: se kirjoittaa tulokset takaisin soluihin, se kääntää likalippuja, ja se voi ratketa eri tavalla kuin tuottava sovellus, kun funktio on tukematon tai ulkoinen viittaus on rikki. Ajo, jonka kuvasit käyttötiimillesi vain luku -kelpoiseksi, tuottaa hiljaa työkirjan, joka ei enää vastaa levyllä olevaa, ja jos mikä tahansa myöhemmin tallentaa sen, levyn tiedosto muuttuu myös
Välimuistiarvojen lukeminen on sopimuksen toinen puolisko. Se vastaa kapeampaan kysymykseen — mitä tuottava sovellus tallensi tähän? — ja kieltäytyy vastaamasta mihinkään muuhun. Kun haluat aidosti tuoreita lukuja, HotXLS antaa edelleen riippuvuusgraafin ohjaaman inkrementaalisen uudelleenlaskennan; pointti on, että poiminnan ja evaluoinnin pitäisi olla kaksi eri kutsua, ei yksi kutsu kahdella mielialalla
Kolme ortogonaalista faktaa yhdestä solusta
Johtopäätös ensin: välimuistitettu kaava-arvo kantaa kolme riippumatonta faktaa, ja niiden kutistaminen yhdeksi Variantiksi menettää tarvitsemasi tiedon. TXLSFormulaCacheInfo pitää ne erillään State-, Kind- ja Value-kenttinä. TXLSFormulaCacheState kirjaa alkuperän viidessä tapauksessa — xlfcsNotFormula, xlfcsMissing, xlfcsLoaded, xlfcsCalculated ja xlfcsInvalidated — kun taas TXLSFormulaCacheValueKind luokittelee sisällön xlfcvBlankiksi, xlfcvNumberiksi, xlfcvDateTimeiksi, xlfcvStringiksi, xlfcvBooleaniksi tai xlfcvErroriksi. Tämä erittely on se, mikä antaa läsnäolon raportoida rehellisesti: välimuistitettu tyhjä, välimuistitettu tyhjä merkkijono, välimuistitettu False, välimuistitettu nolla ja välimuistitettu virhe ovat kaikki todellisia arvoja, joten läsnäoloa ei voi koskaan päätellä VarIsEmptystä tai VarIsNullista. TryGetCachedFormulaValue palauttaa Truen vain xlfcsLoadedille ja xlfcsCalculatedille, ja täyttää silti diagnosoitavan tilan palauttaessaan Falsen
var
Book: TXLSXWorkbook;
Info: TXLSFormulaCacheInfo;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-model.xlsx');
// SheetIndex, Row ja Col ovat kaikki ykköspohjaisia tässä
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;
Miksi välimuistiarvo puuttuu?
Juuri neljä syytä saa TryGetCachedFormulaValuein palauttamaan Falsen, ja tila kertoo, mikä niistä pätee. xlfcsNotFormula tarkoittaa, että solu sisältää literaalin tai ei mitään, ja koordinaattien ollessa alueen ulkopuolella romahtaa samaan vastaukseen. xlfcsMissing tarkoittaa, että solu oikeasti on kaava, mutta tuottaja ei tallentanut sille arvosisältöä — yleinen lopputulos, kun generaattori kirjoittaa kaavat ja antaa Excelin täyttää tulokset ensimmäisellä avauksella. xlfcsInvalidated tarkoittaa, että kaavateksti korvattiin latauksen jälkeen, joten arvo, joka siellä ennen oli, kuvaa lauseketta, jota ei enää ole olemassa. xlfcsCalculated sitä vastoin on onnistumistapaus: se merkitsee arvoa, jonka oma koodisi tai HotXLS:n evaluator tuotti tämän istunnon aikana, toisin kuin xlfcsLoaded, joka tuli tiedostosta
Rehellisyys puuttuvasta välimuistista merkitsee enemmän kuin sen peittäminen. HotXLS kieltäytyy keksimästä arvoa, ja tallennuksessa se on yhtä tiukka — vain xlfcsLoaded ja xlfcsCalculated tuottavat välimuistiarvon, kun taas xlfcsMissing ja xlfcsInvalidated kirjoittavat pelkän kaavan sen sijaan, että jäädyttäisivät vanhentuneen luvun tiedostoon. Se jättää sinulle kolme järkevää vastausta putkessa: ohita rivi ja kirjaa aukko muistiin, laske se yksi työkirja tarkoituksella uudelleen ja hyväksy kustannus, tai evaluoi ja sovita. Jos evaluoitu luku on eri mieltä sen kanssa, mitä tuottava sovellus olisi kirjoittanut, kaavaevaluoinnin jäljittäjä on työkalu siihen, missä kaksi laskentaa eroavat, arvaamisen sijaan tuloksesta
Yksi lukija klassisen, OOXML:n ja ODF:n moottoreiden yli
Putken ei pitäisi välittää, oliko juuri avattu tiedosto BIFF, OOXML vai ODF. IXLSFormulaCacheReader on yksi vain luku -sisääntulopiste kaikille kolmelle: sekä TXLSWorkbook.CreateFormulaCacheReader että TXLSXWorkbook.CreateFormulaCacheReader palauttavat kevyen sovittimen harvan soluhakun yli, jota kukin moottori jo käyttää, identtisillä ykköspohjaisilla arkin, rivin ja sarakkeen koordinaateilla. Työkirjaluokat eivät tarkoituksella toteuta rajapintaa itse — rajapintaviittaus työkirjaan muuttaisi sen omistusseemantiikkaa ja päästäisi kutsujat elinajan vuokrauksen ohi. Sen sijaan työkirjan tuhoaminen tyhjentää raakapointerin kyseisen vuokrauksen sisällä, ja mikä tahansa lukija, jota koodisi edelleen pitää, nostaa EXLSFormulaCacheReaderInvalidatedin seuraavassa kyselyssään vapautetun muistin dereferointiin sijaan. Se on fail-fast-elinaikatarkistus, ei samanaikaisuustakuu
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);
// Laskinta ei ajettu, likalippu ei liikkunut, Book muuttumaton
end;
Missä välimuistitavut oikeasti asuvat
Klassisille .xls-tiedostoille välimuisti on Formula-tietueen FormulaValue-kenttä, kahdeksan tavua, jotka [MS-XLS] §2.5.133 kuvaa. Kun yläsana on yhtä suuri kuin $FFFF, sisältö ei ole IEEE 754 -double vaan leimattu variantti, ja asettelu on helppo saada hienovaraisesti väärin: varianttityyppi istuu val[0]issa ja boolean- tai BErr-sisältö istuu val[2]issa, val[1]in ollessa määrittelemätön. HotXLS luki aiemmin sisällön val[1]istä, mikä on sellainen off-by-one, joka puhkeaa vain niillä tietyillä tiedostoilla, jotka välimuistittavat booleanin tai virheen luvun sijaan. Lukija ja jaetun kaavan kirjoittaja sopivat nyt samoista siirtymistä, joten välimuistitettu TRUE selviää latauksesta ja tallennuksesta ehjänä rapautumatta kohinaksi
Tyypin uskollisuus pakettimuodoissa on erillinen ongelma omalla ansallaan. OOXML:ssä välimuistiarvo roikkuu c-elementistä <v>:nä, t-attribuutin nimetessä tyypin ECMA-376 Part 1 §18.3.1.4:n mukaan. HotXLS lukee t="e":n suoraan varError-Variantiksi ja kartoittaa sen takaisin standardin virhetekstiksi tallennuksessa, joten virheet eivät koskaan naamioitu tavallisiksi kokonaisluvuiksi — mutta Delphi RTL ei auta sinua tässä, koska VarAsType(Integer, varError) nostaa muunnos poikkeuksen. Toimiva rakennelma asettaa TVarData.VTypein ja TVarData.VErrorin suoraan. Päivät noudattavat samaa kuria vastakkaiseen suuntaan: t="d" ja ODF:n päiväarvotyyppi ovat eksplisiittisiä tyyppijulistuksia ja muuttuvat varDateiksi, kun taas BIFF:n numeerinen välimuisti ei kanna päivälippua lainkaan ja pysyy siksi Doublena. HotXLS ei koskaan arvaa päivää solun lukumuodosta, koska lukumuoto on esitys ja välimuisti on dataa. ODF lisää yhden tapauksen, joka on syytä tietää — office:value-type="void" ilmaisee välimuistin, joka on läsnä mutta ei kanna arvoa, ja koska ODF:llä ei ole virheen arvotyyppiä, virheen näköinen teksti säilytetään tekstinä eikä ylennetä virheeksi
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;
Jakavatko jaetut kaavat välimuistiarvonsa?
Ei, ja toisin olettaminen on se, miten kierros päätyy raportoimaan samaa lukua koko sarakkeelle. OOXML:n jaettu kaava jakaa kaavalausekkeen ja tallennusoptimoinnin vain; jokainen jäsen solu omistaa edelleen oman <v>:nsä. HotXLS ei siksi koskaan levitä juurijäsenen välimuistia seuraajalle, joka saapui ilman arvoa, ja seuraaja, joka latasi xlfcsMissinginä, raportoi edelleen xlfcsMissingin tallennuksen ja uudelleenavauksen jälkeen. Jos pohtii, miten ryhmä ensinnäkin tallennetaan ja laajennetaan, jaetun kaavan si-attribuutin ja sen laajennuksen mekaniikka käsitellään erikseen; välimuistin lukemiselle sääntö kutistuu yhdelle riville — kysy jokaiselta solulta, älä luota mihinkään, jota et kysynyt
Välimuistiarvojen lukeminen, yhdistetty moottorien yli kulkeva lukija ja uudelleenlaskentamoottori, jota voit valita ei kutsuttavaksi, toimitetaan kaikki standardin HotXLS Delphi Spreadsheet Component mukana Delphille ja C++Builderille ilman riippuvuutta Excelistä tai mistään OLE-automaatiopalvelimesta; tuotesivu kantaa täyden API-referenssin tässä näytetyille työkirjan ja lukijan sisääntulopisteille