外部工作簿引用
概览
电子表格模型通常使用交叉引用公式从辅助工作簿中提取数据;HotXLS 支持在 BIFF8(经典 XLS)和 OpenXML (XLSX) 文档中解析、写入和保留外部工作簿引用
实时工作簿工作区
lxWorkbookWorkspace 中的 TXLSWorkbookWorkspace 是实时外部工作簿共享的标识与注册核心;TXLSWorkbook 和 TXLSXWorkbook 都暴露 CreateWorkspaceWorkbook,并各自持有一个供公式求值器使用的 ExternalWorkspace,而通过 TXLSXWorkbook 打开的 ODS 工作簿会通过同一个适配器报告 OpenDocument 引擎类型
var
Host, Target: TXLSXWorkbook;
begin
Host.ExternalWorkspace.Add(
'..\Data\Target.xlsx',
'..\Data\Target.xlsx',
'C:\Models\Host.xlsx',
Target.CreateWorkspaceWorkbook);
// The compatibility facade maps an unambiguous name to the
// relationship target already stored in Host.ExternalLinks
Host.RegisterExternalWorkbook('Target.xlsx', Target);
// The matching call revokes a facade-level registration
Host.UnregisterExternalWorkbook('Target.xlsx');
end;
经典工作簿针对 TXLSWorkbook 目标使用同一组兼容方法;RegisterExternalWorkbook 和 UnregisterExternalWorkbook 只管理 facade 层面的名称映射,而 ExternalWorkspace.Remove 和 Clear 撤销的是工作区注册本身。跨引擎的调用方可以把 Classic、XLSX 或 ODS 适配器直接添加到 ExternalWorkspace
- 标识规范化是词法层面的、不区分大小写的,保留路径和扩展名,依据属主的源标识解析相对目标,折叠点段(dot segment),并且不做任何文件系统或网络访问
- 精确标识和显式别名优先解析;仅按基名查找时,只有恰好一个已连接的注册匹配才算成功,否则
Resolve返回xlswrsConflict Add会拒绝精确键冲突、别名冲突和重复工作簿冲突;不同的完整路径和不同的扩展名可以共存Remove和Clear撤销注册,而直接销毁 Classic 或 XLSX 目标会断开其适配器,并在释放模型之前等待活动的读取者结束- 外部工作表先按声明的名称解析,之后才回退到从 1 开始的位置匹配,因此目标工作簿的工作表顺序不必与源链接目录一致
- 公式求值器优先读取已解析的实时工作簿,然后回退到宿主文件的类型化缓存;Classic XLS 宿主会解码稀疏的 XCT 和 CRN 值,XLSX 宿主则使用包模型已持有的外部链接缓存
OnLoadWorkbook是一个可选的受控资源请求;其默认值为nil,因此只要应用程序代码不显式提供该策略,重算依然不会进行任何文件系统或网络访问- 在调用
ResetLoadAttempts之前,每个规范化标识最多触发一次加载器;并发的顶层请求共享进行中的结果,包括类型化的未找到和错误结果,而不是重复打开同一资源 MaxLoadDepth默认为 16,MaxWorkbookCount默认为 64;同标识重入、对已在加载中的标识的嵌套依赖、深度耗尽和工作簿数量耗尽都会返回类型化的诊断信息,而不会在加载周期中阻塞- ODF 外部源的 IRI 保留完整的词法标识,包括 URI scheme 和转义字符,但绝不授权隐式的文件或网络访问
- 标识冲突保持为
#REF!,过期的缓存数据无法掩盖歧义的路由;只有当文件为该工作表声明了有效缓存时,缺失的缓存单元格才视为空值
Resolve 先执行已注册查找,然后进行可选的受控加载;其结果是一个 TXLSWorkspaceResolveStatus(xlswrsResolved、xlswrsNotFound、xlswrsConflict、xlswrsDisconnected、xlswrsLoadNotFound、xlswrsLoadLimit、xlswrsLoadLoop 或 xlswrsLoadError),而 ResolveWithLoader 还会返回一个 TXLSWorkspaceLoadDiagnostic,它把 TXLSWorkspaceLoadDiagnosticCode 与 TXLSWorkspaceLoadResponseStatus(xlswlrsNotFound、xlswlrsResolved、xlswlrsError)配对,用来区分未找到、加载器错误、已断开结果、深度或工作簿数量超限、标识重入、并发依赖以及注册冲突这些结果。加载器回调接收一个 TXLSWorkspaceLoadRequest(标识、深度、已注册数量、限制),并应答一个 TXLSWorkspaceLoadResponse
Recalculate 报告一个 TXLSWorkspaceRecalcStatus,并可选附带 TXLSWorkspaceRecalcResult 明细;求值器会把每个单元格的数据来源记录为 TXLSWorkspaceRuntimeLookup(xlswrlInactive、xlswrlResolved、xlswrlFallbackCache 或 xlswrlError),诊断时就能区分实时读取和缓存回退
IXLSWorkspaceWorkbook 适配器暴露 EngineKind(类型 TXLSWorkspaceEngineKind:xlsweClassic、xlsweOpenXml 或 xlsweOpenDocument)、SourceIdentity、InstanceIdentity 和 Generation,用 IsConnected 测试存活状态,通过 TryGetCellValue 读取单个单元格(返回类型化的 TXLSWorkspaceCellStatus:有值、缺失、无效引用、已断开或错误,外加一个超出已用区域范围的标志),用 Recalculate 刷新模型,并用 Disconnect 从工作簿模型分离
注册表还提供 AddAlias,用于把额外的名称绑定到同一个已注册标识;TryResolve 是无异常的查找变体;BaseNameMatchCount 用于预览有多少个已连接的注册共享同一基名,便于在选定无歧义目标之前做判断
跨工作簿依赖图
BuildDependencyGraph 会对每个已注册的工作簿适配器做快照,并把 Classic XLS、XLSX 和 ODS 模型中的公式节点提取到一个 TXLSWorkspaceDepGraph 中;只要任何一个已注册适配器无法提供依赖元数据,方法就返回 False 且不产出部分图
- 每个公式节点都保留规范的工作簿标识、工作表名称与从 1 开始的工作表位置、从 0 开始的单元格位置、数组输出矩形、易失性以及未解析引用状态
- 单元格和矩形区域引用保留规范的目标工作簿与工作表标识;整列、整行和整表引用保持为一个区间,不会展开成数百万个单元格
- 本地定义名仍可作为符号依赖查询,并且在定义可以静态解析时,还能展开为具体的单元格或区域依赖
- 外部定义名保留外部链接槽位、声明的名称、可选的工作表作用域和规范的目标标识,不会把 DDE、OLE 或用户函数元数据当作工作簿名称
FindDependentsOfCell和图的边构建使用带最大端剪枝的行区间树;LastRangeCandidateChecks和EdgeCandidateChecks公开精确矩形检查的次数,便于性能验证- 公式提取在工作簿读取租约下运行,只扫描已具体化的公式对象,因此打包存储的值保持打包状态,图的创建也不会修改工作簿世代
计划重算
Recalculate 会保留共享的依赖图,只有当工作区注册或某个工作簿的公式依赖世代发生变化时才重建;值世代会开启新一轮脏状态传播,脏状态沿跨工作簿的依赖边扩散
var
RecalcInfo: TXLSWorkspaceRecalcResult;
Status: TXLSWorkspaceRecalcStatus;
begin
Status := Workspace.Recalculate(RecalcInfo);
if Status <> xlswrcOk then
HandleWorkspaceCalculation(Status, RecalcInfo);
end;
- 强连通分量在公式单元格层面计算,因此工作簿之间可以双向链接,只要单元格级依赖路径不构成环,整体仍然无环
- 真正的环成员以及被环阻塞的脏后代会被失效处理并排除在拓扑序之外,避免过期的缓存值被当成成功结果呈现
- 易失引用或静态无法解析的引用会强制采用正确性所要求的保守脏标记行为,而稳定的图会复用之前的依赖分析成果
- 同一轮次内的外部读取使用精确到工作簿实例、工作表、行和列的缓存;被调度的公式在求值之前先使自己的输出区域失效,后续依赖方观察到的就是新值
- 轮次本地缓存永远不会授予资源访问权限;已注册工作簿和可选的调用方控制加载器仍是仅有的实时解析来源,其后依次是类型化文件缓存和
#REF! TXLSWorkspaceRecalcResult公开图是否重建,以及脏、已求值、已失效、环、被阻塞、缓存命中和缓存未命中的计数- 状态可以区分成功、不支持的适配器、已断开的工作簿、循环引用、计算错误,以及轮次期间发生的工作区变更
- 对同一工作区的
Recalculate调用会被串行化,两个计算会话绝不会并发修改共享图或工作簿缓存 Remove或Clear可以在轮次进行中执行;轮次持有安全的适配器快照,会返回xlswrcWorkspaceChanged,而不是去解引用一个已被移除的注册- 销毁工作区会等待其活动轮次结束,而销毁已注册的工作簿会断开其适配器,使后续解析安全地失败
- 加载器失败和未找到结果会按规范化标识各缓存一份,直到调用
ResetLoadAttempts;计算失败会保留本地脏状态,等待显式重试 - 由于保留了依赖图,无变化的后续轮次的开销与已注册工作簿数量成正比,而不是与公式数量成正比;非递归的分量分析和紧凑的区域边让深而宽的模型始终受已具体化公式元数据的规模约束
剥离外部定义名
ConvertExternalDefinedNamesToRefErrors 在 TXLSWorkbook 和 TXLSXWorkbook 上提供相同的公开操作;它返回被替换为原生 #REF! 的工作簿级和工作表级定义数量
var Converted: Integer; begin Converted := Workbook.ConvertExternalDefinedNamesToRefErrors; // External-link parts and ordinary cell formulas remain intact end;
- Classic XLS 的识别使用编译后的 BIFF 引用标记和 XTI 支持工作簿标识,因此同工作簿内的三维引用和不确定的标记流保持不变
- XLSX 的识别按关系文档顺序使用语法感知的数字工作簿槽位,并且只接受工作簿外部链接部件,排除 DDE、OLE、未解析槽位、表引用以及字符串内的方括号文本
- 所有替换都在第一次修改之前准备好,并作为一次写入操作提交;重复调用具有幂等性
- 名称文本、工作簿或工作表作用域、可见性、批注、宏和内置标志、未知的 XLSX 属性以及外部链接目录在转换和往返之后依然保留
- 格式错误和不受支持的定义尽可能保持字节或文本原样,并添加
xlsDiagnosticDefinedNameConversionSkipped诊断,而不是靠猜测处理 - 依赖公式会重算出对应的 Excel 错误值;而对于普通的直接外部公式,即使其实时工作簿之后断开,有效的缓存结果仍然可用
- 该操作是 Excel 特有的,不会重新解释 OpenDocument 的名称公式语义
经典 XLS 外部引用
在经典 XLS 工作簿中,外部链接使用 EXTERNALBOOK 和 EXTERNNAME 记录存储在全局目录块中;HotXLS 在文件读写循环期间维护这些目录,确保远程区域引用在修改循环中得以保留
XLSX 外部关系
对于 OOXML 工作簿,外部链接映射通过关系部件进行管理;请查看以下支持接口的详细信息
- TXLSXExternalLinks 类 — 用于列出和更新电子表格外部链接的 API
- IXLSWorkbook 接口 — 顶层工作簿属性
- IXLSRange.Formula 属性 — 读取和写入跨工作簿公式