Skip to content

Commit 33be429

Browse files
committed
Add OpenXML comment and note retrieval with .NET parity
1 parent 23bba2d commit 33be429

17 files changed

Lines changed: 1221 additions & 18 deletions

.github/workflows/rust.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,37 @@ jobs:
132132
--filter "FullyQualifiedName~MiniExcelOpenXmlTableTests"
133133
--verbosity minimal
134134
135+
comments-parity:
136+
strategy:
137+
matrix:
138+
os: [ubuntu-latest, windows-latest]
139+
runs-on: ${{ matrix.os }}
140+
steps:
141+
- uses: actions/checkout@v4
142+
- name: Checkout MiniExcel .NET
143+
uses: actions/checkout@v4
144+
with:
145+
repository: mini-software/MiniExcel
146+
ref: b9a76d7af62142e0e38545b6905b01a06e8d160e
147+
path: dotnet-source
148+
- name: Install Rust 1.85.0
149+
uses: dtolnay/rust-toolchain@1.85.0
150+
- uses: Swatinem/rust-cache@v2
151+
with:
152+
key: comments-parity-${{ matrix.os }}
153+
- name: Setup .NET
154+
uses: actions/setup-dotnet@v4
155+
with:
156+
dotnet-version: 10.0.x
157+
- name: Run Rust comments tests
158+
run: cargo +1.85.0 test -p miniexcel --test comments --locked
159+
- name: Run MiniExcel .NET comments tests
160+
run: >-
161+
dotnet test dotnet-source/tests/MiniExcel.OpenXml.Tests/MiniExcel.OpenXml.Tests.csproj
162+
--framework net10.0
163+
--filter "FullyQualifiedName~CommentsRetrievalTests"
164+
--verbosity minimal
165+
135166
msrv:
136167
runs-on: ubuntu-latest
137168
steps:

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ serde_json = "1.0.145"
3030
sha2 = "0.10.9"
3131
tempfile = "3.23.0"
3232
thiserror = "2.0.17"
33+
uuid = "1.18.1"
3334
wasm-bindgen = "0.2.127"
3435
zip = { version = "7.2.0", default-features = false, features = ["deflate"] }
3536

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,28 @@ the first worksheet is searched. Column names come from table metadata, the phys
222222
skipped unless `headerRowCount="0"`, and the complete declared range is returned, including totals
223223
rows. Path queries retain the existing bounded-memory two-pass worksheet pipeline.
224224

225+
### Comments And Notes
226+
227+
Read threaded comments and legacy notes without reading worksheet rows:
228+
229+
```rust
230+
let comments = MiniExcel::get_comments("book.xlsx", Some("Data"))?;
231+
232+
for thread in comments.threaded_comments() {
233+
println!("{}: {}", thread.cell(), thread.text());
234+
for reply in thread.replies() {
235+
println!(" {}", reply.text());
236+
}
237+
}
238+
```
239+
240+
`get_comments_from_bytes()` and `get_comments_from_reader()` provide the same metadata for
241+
in-memory and borrowed sources. Results include typed UUIDs and cell references, people, provider
242+
and user IDs, resolution state, local or offset timestamps, replies, and legacy note authors/text.
243+
Compatibility-shadow notes are suppressed only when their `tc={thread-id}` author marker and cell
244+
both match a threaded root; unrelated notes at the same cell remain visible. Comment metadata is
245+
materialized, while worksheet rows are never read.
246+
225247
## Typed Reading
226248

227249
```rust

README.zh-CN.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,27 @@ let rows = MiniExcel::query_table("book.xlsx", "SalesTable", Some("Data"))?
221221
Column name 来自 table metadata;除非 `headerRowCount="0"`,否则会跳过物理 header row;
222222
返回完整声明 range,包括 totals row。Path query 继续使用既有有界内存两遍 worksheet pipeline。
223223

224+
### Comments 与 Notes
225+
226+
可在不读取 worksheet row 的情况下读取 threaded comment 与 legacy note:
227+
228+
```rust
229+
let comments = MiniExcel::get_comments("book.xlsx", Some("Data"))?;
230+
231+
for thread in comments.threaded_comments() {
232+
println!("{}: {}", thread.cell(), thread.text());
233+
for reply in thread.replies() {
234+
println!(" {}", reply.text());
235+
}
236+
}
237+
```
238+
239+
`get_comments_from_bytes()``get_comments_from_reader()` 为内存和 borrowed source 提供
240+
相同 metadata。结果包含 typed UUID/cell reference、person、provider/user ID、resolved
241+
state、local 或 offset timestamp、reply,以及 legacy note author/text。只有 author marker
242+
`tc={thread-id}` 且 cell 同时匹配 threaded root 的 compatibility-shadow note 才会被
243+
抑制;同 cell 的无关 note 仍保留。Comment metadata 会物化,但不会读取 worksheet row。
244+
224245
## 类型化读取
225246

226247
```rust

