|
| 1 | +<div align="center"> |
| 2 | + |
| 3 | +# MiniExcel para Rust |
| 4 | + |
| 5 | +[English](README.md) | [简体中文](README.zh-CN.md) | [繁體中文](README.zh-TW.md) | [Français](README.fr.md) | [日本語](README.ja.md) |
| 6 | + |
| 7 | +[](https://crates.io/crates/miniexcel) |
| 8 | +[](https://crates.io/crates/miniexcel) |
| 9 | +[](https://docs.rs/miniexcel) |
| 10 | +[](https://github.com/mini-software/MiniExcel-Rust/actions/workflows/rust.yml) |
| 11 | +[](https://github.com/mini-software/MiniExcel-Rust) |
| 12 | +[](LICENSE) |
| 13 | + |
| 14 | +**Procesamiento XLSX y CSV rápido, con memoria acotada.** |
| 15 | + |
| 16 | +</div> |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +<div align="center"> |
| 21 | + |
| 22 | +Este proyecto forma parte de la familia [MiniExcel](https://github.com/mini-software/MiniExcel) y usa la biblioteca .NET como referencia de compatibilidad. |
| 23 | + |
| 24 | +</div> |
| 25 | + |
| 26 | +--- |
| 27 | + |
| 28 | +<div align="center"> |
| 29 | + |
| 30 | +**[Abrir Browser Lab](https://mini-software.github.io/MiniExcel-Rust/)** para inspeccionar o generar XLSX localmente. Los datos permanecen en el navegador. |
| 31 | + |
| 32 | +</div> |
| 33 | + |
| 34 | +--- |
| 35 | + |
| 36 | +## Introducción |
| 37 | + |
| 38 | +MiniExcel para Rust es una biblioteca de lectura/escritura XLSX y CSV con streaming de memoria acotada, Serde, análisis y exportaciones RAG. |
| 39 | + |
| 40 | +## Instalación |
| 41 | + |
| 42 | +```bash |
| 43 | +cargo add miniexcel |
| 44 | +``` |
| 45 | + |
| 46 | +Requiere Rust 1.85.0 o posterior. |
| 47 | + |
| 48 | +## Inicio Rápido |
| 49 | + |
| 50 | +```rust |
| 51 | +use miniexcel::MiniExcel; |
| 52 | + |
| 53 | +for row in MiniExcel::query("book.xlsx")? { |
| 54 | + println!("{:?}", row?["A"]); |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +```rust |
| 59 | +use miniexcel::{CellValue, DynamicRow, MiniExcel}; |
| 60 | + |
| 61 | +let mut row = DynamicRow::new(); |
| 62 | +row.insert("Name".into(), CellValue::String("MiniExcel".into())); |
| 63 | +MiniExcel::save_as("book.xlsx", &[row])?; |
| 64 | +``` |
| 65 | + |
| 66 | +Ejecuta los ejemplos incluidos con tus propios archivos: |
| 67 | + |
| 68 | +```bash |
| 69 | +cargo run -p miniexcel --example read -- book.xlsx |
| 70 | +cargo run -p miniexcel --example write -- output.xlsx |
| 71 | +cargo run -p miniexcel --example rag_export -- book.xlsx |
| 72 | +``` |
| 73 | + |
| 74 | +## Flujos De Trabajo Comunes |
| 75 | + |
| 76 | +Todas las API devuelven `miniexcel::Result`. Los fragmentos siguientes se |
| 77 | +pueden colocar dentro de `fn main() -> miniexcel::Result<()>`. Los ejemplos |
| 78 | +tipados requieren `cargo add serde --features derive`; el ejemplo de template |
| 79 | +también requiere `cargo add serde_json`. |
| 80 | + |
| 81 | +### Seleccionar Una Worksheet Y Un Rango |
| 82 | + |
| 83 | +`query()` no usa encabezados por defecto y emplea las letras de columna de |
| 84 | +Excel como claves. Usa `HeaderMode::FirstRow` si la primera fila seleccionada |
| 85 | +contiene nombres. Las celdas inicial y final están incluidas. |
| 86 | + |
| 87 | +```rust |
| 88 | +use miniexcel::{HeaderMode, MiniExcel, ReadOptions}; |
| 89 | + |
| 90 | +let options = ReadOptions::new() |
| 91 | + .with_sheet_name("Data") |
| 92 | + .with_header_mode(HeaderMode::FirstRow) |
| 93 | + .with_start_cell("A1".parse()?) |
| 94 | + .with_end_cell("F100".parse()?) |
| 95 | + .with_ignore_empty_rows(true); |
| 96 | + |
| 97 | +for row in MiniExcel::query_with_options("book.xlsx", &options)? { |
| 98 | + let row = row?; |
| 99 | + println!("{:?}", row["Name"]); |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +El iterador posee un worker acotado. Si se detiene antes, por ejemplo con |
| 104 | +`.take(10)`, al descartarlo también se detiene el resto de la consulta por path. |
| 105 | + |
| 106 | +### Deserializar Filas Tipadas Con Serde |
| 107 | + |
| 108 | +Las consultas tipadas tratan por defecto la primera fila seleccionada como |
| 109 | +encabezados y realizan el mapping fila por fila. |
| 110 | + |
| 111 | +```rust |
| 112 | +use miniexcel::MiniExcel; |
| 113 | + |
| 114 | +#[derive(Debug, serde::Deserialize)] |
| 115 | +#[serde(rename_all = "PascalCase")] |
| 116 | +struct Release { |
| 117 | + name: String, |
| 118 | + version: u32, |
| 119 | +} |
| 120 | + |
| 121 | +for release in MiniExcel::query_as::<Release>("book.xlsx")? { |
| 122 | + let release = release?; |
| 123 | + println!("{} {}", release.name, release.version); |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +Usa `miniexcel::serde_helpers` para convertir estrictamente fechas y horas |
| 128 | +seriales de Excel a tipos `chrono`. |
| 129 | + |
| 130 | +### Crear Un Workbook Desde Filas Serde |
| 131 | + |
| 132 | +```rust |
| 133 | +use miniexcel::{MiniExcel, WriteOptions}; |
| 134 | + |
| 135 | +#[derive(serde::Serialize)] |
| 136 | +#[serde(rename_all = "PascalCase")] |
| 137 | +struct Release<'a> { |
| 138 | + name: &'a str, |
| 139 | + version: u32, |
| 140 | +} |
| 141 | + |
| 142 | +let rows = [ |
| 143 | + Release { name: "MiniExcel", version: 1 }, |
| 144 | + Release { name: "MiniExcel Rust", version: 4 }, |
| 145 | +]; |
| 146 | +let options = WriteOptions::new() |
| 147 | + .with_sheet_name("Releases") |
| 148 | + .with_auto_width(true); |
| 149 | +MiniExcel::save_as_serialized_with_options("releases.xlsx", &rows, &options)?; |
| 150 | +``` |
| 151 | + |
| 152 | +Las API Save crean un workbook y rechazan un path de salida existente salvo que |
| 153 | +se indique explícitamente `with_overwrite_file(true)`. |
| 154 | + |
| 155 | +### Modificar Atómicamente Un Workbook Existente |
| 156 | + |
| 157 | +```rust |
| 158 | +use miniexcel::{InsertOptions, MiniExcel}; |
| 159 | + |
| 160 | +let inserted = MiniExcel::insert_serialized( |
| 161 | + "book.xlsx", |
| 162 | + &rows, |
| 163 | + &InsertOptions::new().with_sheet_name("Archive"), |
| 164 | +)?; |
| 165 | +MiniExcel::rename_sheet("book.xlsx", "Archive", "History")?; |
| 166 | +MiniExcel::reorder_sheet("book.xlsx", "History", 0)?; |
| 167 | +println!("inserted {inserted} rows"); |
| 168 | +``` |
| 169 | + |
| 170 | +Las mutaciones por path bloquean, reescriben y validan el workbook antes de |
| 171 | +reemplazarlo atómicamente. Un nombre de worksheet existente se rechaza por |
| 172 | +defecto; selecciona `ExistingSheetPolicy::Replace` de forma explícita para |
| 173 | +reemplazarla. |
| 174 | + |
| 175 | +### Rellenar Un Template XLSX |
| 176 | + |
| 177 | +Coloca marcadores como `{{title}}`, `{{items.name}}` y `{{items.score}}` en el |
| 178 | +workbook template. Una lista expande su fila de plantilla. |
| 179 | + |
| 180 | +```rust |
| 181 | +use miniexcel::{MiniExcel, TemplateOptions}; |
| 182 | +use serde_json::json; |
| 183 | + |
| 184 | +MiniExcel::save_as_template( |
| 185 | + "report.xlsx", |
| 186 | + "template.xlsx", |
| 187 | + &json!({ |
| 188 | + "title": "Quarterly report", |
| 189 | + "items": [ |
| 190 | + { "name": "Ada", "score": 10 }, |
| 191 | + { "name": "Linus", "score": 20 } |
| 192 | + ] |
| 193 | + }), |
| 194 | + &TemplateOptions::new(), |
| 195 | +)?; |
| 196 | +``` |
| 197 | + |
| 198 | +### Exportar Chunks RAG Con Origen Trazable |
| 199 | + |
| 200 | +```rust |
| 201 | +use miniexcel::{HeaderMode, MiniExcel, RagExportOptions, ReadOptions}; |
| 202 | + |
| 203 | +let read = ReadOptions::new().with_header_mode(HeaderMode::FirstRow); |
| 204 | +let rag = RagExportOptions::new().with_chunk_rows(25).with_max_rows(500); |
| 205 | +let mut export = MiniExcel::export_rag("book.xlsx", &read, &rag)?; |
| 206 | + |
| 207 | +for chunk in export.by_ref() { |
| 208 | + let chunk = chunk?; |
| 209 | + println!("{} {}", chunk.chunk_id(), chunk.data_range()); |
| 210 | +} |
| 211 | +println!("source SHA-256: {}", export.manifest().source_sha256()); |
| 212 | +``` |
| 213 | + |
| 214 | +Cada chunk conserva la identidad de worksheet/rango, direcciones A1, valores |
| 215 | +tipados en caché, texto de fórmulas, style IDs y number formats. Las worksheets |
| 216 | +ocultas requieren autorización explícita. Consulta el |
| 217 | +[contrato RAG](docs/analytics-and-rag.md) para la salida JSONL y Markdown en |
| 218 | +streaming. |
| 219 | + |
| 220 | +### Usar El CLI Del Repositorio |
| 221 | + |
| 222 | +El CLI es una herramienta local del workspace y no se publica como crate |
| 223 | +independiente. |
| 224 | + |
| 225 | +```bash |
| 226 | +cargo run -p miniexcel-cli -- sheets book.xlsx |
| 227 | +cargo run -p miniexcel-cli -- query book.xlsx --sheet Data --header --start-cell A1 --end-cell F100 --format jsonl |
| 228 | +cargo run -p miniexcel-cli -- rag-export book.xlsx --header --chunk-rows 25 --format both --output-prefix ./out/book |
| 229 | +``` |
| 230 | + |
| 231 | +### Elegir Una Forma De I/O |
| 232 | + |
| 233 | +| Entrada o salida | API principales | Comportamiento de memoria | |
| 234 | +| --- | --- | --- | |
| 235 | +| Path de archivo | `query*`, `query_as*`, `save_as*`, `insert*` | Pipeline de filas acotado; las shared strings grandes pueden usar un índice en disco | |
| 236 | +| Bytes XLSX | `query_bytes`, `save_as_bytes`, `visit_rag_chunks_from_bytes` | Los bytes del workbook permanecen en memoria | |
| 237 | +| Streams prestados | `visit_rows_from_reader`, `save_as_to_writer` | El llamador conserva la propiedad del stream | |
| 238 | +| Navegador | `miniexcel-wasm` y [Browser Lab](https://mini-software.github.io/MiniExcel-Rust/) | WebAssembly local; los bytes cargados y las descargas completas usan memoria del navegador | |
| 239 | + |
| 240 | +Hay más programas ejecutables en [`miniexcel/examples`](miniexcel/examples). |
| 241 | +Consulta la [matriz de compatibilidad](docs/compatibility.md) antes de depender |
| 242 | +de la edición de workbooks, templates, fórmulas o formato. |
| 243 | + |
| 244 | +## Capacidades |
| 245 | + |
| 246 | +- Consultas dinámicas, tipadas, estructuradas, Table y CSV con memoria acotada. |
| 247 | +- API por path, bytes y reader/writer prestado. |
| 248 | +- Lectura/escritura Serde, helpers de fecha/hora y mapping exacto de cells. |
| 249 | +- Creación multi-worksheet, opciones de formato y visibilidad. |
| 250 | +- Insert/replace, rename, reorder, copy y visibilidad atómicos de worksheets. |
| 251 | +- Templates, bloques condicionales/agrupados y combinación mediante markers. |
| 252 | +- Análisis agrupados en streaming con límites explícitos. |
| 253 | +- Exportaciones JSONL y Markdown con direcciones de origen para LLM/RAG. |
| 254 | +- Streams async opcionales e independientes del runtime; ZIP/XML/filesystem siguen siendo bloqueantes. |
| 255 | + |
| 256 | +## Semántica Clave |
| 257 | + |
| 258 | +- Las consultas por path transmiten el XML sin conservar todas las filas. |
| 259 | +- La worksheet predeterminada es la primera, no la pestaña activa. |
| 260 | +- La lectura devuelve valores de fórmula en caché; la lectura estructurada también expone texto y formatos. MiniExcel no calcula fórmulas. |
| 261 | +- Save crea un workbook y rechaza paths existentes por defecto; Insert modifica `.xlsx` atómicamente después de validarlo. |
| 262 | +- Las shared strings grandes pueden usar archivos temporales indexados; las consultas bytes/WASM las mantienen en memoria. |
| 263 | +- No compatible: `.xls`, `.xlsb`, `.ods`, macros, creación de imágenes, cálculo de fórmulas o sistema general de estilos. |
| 264 | + |
| 265 | +Consulta la [matriz de compatibilidad](docs/compatibility.md), el [contrato de análisis/RAG](docs/analytics-and-rag.md) y la [guía de migración Insert](docs/insert-v1-migration.md). |
| 266 | + |
| 267 | +## Benchmark De Rust Y .NET |
| 268 | + |
| 269 | +Coloca este repositorio junto a [.NET MiniExcel](https://github.com/mini-software/MiniExcel) y ejecuta: |
| 270 | + |
| 271 | +```powershell |
| 272 | +pwsh ./scripts/compare-dotnet-v1-rust.ps1 -DotNetRepository D:\git\MiniExcel |
| 273 | +``` |
| 274 | + |
| 275 | +El informe se escribe en `target/benchmarks/dotnet-v1-vs-rust.json`. Compara solo resultados de la misma máquina; consulta la [metodología](docs/dotnet-v1-query-benchmark.md). |
0 commit comments