Free Pascal 与 Lazarus 支持
HotXLS 支持 Windows Win32 和 Win64 上的 Free Pascal 3.2.2 搭配 Lazarus/LCL,包括 XLS/XLSX 工作簿 API、公式、格式设置、直接流式读写、TDataToXLS 和 TGridToXLS 导出组件,以及渲染辅助程序
原生 Linux 与 macOS 工作簿核心
限定作用域的目录 API、显式的原生存储所有权、时间戳和规范的经典标识键,参见 复合文件存储
可选启用的 LX_PORTABLE_CORE 配置在 Linux 和 macOS 上暴露不带 LCL 的 TXLSWorkbook 和 TXLSXWorkbook。原生 Linux x64 和 macOS ARM64 已用 Free Pascal 3.3.1 编译并执行;源码版本守卫要求至少 3.2.2,但该下限并不意味着每一种原生编译器与目标组合都经过验证
把 cthreads 和 cwstring 放在 HotXLS 单元之前,将 Lib 加入单元与 include 路径,并为整个构建定义 LX_PORTABLE_CORE。原生控制台应用不需要 Interfaces 或 LCL widgetset
处理 Unicode 文件系统路径时,请在应用程序环境中选择一个已安装的 UTF-8 locale。无效或非 UTF-8 的 locale 可能让原生 RTL 的文件名转换有损;验证辅助程序会显式拒绝该环境,而不是更改 locale 或猜测代码页
program NativeWorkbookExample;
{$mode delphiunicode}
uses
cthreads, cwstring, SysUtils, lxHandleX;
var
Workbook: TXLSXWorkbook;
begin
Workbook:= TXLSXWorkbook.Create;
try
Workbook.Sheets.Add('Data').Cells[1, 1].Value:= 5;
Workbook.Sheets[1].Cells[1, 2].Formula:= 'A1*2';
if Workbook.Recalculate<> 1 then
raise Exception.Create('Workbook calculation failed');
if Workbook.SaveAs('native.xlsx')<> 1 then
raise Exception.Create('Workbook save failed');
finally
Workbook.Free;
end;
end.
| 领域 | 原生核心契约 |
|---|---|
| 工作簿文件 | 通过公开的工作簿 API 创建、打开、编辑、计算并保存经典 BIFF8 XLS、XLSX、受支持的扩展 XLSB 子集和受支持的 ODS 子集,沿用既有的转换检查和格式边界。显式声明 CP1252 和 CP932 编码的经典 BIFF2 记录也经过了导入和 Unicode BIFF8 导出验证 |
| Unicode 与名称 | 工作簿文本保留 UTF-16 语义,文件系统路径在原生边界使用 UTF-8。定义名称标识使用钉定的 Unicode 16 规范化规范化形式和 BMP 全大小写折叠,独立于宿主 locale 保留符号、重音、土耳其语区分和星体平面大小写标识;带限定符的公式保留其精确的工作表作用域。普通工作表文本排序仍使用原生 RTL 的 locale 比较 |
| 基础设施 | 使用原生临界区、全宽线程标识、真实的工作线程、注册的独占临时文件、原子同级文件替换和操作系统随机字节,无需 Windows 模拟 |
| 压缩与加密 | 内置的 Pascal ZIP/压缩和 AES 仍然可用。经典 RC4 和 RC4 CryptoAPI XLS 文件可使用操作系统随机字节原生读写。原生加密 OOXML 读取使用纯复合文件读取器;写入加密 OOXML 复合文件需要 Windows,在原生 Unix 上引发显式的平台异常 |
| 公式 REGEX 函数 | 钉定版本的静态 PCRE2 UTF-16 后端在原生目标上用其 C 编译器编译。编译包含公式引擎的应用之前,先运行 sh Lib/thirdparty/build-pcre2-unix.sh;提供 Linux x64 和 macOS ARM64 静态目标 |
| Windows 服务 | 剪贴板访问、Windows COM 激活、内置 ADO/WinHTTP 查询提供程序、GDI 几何捕获、HTML 背景图像解码和旧版 PDF 导出在其显式边界处引发 EXLSPlatformUnsupported。自定义查询提供程序和文本提供程序仍然可用 |
| 旧版组件 | 经典工作簿模型和 BIFF 读取器/写入器在原生核心中可用。LCL 运行时包、数据集/网格导出组件和可视化控件仍是 Windows/LCL 组件。经典剪贴板方法、HTML 导出和旧版 PDF 导出在原生 Unix 上引发显式的平台异常 |
| 面向字节的文本公式 | 需要 Windows 活动 ANSI/DBCS 代码页的函数在原生 Unix 上返回显式的不受支持公式结果(#NAME?);不选择隐式的 locale 或代码页替代 |
打开和保存方法保留其正常的结果代码和诊断契约。直接的平台边界调用可能引发 EXLSPlatformUnsupported;在共享应用代码中调用仅限 Windows 的服务时请处理该异常
可复现的原生验证
在原生客户机上使用现成的 Free Pascal 工具链、Python 3 和原生 C 编译器运行验证辅助程序。在客户机文件系统上选择源码检出之外的一个新的或空的输出目录
python3 Tests/Lazarus/run_native_core.py --fpc /path/to/fpc --output /guest-local/hotxls-validation
python3 Tests/Lazarus/run_native_core.py --fpc /path/to/fpc --config /path/to/fpc.cfg --output /guest-local/hotxls-validation-2
辅助程序会复制并哈希源输入,在私有构建副本中把 Pascal 单元文件名规范化以适配 FPC 区分大小写的文件名搜索,本地构建 PCRE2,然后运行核心、AES、压缩、REGEX、公开工作簿和集成见证程序。它保留来源清单、编译器日志、产物和失败记录,不更改检出、不安装工具、不修改编译器配置。macOS ARM64 构建显式以 macOS 11 或更高为目标
集成见证程序包括 Unicode 路径、Excel 创作的 XLSX fixture、公式缓存与重算、重复打开、稀疏 ODS 批注与保护、受检转换拒绝、保留调用方目标的取消、不变的作用域名称查找、真实的工作线程故障遏制和加密随机字节
原生经典 XLS 契约
经典 TXLSWorkbook 使用 lxHandle, TXLSXWorkbook 使用 lxHandleX。经典 Recalculate 返回错误计数,零表示成功;其 Calculate 方法返回公式结果。经典单元格的 Formula 赋值要求前导 =。工作簿打开和保存方法保持其既有的成功结果 1
原生经典存储实现使用真实的复合文件层次和流标识,保留嵌套作用域、MiniFAT 数据和扩展 DIFAT 链。流负载会被具体化,写入器在输出前拒绝超出其受支持的有符号 32 位缓冲区/扇区边界的聚合输出;这不是一个多 GB 的流式存储实现
原生复合存储提供直接的流操作、枚举、元数据和复制操作。事务回滚、区域锁定、移动和不受支持的排除模式返回显式的存储错误。它不模拟 Windows COM、剪贴板或 GDI 服务
经典原生文件保存在原子替换已注册的同级文件之前先序列化。调用方流保存会在按原位置复制前暂存完整的复合文件,并在提交前保留前缀和取消失败。最后的进度通知仍不可取消。直接的复合存储辅助写入遵循其普通的直接写入契约,而不是公开工作簿保存事务
普通摘要和文档摘要属性使用有界的标准 OLE 属性集,含 Unicode 文本和 UTC FILETIME 时间戳。经典工作簿数据加密时,这些属性流保持明文,RC4 CryptoAPI 头会显式记录该选择。定义名称共享 XLSX 外观层使用的钉定规范标识键,同时保留其原始拼写和显式的工作表作用域。编码的 PNG/JPEG 绘图数据对模型仍然可用;原生位图/图元文件转换需要单独的受支持渲染器,否则引发 EXLSPlatformUnsupported
用同样的原生核心选项编译 Tests/Lazarus/HotXLSNativeClassicWorkbookSmoke.lpr,然后传入 Tests/Fixtures/classic-native/native-classic.xls、一个用后即弃的客户机本地输出路径以及 Tests/Fixtures/classic-native/native-classic-encrypted.xls。这些检查覆盖 Excel 创作的缓存、重算、精确的 Unicode 名称、明文与加密往返、独立解码的属性时间戳、重复打开、超过 16 MB 的 SST 数据和调用方输出保留
旧版 BIFF 字节编码
TXLSWorkbook.SetCodePage 选择 SaveAs(..., xlExcel5) 使用的显式字节编码,默认 CP1252;导入带可用非零 CODEPAGE 记录的 BIFF2–BIFF5 文件也会为后续的旧版保存选择该页
旧版单元格标签、公式文本常量和数组、缓存的公式字符串、定义名称、工作表名称与引用、字体名称、数字格式、样式名称、页眉、页脚和普通批注文本都使用该声明的代码页,而不是操作系统 locale 或 UTF-8
记录长度和工作表流偏移按编码字节计数;既有的 255 字节旧版标签和公式字面量限制只在完整字符边界截断,因此 CP932 双字节字符绝不会被拆开;长度字段只有一字节的名称和元数据会拒绝超出其可表示长度的编码文本,而不是发出无效长度
无法在所选代码页中往返的文本会在悄悄变成替换字符或最佳适配字符之前引发 EConvertError;请选择能表示文档文本的代码页,或保存为 BIFF8 以使用 Unicode 字符串
跨越 CONTINUE 记录的公式缓存字符串会先按字节组装再解码,因此记录边界可以拆开双字节字符而不破坏缓存值
更改所选代码页会从 Unicode 模型重建类型化的旧版公式和定义名称字节;BIFF8 保存继续使用 Unicode,其磁盘上的 CODEPAGE 值保持 1200
导入的 BIFF5 图表记录为类型化系列和附加标题检查保留其原始代码页,包括在工作簿代码页编辑或图表复制之后;保存到不同代码页时会显式转码受支持的 SeriesText、纯缓存标签和公式字符串、页眉、页脚以及当前工作簿的外部工作表名称,而不是按新声明重新解释保留的字节
延续的图表文本、未建模的旧版外部工作表编码和不透明的旧版字节文本记录会用 EConvertError 拒绝代码页迁移;不可表示的受支持文本同样被拒绝,公开工作簿保存会在提交前保留调用方目标;该文本契约不承诺完整的原生图表外观转换
BIFF5 自定义样式名紧跟在单字节编码长度之后开始,而 BIFF5 SeriesText 带一个标识符和单字节编码长度,没有 Unicode 标志;BIFF8 SeriesText 增加其 Unicode 标志,转换受支持的图表文本会同时更新该记录和图表 BOF 版本
非空的 BIFF5 图表页眉页脚使用单字节编码长度,上限 255 字节;缓存的 LABEL 和 STRING 记录使用双字节长度,而 BIFF8 页眉页脚使用双字节 UTF-16 长度加其 Unicode 标志;迁移检查每条记录的实际布局,并在追加前用 ERangeError 拒绝超大的旧版页眉或页脚
HotXLS 在 Windows、Linux 和 macOS 上按声明页解释旧版字节;本机安装的 Excel 16.0 build 20430 独立展示了一个接收端限制:即使仅把原生文件的 CODEPAGE 记录改为 CP1252 或 CP932,它仍按本地 Windows CP936 解释旧版字节,因此正确的声明字节并不保证在该接收端配置下文本一致
在不同 locale 配置间分发 BIFF5 时,请保留文档的实际编码声明并测试接收应用程序;Unicode BIFF8 可以避开这个特定的旧版字节互操作边界
这一字节编码修正保留既有的 BIFF5 普通批注记录大小限制;它不会向旧版记录添加批注作者、富文本格式或形状几何字段
VBA 模块源码编辑使用项目声明的字节代码页,并保留源码偏移之前的二进制前缀,包括内嵌的 null 字节;这会改变存储的源码文本,不执行宏
fHighByte = 0 的 BIFF8 压缩 Unicode 把每个字节直接映射到 U+0000 到 U+00FF 的 UTF-16 code unit;它不是 CP1252、UTF-8,也不是文件的旧版代码页,即使在 otherwise 为 BIFF8 的文件中出现非标准的 CODEPAGE 记录
原生文本公式保留 UTF-16 长度和位置,包括代理对 code unit;Unicode 的 LOWER、UPPER、SEARCH、TEXTBEFORE/TEXTAFTER 不区分大小写的分隔符、数据库字段标题、不区分大小写的条件和动态文本键使用原生编译器的 Unicode 字符数据,而不是宿主 C locale 仅支持 ASCII 的大小写函数,而普通工作表排序保留其既有的 locale 比较
原生 LET/LAMBDA 本地名称匹配和本地数组存储分类使用同样的 Unicode 感知大小写处理,因此大小写对应项能解析到正确的捕获绑定并保留其数组形状;这不改变钉定的公开定义名称标识契约
经典的不区分大小写 FindText 和 ReplaceText 在原生 FPC 上为字面和 Excel 通配符搜索保留 Unicode 字符匹配;MatchCase 仍区分大小写,字面替换保留其全部替换行为,通配符替换保留其既有的最左跨度行为,公式单元格仍被排除
用原生核心选项编译 Tests/Lazarus/HotXLSNativeLegacyEncodingSmoke.lpr,传入一个用后即弃的输出目录,再传入 Tests/Fixtures/legacy-encoding/native-biff5-cp936-chart.xls;该见证程序独立检查声明的 CP1252、CP932 和 CP936 字节、每个工作表偏移、代码页变更、双字节延续边界、压缩 BIFF8 导入、公式计算、原生创作的图表文本以及保存失败时的目标保留
完整版安装程序
完整版安装程序能够检测 Lazarus,并支持在没有 RAD Studio 的情况下完成安装。在 “IDE Integration” 页面选择 Lazarus / Free Pascal,即可安装运行时包、兼容单元和构建脚本;当检测到的 IDE 只有 Lazarus 时,该选项默认选中
在 “Post-install Compilation” 页面选择 Win32 或 Win64 的 Free Pascal 运行时包。目标只有在检测到其编译器、RTL、LCL 和 LazUtils 单元时才可用;目标不可用时你仍然可以安装包源码,稍后再编译
该包仅含运行时部分,在 Lazarus 中作为项目依赖添加。安装程序不会用 Free Pascal 编译 VCL 演示
构建与测试
在 Lazarus 中打开 Lib/FPC/HotXLSLaz.lpk 并编译运行时包,或从 HotXLS 目录运行这些命令
build-FPC-Lib.cmd Win64
Tests\Lazarus\Run-HotXLSLazarusSmoke.cmd Win64
build-FPC-Lib.cmd Win32
Tests\Lazarus\Run-HotXLSLazarusSmoke.cmd Win32
设置 LAZARUS_DIR 来选择安装;必要时,FPC_EXE 和 LAZBUILD_EXE 可分别指定显式的编译器与包构建可执行文件
所选安装必须包含目标架构的 FPC 和已编译的 LCL/LazUtils 单元;输出与包构建配置会保存在独立的架构目录中
应用程序设置
将 Lib 和 Lib/FPC 加入单元搜索路径,并把 Lib 加入 include 搜索路径,或将运行时包添加为 Lazarus 项目的依赖项
在应用程序的 uses 列表中,把 Interfaces 放在 HotXLS 单元之前,控制台应用也不例外;这会初始化 LCL widgetset 以及 RTL/LCL 边界上的 UTF-8 转换
program WorkbookExample;
{$mode delphiunicode}
uses
Interfaces, SysUtils, lxHandleX;
var
Workbook: TXLSXWorkbook;
begin
Workbook:= TXLSXWorkbook.Create;
try
Workbook.AddSheet('Data');
Workbook.Sheets[1].Cells[1, 1].Value:= 'Hello';
if Workbook.SaveAs('example.xlsx')<> 1 then
raise Exception.Create('Workbook save failed');
finally
Workbook.Free;
end;
end.
HotXLS 源码单元在内部选用 Delphi Unicode 语义,因此字符串、字符和公式文本保留 UTF-16 行为;应用程序代码可以使用自己偏好的 Pascal 模式
兼容性细节
- 运行时包需要 Windows 和 LCL;
TDataToXLS导出 LCL 数据集,TGridToXLS导出TDBGrid,无论有无显式列,而 Linux、macOS、VCL 演示窗体、VCL 工作簿查看器和 DevExpress 网格适配器则不在这个包的范围内 - ZIP 和压缩流使用内置的 Pascal 后端,不需要单独的 zlib DLL
- AES 在两种架构下都使用 Pascal 后端,包括受密码保护的 XLSX 文件
- PNG 保留 Alpha 通道,EMF 使用 Windows 图元文件录制与播放,多页 TIFF 则使用 Windows GDI+ 运行时
- 直接文本读取接受 UTF-8 和带 BOM 的 UTF-16/UTF-32,可处理流式输入,并将流所有权保留给调用方
- 在修改单个字段之前,先用
TXlsCsvImportOptions.Default初始化TXlsCsvImportOptions - FPC 报表表达式和导入中用到的模式采用 Unicode
URegExpr语法,而非 Delphi 的 PCRE 后端。空输入的处理方式与TRegEx相同:接受空字符串的模式(例如^$或.*)会匹配空字符串,而[0-9]+不会;替换空字符串时,只有当模式接受空字符串才会产出替换文本;不支持的模式和空模式会抛出ERegularExpressionError,报表表达式会将其暴露为REPORT_EXPRESSION_INVALID_REGEX