docs/compatibility.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ The Rust MVP implements the smallest useful MiniExcel-style XLSX read/write surf
2323
| `serde_json` | 1.x | Query plans, analytics/RAG output, parity contracts, and CLI JSON | MIT OR Apache-2.0 | Checked with Rust 1.85 |
2424
| `sha2` | 0.10 | Streaming SHA-256 source identity for RAG manifests | MIT OR Apache-2.0 | Checked with Rust 1.85 |
2525
| `thiserror` | 2.x | Public error composition | MIT OR Apache-2.0 | Resolved by the workspace lockfile |
26+
| `uuid` | 1.x | Typed threaded-comment, reply, person, and legacy-note identifiers | MIT OR Apache-2.0 | Resolved by the workspace lockfile |
2627
| `zip` | 7.2 | Incremental worksheet entry decompression | MIT | Locked and checked with Rust 1.85 |
2728

2829
The latest `calamine 0.36` and `rust_xlsxwriter 0.97` require Rust 1.88. The MVP pins the preceding API lines so the declared Rust 1.85 MSRV is executable rather than aspirational.
@@ -43,6 +44,7 @@ The latest `calamine 0.36` and `rust_xlsxwriter 0.97` require Rust 1.88. The MVP
4344
| `GetSheetDimensions` | `MiniExcel::get_sheet_dimensions()` | Returns used ranges in workbook order with 1-based indices |
4445
| `GetColumns` | `MiniExcel::get_columns()` | Returns selected dynamic keys or an empty vector |
4546
| `QueryTable` | `query_table()` / `query_table_as()` / byte and borrowed-reader variants | Case-insensitive table-name lookup, metadata headers, and inclusive table bounds |
47+
| Retrieve comments and notes | `get_comments()` / bytes / borrowed-reader variants | Thread roots, replies, people, resolution/timestamps, and legacy notes |
4648
| `startCell` | `ReadOptions::with_start_cell()` | A1 start coordinate |
4749
| `IgnoreEmptyRows` | `ReadOptions::with_ignore_empty_rows()` | Defaults to `false` for MiniExcel compatibility |
4850
| `FillMergedCells` | `ReadOptions::with_fill_merged_cells()` | Defaults to `false`; applies to dynamic, typed, and byte queries |
@@ -172,6 +174,7 @@ The contract covers only the current common surface: dynamic/typed path queries,
172174
| `GetSheetInformations` ID/index/name/type/visibility/active | Implemented | Rust tests against .NET fixtures |
173175
| `GetSheetDimensions` | Implemented | Rust tests against .NET fixtures |
174176
| Named OpenXML `QueryTable` | Implemented | Rust and .NET focused tests against `TestQueryTable.xlsx` |
177+
| Threaded comments and legacy notes | Implemented | Rust and .NET focused tests against `TestCommentsAndNotes.xlsx` |
175178
| New-workbook `SaveAs`, including multiple sheets | Implemented and roundtrip-tested | Not yet |
176179
| Basic `SaveAsTemplate` scalar/list fill | Implemented and roundtrip-tested | Not yet |
177180
| Byte-array query/write for WASM | Implemented | Rust/browser tests |

docs/compatibility.zh-CN.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Rust MVP 在统一的 `MiniExcel` facade 后实现最小但实用的 MiniExcel
2323
| `serde_json` | 1.x | Query plan、分析/RAG 输出、等价契约和 CLI JSON | MIT OR Apache-2.0 | 已使用 Rust 1.85 检查 |
2424
| `sha2` | 0.10 | 为 RAG manifest 流式计算 SHA-256 源身份 | MIT OR Apache-2.0 | 已使用 Rust 1.85 检查 |
2525
| `thiserror` | 2.x | 公共 error 组合 | MIT OR Apache-2.0 | 由 workspace lockfile 解析 |
26+
| `uuid` | 1.x | Typed threaded-comment、reply、person 与 legacy-note identifier | MIT OR Apache-2.0 | 由 workspace lockfile 解析 |
2627
| `zip` | 7.2 | 增量 worksheet entry 解压 | MIT | 已锁定并使用 Rust 1.85 检查 |
2728

