HotXLS 能重寫既有 XLSX 套件內的單一工作表,而不必剖析或重新壓縮檔案的其餘部分。TXLSDirectWriter.BeginPatch 開啟來源套件,把目標工作表以外的每個項目,以其壓縮後的位元組原樣複製過去,並讓你透過一般的 AddSheet、AddRow 與 Write* 呼叫重新編寫那單一工作表。圖表、樞紐分析快取、佈景主題、樣式與共用字串完全不會被解壓縮
這套流程要解決的問題常見於報表與資料重整。一份活頁簿由業務團隊送來,裡面帶有樞紐分析表、交叉分析篩選器、設定條件式格式,累積了十年的格式設定。每天晚上都要用最新數字替換其中一張資料工作表。載入並重新儲存整份活頁簿,每份檔案要花上好幾分鐘,更重要的是,會讓載入引擎必須重建的功能承擔保真度風險。修補的做法是繞開這兩個問題,作法就是不去動它不需要動的東西
為什麼複製壓縮位元組才是有趣的部分?
一個以壓縮層級複製的 zip 項目,成本只是一次串流複製。同一個項目走一般寫入路徑,進來時要解壓縮、出去時要重新壓縮,而壓縮才是昂貴的那一半。在一份帶有大型樞紐分析快取和幾十張內嵌影像的活頁簿上,這個差異就是「修補作業在寫入新工作表所需的時間內就完成」跟「大部分時間都花在重新壓縮它從未檢視過的位元組上」之間的差別
HotXLS 為此使用 CopyCompressedFrom,直接把來源項目壓縮後的位元組寫進目標壓縮檔。當某個項目因壓縮方式不同或使用弱加密而無法以這種方式複製時,寫入器會退回到解壓縮後的串流複製,而不是直接失敗。目錄標記項目會被略過,因為寫入器會自行產生自己的目錄標記
原地取代,或寫入新檔案
兩個多載函式涵蓋這項工作的兩種形態。原地形式會把結果暫存到原始檔案旁的一個暫存檔,關閉來源控制代碼,再刪除並改名,因此若寫入過程中崩潰,原始檔案仍完好無損。明確指定目標的形式則不動來源,可以選擇取代某張工作表,或附加一張新的:
var
W: TXLSDirectWriter;
begin
W := TXLSDirectWriter.Create;
try
W.BeginPatch('monthly-dashboard.xlsx', 'Data'); // 原地修改
W.AddSheet('Data');
W.AddRow(1);
W.WriteString(1, 'Region');
W.WriteString(2, 'Revenue');
W.AddRow(2);
W.WriteString(1, 'North');
W.WriteNumber(2, 184320.55);
W.AddRow(3);
W.WriteFormula(1, '=SUM(B2:B2)');
W.Close;
finally
W.Free;
end;
end;
插入變體則接受來源與目標路徑加上 InsertSheet:
// 來源檔案保持不動;目標檔案多出一張名為 Extra 的工作表
W.BeginPatch('template.xlsx', 'output.xlsx', 'Extra', True);
W.AddSheet('Extra');
W.AddRow(1);
W.WriteString(1, 'appended by the nightly job');
W.Close;
插入是真正需要複雜簿記手術的部分。寫入器會剖析 xl/workbook.xml 中的工作表登記表,以及把每張工作表綁到其部件的關聯對照表,接著選定下一個可用的部件編號、工作表識別碼與關聯識別碼。關聯類型會沿用來源套件的慣例,因此修補一份嚴格 ISO 29500 活頁簿會輸出嚴格版關聯類型,修補一份過渡版活頁簿則輸出過渡版類型
修補作業會刻意捨棄與限制哪些內容?
計算鏈在兩種模式下都會被捨棄。在取代模式下,其中的項目描述的是一張已不再以那種形式存在的工作表中的儲存格;在插入模式下,工作表索引位移會直接讓計算鏈失效。Excel 會在下一次重新計算時重建這條鏈,因此捨棄它是正確做法,而非有損。這個部件不會出現在複製結果中,其關聯項目與內容類型覆寫也會被外科手術式地移除
修補作業內有兩項撰寫語意會改變,兩者都源自同一項原則:修補不得干擾它沒有重寫的部件。字串是內嵌寫入工作表,而非加進共用字串表,因為來源字串表原樣帶過,不會被動到。而 StyleIndex 指的是來源套件 cellXfs 中的項目,而不是寫入器自建的樣式表中的項目。這代表你可以參照原始活頁簿已定義的格式,這通常正是資料重整所需要的,但也代表你必須知道哪個索引對應哪種格式
// 在修補作業內,StyleIndex 索引的是「來源」套件的 cellXfs。
// 日期需要一個能對應到來源日期格式的明確索引:
W.WriteDateTime(3, EncodeDate(2026, 8, 22), DateStyleIndexFromTemplate);
// 修補模式下不允許使用無樣式的 WriteDateTime 多載,
// 因為它假設用的是寫入器自己的樣式表,
// 而修補作業從不建立那張表
有六個撰寫進入點被鎖住:新增表格、圖表、影像、註解、已定義名稱與儲存格樣式,在修補模式下都會拋出例外,並在關閉時另有一道安全網,只要這些計數器中有任何一個非零就會失敗。這些功能各自都需要編輯那些修補作業原樣複製的部件,而一個修改到一半的套件,比一次被拒絕的操作更糟。每次操作只允許修補一張工作表
何時該修補、何時該載入
當活頁簿很大、變更只侷限在一張工作表、且檔案的其餘部分必須逐位元組保留原樣時,修補是正確的工具。當變更橫跨多張工作表、需要新的格式或新物件,或檔案小到一般載入再儲存完全不費成本時,它就不是對的工具。若是從零開始的大量產生,串流直接寫入器 一文所述的方式仍是更合適的選擇,而且它共用同一套 AddRow 與 Write* API,因此在兩者之間切換是機械式的作業
當你確實需要完整物件模型時,如何在已載入的活頁簿內進行工作表層級的操作,說明於 複製 XLSX 套件中的工作表 一文。而若你考慮修補的原因是整份活頁簿的處理速度變慢,大型活頁簿效能 一文中的量測數據與記憶體行為,值得在選擇方式之前先讀一讀
驗證修補是否真的達到預期效果
有三項檢查幾乎能抓出所有錯誤。確認你預期會保留下來的部件仍在壓縮檔中,確認 xl/calcChain.xml 已經消失,並確認透過 TXLSXWorkbook 重新開啟檔案後回報的工作表數量與預期相符:取代模式應維持不變,插入模式應增加一張。把修補過的工作表讀回來、比對幾個數值與公式,能為整個驗證流程收尾
從開發這項功能的過程中,有一個實作層面的細節值得再說一次,因為任何撰寫類似 zip 層級程式碼的人都可能因此踩雷。工作表部件名稱是以前綴比對的,前綴長度只要差一個字元,判斷式就永遠不會相符,於是新寫入的部件會與既有名稱衝突,而讀取器若採用「取最後一個同名項目」的邏輯,就會悄悄選到錯誤的工作表。若修補後看起來像是兩張工作表的內容被互換了,先檢查名稱比對邏輯,而不是先檢查 XML
原地修補、串流寫入與完整的活頁簿物件模型,全都在同一套適用 Delphi 與 C++Builder 的函式庫中提供;功能清單列於 HotXLS Delphi 試算表元件頁面