HotXLS 現在能求值結構化表格參照,因此 =SUM(Table1[Amount]) 會產生一個數字,而不是被跳過不算。解析器能處理 Table[Column]、Table[[Column]]、像 Table[[Q1]:[Q4]] 這樣的欄範圍,以及項目指定詞 [#Data]、[#All]、[#Headers] 與 [#Totals],並在剖析當下對照活頁簿的表格模型解析每一個,而原始公式文字則會原樣往返
有一種形式是刻意不支援的,而它偏偏是大家最先撞上的那一種。目前列速記法 [@Column] 不受支援,背後有一個值得理解、而不是盲目繞過的結構性原因
為什麼結構化參照不只是換了個好記名字的範圍?
因為已定義名稱會凍結一個位址,而表格參照不會。把 DataBlock 寫成一個指向 Sheet1!$A$2:$D$100 的名稱,它就會一直保持這個矩形範圍,直到有東西改寫它為止。寫 Sales[Amount],意思則是「Sales 表格的 Amount 欄」,不論該表格在求值當下的範圍實際是多少。在表格中加二十列,加總就會涵蓋它們;沒有需要調整的參照,因為公式一開始就沒有寫死的位址
這種象徵性正是這種參照無法靠字串替換來解析的原因。解析器必須依名稱在活頁簿中找到表格,依標題文字查出欄位,判斷所要求的項目指定詞涵蓋哪些列,再產生一個具體的矩形範圍。HotXLS 是在公式編譯期間透過表格模型完成這一切,這也是為什麼一個在表格擴大之前寫下的公式,仍然會依表格目前的範圍求值
HotXLS 能解析的語法
目前支援的規格語法涵蓋單一矩形結果,值得精確說明,因為 Excel 的文件呈現的規格範圍,遠比大多數引擎實際實作的還要大。HotXLS 支援 [Col] 與帶括號的變體 [[Col]]、裸項目指定詞 [#Data]、[#All]、[#Headers] 與 [#Totals]、組合形式 [[#Data],[Col]]、項目指定詞內的範圍 [[#Data],[Col1]:[Col2]],以及純粹的欄範圍 [Col1]:[Col2]
這組語法能給你的,是每一種會產生單一連續區塊的參照形式:一欄、一連串相鄰欄位、只含本文或含標題的切片。非相鄰的聯集與多區域結果則不在其中。當一個參照無法被解析時,公式會維持既有的「略過不給值」行為,而不是替換成一個猜測值,因此一個無法解析的參照,絕不會變成一個看起來合理的錯誤數字
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);
// ... 寫入標題列與 24 列資料 ...
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;
為什麼目前列形式是刻意排除的?
[@Column] 與 [#This Row] 的意思是「這個公式所在那一列上、該欄的儲存格」。它的值因此取決於求值中儲存格的位置,而不是只取決於表格本身。這是另一種性質的參照:不是編譯器能一次解析出來的矩形範圍,而是必須為公式所在的每一列各自重新解析一次
HotXLS 對這些形式的表格範圍解析器會回傳 False,讓它們走入「略過不給值」的路徑。公式文字會被完整保留、原樣寫回,因此使用了 [@Amount] 的活頁簿,經過你的應用程式往返後,在 Excel 中仍能正確開啟;只有 HotXLS 計算出的值會缺席。在「沒有值」與「用錯誤那一列算出的值」之間,前者才是你能偵測得到的那一種
實務上的變通做法很直接:在你自己產生的活頁簿中,寫出等效的 A1 樣式相對參照,這其實正是 Excel 內部對大量表格範圍邏輯所儲存的形式。在你只是處理、而非自行產生的活頁簿中,就別去動這個公式,讀取 Excel 早已存好的快取值即可,這正是一套載入後產出報表的管線通常需要的做法
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
// 類似記錄集的查詢方式,走訪表格本文,回傳以 1 起算的列號
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;
表格改變形狀時會發生什麼事
當結構化參照所指名的對象消失時,它會被判定為失效,而不是被靜默重新指向別處。刪除一欄,參照到該欄的公式就會依 Excel 判定失效的方式失效;刪除或重新命名表格,指向它的參照也會以同樣方式處理。這是正確的行為,也呼應了 插入與刪除時的公式參照調整 中所描述的一般參照調整:引擎的職責是讓公式保持誠實,而不是讓它們看起來還算有效
列數增加則是相反的情況,完全不需要任何調整。因為參照指名的是表格本身、而不是一個矩形範圍,在表格範圍內新增列,只會讓 [#Data] 涵蓋的範圍變寬,不會動到任何一個公式。這正是讓表格值得用在報表範本上的特性:不論匯入結果最終有多少列,總計列都會持續加總所有內容
往返保存原則
HotXLS 保留原始公式文字。一份載入時含有 SUM(SalesTable[Amount]) 的活頁簿,儲存時仍是 SUM(SalesTable[Amount]),而不是解析後的 SUM(D2:D25)。這一點比表面看起來更重要:使用者在 Excel 中開啟你的輸出結果時,期望看到的是自己當初寫下的公式,而一個被解析過的位址,會在不知不覺間,把一個能自我維護的模型變成一個不再涵蓋新增列的脆弱模型
還有兩項相關能力補足了整個圖像。表格定義本身,包括無標題表格與各表格附帶的註解,透過 資料驗證、自動篩選與 Excel 表格 所描述的表格模型往返保存。而當許多儲存格共用同一種模式時,XLSX 會把它們儲存成一個共用公式,其展開與重新輸出方式則涵蓋在 共用公式 si 展開 中。共用公式內部的結構化參照會同時經過這兩條路徑,因此兩者都必須正確運作,而它們也確實如此
HotXLS 能在不安裝 Excel、不使用 Office 自動化的情況下,讓 Delphi 與 C++Builder 讀寫 XLS、XLSX 與 ODS,並以自有引擎求值公式。表格模型、公式引擎與重新計算相關 API 皆記載於 HotXLS Delphi 試算表元件頁面