Teknisk artikel

Levande formler mellan arbetsböcker i Delphi med HotXLS

HotXLS kan beräkna en formel som =[Rates.xlsx]FX!B4*C10 mot en annan arbetsbok som är öppen i minnet, inte mot ett inaktuellt cachat värde. Du registrerar den andra arbetsboken med namn via RegisterExternalWorkbook, och varje omberäkning läser de refererade cellerna direkt från det objektet. Avregistrera den och beräkningen faller tillbaka till cachen för externa länkar som filen redan bär med sig

Detta tätar en specifik och frustrerande lucka. Excel-arbetsböcker refererar rutinmässigt till varandra: en räntetabell, en kontoplan, en prislista som underhålls av ett annat team. När en sådan fil öppnas av ett bibliotek snarare än av Excel löses dessa referenser normalt upp till vilka värden som helst som cachades senaste gången Excel omberäknade, vilket kan vara månader gammalt, och ingen mängd omberäkning fixar det eftersom datan helt enkelt inte finns där

Upplösningsvägar för en HotXLS-formel mellan arbetsböcker i Delphi: en arbetsbok registrerad via RegisterExternalWorkbook levererar färska celler vid Recalculate, en oregistrerad bok faller tillbaka till cachen för externa länkar, och utan cache behåller cellen sitt senast beräknade värde
Registrering läser celler från det levande objektet, cache-reservläget håller avregistrering säker, och en olöslig referens lämnar det senaste goda värdet kvar

Hur ser en referens mellan arbetsböcker ut inuti filen?

En extern referens lagras inte som texten du ser i formelfältet. Arbetsboken håller en tabell över stödjande böcker, varje formeltoken pekar på ett index i den tabellen, och en separat del för cachade värden håller det senast kända resultatet för varje refererad cell. Det är därför en fil öppnad utan sina följeslagare fortfarande visar siffror

Att parsa textformen har sina egna finesser. Lexern behandlar ett hakparentesomslutet boknamn som en del av en enda identifierare, så [Book.xlsx]Sheet!A1 blir en enda token snarare än en obalanserad hakparentes följd av en bladreferens. Namn normaliseras sedan för uppslagning: citering, sökvägsprefix, filändelsen och den numeriska indexformen kollapsar alla till ett versalt basnamn, så 'C:\Rates\[Rates.xlsx]FX'!B4 och [rates.xlsx]FX!B4 löses upp till samma registrerade bok

Att registrera en arbetsbok och omberäkna

Registrering sker via namn, och namnet är det som förekommer i formlerna i arbetsboken som gör referensen:

uses
  lxHandleX;

var
  Main, Rates: TXLSXWorkbook;
begin
  Main := TXLSXWorkbook.Create;
  Rates := TXLSXWorkbook.Create;
  try
    if (Main.Open('valuation.xlsx') <> 1) or
       (Rates.Open('rates.xlsx') <> 1) then
      Exit;

    // Formler i valuation.xlsx som säger [Rates.xlsx]... läser nu
    // från det levande objektet istället för de cachade externa värdena
    Main.RegisterExternalWorkbook('Rates.xlsx', Rates);
    Main.Recalculate;

    Writeln(VarToStr(Main.Sheets[0].Cells[10, 3].Value));

    // Ändra källan och omberäkna igen: det nya värdet flödar igenom
    Rates.Sheets[0].Cells[4, 2].Value := 1.0842;
    Main.Recalculate;
    Writeln(VarToStr(Main.Sheets[0].Cells[10, 3].Value));
  finally
    Main.UnregisterExternalWorkbook('Rates.xlsx');
    Rates.Free;
    Main.Free;
  end;
end;

Registreringen håller en svag referens. HotXLS tar inte över ägandet av den registrerade arbetsboken och kommer inte att frigöra den, vilket betyder att livstiden är din att hantera och ordningen spelar roll: avregistrera innan du frigör källarbetsboken, eller åtminstone omberäkna inte efter att ha frigjort den

Vad händer när en bok inte är registrerad?

Beräkningen faller tillbaka till cachen för externa länkar som lagras i filen, vilket är exakt beteendet hos en arbetsbok öppnad utan sina följeslagare. Det reservläget är anledningen till att avregistrering är säkert snarare än destruktivt

Beteendet efter avregistrering är värt att ange precist, eftersom det överraskar folk. När en referens varken kan lösas från en registrerad bok eller från cachen, ger beräkningen ett fel och det felet skrivs inte tillbaka till cellen. Cellen behåller sitt senast beräknade värde. Registrera om källan och omberäkna, så uppdateras värdet igen