2829
最新的 `calamine 0.36``rust_xlsxwriter 0.97` 需要 Rust 1.88。MVP 固定使用前一条 API 版本线,使声明的 Rust 1.85 MSRV 可实际执行,而不只是目标。
@@ -43,6 +44,7 @@ Rust MVP 在统一的 `MiniExcel` facade 后实现最小但实用的 MiniExcel
4344
| `GetSheetDimensions` | `MiniExcel::get_sheet_dimensions()` | 按 workbook 顺序返回使用范围,index 从 1 开始 |
4445
| `GetColumns` | `MiniExcel::get_columns()` | 返回选中的动态 key,或空 vector |
4546
| `QueryTable` | `query_table()` / `query_table_as()` / byte 和 borrowed-reader variants | 大小写不敏感 table-name lookup、metadata header 与包含端点的 table bounds |
47+
| 读取 comments 与 notes | `get_comments()` / bytes / borrowed-reader variants | Thread root、reply、person、resolved/timestamp 与 legacy note |
4648
| `startCell` | `ReadOptions::with_start_cell()` | A1 起始坐标 |
4749
| `IgnoreEmptyRows` | `ReadOptions::with_ignore_empty_rows()` | 为兼容 MiniExcel,默认值为 `false` |
4850
| `FillMergedCells` | `ReadOptions::with_fill_merged_cells()` | 默认 `false`;适用于动态、类型化和 byte query |
@@ -172,6 +174,7 @@ Rust workflow 会在 Linux 和 Windows 上运行 Rust 契约。其 .NET parity j
172174
| `GetSheetInformations` ID/index/name/type/visibility/active | 已实现 | Rust 使用 .NET fixture 测试 |
173175
| `GetSheetDimensions` | 已实现 | Rust 使用 .NET fixture 测试 |
174176
| 命名 OpenXML `QueryTable` | 已实现 | Rust/.NET 使用 `TestQueryTable.xlsx` 的 focused test |
177+
| Threaded comments 与 legacy notes | 已实现 | Rust/.NET 使用 `TestCommentsAndNotes.xlsx` 的 focused test |
175178
| 新 workbook `SaveAs`(含多工作表) | 已实现并完成 roundtrip 测试 | 尚未 |
176179
| 基础 `SaveAsTemplate` 标量/列表填充 | 已实现并完成 roundtrip 测试 | 尚未 |
177180
| 用于 WASM 的字节数组 query/write | 已实现 | Rust/browser 测试 |

docs/dotnet-feature-gaps.md

