Skip to main content

XTL Conformance Suite

This directory holds the conformance corpus — the test fixtures that any implementation of XTL must pass to claim conformance. The corpus is the executable definition of XTL behavior.

Layout

conformance/
├── README.md ← this file
├── AUTHORING.md ← how to add fixtures (avoid the JS-as-truth trap)
├── runner-protocol.md ← how implementations should run the suite
└── fixtures/
└── <NNN>-<slug>/
├── template.xlsx
├── data.xlsx
├── expected.xlsx ← canonical expected output (single-file case)
├── expected/ ← OR a directory of files (multi-file or zero-output case)
│ └── *.xlsx
├── no expected output ← for expected_error fixtures
├── no static expected ← for expected_dynamic fixtures
└── meta.yaml ← description, spec section refs, tags

What "passing" means

A static-output fixture passes if the implementation, given template.xlsx and data.xlsx, produces output(s) that match expected.xlsx (or the contents of expected/). Stage 1 runners may compare higher-level worksheet/cell values. Stage 2 runners compare byte-equivalent workbook content after canonical normalization of the OOXML zip:

  • Files within the zip sorted by name
  • XML serialized in deterministic canonical form
  • Text-run whitespace preserved
  • Generator metadata stripped (creator, modifiedBy, lastModified)

See runner-protocol.md for the comparison stage and canonicalization rules.

An error fixture passes when the implementation reports an error containing the fixture's expected_error text. Error fixtures do not include expected.xlsx or an expected/ directory.

A dynamic fixture passes when the implementation's output matches the dynamic assertions declared by expected_dynamic in meta.yaml. Dynamic fixtures do not include expected.xlsx or an expected/ directory.

Versioning

Each fixture directory contains meta.yaml declaring the minimum spec version it requires (spec_version: 0.1). Implementations report which spec version they target; the suite filters fixtures accordingly.

Static-output fixtures may also declare comparison_stage. The field defaults to 1; fixtures that require canonical OOXML comparison declare comparison_stage: 2.

Fixture Metadata

meta.yaml fields used by the corpus:

FieldRequiredApplies toMeaning
descriptionyesall fixturesOne-line contract the fixture asserts.
spec_sectionyesall fixturesSpec or ADR section that defines the behavior.
spec_versionyesall fixturesMinimum XTL version required by the fixture.
tagsyesall fixturesFilterable categories for reports and focused runs.
verified_bynoall fixturesIndependent authoring checks, such as hand or manual-script.
expected_warningsnoall fixturesStable warning substrings the implementation should emit.
expected_errornoerror fixturesStable error substring; omit static expected outputs.
expected_dynamicnodynamic fixturesDynamic assertion kind; currently utc_today.
dynamic_cellswith expected_dynamicdynamic fixturesSheet/cell/format assertions computed by the runner.
comparison_stagenostatic-output fixturesMinimum comparison stage; defaults to 1, use 2 for OOXML-sensitive checks.
skip_reasonnoall fixturesTemporary reason a known-broken fixture is skipped.

expected_error and expected_dynamic are mutually exclusive. Static-output fixtures use expected.xlsx or expected/; an empty expected/ directory means zero output files. Error and dynamic fixtures omit static expected outputs.

Fixture Catalog

The XTL 0.1 bootstrap corpus currently contains these fixtures:

