Skip to content

Commit db95441

Browse files
committed
Add atomic worksheet reordering with .NET parity
1 parent 6df7623 commit db95441

14 files changed

Lines changed: 661 additions & 28 deletions

‎.github/workflows/rust.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ jobs:
251251
run: |
252252
cargo +1.85.0 test -p miniexcel --test rename_sheet --locked
253253
cargo +1.85.0 test -p miniexcel --test sheet_visibility --locked
254+
cargo +1.85.0 test -p miniexcel --test reorder_sheet --locked
254255
- name: Run MiniExcel .NET AlterSheet baseline tests
255256
run: >-
256257
dotnet test dotnet-source/tests/MiniExcel.OpenXml.Tests/MiniExcel.OpenXml.Tests.csproj

‎README.md‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,18 @@ The active-tab index is preserved even when the active worksheet is hidden. Unli
444444
`AlterSheet`, Rust rejects hiding the final visible worksheet so the committed workbook always
445445
retains at least one visible sheet. Setting the current state again is a byte-for-byte no-op.
446446

447+
Worksheets can also be moved by zero-based index:
448+
449+
```rust
450+
MiniExcel::reorder_sheet("book.xlsx", "Archive", 0)?;
451+
```
452+
453+
Negative indices clamp to the first position and oversized indices clamp to the last, matching
454+
.NET `AlterSheet`. Rust additionally remaps `activeTab`, `firstSheet`, and defined-name
455+
`localSheetId` values so those references remain attached to the same worksheet. Relationships,
456+
formula text, visibility, and worksheet IDs remain unchanged. Moving to the current effective
457+
index is a byte-for-byte no-op.
458+
447459
Enable the optional `async` feature to feed an existing-workbook Insert from a runtime-neutral
448460
`Stream<Item = miniexcel::Result<DynamicRow>>`:
449461

@@ -542,10 +554,10 @@ Version 1 does not implement `@group`, `@if`, parametrized sheet cloning, `$=` f
542554
- Grouped analytics retain state proportional to distinct groups and stop at `max_groups`.
543555
- RAG exports never recalculate formulas and reject hidden sheets unless explicitly allowed.
544556
- Synchronous streaming queries use one worker thread per active query. Optional async query and Insert APIs use bounded channels around blocking XLSX workers; ZIP/XML/filesystem work is not async I/O.
545-
- Save creates new workbooks and refuses existing target paths by default. `MiniExcel::insert*()` atomically appends or strictly replaces a worksheet in an existing `.xlsx` path, or creates a workbook when the path is missing. `rename_sheet()` and `set_sheet_visibility()` atomically change only existing workbook sheet metadata.
557+
- Save creates new workbooks and refuses existing target paths by default. `MiniExcel::insert*()` atomically appends or strictly replaces a worksheet in an existing `.xlsx` path, or creates a workbook when the path is missing. `rename_sheet()`, `set_sheet_visibility()`, and `reorder_sheet()` atomically change existing workbook sheet metadata.
546558

547559
## Not Supported
548560

549-
`.xls`, `.xlsb`, `.ods`, advanced template directives, macros, images, merged-cell operations, formula authoring, a general style system, and arbitrary worksheet copying/reordering are not currently supported.
561+
`.xls`, `.xlsb`, `.ods`, advanced template directives, macros, images, merged-cell operations, formula authoring, a general style system, and arbitrary worksheet copying are not currently supported.
550562

551563
See the [compatibility matrix](docs/compatibility.md) for the current support scope and the [MiniExcel v1 Insert migration guide](docs/insert-v1-migration.md) for deliberate differences.

