Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Everything above hands back cell *text*, which means the library formats every v
the way out. `parse_typed()` skips that entirely: you give it a schema, and the conversion happens
natively, straight into typed column buffers. On a 65,536 × 14 sheet it is ~8× faster than
`read_all_columnar()`, ~25× faster than `read_all()`, and faster than `polars.read_excel()` — see
[docs/NATIVE_BASELINE.md](../docs/NATIVE_BASELINE.md).
[Reading](#reading) below for the measured numbers.

```python
from excelreader import ColumnSpec, ColumnType
Expand Down
27 changes: 26 additions & 1 deletion rust/excelreader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ a byte slice. Note that format sniffing does not detect CSV - pass `XL_FORMAT_CS
### Arrow export (`arrow` feature)

```toml
excelreader = { version = "0.0.0", features = ["arrow"] }
excelreader = { version = "2.1", features = ["arrow"] }
```

```rust
Expand Down Expand Up @@ -133,6 +133,31 @@ sniff, so there is nothing to fall back on.
`write_sheet` walks the slice once and appends each field to its own buffer, monomorphized per
field. That transpose is the only copy it makes; `write_columns` pays nothing.

### Streaming writes

`write_sheet`/`write_columns` build the whole table in memory first. `writer_handle::WriterHandle` is
the row-by-row alternative, writing directly as each call arrives instead:

```rust
use excelreader::writer_handle::WriterHandle;

let mut handle = WriterHandle::open("out.xlsx", None)?;
handle.start_sheet("Summary")?;
handle.start_row()?;
handle.write_str(Some("Name"))?;
handle.write_i64(Some(42))?;
handle.end_row()?;
handle.end_sheet()?;
```

Call order mirrors the C ABI's `xl_writer_handle`: `open`/`open_with`/`open_memory`, then per sheet
`start_sheet..end_sheet`, each containing `start_row..end_row` with one `write_*` call per cell
(`None` writes a blank cell), left to right. A call out of order returns `Err` rather than
corrupting output. `open_memory` backs the handle with an in-memory buffer instead of a file; read
it out with `bytes()`. Dropping a `WriterHandle` closes and releases it, same as `Workbook` — call
`bytes()` (memory-backed) or reopen the path (file-backed) to observe the result rather than relying
on the drop for that.

## Bounds and panics

`TableView::get` returns `Option<T>` and is `None` outside `0..len()`. The `column_*` accessors used
Expand Down
27 changes: 19 additions & 8 deletions src/ExcelReader.Native/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
NativeAOT shared library exposing ExcelReader's readers over a C ABI, so non-.NET languages
(Python, C, C++, Go, Node) can read XLSX, XLSB, XLS and CSV without a .NET runtime.

- ABI reference: `include/excelreader.h`
- Full contract and rationale: `docs/NATIVE_BINDINGS_PLAN.md`, `docs/NATIVE_HARDENING_PLAN.md`
- ABI reference and full contract: `include/excelreader.h` (doc comments cover every export)
- Python binding: `python/`

## Build
Expand All @@ -23,17 +22,29 @@ Output lands in `bin/Release/net10.0/<rid>/publish/ExcelReader.Native.{dll,so,dy
| `RowBlob.cs` | Row serialization. |
| `include/excelreader.h` | Hand-written C header; keep in sync with `Exports.cs`. |

Writing is exposed through a single one-shot export, `xl_write_typed`, alongside the reading exports
above (`xl_open_file`, `xl_open_file_ex`, `xl_close`, `xl_sheet_count`, `xl_sheet_name`,
`xl_sheet_name_at`, `xl_is_date1904`, `xl_next_row`, `xl_read_all_blob`, `xl_next_row_decoded`,
`xl_free_row`, `xl_read_all_decoded`, `xl_free_rows`, `xl_parse_typed`, `xl_free_table`,
`xl_infer_schema`, `xl_free_schema`, `xl_last_error_ptr`, `xl_parse_arrow`). `xl_write_typed` takes an
Reading exports: `xl_open_file`, `xl_open_file_ex`, `xl_open_memory`, `xl_open_memory_ex`, `xl_close`,
`xl_sheet_count`, `xl_sheet_name`, `xl_sheet_name_at`, `xl_move_to_sheet`, `xl_is_date1904`,
`xl_next_row`, `xl_read_all_blob`, `xl_read_all_decoded`, `xl_free_rows`, `xl_parse_typed`,
`xl_free_table`, `xl_infer_schema`, `xl_free_schema`, `xl_last_error`, `xl_last_error_ptr`,
`xl_parse_arrow`.

Writing has two layers. The one-shot export, `xl_write_typed` (plus its in-memory twin
`xl_write_typed_to_memory`), takes a whole `xl_table` and writes it in a single call; it takes an
`xl_write_options*` that follows the exact same `struct_size` contract as `xl_open_options`: the
caller sets `options->struct_size = sizeof(xl_write_options)` before the call, and a mismatched value
is rejected with `XL_INVALID_ARGUMENT` before anything else is inspected. Phase 1 is single-sheet,
is rejected with `XL_INVALID_ARGUMENT` before anything else is inspected. It is single-sheet,
whole-table-in-memory, no styling beyond the temporal number formats — see `include/excelreader.h` for
the full contract.

The streaming alternative is `xl_writer_handle`: one sheet and one row open at a time, written
directly as each call arrives instead of building an `xl_table` up front. Call order is
`xl_open_write_handle`/`xl_open_write_handle_to_memory`, then per sheet `xl_start_sheet`..`xl_end_sheet`,
each containing `xl_start_row`..`xl_end_row` with one `xl_write_string`/`xl_write_int64`/
`xl_write_float64`/`xl_write_bool`/`xl_write_date`/`xl_write_time`/`xl_write_timestamp`/`xl_write_null`
call per cell, then `xl_close_write_handle`. `xl_write_handle_bytes` reads back a memory-backed
handle's bytes so far without releasing it. Both write paths release their buffers with
`xl_free_buffer` (`xl_table`/`xl_inferred_schema` keep their own `xl_free_table`/`xl_free_schema`).

## Consuming from C

Include `include/excelreader.h` (add `include/excelreader_arrow.h` too if you want the Arrow C Data
Expand Down
2 changes: 1 addition & 1 deletion tests/ExcelReader.NativeSmoke/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Two layers, both in `smoke.c`:
produces garbage values, and these assertions fail on the values.

See `smoke.c`'s top comment for why the library is loaded dynamically (`LoadLibrary`/`dlopen`)
instead of linked at build time, and `docs/NATIVE_HARDENING_PLAN.md` (task B) for the full rationale.
instead of linked at build time.

## Build and run

Expand Down
Loading