Lines changed: 9 additions & 9 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 | `1e3167222142a86710e05269f9fcbbba11185d22` (`0.3.0`) |
11+
| MiniExcel-Rust | `23bba2d448bc4fab14baf750152a7c83cb6ececc` (`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).
@@ -38,12 +38,12 @@ Rust already implements dynamic and Serde-typed XLSX path queries, inclusive A1
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 | Missing | Dynamic/typed CSV query and save, append, columns, DataReader/DataTable, delimiter/newline/encoding/quoting configuration, and CSV/XLSX conversion. |
41-
| Comments and notes | Missing | Retrieve threaded comments, replies, people/authors, resolution state, timestamps, and legacy notes. |
41+
| 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. |
4242
| Fluent mapping | Missing | Address-based object mapping, formula/format mappings, collection start cells and spacing, nested collections, and mapped import/export/template APIs. |
4343
| Attribute-based mapping | Partial | Column index/name attributes, localized headers, formula metadata, custom dynamic formatters, field mapping, and dynamic column ordering/filtering remain. Serde covers rename, alias, defaults, skips, options, and custom serializers; `WriteOptions` covers width/hidden layout by final header name. |
4444
| Read configuration | Partial | Culture-aware conversion, buffer/fast modes, and some null/empty-string behavior. Merged-cell filling and shared-string disk caching are implemented. |
4545
| Write configuration and style | Partial | OOXML tables, shared versus inline strings, and broader cell styling remain. Rust exposes default/minimal cell style modes, header output/style, AutoFilter, right-to-left views, frozen rows/columns, bounded AutoWidth, body wrapping/alignment, and number formats. |
46-
| Sheet metadata/workflow | Partial | Table metadata, comment metadata, dynamic sheet aliases, class-level sheet selection, and traversing all sheets through one reader. Rust already covers names, order, dimensions, visibility, and active state. |
46+
| Sheet metadata/workflow | Partial | Dynamic sheet aliases, class-level sheet selection, and traversing all sheets through one reader remain. Rust covers names, order, dimensions, visibility, active state, table metadata, and comments/notes. |
4747
| Provider/package model | Different by design | .NET composes OpenXML, CSV, templating, and fluent-mapping providers. Rust has a single XLSX crate plus CLI and WASM adapters; those adapters do not replace the missing provider capabilities. |
4848

4949
## Evidence Map
@@ -52,23 +52,23 @@ Rust already implements dynamic and Serde-typed XLSX path queries, inclusive A1
5252
| --- | --- | --- |
5353
| Public read/write boundary | `miniexcel/src/facade.rs`, `miniexcel/src/options.rs` | `src/MiniExcel.OpenXml/Api/OpenXmlImporter.cs`, `OpenXmlExporter.cs` |
5454
| Tables | `MiniExcel::query_table*`; Rust focused tests use the exact `TestQueryTable.xlsx` fixture (SHA-256 `04F719BF9F9E99D9B437A8FB32F8111FD92580A1D29ACAD10B6ED128C0564501`) | `OpenXmlImporter.QueryTableAsync`; `tests/MiniExcel.OpenXml.Tests/Tables/` |
55+
| Comments | `MiniExcel::get_comments*`; Rust focused tests use `TestCommentsAndNotes.xlsx` (SHA-256 `3A855CE896ED62DC27C91797432DD89EE081F07CD03AB05BF1B0CD745543A3FC`) | `OpenXmlImporter.RetrieveCommentsAsync`; `tests/MiniExcel.OpenXml.Tests/Comments/` |
5556
| 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/` |
5657
| Multi-sheet and workbook edits | Writer creates multiple sheets; existing workbooks support append and strict replacement with package preservation and bounded explicit-schema producers | `OpenXmlExporter.InsertSheetAsync`, `CopyAndAddSheetAsync`, `AlterSheetAsync`; `tests/MiniExcel.OpenXml.Tests/MultipleSheets/` and `AlterSheets/` |
5758
| Templates/pictures/merges | Basic template fill implemented; advanced directives and authoring remain deferred | `src/MiniExcel.OpenXml/Api/OpenXmlTemplater.cs`; `tests/MiniExcel.OpenXml.Tests/Templates/` |
5859
| CSV/conversion | XLSX-only core | `src/MiniExcel.Csv/Api/`; `src/MiniExcel/MiniExcelConverter.cs`; `tests/MiniExcel.Csv.Tests/` |
5960
| Mapping | Serde mapping only | `src/MiniExcel.Core/Attributes/MiniExcelColumnAttribute.cs`; `src/MiniExcel.OpenXml.FluentMapping/`; mapping tests |
60-
| Comments | No public comment model/API | `OpenXmlImporter.RetrieveCommentsAsync`; `src/MiniExcel.OpenXml/Models/Comments.cs`; comment tests |
61+
| Comments | `MiniExcel::get_comments*`; Rust focused tests use `TestCommentsAndNotes.xlsx` (SHA-256 `3A855CE896ED62DC27C91797432DD89EE081F07CD03AB05BF1B0CD745543A3FC`) | `OpenXmlImporter.RetrieveCommentsAsync`; `src/MiniExcel.OpenXml/Models/Comments.cs`; comment tests |
6162
| Configuration/style | Narrow `ReadOptions` and `WriteOptions` | `MiniExcelBaseConfiguration`, `OpenXmlConfiguration`, `OpenXmlStyleOptions`; exporter tests |
6263

6364
The .NET APIs marked with `Async` also have generated synchronous counterparts through the repository's sync-version generation. The gap therefore concerns capability, not only method naming.
6465

6566
## Suggested Implementation Order
6667

67-
1. **Comments and notes**: focused OpenXML read features with clear public result models.
68-
2. **CSV provider**: keep a separate format boundary rather than conditionals inside the XLSX parser.
69-
3. **Async query/export/template APIs**: extend runtime-neutral producer/cancellation patterns without presenting blocking ZIP work as async I/O.
70-
4. **Advanced templates and Fluent Mapping**: add grouped/conditional templates, parametrized sheets, and mapping through separate compatibility milestones.
71-
5. **Remaining workbook edits**: copy/add, rename, reorder, and standalone visibility mutation require their own preservation contracts.
68+
1. **CSV provider**: keep a separate format boundary rather than conditionals inside the XLSX parser.
69+
2. **Async query/export/template APIs**: extend runtime-neutral producer/cancellation patterns without presenting blocking ZIP work as async I/O.
70+
3. **Advanced templates and Fluent Mapping**: add grouped/conditional templates, parametrized sheets, and mapping through separate compatibility milestones.
71+
4. **Remaining workbook edits**: copy/add, rename, reorder, and standalone visibility mutation require their own preservation contracts.
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

0 commit comments

Comments
 (0)