‎README.zh-CN.md‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,17 @@ MiniExcel::set_sheet_visibility("book.xlsx", "Archive", SheetVisibility::VeryHid
437437
拒绝隐藏最后一张 visible worksheet,保证 commit 后的 workbook 至少保留一张 visible sheet。
438438
重复设置当前状态是字节级 no-op。
439439

440+
Worksheet 也可按 0-based index 移动:
441+
442+
```rust
443+
MiniExcel::reorder_sheet("book.xlsx", "Archive", 0)?;
444+
```
445+
446+
负数 index 会 clamp 到第一位,过大的 index 会 clamp 到最后一位,与 .NET `AlterSheet`
447+
一致。Rust 还会 remap `activeTab`、`firstSheet` 和 defined-name `localSheetId`,使这些引用
448+
仍归属于原 worksheet。Relationship、formula 文本、visibility 和 worksheet ID 保持不变;
449+
移动到当前有效 index 是字节级 no-op。
450+
440451
启用可选 `async` feature 后,可通过 runtime-neutral
441452
`Stream<Item = miniexcel::Result<DynamicRow>>` 为现有 workbook 的 Insert 提供 row:
442453

@@ -534,10 +545,10 @@ MiniExcel::save_as_template(
534545
- 分组分析保留与不同 group 数量成比例的状态,并在 `max_groups` 停止。
535546
- RAG 导出不会重新计算公式,hidden sheet 未显式允许时会拒绝处理。
536547
- 同步流式 query 每个活动 query 使用一个 worker thread。可选 async query/Insert API 通过 bounded channel 包装 blocking XLSX worker;ZIP/XML/filesystem 工作并不是 async I/O。
537-
- Save 会创建新工作簿,并默认拒绝已有目标路径。`MiniExcel::insert*()` 会向现有 `.xlsx` path 原子 append 或严格 replace worksheet;路径不存在时则创建 workbook。`rename_sheet()` 和 `set_sheet_visibility()` 只原子修改现有 workbook 的 sheet metadata。
548+
- Save 会创建新工作簿,并默认拒绝已有目标路径。`MiniExcel::insert*()` 会向现有 `.xlsx` path 原子 append 或严格 replace worksheet;路径不存在时则创建 workbook。`rename_sheet()`、`set_sheet_visibility()` 和 `reorder_sheet()` 会原子修改现有 workbook 的 sheet metadata。
538549

539550
## 暂不支持
540551

541-
目前不支持 `.xls`、`.xlsb`、`.ods`、高级模板指令、宏、图片、合并单元格操作、公式写入、通用样式系统,以及任意 worksheet copy/reorder。
552+
目前不支持 `.xls`、`.xlsb`、`.ods`、高级模板指令、宏、图片、合并单元格操作、公式写入、通用样式系统,以及任意 worksheet copy。
542553

543554
当前支持范围请查看[兼容性矩阵](docs/compatibility.zh-CN.md);有意保留的差异见 [MiniExcel v1 Insert 迁移说明](docs/insert-v1-migration.zh-CN.md)。

‎docs/compatibility.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,9 @@ The contract covers only the current common surface: dynamic/typed path queries,
189189
| Strict worksheet replacement | Implemented for plain targets and supported target-owned closures | Rust tests; stale calcChain removed and full recalculation requested |
190190
| Atomic worksheet rename | Implemented for existing `.xlsx` paths | Rust package-preservation tests; pinned .NET `AlterSheet` baseline |
191191
| Atomic worksheet visibility mutation | Implemented for existing `.xlsx` paths | Rust invariant/rollback tests; pinned .NET `AlterSheet` baseline |
192+
| Atomic worksheet reorder | Implemented for existing `.xlsx` paths | Rust positional-reference/rollback tests; pinned .NET `AlterSheet` baseline |
192193
| Legacy `.xls`, `.xlsb`, and `.ods` formats | Deferred | No |
193-
| Worksheet copy/reorder | Deferred | No |
194+
| Worksheet copy-and-add | Deferred | No |
194195
| Advanced templates, pictures, and merges | Deferred | No |
195196

196197
This matrix is the coverage claim: Rust does not yet provide complete API parity with the current .NET packages.

‎docs/compatibility.zh-CN.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,9 @@ Rust workflow 会在 Linux 和 Windows 上运行 Rust 契约。其 .NET parity j
189189
| 严格 worksheet replacement | 已支持 plain target 与受支持的 target-owned closure | Rust 测试;删除 stale calcChain 并要求 full recalculation |
190190
| 原子 worksheet rename | 已支持现有 `.xlsx` path | Rust package-preservation 测试;固定 .NET `AlterSheet` 基准 |
191191
| 原子 worksheet visibility mutation | 已支持现有 `.xlsx` path | Rust invariant/rollback 测试;固定 .NET `AlterSheet` 基准 |
192+
| 原子 worksheet reorder | 已支持现有 `.xlsx` path | Rust positional-reference/rollback 测试;固定 .NET `AlterSheet` 基准 |
192193
| 旧式 `.xls`、`.xlsb` 与 `.ods` 格式 | 延后 | 否 |
193-
| Worksheet copy/reorder | 延后 | 否 |
194+
| Worksheet copy-and-add | 延后 | 否 |
194195
| 高级 template、picture 与 merge | 延后 | 否 |
195196

196197
此矩阵就是覆盖声明:Rust 目前尚未提供与当前 .NET package 完整的 API 等价性。

‎docs/dotnet-feature-gaps.md‎

Lines changed: 4 additions & 4 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 `97bcade` (`0.3.0`) |
11+
| MiniExcel-Rust | working tree based on `6df7623` (`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).
@@ -34,7 +34,7 @@ Rust already implements dynamic and Serde-typed XLSX path queries, inclusive A1
3434
| Async and cancellation | Partial | Optional runtime-neutral dynamic/Serde path queries and explicit-schema Insert support bounded streams and cooperative cancellation. Async export/template operations, async write sources, borrowed async readers, and progress callbacks remain unsupported. ZIP and filesystem work remains blocking on dedicated workers. |
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. |
37-
| Existing-workbook operations | Partial | Rust atomically appends, strictly replaces, renames, or changes worksheet visibility while preserving unrelated package parts and worksheet identity. Copy-and-add and reorder remain unsupported. Rename deliberately preserves formula/defined-name text without repairing old-name references, matching .NET `AlterSheet`; visibility rejects hiding the final visible sheet. |
37+
| Existing-workbook operations | Partial | Rust atomically appends, strictly replaces, renames, changes visibility, or reorders worksheets while preserving unrelated package parts and worksheet identity. Copy-and-add remains unsupported. Rename preserves formula text; visibility rejects hiding the final visible sheet; reorder remaps active/view/local-name indices instead of transferring their ownership. |
3838
| Templates | Partial | Rust fills path/byte templates with scalar values and single-row arrays while preserving package parts. Streams, grouping, 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. |
@@ -55,7 +55,7 @@ Rust already implements dynamic and Serde-typed XLSX path queries, inclusive A1
5555
| Tables | `MiniExcel::query_table*`; Rust focused tests use the exact `TestQueryTable.xlsx` fixture (SHA-256 `04F719BF9F9E99D9B437A8FB32F8111FD92580A1D29ACAD10B6ED128C0564501`) | `OpenXmlImporter.QueryTableAsync`; `tests/MiniExcel.OpenXml.Tests/Tables/` |
5656
| Comments | `MiniExcel::get_comments*`; Rust focused tests use `TestCommentsAndNotes.xlsx` (SHA-256 `3A855CE896ED62DC27C91797432DD89EE081F07CD03AB05BF1B0CD745543A3FC`) | `OpenXmlImporter.RetrieveCommentsAsync`; `tests/MiniExcel.OpenXml.Tests/Comments/` |
5757
| 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/` |
58-
| Multi-sheet and workbook edits | Writer creates multiple sheets; existing workbooks support append, strict replacement, atomic rename, and atomic visibility mutation with package preservation | `OpenXmlExporter.InsertSheetAsync`, `CopyAndAddSheetAsync`, `AlterSheetAsync`; `tests/MiniExcel.OpenXml.Tests/MultipleSheets/` and `AlterSheets/` |
58+
| Multi-sheet and workbook edits | Writer creates multiple sheets; existing workbooks support append, strict replacement, and all independent `AlterSheet` metadata operations with atomic package preservation | `OpenXmlExporter.InsertSheetAsync`, `CopyAndAddSheetAsync`, `AlterSheetAsync`; `tests/MiniExcel.OpenXml.Tests/MultipleSheets/` and `AlterSheets/` |
5959
| Templates/pictures/merges | Basic template fill implemented; advanced directives and authoring remain deferred | `src/MiniExcel.OpenXml/Api/OpenXmlTemplater.cs`; `tests/MiniExcel.OpenXml.Tests/Templates/` |
6060
| CSV/conversion | `MiniExcel::query_csv*`, `save_csv*`, and `append_csv*`; Rust tests use exact `TestHeader.csv` (`6C2FC27FCA2876F1ECCA17061B8EE23E133ECDB726F8E0B84167E58D86234432`) and GB2312 (`BA8A2505AB271D5575C58CC1FCBE5A5002CEB9E2F43CB95412246E25A50E8B5A`) fixtures | `src/MiniExcel.Csv/Api/`; `src/MiniExcel/MiniExcelConverter.cs`; `tests/MiniExcel.Csv.Tests/` |
6161
| Mapping | Serde mapping only | `src/MiniExcel.Core/Attributes/MiniExcelColumnAttribute.cs`; `src/MiniExcel.OpenXml.FluentMapping/`; mapping tests |
@@ -68,7 +68,7 @@ The .NET APIs marked with `Async` also have generated synchronous counterparts t
6868

6969
1. **Async export/template APIs**: extend runtime-neutral producer/cancellation patterns without presenting blocking ZIP work as async I/O.
7070
2. **Advanced templates and Fluent Mapping**: add grouped/conditional templates, parametrized sheets, and mapping through separate compatibility milestones.
71-
3. **Remaining workbook edits**: copy/add and reorder require their own preservation contracts.
71+
3. **Remaining workbook edits**: copy-and-add requires a relationship-closure cloning contract.
7272

7373
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.
7474

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

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

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

1414
比对依据包括同级目录 `../MiniExcel` 中的 .NET 公开 API、控制实现和聚焦测试,以及 Rust 的公开 `MiniExcel` 门面、选项、集成测试和[兼容性边界](compatibility.zh-CN.md)。
@@ -34,7 +34,7 @@ Rust 已支持动态及 Serde 强类型 XLSX 路径查询、闭区间 A1 范围
3434
| 异步与取消 | 部分实现 | 可选、runtime-neutral 的动态/Serde path query 与显式 schema Insert 已支持 bounded stream 和协作式 cancellation。Async export/template、async write source、borrowed async reader 与 progress callback 仍不支持;ZIP 和 filesystem 工作仍在专用 blocking worker 上执行。 |
3535
| 通用保存输入 | 部分实现 | 从普通对象/可枚举对象、字典、`DataTable`、`IDataReader` 和异步枚举导出,并报告进度。Rust 接受动态行或同类型 Serde 切片,并返回每张工作表的行数。 |
3636
| 多工作表导出 | 部分实现 | Rust 可按输入顺序创建 visible、hidden 和 very-hidden 工作表,但尚不能在一次调用中接受异构 Serde 行类型。 |
37-
| 修改现有工作簿 | 部分实现 | Rust 可原子 append、严格 replace、rename 或修改 worksheet visibility,并保留无关 package part 与 worksheet identity。Copy-and-add 与 reorder 仍不支持。Rename 有意保留 formula/defined-name 文本而不修复旧名称引用,与 .NET `AlterSheet` 一致;visibility 会拒绝隐藏最后一张 visible sheet。 |
37+
| 修改现有工作簿 | 部分实现 | Rust 可原子 append、严格 replace、rename、修改 visibility 或 reorder worksheet,并保留无关 package part 与 worksheet identity。Copy-and-add 仍不支持。Rename 保留 formula 文本;visibility 拒绝隐藏最后一张 visible sheet;reorder 会 remap active/view/local-name index,避免转移其归属。 |
3838
| 模板 | 部分实现 | Rust 可使用标量和单 row 数组填充 path/byte 模板并保留 package part。stream、分组、条件、参数化 sheet、`$=` 公式、公式引用调整和 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。 |
@@ -55,7 +55,7 @@ Rust 已支持动态及 Serde 强类型 XLSX 路径查询、闭区间 A1 范围
5555
| 表格 | `MiniExcel::query_table*`;Rust focused test 使用完全相同的 `TestQueryTable.xlsx` fixture(SHA-256 `04F719BF9F9E99D9B437A8FB32F8111FD92580A1D29ACAD10B6ED128C0564501`) | `OpenXmlImporter.QueryTableAsync`;`tests/MiniExcel.OpenXml.Tests/Tables/` |
5656
| Comments | `MiniExcel::get_comments*`;Rust focused test 使用 `TestCommentsAndNotes.xlsx`(SHA-256 `3A855CE896ED62DC27C91797432DD89EE081F07CD03AB05BF1B0CD745543A3FC`) | `OpenXmlImporter.RetrieveCommentsAsync`;`tests/MiniExcel.OpenXml.Tests/Comments/` |
5757
| DataReader/DataTable | Rust iterator 与 borrowed visitor 是原生抽象;不计划逐字复制 .NET tabular adapter | `OpenXmlImporter.GetDataReader`、`GetAsyncDataReader`、`QueryAsDataTableAsync`;`tests/MiniExcel.OpenXml.Tests/DataReader/` |
58-
| 多工作表与工作簿修改 | Writer 可创建多个工作表;现有 workbook 支持 append、严格 replacement、原子 rename 与原子 visibility mutation,并保留 package | `OpenXmlExporter.InsertSheetAsync`、`CopyAndAddSheetAsync`、`AlterSheetAsync`;`MultipleSheets/` 与 `AlterSheets/` 测试 |
58+
| 多工作表与工作簿修改 | Writer 可创建多个工作表;现有 workbook 支持 append、严格 replacement,并以原子 package preservation 覆盖全部独立 `AlterSheet` metadata 操作 | `OpenXmlExporter.InsertSheetAsync`、`CopyAndAddSheetAsync`、`AlterSheetAsync`;`MultipleSheets/` 与 `AlterSheets/` 测试 |
5959
| 模板/图片/合并 | 已实现基础模板填充;高级指令与 authoring 仍延期 | `src/MiniExcel.OpenXml/Api/OpenXmlTemplater.cs`;`tests/MiniExcel.OpenXml.Tests/Templates/` |
6060
| CSV/转换 | `MiniExcel::query_csv*`、`save_csv*` 与 `append_csv*`;Rust 测试使用完全相同的 `TestHeader.csv`(`6C2FC27FCA2876F1ECCA17061B8EE23E133ECDB726F8E0B84167E58D86234432`)和 GB2312(`BA8A2505AB271D5575C58CC1FCBE5A5002CEB9E2F43CB95412246E25A50E8B5A`)fixture | `src/MiniExcel.Csv/Api/`;`src/MiniExcel/MiniExcelConverter.cs`;`tests/MiniExcel.Csv.Tests/` |
6161
| 映射 | 仅 Serde 映射 | `src/MiniExcel.Core/Attributes/MiniExcelColumnAttribute.cs`;`src/MiniExcel.OpenXml.FluentMapping/`;映射测试 |
@@ -68,7 +68,7 @@ Rust 已支持动态及 Serde 强类型 XLSX 路径查询、闭区间 A1 范围
6868

6969
1. **Async export/template API**:延续 runtime-neutral producer/cancellation 模式,不把 blocking ZIP 工作描述成 async I/O。
7070
2. **高级模板与 Fluent Mapping**:通过独立兼容里程碑增加分组/条件模板、参数化 sheet 和 mapping。
71-
3. **剩余 workbook edit**:copy/add 与 reorder 需要各自的 preservation contract。
71+
3. **剩余 workbook edit**:copy-and-add 需要 relationship closure cloning contract。
7272

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

0 commit comments

Comments
 (0)