Denna regel om att bevara vid fel är avsiktlig. Alternativet, att skriva ett felvärde till varje beroende cell i samma ögonblick en källa blir otillgänglig, skulle förvandla en tillfälligt saknad räntetabell till en arbetsbok full av felmarkörer som en senare lyckad omberäkning måste reparera. Att behålla det senaste goda värdet betyder att en avbruten pipeline lämnar ett dokument som fortfarande läses förnuftigt

Ändringsspridning stannar vid arbetsbokens gräns

Att redigera en cell i den registrerade källan markerar inte beroende celler i huvudarbetsboken som ändrade. Inkrementell omberäkning spårar beroenden inuti en arbetsbok; den bevakar inte över gränsen. Excel beter sig på samma sätt, vilket är anledningen till att dess externa referenser uppdateras vid öppning eller vid en explicit uppdatering snarare än kontinuerligt

Den praktiska konsekvensen är att en ändring av ett källvärde kräver en explicit omberäkning av den refererande arbetsboken, som i exemplet ovan. Om din applikation redigerar båda böckerna i en loop, omberäkna en gång i slutet snarare än efter varje redigering; motorn för inkrementell beräkning som beskrivs i inkrementell omberäkning och beroendegrafen gör då minsta möjliga arbete inuti varje arbetsbok

Tre former denna funktion passar

Konsolidering är den uppenbara. En överordnad arbetsbok refererar till tolv månadsfiler; registrera alla tolv, omberäkna en gång, och läs de konsoliderade siffrorna utan att någonsin öppna Excel. Registreringsnamnet för var och en måste matcha vad den överordnade arbetsbokens formler faktiskt säger, vilket är värt att verifiera mot formeltexten snarare än att anta att det matchar filnamnet

Scenarioanalys är den andra. Registrera en källa, skriv en uppsättning antaganden till den, omberäkna den beroende modellen, registrera resultaten, och upprepa. Eftersom källan är ett levande objekt skrivs ingen fil mellan iterationerna, och hundra scenarier kostar hundra omberäkningar snarare än hundra spara-och-ladda-om-cykler

Validering före leverans är den tredje och den minst uppenbara. Öppna en arbetsbok, registrera dess följeslagare, omberäkna, och jämför resultaten mot de värden filen redan bar med sig. En avvikelse betyder att de cachade värdena är inaktuella, vilket är en defekt värd att fånga innan en klient tar emot filen. Den jämförelsen är en naturlig följeslagare till jämförelsen av arbetsböcker som beskrivs i att jämföra två Excel-arbetsböcker

Tre former av levande beräkning mellan arbetsböcker med HotXLS i Delphi: konsolidering över tolv månadsfiler, scenarioanalys som aldrig skriver en fil mellan körningar, och validering som jämför omberäknade resultat mot de cachade värden en fil bär med sig
Konsolidering registrerar varje månadsfil och omberäknar en gång, scenarioloopar rör aldrig disken, och validering jämför färska resultat mot cachen en klient skulle ha fått

Referenshygien innan du förlitar dig på det

Kör en formelanalyspassering först. Den rapporterar saknade funktioner, externa referenser och cirkulära beroendekomponenter i en enda genomgång, vilket talar om exakt vilka boknamn ett dokument förväntar sig innan du börjar leta efter filerna:

var
  Issues: TArray<TXLSXFormulaIssue>;
  I: Integer;
begin
  Issues := Main.AnalyzeFormulaIssues;
  for I := 0 to High(Issues) do
    if Issues[I].Kind = xlsxFormulaIssueExternalReference then
      Writeln(Format('sheet %d R%dC%d needs [%s] (%s)',
        [Issues[I].Sheet, Issues[I].Row, Issues[I].Col,
         Issues[I].ExternalWorkbook, Issues[I].SourceFileName]));
end;

Två andra vanor sparar tid. Håll registreringsnamnet kanoniskt — registrera under det enkla filnamnet och låt normaliseringen hantera citerade och sökvägsprefixade varianter i formlerna. Och behandla en arbetsbok som refererar till filer du inte kan tillhandahålla som ett skrivskyddat artefakt: att omberäkna den bevarar de cachade värdena, men varje redigering som ändrar en beroende cell lämnar dokumentet i ett tillstånd där vissa värden är aktuella och andra historiska, utan något i filen som säger vilket som är vilket. Att kopiera formler mellan arbetsböcker är en annan operation med sina egna ombindningsregler, som täcks i kopiering mellan arbetsböcker och ombindning av formler, och namngivna intervall lägger till ytterligare ett lager som beskrivs i definierade namn och formler mellan blad

Levande beräkning mellan arbetsböcker, inkrementell omberäkning och formelanalys körs på samma motor för Delphi och C++Builder; den fullständiga funktionslistan finns på HotXLS Delphi kalkylbladskomponentsidan