Teknisk artikel

Strukturerade Excel-tabellreferenser i Delphi med HotXLS

HotXLS utvärderar nu strukturerade tabellreferenser, så =SUM(Table1[Amount]) ger ett tal i stället för att hoppas över. Upplösaren hanterar Table[Column], Table[[Column]], kolumnintervall som Table[[Q1]:[Q4]], och postspecifikationerna [#Data], [#All], [#Headers] och [#Totals], och löser var och en mot arbetsbokens tabellmodell vid tolkningstillfället medan den ursprungliga formeltexten går ordagrant tur och retur

En form saknas medvetet, och det är den folk stöter på först. Kortformen för aktuell rad, [@Column], stöds inte, av ett strukturellt skäl värt att förstå snarare än att kringgå blint

Varför är en strukturerad referens inte bara ett intervall med ett vänligt namn?

Därför att ett definierat namn fryser en adress medan en tabellreferens inte gör det. Skriv DataBlock som ett namn som pekar på Sheet1!$A$2:$D$100 och det förblir den rektangeln tills något skriver om det. Skriv Sales[Amount] och det betyder ”Amount-kolumnen i Sales-tabellen”, vad tabellens utsträckning än råkar vara när formeln utvärderas. Lägg till tjugo rader i tabellen och summan täcker dem; det finns ingen referens att justera eftersom det aldrig fanns någon adress i formeln från början

Den symboliska egenskapen är precis varför referensen inte kan lösas genom strängsubstitution. Upplösaren måste hitta tabellen efter namn i arbetsboken, slå upp kolumnen efter dess rubriktext, avgöra vilka rader den begärda postspecifikationen täcker, och producera en konkret rektangel. HotXLS gör detta under formelkompileringen genom tabellmodellen, vilket är varför en formel skriven innan tabellen växer ändå utvärderas mot tabellens aktuella utsträckning

Grammatiken HotXLS löser

Den stödda specifikationsgrammatiken täcker ett enda rektangulärt resultat och är värd att ange precist, eftersom Excels dokumentation presenterar en betydligt större yta än de flesta motorer implementerar. HotXLS accepterar [Col] och den inhägnade varianten [[Col]], de bara postspecifikationerna [#Data], [#All], [#Headers] och [#Totals], den kombinerade formen [[#Data],[Col]], ett intervall inuti en postspecifikation som [[#Data],[Col1]:[Col2]], och ett vanligt intervall [Col1]:[Col2]

Det den mängden ger dig är varje referensform som producerar ett enda sammanhängande block: en kolumn, en följd av intilliggande kolumner, en enbart-kropp- eller rubrik-inkluderande skiva av endera. Icke-intilliggande unioner och flerområdesresultat ligger utanför den. När en referens inte kan lösas behåller formeln det tidigare hoppa-över-utan-värde-beteendet i stället för att ersätta med en gissning, så en olöslig referens blir aldrig ett plausibelt fel tal

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Cols: TStringList;
begin
  Book := TXLSXWorkbook.Create;
  Cols := TStringList.Create;
  try
    Sheet := Book.Sheets.Add('Sales');
    Cols.Add('Region');
    Cols.Add('Q1');
    Cols.Add('Q2');
    Cols.Add('Amount');
    Sheet.Tables.Add('SalesTable', 'A1:D25', Cols);
    // ... skriv rubrikraden och 24 datarader ...

    Sheet.Cells[27, 4].Formula := 'SUM(SalesTable[Amount])';
    Sheet.Cells[28, 4].Formula := 'SUM(SalesTable[[Q1]:[Q2]])';
    Sheet.Cells[29, 4].Formula := 'COUNTA(SalesTable[[#Data],[Region]])';
    Sheet.Cells[30, 4].Formula := 'ROWS(SalesTable[#All])';

    Book.Recalculate;
    Book.SaveAs('sales.xlsx');
  finally
    Cols.Free;
    Book.Free;
  end;
end;

Varför utesluts formen för aktuell rad medvetet?

[@Column] och [#This Row] betyder ”cellen i den kolumnen på raden där den här formeln lever”. Värdet beror därför på den utvärderande cellens position, inte bara på tabellen. Det är en annan sorts referens: inte en rektangel kompilatorn kan lösa en gång, utan en per-cell-upplösning som måste göras om för varje rad formeln upptar

HotXLS returnerar False från tabellintervallsupplösaren för de formerna, vilket dirigerar dem in i hoppa-över-utan-värde-vägen. Formeltexten bevaras och skrivs tillbaka oförändrad, så en arbetsbok som använder [@Amount] öppnas korrekt i Excel efter en tur och retur genom din applikation; bara det HotXLS-beräknade värdet saknas. Givet valet mellan ett frånvarande värde och ett värde beräknat mot fel rad är frånvaro det du kan upptäcka

Den praktiska lösningen är mekanisk: i en arbetsbok du genererar, skriv den motsvarande A1-relativa referensen, vilket är vad Excel ändå lagrar internt för en stor del av den tabellbundna logiken. I en arbetsbok du bara bearbetar, lämna formeln orörd och läs det cachade värde Excel redan lagrade, vilket är vad en läs-och-rapportera-pipeline vanligtvis vill ha

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Table: TXLSXTable;
  Row: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open('sales.xlsx') <> 1 then Exit;
    Sheet := Book.Sheets[1];

    Table := Sheet.Tables.FindByName('SalesTable');
    if Table <> nil then
    begin
      // Recordset-liknande uppslag över tabellkroppen, 1-baserat radresultat
      Row := Table.FindFirst(Sheet, 'Region', 'EMEA');
      while Row > 0 do
      begin
        Log(VarToStr(Sheet.Cells[Row, 4].Value));
        Row := Table.FindNext(Sheet, 'Region', 'EMEA', Row);
      end;
    end;
  finally
    Book.Free;
  end;
end;

Vad händer när tabellen ändrar form

Strukturerade referenser ogiltigförklaras i stället för att tyst peka om när det de namnger försvinner. Ta bort en kolumn och formler som refererar till den kolumnen ogiltigförklaras på samma sätt som Excel ogiltigförklarar dem; ta bort eller döp om tabellen och referenser till den hanteras på samma sätt. Det här är korrekt beteende och det speglar vanlig referensjustering, som beskrivs i formelreferensjustering vid infogning och borttagning, där motorns uppgift är att hålla formler ärliga snarare än att få dem att se giltiga ut

Radtillväxt är det motsatta fallet och behöver ingen justering alls. Eftersom referensen namnger tabellen snarare än en rektangel breddar tillagda rader inom tabellens intervall det [#Data] täcker utan att röra en enda formel. Det är egenskapen som gör tabeller värda att använda i en rapportmall: totalsummeraden fortsätter summera allt importen producerade, oavsett hur många rader det blev

Tur-och-retur-disciplin

HotXLS behåller den ursprungliga formeltexten. En arbetsbok inläst med SUM(SalesTable[Amount]) sparas med SUM(SalesTable[Amount]), inte med den upplösta SUM(D2:D25). Det spelar större roll än det kan verka: en användare som öppnar din utdata i Excel förväntar sig att se formeln de skrev, och en upplöst adress skulle tyst förvandla en självunderhållande modell till en skör en som slutar täcka nya rader

Två besläktade funktioner kompletterar bilden. Själva tabelldefinitionerna, inklusive rubrikslösa tabeller och per-tabell-kommentarer, går tur och retur genom tabellmodellen som beskrivs i datavalidering, AutoFilter och Excel-tabeller. Och när många celler delar ett mönster lagrar XLSX dem en gång som en delad formel, vilken expanderas och sänds ut igen enligt beskrivningen i delad-formel-si-expansion. Strukturerade referenser inuti delade formler går genom båda vägarna, så båda måste bete sig korrekt, och det gör de

HotXLS läser och skriver XLS, XLSX och ODS från Delphi och C++Builder utan Excel-installation och utan Office-automatisering, och utvärderar formler i sin egen motor. Tabellmodellen, formelmotorn och omräknings-API:et dokumenteras på sidan för HotXLS Delphi kalkylarkskomponent