IDFixtureContract
001bracket-substitutionSingle bracketed source-column expressions render one output row per source row.
002if-functionIF(condition, then, else) evaluates comparisons inside the current data row.
003list-sheet-filter@filter [field] in _ListSheet keeps matching rows and removes the list sheet from output.
004repeat-right-default@repeat right without an explicit count defaults to colSpan = 1.
005round-half-away-from-zeroROUND() uses Excel-style half-away-from-zero rounding.
006filename-forbidden-charsForbidden filename characters are replaced with _.
007filename-reserved-nameWindows reserved device basenames get a single trailing _.
008numfmt-numeric-string-coercionNumeric template formats coerce numeric strings to numbers.
009numfmt-date-string-coercionDate template formats coerce date-like strings to date values.
010numfmt-text-format-coercionText format @ coerces a single-expression value to a string.
011text-date-formatTEXT(date, "YYYY-MM-DD") returns a string using XTL date tokens.
012text-number-formatTEXT(number, format) supports the minimum XTL 0.1 numeric format subset.
013rich-text-template-expressionRich-text template cells are parsed by concatenating text runs before expression detection.
014source-formula-cached-resultSource formula cells use cached results and are not recalculated by XTL.
015source-sheet-prefix-first-matchsource_sheet prefix patterns select the first matching worksheet in workbook order.
016text-number-negative-roundingNumeric TEXT() formats round negative .5 boundaries half away from zero.
017source-sheet-prefix-no-match-errorMissing source_sheet prefix matches report a stable error.
018source-formula-missing-cached-result-errorSource formula cells without cached results report a stable error.
019filename-empty-basename-errorFilename sanitization reports an error for an empty basename.
020filename-length-overflow-errorFilename sanitization reports an error above the 255-byte limit.
021numfmt-number-coercion-errorNumeric template formats report an error when coercion fails.
022numfmt-date-coercion-errorDate template formats report an error when coercion fails.
023today-utc-dynamicTODAY() renders the runner-start UTC date through a dynamic assertion.
024stage2-merge-preservationStage 2 comparison verifies merged ranges below expanded data blocks are preserved.
025stage2-style-numfmt-preservationStage 2 comparison verifies rendered cells preserve template style and numFmt.
026stage2-splice-merge-style-preservationStage 2 comparison verifies row expansion preserves both shifted merges and styled/number-formatted rendered cells.
027stage2-cross-writer-canonicalizationStage 2 comparison verifies known OOXML writer differences canonicalize to the same workbook content.
028source-table-row-shorthandsource_table = N selects row N as source column names and reads rows below it.
029source-table-open-rangesource_table = B3:D selects a column window and reads rows below through the used row end.
030source-table-finite-rangesource_table = B3:D4 stops reading at the declared end row.
031source-table-zero-data-rangesource_table = B3:D3 is valid and produces zero source rows.
032source-table-empty-column-name-errorEmpty source column names inside the selected span report a stable error.
033source-table-duplicate-column-name-errorDuplicate source column names report a stable error.
034source-table-invalid-selector-errorInvalid selectors such as row zero report a stable error.
035source-table-rich-text-headerRich-text source column-name cells are concatenated before source_table parsing.
036source-table-formula-headerFormula source column-name cells use cached results.
037source-table-formula-header-missing-cache-errorFormula source column-name cells without cached results report a stable error.
038source-sheet-exact-match-beats-prefixExact source_sheet matches take precedence over prefix patterns.
039source-sheet-default-first-worksheetIf source_sheet is omitted, the first worksheet in workbook order is used.
040list-sheet-hidden-states-removedHidden and very hidden list sheets are still removed from output workbooks.
041row-function-inside-repeat-blockROW() returns the 1-based index of the current rendered data row inside a repeat block.
042row-function-outside-repeat-block-errorCalling ROW() outside a repeat block reports a stable error.
043ifempty-functionIFEMPTY() returns the fallback for empty values and passes through non-empty values.
044sort-and-top-order@sort runs before @top, so the top N rows come from the sorted set.
045list-sheet-not-in-filter@filter ... !in _Sheet keeps rows whose values are not present in the list sheet and removes the list sheet from output.
046count-field-non-emptyCOUNT([field]) counts non-empty values in the current row set.
047aggregate-functionsCore aggregates operate on the current rendered row set.
048if-and-comparison-boundariesComparison operators drive IF() and @filter behavior around the zero boundary.
049filename-sanitization-warningSanitizing a rendered filename emits a warning without changing output semantics.
050empty-ifempty-whitespace-onlyIFEMPTY treats whitespace-only strings as empty per ADR-0007.
051empty-ifempty-zero-not-emptyIFEMPTY preserves the number 0; numbers are never empty per ADR-0007.
052empty-count-field-whitespace-zero-falseCOUNT([field]) counts non-empty values per ADR-0007 — whitespace empty, 0 and FALSE non-empty.
053empty-row-skip-whitespace-onlyA source row whose every cell is empty per ADR-0007 is skipped, including whitespace-only cells.
054empty-list-membershipList sheets drop empty entries on read; an empty source-row value never matches @filter ... in _Sheet per ADR-0007.
055if-truthy-zero-and-emptyIF treats 0 and empty values as falsy; non-zero numbers, non-empty strings, and TRUE are truthy per ADR-0008.
056if-truthy-string-zero-not-specialIF("0", …) and IF("false", …) take the truthy branch — no special case for stringly-typed flag values.
057if-truthy-booleanA Boolean source cell drives IF truthiness directly per ADR-0008.
058if-comparison-resultA comparison expression's Boolean result feeds IF truthiness directly per ADR-0008.
059compare-numeric-string-vs-numberComparison parses numbers and numeric strings under the shared compareValues per ADR-0009.
060compare-string-codepoint-orderString fallback comparison uses Unicode code-point order — no locale-aware collation per ADR-0009.
061concat-canonical-form& stringifies operands using the canonical string form per ADR-0009 (booleans uppercase, integers without decimal).
062concat-empty-stringifies-to-empty& over an empty operand contributes the empty string per ADR-0009.
063compare-empty-vs-valueTwo empty operands compare equal; exactly one empty makes = false per ADR-0009 rules 1 and 2.
064compare-unicode-minus-not-numericA string with Unicode minus (U+2212) is not parsed as a number; comparison falls through to the canonical-string fallback per ADR-0009.
065input-text-default-appliedA _inputs text input default fills in when the host omits the value (ADR-0010).
066input-text-host-suppliedHost-supplied input flows through cells, sheet names, and the output filename pattern (ADR-0010).
067input-missing-required-errorA required _inputs declaration (no default) that the host omits is an error (ADR-0010).
068input-select-host-suppliedA select input accepts a host value listed in the declared pipe-separated options (ADR-0010).
069source-multi-declarationA __sources__ sheet declares an additional named source; aggregates over it operate on its full row set per ADR-0012.
070source-aggregate-cross-sourceCOUNT/MIN/MAX over a named source operate on its full row set per ADR-0012.
071source-directive-active@source SourceName scopes a data block; inside it [Column] resolves to that source per ADR-0012.
072source-undeclared-error@source referencing a source not declared in __sources__ is a parse-time error per ADR-0012.
073source-row-cross-errorRow-level reference to a non-active source's column is an error per ADR-0012.
074xlookup-basic3-arg XLOOKUP returns the matched return-array column for the first row whose lookup-array matches per ADR-0013.
075xlookup-fallback4-arg XLOOKUP returns the fallback when no row matches per ADR-0013.
076xlookup-no-match-error3-arg XLOOKUP without a fallback errors when no row matches per ADR-0013.
077xlookup-source-mismatch-errorXLOOKUP arg 2 and arg 3 must reference the same source per ADR-0013.
078xlookup-bare-bracket-errorXLOOKUP arg 2 / arg 3 require a source-prefixed bracket reference per ADR-0013.
079join-basic-inner@join pairs each primary row with the first matching joined row per ADR-0014.
080join-no-match-dropped@join uses inner semantics — primary rows without a match are dropped per ADR-0014.
081join-undeclared-source-error@join referencing a source not declared in __sources__ is a parse-time error per ADR-0014.
082join-bad-on-clause-error@join on-clause must reference the joined source and the block's primary source per ADR-0014.
083sort-stable-equal-keys@sort is stable — rows with equal keys preserve source order per ADR-0016.
084sort-multi-stable-priorityMultiple @sort directives apply with first = primary key, later directives as tiebreakers per ADR-0016.
085file-group-first-seen-orderFile groups emit in first-seen order over the source rows per ADR-0016.
086sheet-group-first-seen-orderSheet groups within a file emit in first-seen order per ADR-0016.
087date-canonical-string-concatA Date in & produces YYYY-MM-DD (midnight) or YYYY-MM-DDTHH:mm:ss per ADR-0017.
088date-comparison-equalityDate values compare via canonical string form against a string filter value per ADR-0017.
089error-sentinel-emptyExcel error cells (#N/A, #VALUE!, …) read as empty per ADR-0017.
090percentage-numeric-flowPercentage-formatted cells flow as their underlying Number per ADR-0017 (50% → 0.5).

Status

XTL 0.1 corpus is bootstrap state. Fixtures should be added only for behavior already stated in spec/README.md, following the same pattern used by standards projects such as CommonMark: prose defines the rule, fixtures make the rule executable, and implementations report which fixtures they pass.

The reference implementation does not make its own behavior normative. When a fixture and the implementation disagree, update the implementation or the fixture according to the spec precedence in spec/README.md.

Fixtures for XTL 0.1 core behavior avoid implementation-defined extensions such as TEXT() formats outside the minimum table in spec/language.md.