跳转到主要内容

XTL 一致性套件

本目录存放一致性语料——任何 XTL 实现想要声明一致性都必须通过的测试用例(fixture)。该语料是 XTL 行为的可执行定义。

目录结构

conformance/
├── README.md ← 本文件
├── AUTHORING.md ← 如何添加 fixture(避免"以 JS 为真理"的陷阱)
├── runner-protocol.md ← 实现应如何运行此套件
└── fixtures/
└── <NNN>-<slug>/
├── template.xlsx
├── data.xlsx
├── expected.xlsx ← 规范的预期输出(单文件场景)
├── expected/ ← 或者一个目录的文件(多文件或零输出场景)
│ └── *.xlsx
├── no expected output ← 用于 expected_error fixture
├── no static expected ← 用于 expected_dynamic fixture
└── meta.yaml ← 描述、规范小节引用、标签

"通过"的含义

静态输出 fixture 通过的条件是:当实现以 template.xlsxdata.xlsx 作为输入时,产生的输出与 expected.xlsx(或 expected/ 目录下的内容)匹配。阶段 1 的运行器可以比较更上层的工作表/单元格值。阶段 2 的运行器在对 OOXML zip 进行**规范化(canonical normalization)**之后,按字节比较工作簿内容:

  • zip 内文件按名称排序
  • XML 以确定性的规范形式序列化
  • 文本运行中的空白保留
  • 剥离生成器元数据(creator、modifiedBy、lastModified)

比较阶段与规范化规则参见 runner-protocol.md

错误 fixture 通过的条件是:实现报告的错误信息包含 fixture 中 expected_error 指定的文本。错误 fixture 不包含 expected.xlsxexpected/ 目录。

动态 fixture 通过的条件是:实现的输出与 meta.yamlexpected_dynamic 声明的动态断言相符。动态 fixture 不包含 expected.xlsxexpected/ 目录。

版本管理

每个 fixture 目录的 meta.yaml 中声明其所需的最低规范版本(spec_version: 0.1)。实现报告其目标规范版本;套件据此过滤 fixture。

静态输出 fixture 还可以声明 comparison_stage。该字段默认为 1;需要规范 OOXML 比较的 fixture 声明为 comparison_stage: 2

Fixture 元数据

语料使用的 meta.yaml 字段:

字段必填适用范围含义
description所有 fixturefixture 所断言契约的一行描述。
spec_section所有 fixture定义该行为的规范或 ADR 小节。
spec_version所有 fixturefixture 所需的 XTL 最低版本。
tags所有 fixture用于报告和定向运行的可过滤分类。
verified_by所有 fixture独立的撰写校验,如 handmanual-script
expected_warnings所有 fixture实现应当(SHOULD)发出的稳定警告子串。
expected_error错误 fixture稳定的错误子串;省略静态预期输出。
expected_dynamic动态 fixture动态断言种类;当前仅 utc_today
dynamic_cellsexpected_dynamic 配合动态 fixture由运行器计算的工作表/单元格/格式断言。
comparison_stage静态输出 fixture最低比较阶段;默认为 1,对 OOXML 敏感的检查使用 2
skip_reason所有 fixture已知失效 fixture 被临时跳过的原因。

expected_errorexpected_dynamic 互斥。静态输出 fixture 使用 expected.xlsxexpected/;空的 expected/ 目录表示零个输出文件。错误与动态 fixture 省略静态预期输出。

Fixture 目录

XTL 0.1 引导语料当前包含如下 fixture:

IDFixture契约
001bracket-substitution单个方括号包裹的源列表达式按每个源行渲染一个输出行。
002if-functionIF(condition, then, else) 在当前数据行内对比较表达式求值。
003list-sheet-filter@filter [field] in _ListSheet 保留匹配行并把列表工作表从输出中移除。
004repeat-right-default没有显式计数的 @repeat right 默认 colSpan = 1
005round-half-away-from-zeroROUND() 使用 Excel 风格的"四舍五入背离零"。
006filename-forbidden-chars禁用的文件名字符被替换为 _
007filename-reserved-nameWindows 保留的设备基名末尾补一个 _
008numfmt-numeric-string-coercion数字模板格式将数值字符串强制为数字。
009numfmt-date-string-coercion日期模板格式将类日期字符串强制为日期值。
010numfmt-text-format-coercion文本格式 @ 将单表达式值强制为字符串。
011text-date-formatTEXT(date, "YYYY-MM-DD") 使用 XTL 日期占位符返回字符串。
012text-number-formatTEXT(number, format) 支持 XTL 0.1 数字格式的最小子集。
013rich-text-template-expression富文本模板单元格在表达式检测前会先拼接文本运行。
014source-formula-cached-result源公式单元格使用缓存结果,XTL 不重新计算。
015source-sheet-prefix-first-matchsource_sheet 前缀模式按工作簿顺序选中第一个匹配的工作表。
016text-number-negative-rounding数字 TEXT() 格式在负数 .5 边界处四舍五入背离零。
017source-sheet-prefix-no-match-errorsource_sheet 前缀无匹配时报告稳定错误。
018source-formula-missing-cached-result-error没有缓存结果的源公式单元格报告稳定错误。
019filename-empty-basename-error文件名清洗对空基名报告错误。
020filename-length-overflow-error文件名清洗对超过 255 字节的长度报告错误。
021numfmt-number-coercion-error数字模板格式在强制失败时报告错误。
022numfmt-date-coercion-error日期模板格式在强制失败时报告错误。
023today-utc-dynamicTODAY() 通过动态断言渲染运行器启动时的 UTC 日期。
024stage2-merge-preservation阶段 2 比较验证展开数据块下方的合并区域被保留。
025stage2-style-numfmt-preservation阶段 2 比较验证渲染单元格保留模板样式与 numFmt。
026stage2-splice-merge-style-preservation阶段 2 比较验证行展开同时保留被位移的合并以及带样式/数字格式的渲染单元格。
027stage2-cross-writer-canonicalization阶段 2 比较验证已知的 OOXML 写出端差异规范化为相同的工作簿内容。
028source-table-row-shorthandsource_table = N 选取第 N 行作为源列名,并读取其下方的行。
029source-table-open-rangesource_table = B3:D 选取一个列窗口,并读取其下方直至已用行末的行。
030source-table-finite-rangesource_table = B3:D4 在声明的末行处停止读取。
031source-table-zero-data-rangesource_table = B3:D3 有效,产生零个源行。
032source-table-empty-column-name-error选中跨度内的空源列名报告稳定错误。
033source-table-duplicate-column-name-error重复的源列名报告稳定错误。
034source-table-invalid-selector-error无效选择子(如第零行)报告稳定错误。
035source-table-rich-text-header富文本的源列名单元格在 source_table 解析前会先拼接。
036source-table-formula-header公式型的源列名单元格使用缓存结果。
037source-table-formula-header-missing-cache-error没有缓存结果的公式型源列名单元格报告稳定错误。
038source-sheet-exact-match-beats-prefixsource_sheet 的精确匹配优先于前缀模式。
039source-sheet-default-first-worksheet如果省略 source_sheet,使用工作簿中按顺序的第一个工作表。
040list-sheet-hidden-states-removed隐藏与极度隐藏的列表工作表仍会从输出工作簿中移除。
041row-function-inside-repeat-blockROW() 返回 repeat 块内当前已渲染数据行的 1 起始索引。
042row-function-outside-repeat-block-error在 repeat 块之外调用 ROW() 报告稳定错误。
043ifempty-functionIFEMPTY() 对空值返回回退值,对非空值原样透传。
044sort-and-top-order@sort@top 之前运行,因此前 N 行来自已排序集合。
045list-sheet-not-in-filter@filter ... !in _Sheet 保留值不在列表工作表中的行,并把列表工作表从输出中移除。
046count-field-non-emptyCOUNT([field]) 统计当前行集中非空的值。
047aggregate-functions核心聚合函数作用于当前已渲染的行集。
048if-and-comparison-boundaries比较运算符在零边界附近驱动 IF()@filter 行为。
049filename-sanitization-warning对渲染后的文件名进行清洗会发出警告,但不改变输出语义。
050empty-ifempty-whitespace-only按 ADR-0007,IFEMPTY 将仅空白的字符串视为空。
051empty-ifempty-zero-not-empty按 ADR-0007,IFEMPTY 保留数字 0;数字永远不为空。
052empty-count-field-whitespace-zero-false按 ADR-0007,COUNT([field]) 统计非空值——空白为空,0 和 FALSE 非空。
053empty-row-skip-whitespace-only按 ADR-0007,若一个源行的每个单元格都为空(包括仅空白的单元格),该行被跳过。
054empty-list-membership按 ADR-0007,列表工作表在读入时丢弃空条目;空的源行值永远不匹配 @filter ... in _Sheet
055if-truthy-zero-and-empty按 ADR-0008,IF 将 0 与空值视为假值;非零数字、非空字符串以及 TRUE 视为真值。
056if-truthy-string-zero-not-specialIF("0", …)IF("false", …) 取真值分支——对字符串型的标志值没有特殊处理。
057if-truthy-boolean按 ADR-0008,布尔源单元格直接驱动 IF 的真值。
058if-comparison-result按 ADR-0008,比较表达式的布尔结果直接喂给 IF 的真值。
059compare-numeric-string-vs-number按 ADR-0009,在共享的 compareValues 下,比较会解析数字与数值字符串。
060compare-string-codepoint-order按 ADR-0009,字符串回退比较使用 Unicode 码点顺序——不进行区域感知的整理。
061concat-canonical-form按 ADR-0009,& 使用规范字符串形式对操作数字符串化(布尔为大写、整数不带小数)。
062concat-empty-stringifies-to-empty按 ADR-0009,& 对空操作数贡献空字符串。
063compare-empty-vs-value按 ADR-0009 规则 1 与 2,两个空操作数比较相等;恰好一个为空则 = 为假。
064compare-unicode-minus-not-numeric按 ADR-0009,含 Unicode 减号(U+2212)的字符串不解析为数字;比较回落到规范字符串。
065input-text-default-applied按 ADR-0010,宿主省略值时 _inputs 文本输入的默认值会填入。
066input-text-host-supplied按 ADR-0010,宿主提供的输入会流经单元格、工作表名和输出文件名模式。
067input-missing-required-error按 ADR-0010,宿主省略必填的(无默认值的)_inputs 声明属于错误。
068input-select-host-supplied按 ADR-0010,select 输入接受声明的竖线分隔选项中列出的宿主值。
069source-multi-declaration按 ADR-0012,__sources__ 工作表声明了一个额外的具名源;对其聚合作用于其完整行集。
070source-aggregate-cross-source按 ADR-0012,对具名源的 COUNT/MIN/MAX 作用于其完整行集。
071source-directive-active按 ADR-0012,@source SourceName 限定数据块范围;其中 [Column] 解析到该源。
072source-undeclared-error按 ADR-0012,@source 引用未在 __sources__ 中声明的源属于解析期错误。
073source-row-cross-error按 ADR-0012,对非活动源列的行级引用属于错误。
074xlookup-basic按 ADR-0013,三参数 XLOOKUP 对首个查找数组匹配的行返回对应的返回数组列。
075xlookup-fallback按 ADR-0013,四参数 XLOOKUP 在没有匹配时返回回退值。
076xlookup-no-match-error按 ADR-0013,无回退值的三参数 XLOOKUP 在没有匹配时报错。
077xlookup-source-mismatch-error按 ADR-0013,XLOOKUP 的第 2 个与第 3 个参数必须引用同一个源。
078xlookup-bare-bracket-error按 ADR-0013,XLOOKUP 的第 2/3 个参数要求带源前缀的方括号引用。
079join-basic-inner按 ADR-0014,@join 将每个主行与首个匹配的被联结行配对。
080join-no-match-dropped按 ADR-0014,@join 使用 inner 语义——没有匹配的主行被丢弃。
081join-undeclared-source-error按 ADR-0014,@join 引用未在 __sources__ 中声明的源属于解析期错误。
082join-bad-on-clause-error按 ADR-0014,@join 的 on 子句必须引用被联结的源以及该块的主源。
083sort-stable-equal-keys按 ADR-0016,@sort 是稳定的——键相等的行保留源顺序。
084sort-multi-stable-priority按 ADR-0016,多条 @sort 指令按首条 = 主键、后续指令为决胜键的方式应用。
085file-group-first-seen-order按 ADR-0016,文件分组按源行中的首次出现顺序输出。
086sheet-group-first-seen-order按 ADR-0016,文件内的工作表分组按首次出现顺序输出。
087date-canonical-string-concat按 ADR-0017,& 中的日期产出 YYYY-MM-DD(午夜)或 YYYY-MM-DDTHH:mm:ss。
088date-comparison-equality按 ADR-0017,日期值通过规范字符串形式与字符串过滤值比较。
089error-sentinel-empty按 ADR-0017,Excel 错误单元格(#N/A#VALUE!……)读取为空。
090percentage-numeric-flow按 ADR-0017,百分比格式的单元格以其底层数字流转(50% → 0.5)。

状态

XTL 0.1 语料处于引导状态。仅应针对已在 spec/README.md 中表述的行为添加 fixture,遵循 CommonMark 等标准项目的同一模式:散文定义规则,fixture 让规则可执行,实现报告其通过了哪些 fixture。

参考实现本身的行为不具有规范性。当 fixture 与实现不一致时,依据 spec/README.md 中的规范优先级,更新实现或更新 fixture。

XTL 0.1 核心行为的 fixture 避免使用实现定义的扩展,例如 spec/language.md 中最小表格之外的 TEXT() 格式。