Skip to content

Commit 6c74aa3

Browse files
committed
Add enumerable-cell template conditions with .NET parity
1 parent 6a7ea00 commit 6c74aa3

10 files changed

Lines changed: 290 additions & 13 deletions

File tree

‎.github/workflows/rust.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ jobs:
349349
run: >-
350350
dotnet test dotnet-source/tests/MiniExcel.OpenXml.Tests/MiniExcel.OpenXml.Tests.csproj
351351
--framework net10.0
352-
--filter "FullyQualifiedName~MiniExcelTemplateAsyncTests.TemplateAsyncBasicTest|FullyQualifiedName~MiniExcelTemplateAsyncTests.SaveAsByTemplateAsync_TakeCancel_Throws_TaskCanceledException"
352+
--filter "FullyQualifiedName~MiniExcelTemplateAsyncTests.TemplateAsyncBasicTest|FullyQualifiedName~MiniExcelTemplateAsyncTests.SaveAsByTemplateAsync_TakeCancel_Throws_TaskCanceledException|FullyQualifiedName~MiniExcelTemplateAsyncTests.TestIEnumerableConditional"
353353
--verbosity minimal
354354
355355
msrv:

‎README.md‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,14 +580,32 @@ MiniExcel::save_as_template(
580580

581581
Scalar placeholders use `{{title}}`. A row containing `{{items.name}}` is repeated once per array item. Exact number, boolean, and null placeholders become native cell values; mixed text becomes an inline string. Missing variables are blank by default and can be rejected with `with_ignore_missing_variables(false)`. Path output refuses existing files unless `with_overwrite_file(true)` is set. `save_as_template_bytes()` supports in-memory templates.
582582

583+
Cells in an enumerable row may select one line of content per item with a multiline conditional
584+
block:
585+
586+
```text
587+
@if(name == Jack)
588+
{{items.name}}
589+
@elseif(score >= 10)
590+
Top {{items.name}}
591+
@else
592+
{{items.department}}
593+
@endif
594+
```
595+
596+
Direct item fields support string `==`/`!=`, numeric comparisons, and boolean `==`/`!=`. Branch
597+
markers and bodies each occupy one line; every item still emits one row. Malformed blocks and
598+
missing fields return template errors. Nested blocks, logical expressions, and conditional formula
599+
branches are not supported.
600+
583601
With the optional `async` feature, `save_as_template_async()` and
584602
`save_as_template_async_with_cancellation()` run blocking template ZIP/XML work on a worker thread
585603
and atomically publish the validated path output. Pre-cancellation, rendering errors, dropped
586604
futures, and cancellation before commit preserve an existing destination byte-for-byte or leave a
587605
missing destination absent. This does not make ZIP/filesystem operations asynchronous and retains
588606
the current in-memory template rendering model.
589607

590-
Version 1 does not implement `@group`, `@if`, parametrized sheet cloning, `$=` formula templates, or formula recalculation.
608+
Version 1 does not implement `@group`, parametrized sheet cloning, `$=` formula templates, or formula recalculation.
591609

592610
## Important Semantics
593611

‎README.zh-CN.md‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -570,13 +570,29 @@ MiniExcel::save_as_template(
570570

571571
标量占位符使用 `{{title}}`。包含 `{{items.name}}` 的 row 会按数组 item 数量重复。单独的 number、boolean 和 null 占位符会写成原生 cell value;混合文本写成 inline string。缺失变量默认留空,也可用 `with_ignore_missing_variables(false)` 拒绝。路径输出默认拒绝已有文件,设置 `with_overwrite_file(true)` 后才覆盖。内存模板可使用 `save_as_template_bytes()`。
572572

573+
Enumerable row 中的 cell 可通过多行 conditional block 为每个 item 选择一行内容:
574+
575+
```text
576+
@if(name == Jack)
577+
{{items.name}}
578+
@elseif(score >= 10)
579+
Top {{items.name}}
580+
@else
581+
{{items.department}}
582+
@endif
583+
```
584+
585+
直接 item field 支持 string `==`/`!=`、number 比较和 boolean `==`/`!=`。每个 branch marker
586+
和正文各占一行;每个 item 仍输出一行。Malformed block 与缺失 field 会返回 template error。
587+
Nested block、逻辑表达式和 conditional formula branch 尚不支持。
588+
573589
启用可选 `async` feature 后,`save_as_template_async()` 与
574590
`save_as_template_async_with_cancellation()` 会在 worker thread 执行 blocking template
575591
ZIP/XML 工作,并原子发布通过验证的 path output。Pre-cancellation、render error、drop future
576592
以及 commit 前 cancellation 会保留已有 destination 的原始字节,或让缺失 destination 继续
577593
不存在。该 API 不会把 ZIP/filesystem 操作变成 async,并保留当前模板内存渲染模型。
578594

579-
版本 1 尚不支持 `@group`、`@if`、参数化 sheet 克隆、`$=` 公式模板或公式重算。
595+
版本 1 尚不支持 `@group`、参数化 sheet 克隆、`$=` 公式模板或公式重算。
580596

581597
## 重要语义
582598

‎docs/compatibility.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ The contract covers only the current common surface: dynamic/typed path queries,
181181
| CSV dynamic/typed query, save, append, and columns | Implemented | Rust tests plus pinned .NET CSV fixtures/baseline tests |
182182
| New-workbook `SaveAs`, including multiple sheets | Implemented and roundtrip-tested | Not yet |
183183
| Basic `SaveAsTemplate` scalar/list fill | Implemented and roundtrip-tested | Not yet |
184+
| Enumerable-cell conditional template blocks | Implemented for string/number/bool comparisons | Rust sync/async tests; scoped .NET conditional test |
184185
| Byte-array query/write for WASM | Implemented | Rust/browser tests |
185186
| Versioned grouped analytics | Rust research extension | No |
186187
| Addressed JSONL/Markdown/manifest RAG export | Rust research extension | No |
@@ -202,4 +203,4 @@ This matrix is the coverage claim: Rust does not yet provide complete API parity
202203

203204
## Deferred Work
204205

205-
SQL text parsing, `HAVING`, `ORDER BY`, joins, windows, pivots, disk-spill aggregation, vector indexing, model calls, old Excel formats, advanced template directives and sheet cloning, image authoring, merged-cell APIs, formula calculation/dependency expansion, formula authoring, general styling, async template streams, inferred/typed async export sources, async borrowed readers/writers, and borrowed XLSX lazy readers require separate design and acceptance milestones. CSV DataReader/DataTable adapters are intentionally replaced by Rust iterators, and a one-call CSV/XLSX converter is not exposed; callers compose query and save APIs. See the [Insert migration guide](insert-v1-migration.md) for supported workflows and deliberate differences.
206+
SQL text parsing, `HAVING`, `ORDER BY`, joins, windows, pivots, disk-spill aggregation, vector indexing, model calls, old Excel formats, template grouping, nested/logical conditions, formula templates and sheet cloning, image authoring, merged-cell APIs, formula calculation/dependency expansion, formula authoring, general styling, async template streams, inferred/typed async export sources, async borrowed readers/writers, and borrowed XLSX lazy readers require separate design and acceptance milestones. CSV DataReader/DataTable adapters are intentionally replaced by Rust iterators, and a one-call CSV/XLSX converter is not exposed; callers compose query and save APIs. See the [Insert migration guide](insert-v1-migration.md) for supported workflows and deliberate differences.

‎docs/compatibility.zh-CN.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ Rust workflow 会在 Linux 和 Windows 上运行 Rust 契约。其 .NET parity j
181181
| CSV 动态/类型化 query、save、append 与 columns | 已实现 | Rust 测试以及固定 .NET CSV fixture/基准测试 |
182182
| 新 workbook `SaveAs`(含多工作表) | 已实现并完成 roundtrip 测试 | 尚未 |
183183
| 基础 `SaveAsTemplate` 标量/列表填充 | 已实现并完成 roundtrip 测试 | 尚未 |
184+
| Enumerable-cell conditional template block | 已支持 string/number/bool 比较 | Rust sync/async 测试;scoped .NET conditional test |
184185
| 用于 WASM 的字节数组 query/write | 已实现 | Rust/browser 测试 |
185186
| 版本化分组分析 | Rust 研究扩展 | 否 |
186187
| 带地址 JSONL/Markdown/manifest RAG 导出 | Rust 研究扩展 | 否 |
@@ -202,4 +203,4 @@ Rust workflow 会在 Linux 和 Windows 上运行 Rust 契约。其 .NET parity j
202203

203204
## 延后工作
204205

205-
SQL 文本解析、`HAVING`、`ORDER BY`、join、window、pivot、磁盘 spill 聚合、向量索引、模型调用、旧 Excel 格式、高级 template 指令与 sheet 克隆、image authoring、merged-cell API、公式计算/依赖展开、公式编写、通用 style、async template stream、推断/类型化 async export source、async borrowed reader/writer,以及 borrowed XLSX lazy reader,都需要独立的设计与验收里程碑。CSV DataReader/DataTable adapter 已有意替换为 Rust iterator;当前不提供一步式 CSV/XLSX converter,调用方可组合 query 与 save API。支持工作流与有意保留的差异见 [Insert 迁移说明](insert-v1-migration.zh-CN.md)。
206+
SQL 文本解析、`HAVING`、`ORDER BY`、join、window、pivot、磁盘 spill 聚合、向量索引、模型调用、旧 Excel 格式、template grouping、nested/logical condition、formula template 与 sheet cloning、image authoring、merged-cell API、公式计算/依赖展开、公式编写、通用 style、async template stream、推断/类型化 async export source、async borrowed reader/writer,以及 borrowed XLSX lazy reader,都需要独立的设计与验收里程碑。CSV DataReader/DataTable adapter 已有意替换为 Rust iterator;当前不提供一步式 CSV/XLSX converter,调用方可组合 query 与 save API。支持工作流与有意保留的差异见 [Insert 迁移说明](insert-v1-migration.zh-CN.md)。

‎docs/dotnet-feature-gaps.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This report compares observable public capabilities in the local checkouts below
88

99
| Project | Revision |
1010
| --- | --- |
11-
| MiniExcel-Rust | working tree based on `4078f8f` (`0.3.0`) |
11+
| MiniExcel-Rust | working tree based on `6a7ea00` (`0.3.0`) |
1212
| MiniExcel .NET | `b9a76d7af62142e0e38545b6905b01a06e8d160e` |
1313

1414
The comparison uses the .NET public APIs, their controlling implementations, and focused tests under the sibling `../MiniExcel` checkout. Rust status is based on the public `MiniExcel` facade, options, integration tests, and [compatibility boundary](compatibility.md).
@@ -35,7 +35,7 @@ Rust already implements dynamic and Serde-typed XLSX path queries, inclusive A1
3535
| General save inputs | Partial | Export from general objects/enumerables, dictionaries, `DataTable`, `IDataReader`, and async enumerables, with progress. Rust accepts dynamic or same-type Serde slices and reports per-sheet row counts. |
3636
| Multi-sheet export | Partial | Rust creates ordered visible, hidden, and very-hidden worksheets, but does not yet accept heterogeneous Serde row types in one call. |
3737
| Existing-workbook operations | Implemented | Rust atomically appends, strictly replaces, renames, changes visibility, reorders, and performs .NET-style source-workbook copy-and-add while preserving unrelated package parts and worksheet identity. Rename preserves formula text; visibility rejects hiding the final visible sheet; reorder remaps active/view/local-name indices; copy-and-add preserves the source and atomically publishes a separate destination. |
38-
| Templates | Partial | Rust fills path/byte templates with scalar values and single-row arrays while preserving package parts; path output also has a cancellable async wrapper. Streams, grouping, conditions, parametrized sheets, `$=` formulas, formula-reference updates, and calculation-chain handling remain unsupported. |
38+
| Templates | Partial | Rust fills path/byte templates with scalars, arrays, and enumerable-cell conditional blocks while preserving package parts; path output also has a cancellable async wrapper. Streams, grouping, nested/logical conditions, parametrized sheets, `$=` formulas, formula-reference updates, and calculation-chain handling remain unsupported. |
3939
| Pictures and merge processing | Missing | Add anchored pictures and merge adjacent identical cells through the templater surface. Structured reads do not provide authoring parity. |
4040
| CSV | Implemented | Dynamic/Serde path, byte, and borrowed query/save APIs; column discovery; inferred/explicit-schema append; delimiter/newline/encoding/BOM/null/quoting configuration; and `query-csv` CLI. DataReader/DataTable are replaced by Rust iterators. Async/progress APIs and a one-call CSV/XLSX converter are not exposed. |
4141
| Comments and notes | Implemented | Path/bytes/borrowed APIs return threaded roots, replies, unresolved person IDs, people/provider/user IDs, resolution state, typed timestamps, and legacy notes. |
@@ -54,6 +54,7 @@ Rust already implements dynamic and Serde-typed XLSX path queries, inclusive A1
5454
| Async query | `MiniExcel::query_async*` and `query_as_async*`; Rust focused parity/cancellation/error/cleanup tests | `OpenXmlImporter.QueryAsync`; `MiniExcelOpenXmlImporterAsyncTests` |
5555
| Async export | `MiniExcel::save_as_with_schema_async*`; Rust focused rollback/cancellation/cleanup tests | `OpenXmlExporter.ExportAsync`; async-enumerable exporter tests |
5656
| Async template | `MiniExcel::save_as_template_async*`; Rust focused rollback/cancellation/cleanup tests | `OpenXmlTemplater.SaveAsByTemplateAsync`; scoped basic/cancellation tests |
57+
| Template conditions | Enumerable-cell `@if`/`@elseif`/`@else` blocks; Rust sync/async branch/error/style tests | `TestIEnumerableConditional` |
5758
| Tables | `MiniExcel::query_table*`; Rust focused tests use the exact `TestQueryTable.xlsx` fixture (SHA-256 `04F719BF9F9E99D9B437A8FB32F8111FD92580A1D29ACAD10B6ED128C0564501`) | `OpenXmlImporter.QueryTableAsync`; `tests/MiniExcel.OpenXml.Tests/Tables/` |
5859
| Comments | `MiniExcel::get_comments*`; Rust focused tests use `TestCommentsAndNotes.xlsx` (SHA-256 `3A855CE896ED62DC27C91797432DD89EE081F07CD03AB05BF1B0CD745543A3FC`) | `OpenXmlImporter.RetrieveCommentsAsync`; `tests/MiniExcel.OpenXml.Tests/Comments/` |
5960
| DataReader/DataTable | Rust iterators and borrowed visitors are the native abstraction; no literal .NET tabular adapter is planned | `OpenXmlImporter.GetDataReader`, `GetAsyncDataReader`, `QueryAsDataTableAsync`; `tests/MiniExcel.OpenXml.Tests/DataReader/` |
@@ -69,7 +70,7 @@ The .NET APIs marked with `Async` also have generated synchronous counterparts t
6970
## Suggested Implementation Order
7071

7172
1. **Broader async export/stream APIs**: extend runtime-neutral cancellation to typed/inferred producers and borrowed streams without presenting blocking ZIP work as async I/O.
72-
2. **Advanced templates and Fluent Mapping**: add grouped/conditional templates, parametrized sheets, and mapping through separate compatibility milestones.
73+
2. **Advanced templates and Fluent Mapping**: add grouped templates, richer conditional expressions, parametrized sheets, and mapping through separate compatibility milestones.
7374
3. **Selected-sheet cloning**: not part of .NET `CopyAndAddSheet`; add only for a concrete Rust use case with a relationship-closure cloning contract.
7475

7576
DataReader/DataTable are .NET ecosystem abstractions and are intentionally not literal Rust parity requirements. A Rust-native record-batch or tabular adapter is appropriate only when a concrete integration requires it.

‎docs/dotnet-feature-gaps.zh-CN.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
| 项目 | 版本基线 |
1010
| --- | --- |
11-
| MiniExcel-Rust | 基于 `4078f8f` 的工作区(`0.3.0`) |
11+
| MiniExcel-Rust | 基于 `6a7ea00` 的工作区(`0.3.0`) |
1212
| MiniExcel .NET | `b9a76d7af62142e0e38545b6905b01a06e8d160e` |
1313

1414
比对依据包括同级目录 `../MiniExcel` 中的 .NET 公开 API、控制实现和聚焦测试,以及 Rust 的公开 `MiniExcel` 门面、选项、集成测试和[兼容性边界](compatibility.zh-CN.md)。
@@ -35,7 +35,7 @@ Rust 已支持动态及 Serde 强类型 XLSX 路径查询、闭区间 A1 范围
3535
| 通用保存输入 | 部分实现 | 从普通对象/可枚举对象、字典、`DataTable`、`IDataReader` 和异步枚举导出,并报告进度。Rust 接受动态行或同类型 Serde 切片,并返回每张工作表的行数。 |
3636
| 多工作表导出 | 部分实现 | Rust 可按输入顺序创建 visible、hidden 和 very-hidden 工作表,但尚不能在一次调用中接受异构 Serde 行类型。 |
3737
| 修改现有工作簿 | 已实现 | Rust 可原子 append、严格 replace、rename、修改 visibility、reorder,并执行 .NET 风格的 source-workbook copy-and-add,同时保留无关 package part 与 worksheet identity。Rename 保留 formula 文本;visibility 拒绝隐藏最后一张 visible sheet;reorder remap active/view/local-name index;copy-and-add 保留 source 并原子发布独立 destination。 |
38-
| 模板 | 部分实现 | Rust 可使用标量和单 row 数组填充 path/byte 模板并保留 package part;path output 也提供 cancellable async wrapper。Stream、分组、条件、参数化 sheet、`$=` 公式、公式引用调整和 calculation chain 处理仍不支持。 |
38+
| 模板 | 部分实现 | Rust 可使用 scalar、array 与 enumerable-cell conditional block 填充 path/byte template 并保留 package part;path output 也提供 cancellable async wrapper。Stream、grouping、nested/logical condition、参数化 sheet、`$=` formula、formula reference 调整和 calculation chain 处理仍不支持。 |
3939
| 图片与合并处理 | 未实现 | 添加锚定图片,以及通过模板 API 合并相邻相同单元格。结构化读取不等于具备写入能力。 |
4040
| CSV | 已实现 | 动态/Serde path、byte、borrowed query/save API;column discovery;推断/显式 schema append;delimiter/newline/encoding/BOM/null/quoting 配置,以及 `query-csv` CLI。DataReader/DataTable 由 Rust iterator 替代;未暴露 async/progress API 和一步式 CSV/XLSX converter。 |
4141
| 批注与注释 | 已实现 | Path/bytes/borrowed API 返回 threaded root、reply、未解析 person ID、person/provider/user ID、resolved state、typed timestamp 与 legacy note。 |
@@ -54,6 +54,7 @@ Rust 已支持动态及 Serde 强类型 XLSX 路径查询、闭区间 A1 范围
5454
| Async query | `MiniExcel::query_async*` 与 `query_as_async*`;Rust focused parity/cancellation/error/cleanup 测试 | `OpenXmlImporter.QueryAsync`;`MiniExcelOpenXmlImporterAsyncTests` |
5555
| Async export | `MiniExcel::save_as_with_schema_async*`;Rust focused rollback/cancellation/cleanup 测试 | `OpenXmlExporter.ExportAsync`;async-enumerable exporter 测试 |
5656
| Async template | `MiniExcel::save_as_template_async*`;Rust focused rollback/cancellation/cleanup 测试 | `OpenXmlTemplater.SaveAsByTemplateAsync`;scoped basic/cancellation 测试 |
57+
| Template condition | Enumerable-cell `@if`/`@elseif`/`@else` block;Rust sync/async branch/error/style 测试 | `TestIEnumerableConditional` |
5758
| 表格 | `MiniExcel::query_table*`;Rust focused test 使用完全相同的 `TestQueryTable.xlsx` fixture(SHA-256 `04F719BF9F9E99D9B437A8FB32F8111FD92580A1D29ACAD10B6ED128C0564501`) | `OpenXmlImporter.QueryTableAsync`;`tests/MiniExcel.OpenXml.Tests/Tables/` |
5859
| Comments | `MiniExcel::get_comments*`;Rust focused test 使用 `TestCommentsAndNotes.xlsx`(SHA-256 `3A855CE896ED62DC27C91797432DD89EE081F07CD03AB05BF1B0CD745543A3FC`) | `OpenXmlImporter.RetrieveCommentsAsync`;`tests/MiniExcel.OpenXml.Tests/Comments/` |
5960
| DataReader/DataTable | Rust iterator 与 borrowed visitor 是原生抽象;不计划逐字复制 .NET tabular adapter | `OpenXmlImporter.GetDataReader`、`GetAsyncDataReader`、`QueryAsDataTableAsync`;`tests/MiniExcel.OpenXml.Tests/DataReader/` |
@@ -69,7 +70,7 @@ Rust 已支持动态及 Serde 强类型 XLSX 路径查询、闭区间 A1 范围
6970
## 建议实现顺序
7071

7172
1. **更广泛 async export/stream API**:把 runtime-neutral cancellation 扩展到类型化/推断 producer 和 borrowed stream,不把 blocking ZIP 工作描述成 async I/O。
72-
2. **高级模板与 Fluent Mapping**:通过独立兼容里程碑增加分组/条件模板、参数化 sheet 和 mapping。
73+
2. **高级模板与 Fluent Mapping**:通过独立兼容里程碑增加 grouped template、更丰富 conditional expression、参数化 sheet 和 mapping。
7374
3. **选定 worksheet cloning**:不属于 .NET `CopyAndAddSheet`;仅在有具体 Rust 使用场景时,通过 relationship closure cloning contract 增加。
7475

7576
DataReader/DataTable 属于 .NET 生态抽象,明确不作为逐字 Rust parity 要求。只有出现具体集成需求时,才应设计 Rust-native record-batch 或 table adapter。

0 commit comments

Comments
 (0)