xlsxrb 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +71 -253
  4. data/Rakefile +2 -1
  5. data/docs/PEER_LIBRARIES.md +121 -0
  6. data/docs/assets/benchmark_results.svg +144 -0
  7. data/docs/assets/lsp_autocompletion.png +0 -0
  8. data/docs/assets/playground_preview.png +0 -0
  9. data/docs/visual/VisualGallery.md +183 -0
  10. data/docs/wasm/ruby.wasm +0 -0
  11. data/lib/xlsxrb/chart_builder.rb +333 -0
  12. data/lib/xlsxrb/elements/workbook.rb +2 -2
  13. data/lib/xlsxrb/ooxml/reader/listeners.rb +6450 -0
  14. data/lib/xlsxrb/ooxml/reader.rb +2 -6438
  15. data/lib/xlsxrb/ooxml/workbook_writer.rb +10 -10
  16. data/lib/xlsxrb/ooxml/worksheet_parser.rb +161 -116
  17. data/lib/xlsxrb/ooxml/worksheet_writer.rb +5 -1
  18. data/lib/xlsxrb/stream_row.rb +5 -3
  19. data/lib/xlsxrb/stream_sheet.rb +125 -0
  20. data/lib/xlsxrb/stream_writer.rb +1646 -0
  21. data/lib/xlsxrb/version.rb +1 -1
  22. data/lib/xlsxrb/workbook_builder.rb +309 -0
  23. data/lib/xlsxrb/worksheet_builder.rb +691 -0
  24. data/lib/xlsxrb.rb +238 -2968
  25. data/sig/generated/xlsxrb/chart_builder.rbs +269 -0
  26. data/sig/generated/xlsxrb/ooxml/reader/listeners.rbs +1074 -0
  27. data/sig/generated/xlsxrb/ooxml/reader.rbs +0 -1067
  28. data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +3 -3
  29. data/sig/generated/xlsxrb/stream_row.rbs +3 -2
  30. data/sig/generated/xlsxrb/stream_sheet.rbs +100 -0
  31. data/sig/generated/xlsxrb/stream_writer.rbs +954 -0
  32. data/sig/generated/xlsxrb/workbook_builder.rbs +146 -0
  33. data/sig/generated/xlsxrb/worksheet_builder.rbs +349 -0
  34. data/sig/generated/xlsxrb.rbs +103 -1563
  35. metadata +17 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9930be8e04f9dee4e380c4789398da0b6a95b0c54d2a157265a2d1ee14340132
4
- data.tar.gz: '086f0abccab70ef69d1e885b9f8d0ec69a77e81e3067fa718764c24c0f37a13f'
3
+ metadata.gz: f865582e17bc03ede4f300f348f6166a666d82cd088f42a501a6518979a7b999
4
+ data.tar.gz: 25ba8105a6ca0318aa9bb4766a0de59da4818d18d226d212f1ec941fde3082c9
5
5
  SHA512:
6
- metadata.gz: 2097ccb3e8b3e5344e5d55c459db6b06c298b116074bcc41a06fafa034eda67bf0c46d7eaa58184aebc970812cfa91f68388c4a6b4e7d7069a45114507aa9e83
7
- data.tar.gz: d95b39449b15b2eec1415d1ae65e398cc2c006adefd19b54340727fc4ee2135322040a73bf0b3d2b0ca8f6d171153e88b8f64badb53b7fb841e9d7b8a87afd55
6
+ metadata.gz: 6e35bcdabcff60af9084c9860fcf1838abfb507065a2eabb1004f40f16ab6cf6f7a11d136bbd83e97f46f0e460b906601db1584ef23b00c05076bfc240417baf
7
+ data.tar.gz: 8d4597dad8fe327f4ad887934a7f2bc18fcd65b606ef265ab38bcef0a0ae2565dc09f9e7f04dd8dc5bf7c3403fadc2a265d3ce074c9fd623489d8f4004065802
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## [0.1.10] - 2026-08-19
2
+
3
+ ### Added
4
+ - **Peer Libraries Ecosystem Guide ([docs/PEER_LIBRARIES.md](docs/PEER_LIBRARIES.md))**: Introduced a respectful overview of the Ruby XLSX ecosystem featuring official self-descriptions, architectural tradeoffs (SST vs. Inline Strings, Streaming vs. In-Memory), and reproducible benchmarks across 9 popular Ruby XLSX gems.
5
+ - **Visual Assets & Screen Previews**:
6
+ - Embedded interactive WebAssembly Playground live demo preview in `README.md`.
7
+ - Added real-world Ruby LSP autocompletion and RBS type hint preview in `README.md`.
8
+ - Created accurate, neutral linear-scale SVG benchmark performance chart.
9
+ - **Enterprise-Grade Test Suite Expansion**:
10
+ - **ECMA-376 XSD Schema Validation**: Comprehensive XML schema validation suite ensuring strict element ordering and ISO/IEC 29500 compliance.
11
+ - **Contract Testing Suite**: Comprehensive parity verification between Streaming (`Xlsxrb.write`) and In-Memory (`Xlsxrb.build`) APIs.
12
+ - **Property-Based Testing (PBT)**: Expanded automated random generation tests for Row/Column invariants, styles, and edge cases.
13
+ - **Visual Regression Testing (VRT)**: Added new visual baselines for table styles, drawing shapes, and pivot tables.
14
+ - **E2E Interoperability Suite**: Added tests for namespace-prefixed XML streaming, conditional formatting, and table structures.
15
+
16
+ ### Fixed
17
+ - **WebAssembly (ruby.wasm) Compatibility**: Bundled `pp` and `prettyprint` standard libraries in `ruby.wasm` package to resolve REXML LoadError during browser-based evaluation.
18
+
19
+ ### Changed
20
+ - **Streamlined README**: Refactored README from 363 to 175 lines, focusing on core motivation, clean 4-column feature matrix, concise usage examples, and direct links to specialized documentation.
21
+
1
22
  ## [0.1.9] - 2026-08-18
2
23
 
3
24
  ### Added
data/README.md CHANGED
@@ -4,83 +4,61 @@ A Ruby library for reading and writing XLSX files with streaming support.
4
4
 
5
5
  ## Motivation
6
6
 
7
- The Ruby ecosystem already has great XLSX libraries. Each is well-designed for its purpose:
8
-
9
- | Library | Read | Write | Model | Write String Storage | Rich Formatting | Password Encryption |
10
- | ---------------------------------------------------------- | ---- | ----- | --------------------- | -------------------- | --------------- | :-----------------: |
11
- | [roo](https://rubygems.org/gems/roo) | ✅ | ❌ | Streaming | N/A (Read-only) | ⚠️ (Formulas, Basic styles) | ❌ |
12
- | [creek](https://rubygems.org/gems/creek) | ✅ | ❌ | Streaming | N/A (Read-only) | ❌ (Raw cell values) | ❌ |
13
- | [xsv](https://rubygems.org/gems/xsv) | ✅ | ❌ | Streaming | N/A (Read-only) | ❌ (Fast plain text) | ❌ |
14
- | [simple_xlsx_reader](https://rubygems.org/gems/simple_xlsx_reader) | ✅ | ❌ | Streaming | N/A (Read-only) | ❌ (Plain data & types) | ❌ |
15
- | [caxlsx / axlsx](https://rubygems.org/gems/caxlsx) | ❌ | ✅ | In-Memory | Inline (opt: SST) | ✅ (Charts, Styles) | ❌ |
16
- | [write_xlsx](https://rubygems.org/gems/write_xlsx) | ❌ | ✅ | In-Memory | SST | ✅ (Charts, Styles) | ❌ |
17
- | [xlsxtream](https://rubygems.org/gems/xlsxtream) | ❌ | ✅ | Streaming | Inline (opt: SST) | ❌ (Plain data only) | ❌ |
18
- | [fast_excel](https://rubygems.org/gems/fast_excel) | ❌ | ✅ | Streaming (C Ext) | SST (opt: Inline) | ⚠️ (Basic styles) | ❌ |
19
- | [rubyXL](https://rubygems.org/gems/rubyXL) | ✅ | ✅ | In-Memory | Inline / Direct | ✅ (DOM editing) | ❌ |
20
- | **[xlsxrb](https://github.com/niku/xlsxrb)** | | | **Streaming / In-Memory** | **SST** | ✅ **(Full Features)** | **(Standard & Agile)** |
21
-
22
- Each of these libraries makes deliberate tradeoffs, and they do so thoughtfully:
23
- * **Memory & Execution Model (Streaming vs In-Memory)**: Streaming libraries write or read rows sequentially on-the-fly to maintain a constant, low-memory footprint regardless of row count. In-memory libraries build complete document object trees, offering flexible random access and cell updates at the cost of high RAM usage on large sheets.
24
- * **String Storage Architecture (SST vs Inline Strings)**:
25
- * **SST (Shared String Table)**: De-duplicates strings into a central dictionary (`xl/sharedStrings.xml`), referencing them by numeric IDs in cell entries (`<c t="s"><v>0</v></c>`). This is standard Microsoft Excel behavior, producing significantly smaller raw XML documents (50–100% smaller) and reducing Excel's memory footprint when opening spreadsheets.
26
- * **Inline Strings**: Writes text directly into cell payloads (`<c t="inlineStr"><is><t>...</t></is></c>`). Bypassing the dictionary enables blazing-fast raw throughput for simple data exports, but inflates uncompressed XML size and limits advanced formatting (e.g. styling, cell merges, charts).
27
-
28
- Traditionally, attempting to build a "complete package" that offers both reading and writing, rich features, high performance, strict compatibility, and comprehensive documentation presents an inherent open-source challenge: the cumulative maintenance overhead often exceeds the capacity of individual human maintainers.
29
-
30
- `xlsxrb` is born from a different premise. We believe that Advanced Agentic AI (AI Coders) can help manage this maintenance demand. By utilizing AI agents to automate rigorous E2E testing, visual regression testing, specification compliance checks, and documentation updates, we can reconcile these competing engineering requirements. This allows us to build and continuously maintain a feature-rich, high-performance, and deeply compatible "all-in-one" XLSX library that remains sustainable for the long run.
7
+ The Ruby ecosystem already has great XLSX libraries, each designed for specific tradeoffs:
8
+
9
+ | Library | Read | Write | Streaming | In-Memory |
10
+ | :--- | :---: | :---: | :---: | :---: |
11
+ | [roo](https://rubygems.org/gems/roo) | ✅ | ❌ | | ❌ |
12
+ | [creek](https://rubygems.org/gems/creek) | ✅ | ❌ | | ❌ |
13
+ | [xsv](https://rubygems.org/gems/xsv) | ✅ | ❌ | | ❌ |
14
+ | [simple_xlsx_reader](https://rubygems.org/gems/simple_xlsx_reader) | ✅ | ❌ | | ❌ |
15
+ | [caxlsx / axlsx](https://rubygems.org/gems/caxlsx) | ❌ | ✅ | | ✅ |
16
+ | [write_xlsx](https://rubygems.org/gems/write_xlsx) | ❌ | ✅ | | ✅ |
17
+ | [xlsxtream](https://rubygems.org/gems/xlsxtream) | ❌ | ✅ | | ❌ |
18
+ | [fast_excel](https://rubygems.org/gems/fast_excel) | ❌ | ✅ | | ❌ |
19
+ | [rubyXL](https://rubygems.org/gems/rubyXL) | ✅ | ✅ | | ✅ |
20
+ | **[xlsxrb](https://github.com/niku/xlsxrb)** | **✅** | **✅** | **✅** | **✅** |
21
+
22
+ Each of these libraries makes deliberate architectural choices:
23
+ * **Streaming Model**: Writes or reads rows sequentially on-the-fly to maintain a constant $O(1)$, low-memory footprint regardless of dataset size.
24
+ * **In-Memory Model**: Builds a complete document object model, offering flexible random access, cell updates, and document templates at the cost of memory usage on large spreadsheets.
25
+
26
+ Traditionally, maintaining an all-in-one gem that offers both reading and writing across both streaming and in-memory models, alongside rich OOXML features, high performance, and strict compatibility, presents an inherent open-source challenge: the cumulative maintenance overhead often exceeds the capacity of individual human maintainers.
27
+
28
+ `xlsxrb` is built on a modern premise: **Advanced Agentic AI (AI Coders) can sustainably handle this maintenance demand.** By utilizing AI agents to automate end-to-end testing, visual regression testing, specification compliance verification, and documentation updates, `xlsxrb` delivers a fast, specification-compliant, and fully-featured XLSX library built for long-term sustainability.
31
29
 
32
30
  ### Design Principles
33
31
 
34
- - Minimal Dependencies (Zero Core Logic Dependencies): This library avoids heavy third-party XLSX/XML/ZIP gems, building all core parsing and writing features purely on the Ruby standard library and bundled gems (`zlib`, `rexml`, etc.). The only runtime dependency is `opentelemetry-api`, which provides zero-overhead observability. If you do not configure an OpenTelemetry SDK in your application, it acts as a lightweight no-op, keeping the runtime footprint extremely small.
35
- - Streaming Support: Both reading and writing are designed to handle large files efficiently by streaming data, keeping memory usage low and predictable.
36
- - Memory-Efficient XML Parsing: For reading operations, the library uses a custom byte-level streaming parser for worksheet rows (with targeted SAX parsing where appropriate) instead of DOM-based parsing, so entire XML documents are never loaded into memory. This enables true streaming capability for large spreadsheets.
37
- - Strict Microsoft Excel & OpenXML Interoperability: It is designed to closely follow the Microsoft Office implementation of the ISO 29500 standard. We ensure absolute bidirectional compatibility (both reading and writing) with Microsoft Excel by continuously validating files against the official Microsoft [Open XML SDK](https://github.com/dotnet/Open-XML-SDK).
38
- - AI-Agent Assisted Maintenance (Managing the Engineering Tradeoff): Building a library that is specification-compliant, rich in features, highly compatible, well-documented, and extremely fast typically presents a substantial maintenance challenge. `xlsxrb` addresses this inherent constraint by leveraging Advanced Agentic AI (AI Coders) to automate testing, feature expansion, and compatibility verification. This AI-assisted development process supports the project's long-term sustainability and high software quality.
39
- - Modern Ruby 4.0+: Built for the future with Ruby 4.0 or higher.
32
+ - **Minimal Dependencies**: Zero core logic dependencies. Built purely on the Ruby standard library and bundled gems (`zlib`, `rexml`, etc.). The only runtime dependency is `opentelemetry-api` (zero-overhead no-op when unconfigured).
33
+ - **Streaming Support**: True $O(1)$ constant memory streaming for both reading and writing massive spreadsheets.
34
+ - **Strict OpenXML Interoperability**: Fully compliant with ISO/IEC 29500 (ECMA-376) and validated continuously against the official Microsoft [Open XML SDK](https://github.com/dotnet/Open-XML-SDK).
35
+ - **AI-Assisted Sustainability**: Leveraging AI coding agents for automated quality assurance, E2E validation, and continuous feature expansion.
36
+ - **Modern Ruby**: Built for Ruby 4.0 or higher.
40
37
 
41
38
  ## Installation
42
39
 
43
40
  ```bash
44
41
  bundle add xlsxrb
42
+ # Or without Bundler: gem install xlsxrb
45
43
  ```
46
44
 
47
- Or without Bundler:
48
-
49
- ```bash
50
- gem install xlsxrb
51
- ```
52
-
53
- On Ruby 4+, some components used by `xlsxrb` and its test suite are shipped as bundled gems rather than built-in default libraries. When using Bundler, those bundled gems are resolved and installed in the usual way.
54
-
55
45
  ## Interactive Playground (WebAssembly)
56
46
 
57
- You can try `xlsxrb` directly in your browser without installing anything!
47
+ Try `xlsxrb` directly in your browser without installing anything!
58
48
 
59
49
  [👉 Try the Live Demo / Interactive Playground](https://niku.github.io/xlsxrb/docs/visual/VisualGallery_md.html)
60
50
 
61
- We have integrated an interactive WebAssembly-powered playground into our RDoc documentation. You can edit the code examples, run them in the browser sandbox, and download the generated `.xlsx` spreadsheets immediately.
51
+ <p align="center">
52
+ <img src="docs/assets/playground_preview.png" width="100%" alt="Interactive WebAssembly Playground with Live LibreOffice Preview"/>
53
+ </p>
62
54
 
63
- To launch the playground locally:
64
- 1. Generate the WebAssembly bundle and interactive RDoc:
65
- ```bash
66
- bundle exec rake doc
67
- ```
68
- 2. Start the local preview server:
69
- ```bash
70
- bundle exec rake doc:preview
71
- ```
72
- 3. Open [http://localhost:8000](http://localhost:8000) in your browser, hover over any code block, and click the "Live Preview" or "Download XLSX" buttons!
55
+ You can also browse 50+ rendered visual examples across all features in the [Visual Examples Gallery](docs/visual/VisualGallery.md).
73
56
 
74
57
  ## Usage
75
58
 
76
- `xlsxrb` supports both low-memory Streaming (recommended for large files) and full In-Memory document manipulation (for random-access cell modifications or updating existing sheets).
77
-
78
- For visual demonstrations of various features, check the [Visual Examples Gallery](docs/visual/VisualGallery.md).
59
+ ### Quick Start: Streaming (Recommended for Large Files)
79
60
 
80
- ### Quick Start: Streaming (Recommended)
81
-
82
- #### Streaming Write
83
- Generate large files efficiently with $O(1)$ constant memory by writing data directly to the stream:
61
+ #### Streaming Write ($O(1)$ Memory)
84
62
  ```ruby
85
63
  require "xlsxrb"
86
64
 
@@ -93,12 +71,10 @@ Xlsxrb.write("large_output.xlsx") do |writer|
93
71
  end
94
72
  ```
95
73
 
96
- #### Streaming Read
97
- Read rows and cells lazily one at a time with $O(1)$ constant memory (even for wide sheets with thousands of columns):
74
+ #### Streaming Read ($O(1)$ Memory)
98
75
  ```ruby
99
76
  require "xlsxrb"
100
77
 
101
- # Stream row-by-row and cell-by-cell (O(1) memory)
102
78
  Xlsxrb.read("large_file.xlsx") do |sheet|
103
79
  sheet.each_row do |row|
104
80
  row.each_cell do |cell|
@@ -106,39 +82,14 @@ Xlsxrb.read("large_file.xlsx") do |sheet|
106
82
  end
107
83
  end
108
84
  end
109
-
110
- # Or stream all cells across the sheet directly
111
- Xlsxrb.read("large_file.xlsx") do |sheet|
112
- sheet.each_cell do |cell|
113
- puts "#{cell.ref} = #{cell.value}"
114
- end
115
- end
116
85
  ```
117
86
 
118
- ### Ruby-Idiomatic Core APIs
119
-
120
- `xlsxrb` provides clean, standard Ruby interfaces (`Enumerable`, `Row#to_a`, `sheet["A1"]`) that feel natural to every Ruby developer without learning complex library-specific APIs:
87
+ ### In-Memory Building & Modifying
121
88
 
122
- #### Reading Spreadsheets
89
+ #### Creating & Exporting (Rails / Mailers)
123
90
  ```ruby
124
91
  require "xlsxrb"
125
92
 
126
- # 1. Read from file path, IO, or raw binary string (O(1) constant memory streaming)
127
- workbook = Xlsxrb.read("data.xlsx")
128
- sheet = workbook.sheets.first
129
-
130
- # 2. Extract sheet data into 2D array of values via standard Enumerable
131
- matrix = sheet.map(&:to_a) # => [["Name", "Score"], ["Alice", 100], ["Bob", 95]]
132
-
133
- # 3. Explicitly load into memory for coordinate random access (e.g. sheet["A1"])
134
- doc_sheet = sheet.load
135
- doc_sheet["A1"] # => #<Xlsxrb::Elements::Cell value="Name" ...>
136
- doc_sheet["A1"].value # => "Name"
137
- ```
138
-
139
- #### Writing & In-Memory Export (Rails & Mailers)
140
- ```ruby
141
- # Build workbook
142
93
  wb = Xlsxrb.build do |b|
143
94
  b.sheet("Report") do |s|
144
95
  s.row(["Metric", "Value"])
@@ -146,217 +97,84 @@ wb = Xlsxrb.build do |b|
146
97
  end
147
98
  end
148
99
 
149
- # Save directly to file:
100
+ # Save to file or get binary string for Rails send_data
150
101
  Xlsxrb.write("report.xlsx", wb)
151
-
152
- # Or export to binary string (ideal for Rails send_data & ActionMailer):
153
102
  binary_data = Xlsxrb.write(wb)
154
103
  ```
155
104
 
156
- ### In-Memory Building & Modifying
157
-
158
- `xlsxrb` provides a powerful, immutable-by-default API for modifying existing Excel files or building templates in-memory.
159
-
160
105
  #### Modifying an Existing File
161
- You can update specific cells or sheets using the functional `Xlsxrb.modify` API, which yields the parsed `Elements::Workbook`.
162
-
163
106
  ```ruby
164
107
  require "xlsxrb"
165
108
 
166
- # Create a template.xlsx for this example
167
- Xlsxrb.build { |builder| builder.sheet("Invoice") }.write("template.xlsx")
168
-
169
109
  Xlsxrb.modify("template.xlsx", "output.xlsx") do |workbook|
170
110
  workbook.update_sheet("Invoice") do |sheet|
171
- # Update specific cells (returns updated sheet)
172
- sheet = sheet.update_cell("C4", value: "INV-10042")
173
- sheet = sheet.update_cell("C5", value: Date.today)
174
-
175
- # Or append new rows
176
- sheet.with(rows: sheet.rows + [
177
- Xlsxrb::Elements::Row.new(index: sheet.rows.size, cells: [])
178
- ])
111
+ sheet.update_cell("C4", value: "INV-10042")
112
+ .update_cell("C5", value: Date.today)
179
113
  end
180
114
  end
181
115
  ```
182
116
 
183
- #### Hash & Range Styling (Syntactic Sugar)
184
- You can directly apply inline styles or use Ranges for multiple columns without boilerplate:
117
+ ### Password Protection & Encryption ([MS-OFFCRYPTO])
185
118
 
186
- ```ruby
187
- Xlsxrb.build do |builder|
188
- # Use [] accessor for sheets
189
- builder["Report"].row(
190
- ["ID", "Name", "Score", "Rank"],
191
- # Apply 'header' style to first two columns, and bold inline style to the third
192
- styles: { 0..1 => "header", 2 => { font: { bold: true, color: "red" } } }
193
- )
194
-
195
- # Set multiple column widths at once using Ranges
196
- builder["Report"].column("A".."D", width: 15.0)
197
- end
198
- ```
199
-
200
- ### Password Protection & Document Encryption ([MS-OFFCRYPTO])
201
-
202
- `xlsxrb` natively supports reading and writing password-protected Excel files with zero external C-extension dependencies, fully compliant with **[MS-OFFCRYPTO]** (Standard Encryption & Agile Encryption) and **[MS-CFB]** (Compound File Binary Format):
119
+ Natively supports reading and writing encrypted XLSX files (Standard & Agile Encryption) with zero external C-extensions:
203
120
 
204
- #### Writing Password-Protected Spreadsheets
205
121
  ```ruby
206
- # 1. Streaming write with password protection
207
- Xlsxrb.write("confidential.xlsx", password: "SecretPassword123") do |writer|
208
- writer.sheet("Financials") do |sheet|
209
- sheet.row(["Account", "Balance"])
210
- sheet.row(["Assets", 5_000_000])
211
- end
212
- end
122
+ require "xlsxrb"
213
123
 
214
- # 2. In-Memory write with password protection
215
- wb = Xlsxrb.build do |b|
216
- b.sheet("Private") { |s| s.row(["Confidential", 100]) }
124
+ # Write password-protected file
125
+ Xlsxrb.write("confidential.xlsx", password: "SecretPassword123") do |writer|
126
+ writer.sheet("Financials") { |sheet| sheet.row(["Assets", 5_000_000]) }
217
127
  end
218
- encrypted_binary = Xlsxrb.write(wb, password: "SecretPassword123")
219
- ```
220
128
 
221
- #### Reading Password-Protected Spreadsheets
222
- ```ruby
223
- # 1. Read directly with password (O(1) constant memory streaming)
129
+ # Read password-protected file
224
130
  Xlsxrb.read("confidential.xlsx", password: "SecretPassword123") do |sheet|
225
131
  sheet.each_row { |row| puts row.cells.map(&:value) }
226
132
  end
227
-
228
- # 2. In-Memory load with password
229
- workbook = Xlsxrb.read("confidential.xlsx", password: "SecretPassword123").load
230
- puts workbook.sheets.first["A1"].value
231
-
232
- # Attempting to read without password or with invalid password raises clear exceptions:
233
- # Xlsxrb::EncryptedFileError -> Missing password
234
- # Xlsxrb::InvalidPasswordError -> Incorrect password
235
133
  ```
236
134
 
237
135
  ### IDE Autocompletion & Ruby LSP Support
238
136
 
239
- `xlsxrb` bundles a native **Ruby LSP Add-on** (`RubyLsp::Xlsxrb::Addon`) and full **RBS signatures**, enabling zero-configuration method autocompletion and rich Markdown documentation in VS Code and other LSP-enabled editors.
137
+ Includes a native **Ruby LSP Add-on** and full **RBS signatures** for zero-configuration method autocompletion and hover documentation in VS Code and other editors:
240
138
 
241
- Whether you use standard descriptive block variable names (`|stream_writer|`, `|sheet|`, `|workbook|`) or short names (`|wb|`, `|s|`), your editor will automatically provide complete method suggestions and parameter hints:
242
-
243
- ```ruby
244
- Xlsxrb.write("output.xlsx") do |writer| # or |wb|
245
- writer.sheet("Data") do |sheet| # or |s|
246
- sheet.row(["Product", "Price"], styles: :bold)
247
- sheet.auto_filter("A1:B100")
248
- end
249
- end
250
- ```
139
+ <p align="center">
140
+ <img src="docs/assets/lsp_autocompletion.png" width="100%" alt="Ruby LSP Autocompletion & Type Signature Hints in VS Code"/>
141
+ </p>
251
142
 
252
143
  ## Feature Support & ECMA-376 Compliance
253
144
 
254
- `xlsxrb` is designed for full interoperability and strict compliance with the ECMA-376 (Office Open XML) Transitional specification. It supports nearly all major spreadsheet features required for business reports:
255
-
256
- * Cells & Layout: Formulas, Hyperlinks, Merge Cells, Freeze & Split Panes, Page Setup (margins, headers/footers, scaling, gridlines).
257
- * Data & Controls: Auto Filters, Data Validations (dropdowns, range limits), Sheet Protection.
258
- * Formatting & Styling: Rich Text, Cell Tables, Conditional Formatting (color scales, data bars, icon sets).
259
- * Graphics & Charts: Embedded Images, Shapes & Drawings, Sparklines, Charts (Line, Bar, Pie, Area, Radar, Scatter).
260
- * Workbook Level: Defined Names, Print Areas, Workbook Protection, and Document Metadata (core, app, custom properties).
145
+ `xlsxrb` supports nearly all major business spreadsheet features:
146
+ * **Layout & Structure**: Formulas, Hyperlinks, Merge Cells, Freeze/Split Panes, Page Setup, Auto Filters, Data Validations, Sheet/Workbook Protection.
147
+ * **Styling & Media**: Rich Text, Cell Styles & Fills, Conditional Formatting (color scales, data bars), Embedded Images, Charts (Line, Bar, Pie, Radar, Scatter).
261
148
 
262
- For detailed specification references and policies, see [SPEC_SOURCES.md](docs/SPEC_SOURCES.md).
149
+ For full details, see [docs/SPEC_SOURCES.md](docs/SPEC_SOURCES.md).
263
150
 
264
151
  ## Benchmarks
265
152
 
266
- The following benchmarks measure the time, peak memory, and GC count required to process a 1,000,000 cells (100,000 rows × 10 columns) spreadsheet across popular Ruby Excel libraries. Each test is executed across 3 independent runs in isolated subprocesses; median values are reported along with the mean execution time.
267
-
268
- ### Write Performance (1,000,000 cells)
269
-
270
- | Library | Model | Write String Storage | Time (Median) | Time (Mean) | Peak Memory | GC Count |
271
- | ---------------------- | ----------- | -------------------- | ------------- | ----------- | ----------- | -------- |
272
- | xlsxtream 3.1.0 | Streaming | Inline String | 1.19 s | 1.20 s | 18.2 MB | 1072.0 |
273
- | xlsxrb (Streaming) | Streaming | SST (Shared) | 1.73 s | 1.65 s | 94.4 MB | 39.0 |
274
- | fast_excel 0.5.0 (C) | Streaming | SST (Shared) | 1.89 s | 1.89 s | 148.2 MB | 245.0 |
275
- | xlsxrb (In-Memory) | In-Memory | SST (Shared) | 3.84 s | 3.83 s | 278.3 MB | 32.0 |
276
- | write_xlsx 1.15.0 | In-Memory | SST (Shared) | 4.32 s | 4.34 s | 201.2 MB | 33.0 |
277
- | caxlsx 4.5.0 | In-Memory | Inline String | 5.15 s | 5.12 s | 188.6 MB | 23.0 |
278
- | rubyXL 3.4.38 | In-Memory | Inline String | 38.81 s | 37.82 s | 2186.8 MB | 103.0 |
279
-
280
- > **Note**: All libraries are evaluated in their **default, out-of-the-box configuration**. Under the same Microsoft Excel-standard Shared String Table (SST) architecture, Pure Ruby `xlsxrb` (Streaming: 1.73s, In-Memory: 3.84s) writes 1,000,000 cells faster than the C-extension `fast_excel` (1.89s) and in-memory gems like `write_xlsx` (4.32s) and `caxlsx` (5.15s).
153
+ Benchmark processing 1,000,000 cells (100,000 rows × 10 cols) across popular Ruby gems:
281
154
 
282
- ### Read Performance (1,000,000 cells)
155
+ <p align="center">
156
+ <img src="docs/assets/benchmark_results.svg" width="100%" alt="Ruby XLSX Performance Benchmarks"/>
157
+ </p>
283
158
 
284
- | Library | Model | Time (Median) | Time (Mean) | Peak Memory | GC Count |
285
- | ------------------------ | ----------- | ------------- | ----------- | ----------- | -------- |
286
- | xlsxrb (Streaming) | Streaming | 3.17 s | 3.22 s | 91.4 MB | 43.0 |
287
- | simple_xlsx_reader 5.1.0 | Streaming | 4.48 s | 4.45 s | 38.5 MB | 1669.0 |
288
- | xlsxrb (In-Memory) | In-Memory | 5.71 s | 5.89 s | 224.8 MB | 63.0 |
289
- | creek 2.6.3 | Streaming | 8.14 s | 8.02 s | 834.6 MB | 477.0 |
290
- | xsv 1.4.1 | Streaming | 14.61 s | 14.50 s | 76.1 MB | 2224.0 |
291
- | roo 3.0.0 | Streaming | 15.69 s | 13.36 s | 119.7 MB | 441.0 |
292
- | rubyXL 3.4.38 | In-Memory | 37.13 s | 40.35 s | 2537.6 MB | 146.0 |
159
+ For detailed metrics (peak memory, GC count, mean/median times) and architectural tradeoffs (SST vs. Inline Strings), see [docs/PEER_LIBRARIES.md](docs/PEER_LIBRARIES.md).
293
160
 
294
- ### Running the Benchmarks Locally (Reproducibility)
295
-
296
- The benchmark suite leverages [`bundler/inline`](https://bundler.io/v2.5/guides/bundler_in_a_single_file_ruby_script.html) to automatically manage and download all peer ecosystem gems without modifying the project's core `Gemfile` or requiring manual global `gem install` steps. Each library is executed in an isolated subprocess (`Bundler.with_unbundled_env`) across multiple runs with standard business dataset rows (integers, strings, floats, booleans, dates) to ensure clean memory and GC measurements without cross-contamination.
297
-
298
- To run the complete benchmark suite:
299
- ```bash
300
- ruby benchmark.rb 100000 10
301
- ```
161
+ To reproduce locally: `ruby benchmark.rb 100000 10`
302
162
 
303
- ## Security (Protection against CSV/Excel Injection)
163
+ ## Quality Assurance & Testing
304
164
 
305
- Unlike CSV files which lack type definitions and force Excel to guess types (often inadvertently executing strings starting with `=`), `.xlsx` files generated by `xlsxrb` are strictly typed.
165
+ Backed by an enterprise-grade QA architecture to guarantee absolute reliability:
166
+ * **Official Microsoft Open XML SDK Validation**: Validates generated OOXML structures against Microsoft's official SDK.
167
+ * **Visual Regression Testing (VRT)**: Headless LibreOffice Calc pixel-by-pixel rendering checks.
168
+ * **Contract & Round-Trip Tests**: Verifies parity between Streaming and In-Memory APIs and round-trip read/write accuracy.
169
+ * **Security & DoS Protection**: Formula injection mitigation and ZIP bomb protection.
306
170
 
307
- When you pass a Ruby `String` to `xlsxrb`, it explicitly writes it as a `String` (`t="s"`) into the OOXML file. Therefore, **even if a string starts with `=`, Excel will never evaluate it as a formula**. To write a formula, you must explicitly use `Xlsxrb::Elements::Formula.new`. This design completely mitigates CSV/Formula Injection vulnerabilities by default without requiring additional sanitization.
171
+ For full architectural details, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [docs/QUALITY_ASSURANCE.md](docs/QUALITY_ASSURANCE.md).
308
172
 
309
- ### External Link Updates (`update_links`)
173
+ ## Development & Contributing
310
174
 
311
- As an extra layer of "defense in depth", `xlsxrb` configures the workbook to **never automatically update external links** when opened (`updateLinks="never"`). This is intentionally set to `never` by default to prevent Excel from silently reaching out to external resources or executing DDE (Dynamic Data Exchange) links, which is a known vector for malware.
312
-
313
- If you absolutely need external links to update automatically, you can explicitly override this (though **it is highly discouraged due to security risks**):
314
-
315
- ```ruby
316
- Xlsxrb.write("file.xlsx") do |wb|
317
- # WARNING: Enabling this can expose users to malicious external reference vulnerabilities!
318
- wb.workbook_property(:update_links, "always")
319
- # ...
320
- end
321
- ```
322
-
323
- ## Testing & Quality Assurance
324
-
325
- To support reliability, compliance with the ECMA-376 specification, and consistent updates, `xlsxrb` is backed by a highly rigorous, enterprise-grade Quality Assurance (QA) and testing architecture.
326
-
327
- ### Multi-Tier Testing Strategy
328
- * **Round-Trip Testing**: Unit tests verify that every generated sheet can be reliably parsed back by the reader with identical content and styling.
329
- * **Contract Consistency**: Ensures semantic output consistency between the Streaming (`Xlsxrb.write`) and In-Memory (`Xlsxrb.build`) APIs.
330
- * **Property-Based Testing (PBT)**: Automatically generates random data to catch edge cases (e.g., huge numbers, special characters) preventing unexpected crashes.
331
- * **Concurrency Validation**: Thread and Ractor safety checks to guarantee no global variable pollution during parallel execution.
332
- * **Security & DoS Protection**: Hardened against malicious files, including memory exhaustion (ZIP Bombs) and infinite parsing loops.
333
-
334
- ### Strict Interoperability & Rendering
335
- * **Official Open XML SDK Validation (E2E)**: Every generated spreadsheet is structurally validated against the official Microsoft Open XML SDK to prevent file corruption warnings in Microsoft Excel.
336
- * **Visual Regression Testing (VRT)**: Spreadsheets are rendered via a headless LibreOffice Calc engine and compared pixel-by-pixel against visual baselines to catch subtle rendering regressions.
337
-
338
- ### Performance & Types
339
- * **Continuous Performance & Memory Validation**: Memory retention and processing throughput are profiled in CI on large datasets to prevent regressions and OOM leaks.
340
- * **Runtime Type Validation**: Strong dynamic typing using `RBS::Test` to ensure the library's types are perfectly sound at runtime.
341
-
342
- For a comprehensive breakdown of our QA matrix, see [docs/QUALITY_ASSURANCE.md](docs/QUALITY_ASSURANCE.md). For details on running tests locally, see [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md).
343
-
344
- ## Development
345
-
346
- We welcome contributions! The project is configured with a ready-to-use Dev Container to streamline local environment setup.
347
-
348
- For contribution guidelines, E2E testing policies, and the step-by-step development workflow (including how to run the Dev Container from your terminal), please refer to [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md).
349
-
350
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
351
-
352
- ## Contributing
353
-
354
- Bug reports and pull requests are welcome on GitHub at https://github.com/niku/xlsxrb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](CODE_OF_CONDUCT.md).
175
+ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for local setup (Dev Container support), testing workflows, and contribution guidelines.
355
176
 
356
177
  ## License
357
178
 
358
179
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
359
180
 
360
- ## Code of Conduct
361
-
362
- Everyone interacting in the Xlsxrb project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -295,7 +295,8 @@ task :wasm do
295
295
  # B. Copy necessary standard libraries dynamically from host's $LOAD_PATH
296
296
  stdlib_files = [
297
297
  "date.rb", "delegate.rb", "forwardable.rb", "securerandom.rb",
298
- "random/formatter.rb", "set.rb", "tempfile.rb", "tmpdir.rb", "fileutils.rb"
298
+ "random/formatter.rb", "set.rb", "tempfile.rb", "tmpdir.rb", "fileutils.rb",
299
+ "pp.rb", "prettyprint.rb"
299
300
  ]
300
301
  stdlib_files.each do |name|
301
302
  path = $LOAD_PATH.find { |p| File.exist?(File.join(p, name)) }
@@ -0,0 +1,121 @@
1
+ # Ruby XLSX Ecosystem & Peer Libraries
2
+
3
+ The Ruby ecosystem is fortunate to have a rich set of mature, well-engineered XLSX libraries. Each library represents deliberate architectural choices tailored for specific problem spaces.
4
+
5
+ This document provides a respectful overview of the peer libraries in the Ruby ecosystem, explains the underlying engineering tradeoffs (such as Streaming vs. In-Memory and Shared String Tables vs. Inline Strings), and shares comprehensive benchmark measurements.
6
+
7
+ ---
8
+
9
+ ## The Peer Libraries
10
+
11
+ | Library | I/O | Official Self-Description / Focus | Best Fit (In Our View) |
12
+ | :--- | :---: | :--- | :--- |
13
+ | **[roo](https://rubygems.org/gems/roo)** | `R` | *"Roo can access the contents of various spreadsheet files (Excelx, LibreOffice, OpenOffice, CSV)."* | Unified interface for reading across diverse spreadsheet formats. |
14
+ | **[creek](https://rubygems.org/gems/creek)** | `R` | *"A Ruby gem that streams and parses large Excel (xlsx and xlsm) files fast and efficiently."* | Streaming large spreadsheet uploads row-by-row with lightweight SAX parsing. |
15
+ | **[xsv](https://rubygems.org/gems/xsv)** | `R` | *"A fast and lightweight xlsx parser that provides nothing a CSV parser wouldn't."* | High-speed, CSV-like tabular data ingestion without styling overhead. |
16
+ | **[simple_xlsx_reader](https://rubygems.org/gems/simple_xlsx_reader)** | `R` | *"Read xlsx data the Ruby way"* — parses sheets into Ruby primitives with low memory. | Memory-conscious tabular data extraction directly into Ruby types. |
17
+ | **[caxlsx / axlsx](https://rubygems.org/gems/caxlsx)** | `W` | *"Excel OOXML (xlsx) with charts, styles, images and autowidth columns"* with full schema validation. | Generating rich, styled business reports with charts, images, and visual design. |
18
+ | **[write_xlsx](https://rubygems.org/gems/write_xlsx)** | `W` | Pure Ruby port of Perl's `Excel::Writer::XLSX` to create files in modern Excel 2007+ format. | Creating complex spreadsheets requiring exact Excel feature parity. |
19
+ | **[xlsxtream](https://rubygems.org/gems/xlsxtream)** | `W` | *"A streaming XLSX spreadsheet writer"* allowing very efficient writing of CSV-style data. | Ultra-fast, low-memory streaming exports of massive tabular datasets. |
20
+ | **[fast_excel](https://rubygems.org/gems/fast_excel)** | `W` | *"Ultra Fast Excel Writer"* — C-extension wrapper for `libxlsxwriter` with constant memory mode. | Maximum-throughput spreadsheet generation when C-extensions are available. |
21
+ | **[rubyXL](https://rubygems.org/gems/rubyXL)** | `RW` | *"Allows the parsing, creation, and manipulation of Microsoft Excel (.xlsx/.xlsm) Documents."* | Full document DOM inspection, in-memory cell modification, and template editing. |
22
+ | **[xlsxrb](https://github.com/niku/xlsxrb)** | `RW` | Pure Ruby library unifying streaming read/write ($O(1)$ memory) and in-memory manipulation with native encryption. | Unified reading, writing, template modification, and password encryption in pure Ruby. |
23
+
24
+
25
+ ---
26
+
27
+ ## Architectural Tradeoffs
28
+
29
+ Spreadsheet libraries must balance multiple competing dimensions: memory consumption, execution speed, formatting capabilities, and strict specification compliance.
30
+
31
+ ### 1. Memory & Execution Model: Streaming vs. In-Memory
32
+
33
+ ```
34
+ ┌─────────────────────────────────────────────────────────────┐
35
+ │ Execution Models │
36
+ ├──────────────────────────────┬──────────────────────────────┤
37
+ │ Streaming Model │ In-Memory Model │
38
+ ├──────────────────────────────┼──────────────────────────────┤
39
+ │ • Processes rows on-the-fly │ • Builds complete DOM tree │
40
+ │ • O(1) constant RAM footprint│ • Enables random cell access │
41
+ │ • Cannot seek backward │ • High RAM on large datasets │
42
+ │ • Ideal for batch exports/ETL│ • Ideal for templates/edits │
43
+ └──────────────────────────────┴──────────────────────────────┘
44
+ ```
45
+
46
+ * **Streaming Model** (`xlsxrb`, `xlsxtream`, `simple_xlsx_reader`, `roo`, `creek`, `xsv`):
47
+ Rows and cells are processed sequentially and flushed/discarded immediately. This keeps memory usage completely flat and predictable, regardless of whether the file has 10 rows or 1,000,000 rows. However, random access (e.g., modifying `cell("A1")` after writing row 100) is not possible.
48
+ * **In-Memory Model** (`xlsxrb`, `caxlsx`, `write_xlsx`, `rubyXL`):
49
+ The entire workbook structure is parsed into Ruby objects, providing complete flexibility to inspect, modify, insert, or reorder cells and worksheets. The tradeoff is that memory consumption scales with the number of cells.
50
+
51
+ ---
52
+
53
+ ### 2. String Storage Architecture: Shared String Table (SST) vs. Inline Strings
54
+
55
+ The OpenXML (ECMA-376) specification defines two ways to store text in cells:
56
+
57
+ ```xml
58
+ <!-- 1. Shared String Table (SST): Deduplicated dictionary reference -->
59
+ <c r="A1" t="s"><v>0</v></c>
60
+
61
+ <!-- 2. Inline String: Raw text payload inside the cell -->
62
+ <c r="A1" t="inlineStr"><is><t>Hello World</t></is></c>
63
+ ```
64
+
65
+ #### Shared String Table (SST)
66
+ * **How it works**: Strings across all worksheets are collected into a single central dictionary (`xl/sharedStrings.xml`). Cells only store numeric integer IDs pointing to dictionary entries.
67
+ * **Strengths**:
68
+ * **Smaller file footprint**: Deduplication significantly reduces the uncompressed XML size (typically 50% to 80% smaller for business datasets with repetitive categories, statuses, dates, and labels).
69
+ * **Standard Microsoft Excel behavior**: Excel defaults to SST. Opening SST-based spreadsheets in Excel consumes less memory and renders faster.
70
+ * **Rich Text & Shared Styles**: Supports rich text formatting within strings.
71
+ * **Tradeoff**:
72
+ * Writing requires managing a string table dictionary or making a multi-pass serialization, adding slight CPU overhead during generation.
73
+
74
+ #### Inline Strings
75
+ * **How it works**: Text is written directly into each `<c>` element (`<is><t>...</t></is>`) as the stream proceeds.
76
+ * **Strengths**:
77
+ * **Raw Throughput**: Bypassing string deduplication allows immediate row-by-row flushing with minimal CPU overhead (as demonstrated by `xlsxtream`).
78
+ * **Tradeoff**:
79
+ * Produces significantly larger raw XML files when strings repeat, and some third-party spreadsheet viewers or legacy tools have limited support for inline strings compared to SST.
80
+
81
+ ---
82
+
83
+ ## Detailed Benchmark Results
84
+
85
+ The following benchmarks evaluate processing **1,000,000 cells** (100,000 rows × 10 columns) containing standard business data (integers, strings, floats, booleans, and dates) across 3 isolated subprocess runs.
86
+
87
+ ### Write Performance (1,000,000 cells)
88
+
89
+ | Library | Version | Model | String Storage | Time (Median) | Time (Mean) | Peak Memory | GC Count |
90
+ | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
91
+ | **xlsxtream** | 3.1.0 | Streaming | Inline String | **1.19 s** | 1.20 s | **18.2 MB** | 1072.0 |
92
+ | **xlsxrb (Streaming)** | - | Streaming | SST (Shared) | **1.73 s** | 1.65 s | 94.4 MB | 39.0 |
93
+ | **fast_excel** | 0.5.0 | Streaming | SST (Shared) | 1.89 s | 1.89 s | 148.2 MB | 245.0 |
94
+ | **xlsxrb (In-Memory)** | - | In-Memory | SST (Shared) | 3.84 s | 3.83 s | 278.3 MB | 32.0 |
95
+ | **write_xlsx** | 1.15.0 | In-Memory | SST (Shared) | 4.32 s | 4.34 s | 201.2 MB | 33.0 |
96
+ | **caxlsx** | 4.5.0 | In-Memory | Inline String | 5.15 s | 5.12 s | 188.6 MB | 23.0 |
97
+ | **rubyXL** | 3.4.38 | In-Memory | Inline String | 38.81 s | 37.82 s | 2186.8 MB | 103.0 |
98
+
99
+ ### Read Performance (1,000,000 cells)
100
+
101
+ | Library | Version | Model | Time (Median) | Time (Mean) | Peak Memory | GC Count |
102
+ | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
103
+ | **xlsxrb (Streaming)** | - | Streaming | **3.17 s** | 3.22 s | 91.4 MB | 43.0 |
104
+ | **simple_xlsx_reader** | 5.1.0 | Streaming | 4.48 s | 4.45 s | **38.5 MB** | 1669.0 |
105
+ | **xlsxrb (In-Memory)** | - | In-Memory | 5.71 s | 5.89 s | 224.8 MB | 63.0 |
106
+ | **creek** | 2.6.3 | Streaming | 8.14 s | 8.02 s | 834.6 MB | 477.0 |
107
+ | **xsv** | 1.4.1 | Streaming | 14.61 s | 14.50 s | 76.1 MB | 2224.0 |
108
+ | **roo** | 3.0.0 | Streaming | 15.69 s | 13.36 s | 119.7 MB | 441.0 |
109
+ | **rubyXL** | 3.4.38 | In-Memory | 37.13 s | 40.35 s | 2537.6 MB | 146.0 |
110
+
111
+ ---
112
+
113
+ ## Reproducing Benchmarks Locally
114
+
115
+ The benchmark suite leverages [`bundler/inline`](https://bundler.io/v2.5/guides/bundler_in_a_single_file_ruby_script.html) to run each library in an isolated subprocess (`Bundler.with_unbundled_env`), eliminating cross-gem pollution and ensuring accurate memory measurements.
116
+
117
+ To run the suite on your machine:
118
+
119
+ ```bash
120
+ ruby benchmark.rb 100000 10
121
+ ```