xlsxrb 0.1.12 → 0.1.14

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 (198) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +116 -42
  3. data/README.md +28 -22
  4. data/lib/xlsxrb/colors.rb +118 -0
  5. data/lib/xlsxrb/dsl_helpers.rb +59 -0
  6. data/lib/xlsxrb/elements/cell.rb +205 -16
  7. data/lib/xlsxrb/elements/column.rb +6 -4
  8. data/lib/xlsxrb/elements/coordinate_access.rb +32 -0
  9. data/lib/xlsxrb/elements/image.rb +128 -0
  10. data/lib/xlsxrb/elements/row.rb +206 -9
  11. data/lib/xlsxrb/elements/styles.rb +157 -0
  12. data/lib/xlsxrb/elements/types.rb +95 -1
  13. data/lib/xlsxrb/elements/workbook.rb +96 -4
  14. data/lib/xlsxrb/elements/worksheet.rb +424 -29
  15. data/lib/xlsxrb/elements.rb +2 -0
  16. data/lib/xlsxrb/number_formatter.rb +429 -0
  17. data/lib/xlsxrb/ooxml/cfb.rb +1 -1
  18. data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +7 -5
  19. data/lib/xlsxrb/ooxml/reader/listeners/drawing_listeners.rb +11 -4
  20. data/lib/xlsxrb/ooxml/reader.rb +12 -14
  21. data/lib/xlsxrb/ooxml/shared_strings_parser.rb +10 -6
  22. data/lib/xlsxrb/ooxml/styles_parser.rb +39 -9
  23. data/lib/xlsxrb/ooxml/utils.rb +34 -18
  24. data/lib/xlsxrb/ooxml/workbook_parser.rb +53 -15
  25. data/lib/xlsxrb/ooxml/workbook_writer.rb +70 -21
  26. data/lib/xlsxrb/ooxml/worksheet_parser.rb +466 -63
  27. data/lib/xlsxrb/ooxml/worksheet_writer.rb +206 -61
  28. data/lib/xlsxrb/ooxml/writer/features_xml.rb +4 -2
  29. data/lib/xlsxrb/ooxml/writer.rb +16 -1
  30. data/lib/xlsxrb/ooxml/xml_builder.rb +20 -0
  31. data/lib/xlsxrb/ooxml/zip_reader.rb +452 -82
  32. data/lib/xlsxrb/ooxml/zip_writer.rb +32 -5
  33. data/lib/xlsxrb/stream_row.rb +243 -16
  34. data/lib/xlsxrb/stream_sheet.rb +504 -17
  35. data/lib/xlsxrb/stream_writer.rb +246 -83
  36. data/lib/xlsxrb/style_builder.rb +308 -22
  37. data/lib/xlsxrb/utils.rb +158 -0
  38. data/lib/xlsxrb/version.rb +1 -1
  39. data/lib/xlsxrb/workbook_builder.rb +74 -4
  40. data/lib/xlsxrb/worksheet_builder.rb +230 -47
  41. data/lib/xlsxrb.rb +1005 -229
  42. data/sig/generated/xlsxrb/colors.rbs +40 -0
  43. data/sig/generated/xlsxrb/dsl_helpers.rbs +32 -0
  44. data/sig/generated/xlsxrb/elements/cell.rbs +113 -8
  45. data/sig/generated/xlsxrb/elements/column.rbs +3 -2
  46. data/sig/generated/xlsxrb/elements/coordinate_access.rbs +18 -0
  47. data/sig/generated/xlsxrb/elements/image.rbs +82 -0
  48. data/sig/generated/xlsxrb/elements/row.rbs +83 -4
  49. data/sig/generated/xlsxrb/elements/styles.rbs +79 -0
  50. data/sig/generated/xlsxrb/elements/types.rbs +43 -0
  51. data/sig/generated/xlsxrb/elements/workbook.rbs +58 -2
  52. data/sig/generated/xlsxrb/elements/worksheet.rbs +231 -18
  53. data/sig/generated/xlsxrb/number_formatter.rbs +132 -0
  54. data/sig/generated/xlsxrb/ooxml/reader.rbs +6 -0
  55. data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +2 -2
  56. data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +5 -2
  57. data/sig/generated/xlsxrb/ooxml/utils.rbs +17 -8
  58. data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +9 -3
  59. data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +31 -8
  60. data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +3 -1
  61. data/sig/generated/xlsxrb/ooxml/writer.rbs +2 -0
  62. data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +11 -0
  63. data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +106 -8
  64. data/sig/generated/xlsxrb/ooxml/zip_writer.rbs +5 -1
  65. data/sig/generated/xlsxrb/stream_row.rbs +100 -6
  66. data/sig/generated/xlsxrb/stream_sheet.rbs +288 -14
  67. data/sig/generated/xlsxrb/stream_writer.rbs +120 -14
  68. data/sig/generated/xlsxrb/style_builder.rbs +55 -18
  69. data/sig/generated/xlsxrb/utils.rbs +119 -0
  70. data/sig/generated/xlsxrb/workbook_builder.rbs +26 -0
  71. data/sig/generated/xlsxrb/worksheet_builder.rbs +107 -11
  72. data/sig/generated/xlsxrb.rbs +202 -36
  73. metadata +16 -146
  74. data/.devcontainer/Dockerfile +0 -65
  75. data/.devcontainer/devcontainer.json +0 -17
  76. data/.gem_rbs_collection/ast/2.4/.rbs_meta.yaml +0 -9
  77. data/.gem_rbs_collection/ast/2.4/ast.rbs +0 -73
  78. data/.gem_rbs_collection/concurrent-ruby/1.1/.rbs_meta.yaml +0 -9
  79. data/.gem_rbs_collection/concurrent-ruby/1.1/array.rbs +0 -4
  80. data/.gem_rbs_collection/concurrent-ruby/1.1/atomic_reference.rbs +0 -16
  81. data/.gem_rbs_collection/concurrent-ruby/1.1/executor.rbs +0 -96
  82. data/.gem_rbs_collection/concurrent-ruby/1.1/hash.rbs +0 -4
  83. data/.gem_rbs_collection/concurrent-ruby/1.1/map.rbs +0 -68
  84. data/.gem_rbs_collection/concurrent-ruby/1.1/promises.rbs +0 -249
  85. data/.gem_rbs_collection/concurrent-ruby/1.1/set.rbs +0 -4
  86. data/.gem_rbs_collection/concurrent-ruby/1.1/timer_task.rbs +0 -47
  87. data/.gem_rbs_collection/concurrent-ruby/1.1/utility/processor_counter.rbs +0 -5
  88. data/.gem_rbs_collection/csv/3.3/.rbs_meta.yaml +0 -9
  89. data/.gem_rbs_collection/csv/3.3/csv.rbs +0 -3871
  90. data/.gem_rbs_collection/csv/3.3/manifest.yaml +0 -3
  91. data/.gem_rbs_collection/lint_roller/1.1/.rbs_meta.yaml +0 -9
  92. data/.gem_rbs_collection/lint_roller/1.1/lint_roller.rbs +0 -48
  93. data/.gem_rbs_collection/listen/3.9/.rbs_meta.yaml +0 -9
  94. data/.gem_rbs_collection/listen/3.9/listen.rbs +0 -25
  95. data/.gem_rbs_collection/listen/3.9/listener.rbs +0 -24
  96. data/.gem_rbs_collection/logger/1.7/.rbs_meta.yaml +0 -9
  97. data/.gem_rbs_collection/logger/1.7/formatter.rbs +0 -45
  98. data/.gem_rbs_collection/logger/1.7/log_device.rbs +0 -100
  99. data/.gem_rbs_collection/logger/1.7/logger.rbs +0 -796
  100. data/.gem_rbs_collection/logger/1.7/manifest.yaml +0 -2
  101. data/.gem_rbs_collection/logger/1.7/period.rbs +0 -17
  102. data/.gem_rbs_collection/logger/1.7/severity.rbs +0 -34
  103. data/.gem_rbs_collection/nokogiri/1.11/.rbs_meta.yaml +0 -9
  104. data/.gem_rbs_collection/nokogiri/1.11/nokogiri.rbs +0 -2332
  105. data/.gem_rbs_collection/nokogiri/1.11/patch.rbs +0 -4
  106. data/.gem_rbs_collection/parallel/1.20/.rbs_meta.yaml +0 -9
  107. data/.gem_rbs_collection/parallel/1.20/parallel.rbs +0 -86
  108. data/.gem_rbs_collection/parser/3.2/.rbs_meta.yaml +0 -9
  109. data/.gem_rbs_collection/parser/3.2/manifest.yaml +0 -7
  110. data/.gem_rbs_collection/parser/3.2/parser.rbs +0 -194
  111. data/.gem_rbs_collection/parser/3.2/polyfill.rbs +0 -4
  112. data/.gem_rbs_collection/rainbow/3.0/.rbs_meta.yaml +0 -9
  113. data/.gem_rbs_collection/rainbow/3.0/global.rbs +0 -7
  114. data/.gem_rbs_collection/rainbow/3.0/presenter.rbs +0 -209
  115. data/.gem_rbs_collection/rainbow/3.0/rainbow.rbs +0 -5
  116. data/.gem_rbs_collection/rake/13.0/.rbs_meta.yaml +0 -9
  117. data/.gem_rbs_collection/rake/13.0/manifest.yaml +0 -2
  118. data/.gem_rbs_collection/rake/13.0/rake.rbs +0 -39
  119. data/.gem_rbs_collection/regexp_parser/2.8/.rbs_meta.yaml +0 -9
  120. data/.gem_rbs_collection/regexp_parser/2.8/regexp_parser.rbs +0 -17
  121. data/.gem_rbs_collection/rubocop/1.57/.rbs_meta.yaml +0 -9
  122. data/.gem_rbs_collection/rubocop/1.57/rubocop.rbs +0 -208
  123. data/.gem_rbs_collection/rubocop-ast/1.46/.rbs_meta.yaml +0 -9
  124. data/.gem_rbs_collection/rubocop-ast/1.46/rubocop-ast.rbs +0 -903
  125. data/.gem_rbs_collection/rubyzip/3.2/.rbs_meta.yaml +0 -9
  126. data/.gem_rbs_collection/rubyzip/3.2/manifest.yaml +0 -8
  127. data/.gem_rbs_collection/rubyzip/3.2/zip/central_directory.rbs +0 -42
  128. data/.gem_rbs_collection/rubyzip/3.2/zip/compressor.rbs +0 -5
  129. data/.gem_rbs_collection/rubyzip/3.2/zip/constants.rbs +0 -47
  130. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/aes_encryption.rbs +0 -30
  131. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/decrypted_io.rbs +0 -9
  132. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/encryption.rbs +0 -7
  133. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/null_encryption.rbs +0 -19
  134. data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/traditional_encryption.rbs +0 -31
  135. data/.gem_rbs_collection/rubyzip/3.2/zip/decompressor.rbs +0 -18
  136. data/.gem_rbs_collection/rubyzip/3.2/zip/deflater.rbs +0 -12
  137. data/.gem_rbs_collection/rubyzip/3.2/zip/dirtyable.rbs +0 -11
  138. data/.gem_rbs_collection/rubyzip/3.2/zip/dos_time.rbs +0 -13
  139. data/.gem_rbs_collection/rubyzip/3.2/zip/entry.rbs +0 -95
  140. data/.gem_rbs_collection/rubyzip/3.2/zip/entry_set.rbs +0 -31
  141. data/.gem_rbs_collection/rubyzip/3.2/zip/errors.rbs +0 -58
  142. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/aes.rbs +0 -24
  143. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/generic.rbs +0 -17
  144. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/ntfs.rbs +0 -23
  145. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/old_unix.rbs +0 -22
  146. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/universal_time.rbs +0 -30
  147. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/unix.rbs +0 -20
  148. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/unknown.rbs +0 -15
  149. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/zip64.rbs +0 -26
  150. data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field.rbs +0 -21
  151. data/.gem_rbs_collection/rubyzip/3.2/zip/file.rbs +0 -131
  152. data/.gem_rbs_collection/rubyzip/3.2/zip/file_split.rbs +0 -14
  153. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/dir.rbs +0 -33
  154. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/directory_iterator.rbs +0 -21
  155. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/file.rbs +0 -63
  156. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/file_stat.rbs +0 -55
  157. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/zip_file_name_mapper.rbs +0 -35
  158. data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem.rbs +0 -7
  159. data/.gem_rbs_collection/rubyzip/3.2/zip/inflater.rbs +0 -10
  160. data/.gem_rbs_collection/rubyzip/3.2/zip/input_stream.rbs +0 -22
  161. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras/abstract_input_stream.rbs +0 -29
  162. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras/abstract_output_stream.rbs +0 -17
  163. data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras.rbs +0 -13
  164. data/.gem_rbs_collection/rubyzip/3.2/zip/null_compressor.rbs +0 -10
  165. data/.gem_rbs_collection/rubyzip/3.2/zip/null_decompressor.rbs +0 -8
  166. data/.gem_rbs_collection/rubyzip/3.2/zip/null_input_stream.rbs +0 -6
  167. data/.gem_rbs_collection/rubyzip/3.2/zip/output_stream.rbs +0 -30
  168. data/.gem_rbs_collection/rubyzip/3.2/zip/pass_thru_compressor.rbs +0 -10
  169. data/.gem_rbs_collection/rubyzip/3.2/zip/pass_thru_decompressor.rbs +0 -10
  170. data/.gem_rbs_collection/rubyzip/3.2/zip/streamable_directory.rbs +0 -5
  171. data/.gem_rbs_collection/rubyzip/3.2/zip/streamable_stream.rbs +0 -15
  172. data/.gem_rbs_collection/rubyzip/3.2/zip/version.rbs +0 -3
  173. data/.gem_rbs_collection/rubyzip/3.2/zip.rbs +0 -40
  174. data/.mutant.yml +0 -8
  175. data/Rakefile +0 -633
  176. data/Steepfile +0 -18
  177. data/benchmark.rb +0 -406
  178. data/docs/ARCHITECTURE.md +0 -514
  179. data/docs/DEVELOPMENT.md +0 -137
  180. data/docs/MUTATION_TESTING.md +0 -52
  181. data/docs/PEER_LIBRARIES.md +0 -121
  182. data/docs/QUALITY_ASSURANCE.md +0 -26
  183. data/docs/SPEC_SOURCES.md +0 -48
  184. data/docs/assets/benchmark_results.svg +0 -144
  185. data/docs/assets/lsp_autocompletion.png +0 -0
  186. data/docs/assets/playground_preview.png +0 -0
  187. data/docs/coi-serviceworker.js +0 -82
  188. data/docs/office_thread.js +0 -77
  189. data/docs/preview.html +0 -719
  190. data/docs/visual/VisualGallery.md +0 -4683
  191. data/docs/wasm/ruby.wasm +0 -0
  192. data/docs/wasm/wasm_doc_helper.css +0 -351
  193. data/docs/wasm/wasm_doc_helper.js +0 -439
  194. data/docs/zeta.js +0 -1107
  195. data/rbs_collection.lock.yaml +0 -252
  196. data/rbs_collection.yaml +0 -19
  197. data/vendor/sdk_runner/Program.cs +0 -93
  198. data/vendor/sdk_runner/sdk_runner.csproj +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15a818d0e9e58b9ada1a11c90db9f9baa55fc76b3a7e62303d33a61f2e6f9896
4
- data.tar.gz: c7b5f4af516d9911a1ee37ef5413939e8425bc270304900132e0962fad18905d
3
+ metadata.gz: 476e6f6e689eb65fecbbda4b81592c124b50f16fff0bbc9fec027a1f55ed4454
4
+ data.tar.gz: 8364dc6bff9b5f9601dc4cb375ef27b0456b7f88e721b2b52d1db7ba342c7de5
5
5
  SHA512:
6
- metadata.gz: 9deec47109a21a62aafb4a93efcee64f61f5ffb4566fd009cd1d5fdb9557e1193a53155d60ff7e4cc565317d49fb4ce8b3b89542234dff6a900981cd9220d14a
7
- data.tar.gz: b536ef03e3ae01c49ff8e00c693d86fb72a9248cae1ccf93802962127d9b86ef676b5b17ca4b896613961bab8869139644e439542b1bca5adb6a7452d971f124
6
+ metadata.gz: 31f365a17ad3f5976033ddc7a9220c5606c6d8f6acfca356ef1b3404cd1ed6d1f86d2c86d57a5e27daf15a46bea3d1c193f4fb60662b2eccbc7078fe41777bbb
7
+ data.tar.gz: 67fe19a961f2bb204d19e5b9a49ade2c01e48de7d6a09ac1c87f7c591b570c0ba64494b43ce59d3ea492a70e18cf967d775af4fb55623287ab6b7b648bb0843c
data/CHANGELOG.md CHANGED
@@ -1,20 +1,94 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.14] - 2026-10-10
4
+
5
+ ### Added
6
+ - Non-self-closing `<sheet>` and `<xf>` tag compatibility: Verify and support spreadsheet XML where `<sheet>...</sheet>` in `workbook.xml` or `<xf>...</xf>` in `styles.xml` are serialized with explicit closing tags rather than self-closing syntax.
7
+ - Namespace prefix resilience for relationship and embed attributes: Match relationship and embed attributes by local name or any namespace prefix (`r:id`, `d3p1:id`, `rel:id`, `id`, `r:embed`, `embed`) across `WorkbookListener`, `HyperlinksListener`, `DrawingListener`, and `WorksheetParser`, ensuring reliable sheet, hyperlink, and media resolution for non-standard XML generators.
8
+ - UTF-16LE, UTF-16BE, and UTF-8 BOM encoding normalization in `ZipReader`: Automatically detect and transcode Byte Order Mark (BOM) encoded XML streams (`\xFF\xFE`, `\xFE\xFF`, and `\xEF\xBB\xBF`) to UTF-8 in `ZipReader#read_entry` and `EntryStreamIO`, enabling seamless parsing of workbooks generated on Windows Phone and legacy platforms.
9
+ - XML 1.0 line ending normalization (`\r\n` and `\r` to `\n`): Normalize line breaks in shared strings, inline strings, and cell text values per W3C XML 1.0 §2.11 across `SharedStringsParser` and `WorksheetParser`.
10
+ - Whitespace and newline tolerance in cell regex scanning: Allow whitespace and newline sequences between `<c>` child tags (`<f>`, `<v>`, `<is>`) and strip whitespace on extracted values in `WorksheetParser`, ensuring reliable parsing of pretty-printed or formatted worksheet XML.
11
+ - Sequential row index fallback for omitted `r` attributes: Track previous row index and fall back to sequential 1-based indexing when `<row>` elements omit the `r` attribute per ECMA-376 Part 1 §18.3.1.73 across direct string scanning, chunked stream scanning, and event scanning in `WorksheetParser`.
12
+ - Case-insensitive part name lookup in `ZipReader`: Fall back to case-insensitive lookup for package part entries per ECMA-376 Part 2 §8.1.1, allowing workbooks with case-varying part names (such as lower-case `xl/sharedstrings.xml` or `xl/styles.xml`) to resolve transparently.
13
+ - Windows backslash path separator normalization in `ZipReader`: Normalize Windows backslash separators (`\`) in ZIP central directory and local headers to standard forward slashes (`/`), enabling transparent extraction and catalog lookup for XLSX archives generated on Windows.
14
+ - First-class sparkline groups on worksheet and `add_sparkline` helper: Expose `sparkline_groups` reader and keyword argument on `Elements::Worksheet`, propagate `ws.sparkline_groups` during `Xlsxrb.write` DOM serialization, and add `add_sparkline(location:, range:, type:, **opts)` helper to `WorksheetBuilder`, `StreamWriter`, and `StreamWriter::SheetContext` supporting `:win_loss` mapping to `"stacked"`.
15
+ - High-level print area and print titles DSL: Add `print_area`, `print_titles`, `repeat_rows`, and `repeat_columns` to `WorksheetBuilder`, `StreamWriter::SheetContext`, and `StreamWriter`, expose `print_area` and `print_titles` on `Elements::Worksheet`, and auto-generate `_xlnm.Print_Area` and `_xlnm.Print_Titles` defined names in `WorkbookBuilder#build` and `Xlsxrb.write` DOM export.
16
+ - ECMA-376 indexed color palette and `Xlsxrb::Colors`: Add `Xlsxrb::Colors` providing `INDEXED_COLORS` (indices 0..63 per ECMA-376 Part 4 §18.8.27), `palette_color`, and `to_hex` utilities, expose `Xlsxrb.palette_color` and `Xlsxrb.to_hex_color` facade methods, and resolve integer palette indices 0..63 automatically in `StyleBuilder.resolve_color`.
17
+ - Flexible rich text run input in `Xlsxrb.rich_text`: Support `[text, font_hash]` pair arrays, alternating varargs of plain strings and format hashes or objects responding to `to_font_hash`, and mixed `Elements::RichTextRun` instances.
18
+ - First-class `Elements::Hyperlink`, `Xlsxrb.hyperlink`, and `Cell#tooltip`: Add `Elements::Hyperlink` data class and `Xlsxrb.hyperlink` factory helper with `tooltip:` support, expose `Cell#tooltip` and `:tooltip`, `:url` symbol key access on `Elements::Cell`, and support `Elements::Hyperlink` instances in `WorksheetBuilder#hyperlink`, `StreamWriter#hyperlink`, and `Ooxml::Writer#hyperlink`.
19
+ - Array and dynamic array formula serialization and `Xlsxrb.array_formula`: Add `Xlsxrb.array_formula(expression, ref, cached_value:)` factory method, extend `Xlsxrb.formula` with optional `type:` and `ref:` keywords, and serialize `<f t="array" ref="...">` and shared formula attributes across all `WorksheetWriter` serialization paths.
20
+ - First-class `style_index:` keyword on `Elements::Column`: Add `style_index:` keyword argument directly to `Elements::Column.new`, storing it in `Data.define` attributes and populating it from `rc[:style_index]` during worksheet parsing and DSL column definitions.
21
+ - In-memory streaming write and row append chaining: Support `Xlsxrb.write` with omitted target in block mode to return an in-memory binary String, support self-returning `<<` row appending for chaining across `StreamWriter`, `SheetContext`, and `WorksheetBuilder`, and add `add_row` aliases.
22
+ - Unified color and border style normalizers in `StyleBuilder`: Add `StyleBuilder.normalize_color` (supporting 140+ CSS named colors, `#RGB`, `#RRGGBB`, `#AARRGGBB` hex strings, numeric RGB integers, and optional alpha channel) and `StyleBuilder.normalize_border_style` (supporting border style symbols, aliases, strings, and 0..13 numeric border indices).
23
+ - Public date and time serial conversion utilities: Expose `date_to_serial`, `serial_to_date`, `datetime_to_serial`, and `serial_to_time` (aliased `serial_to_datetime`) on `Xlsxrb` and `Xlsxrb::Utils` for converting Ruby `Date` and `Time` instances to and from Excel 1900 and 1904 serial numbers.
24
+ - Cascade style inheritance and column styles: Support cascade style inheritance (Cell Style > Row Style > Column Style) in `WorksheetWriter`, and add `style:` parameter to `StreamWriter#column` and `WorksheetBuilder#column`.
25
+ - Embedded drawing and image extraction: Add `Elements::Image` representing anchored embedded images, expose `images`, `images_at`, and `with_images` across `StreamSheet` and `Elements::Worksheet`, and expose `images` and `images_at` across `Elements::Workbook`.
26
+ - Configurable sparse vs padded row and cell iteration: Add `pad_empty_rows` and `pad_empty_cells` options to `Xlsxrb.read`, `Xlsxrb.build_worksheet`, `StreamSheet`, `WorksheetParser.each_row`, and `Elements::Worksheet` to pad skipped rows or missing cells up to the maximum column, and add `StreamRow#to_h` and `Elements::Row#cells_hash` for coordinate-keyed row maps.
27
+ - Row XML metadata accessors and serialization: Expose row XML metadata (`collapsed`, `collapsed?`, `hidden?`, `custom_height?`, `attributes`) on `StreamRow` and `Elements::Row`, support `collapsed:` in `StreamWriter#row` and `WorksheetBuilder#row`, and serialize `collapsed="1"` in `WorksheetWriter`.
28
+ - Format classification helpers: Add `Elements::Styles#format_code_for` and `#classification_for` aliases, allow `NumberFormatter` format predicates (`date_format?`, `time_format?`, etc.) to resolve built-in formats by `num_fmt_id`, and expose `format_type`, `date_format?`, and `time_format?` on `Elements::Cell`.
29
+ - Date1904 accessor: Expose `Ooxml::Reader#date1904?` and add `date1904:` keyword argument to `Elements::Workbook#initialize`.
30
+ - Coordinate utilities module: Add `Xlsxrb::Utils` exposing public coordinate helpers (`ref_to_row_col`, `row_col_to_ref`, `col_name_to_index`, `col_index_to_name`, `split_coordinate`).
31
+ - Configurable trailing empty row trimming: Add `trim_empty_rows` option across `Xlsxrb.read`, `Xlsxrb.build_worksheet`, `WorksheetParser.each_row`, `StreamSheet#initialize`, `StreamSheet#each_row`, `StreamSheet#each_row_values`, `StreamSheet#each`, `Elements::Worksheet#initialize`, `Elements::Worksheet#each_row`, and `Elements::Worksheet#each_row_values` to omit trailing empty rows, and add `empty?` predicate to `StreamRow` and `Elements::Row`.
32
+ - Direct typed values streaming iterator: Add `StreamSheet#each_row_values` and `Elements::Worksheet#each_row_values` to stream row values directly as Arrays without allocating `Elements::Cell` objects, and add `type_cast: true` support across `StreamSheet#each_row_values`, `Elements::Worksheet#each_row_values`, `StreamRow#values`, and `Elements::Row#values` to coerce date/time serial numbers into `Date` and `Time` instances.
33
+ - Precomputed number format classifications in Styles: Add `Xlsxrb::Elements::Styles` (subclass of `Hash`) with precomputed $O(1)$ format classification lookups (`number_format`, `format_type`, `date_format?`, `datetime_format?`, `time_format?`, `date_only_format?`), update `StylesParser.parse` to return `Elements::Styles`, and add classification helper methods to `Xlsxrb::NumberFormatter`.
34
+ - Worksheet dimension and coordinate bounds: Expose `dimension`, `first_row`, `first_column` (aliased `first_col`), `last_row`, and `last_column` (aliased `last_col`) on `StreamSheet` and `Elements::Worksheet` (and `Elements::CoordinateAccess`), lazily extracting `<dimension ref="..."/>` in $O(1)$ memory without scanning the full sheet.
35
+ - Date1904 propagation to streaming sheets and rows: Propagate the workbook's 1904 date system flag to `StreamSheet` and `StreamRow`, expose `date1904?` on `StreamSheet`, `StreamRow`, `Elements::Worksheet`, and `Elements::Cell`, and default `Cell#to_date` and `Cell#to_time` to the cell's `date1904` setting.
36
+ - `Elements::RichTextRun`: Add `RichTextRun` data class representing formatted text runs within `RichText`, supporting hash-like bracket access and seamless integration with `Xlsxrb.rich_text`.
37
+ - First-class conditional formatting and data validation on `Elements::Worksheet`: Expose `conditional_formatting` (and alias `conditional_formats`) and `data_validations` as first-class fields on `Elements::Worksheet`, populate them during round-trip parsing in `Xlsxrb.build_worksheet`, and serialize them in in-memory workbook serialization.
38
+ - Date1904 calendar system arithmetic: Support the 1904 date system (1904-01-01 base epoch) across serial date conversions, `Cell#to_date`, `Cell#to_time`, `Elements::Workbook#date1904?`, `Ooxml::Reader`, `Ooxml::WorksheetWriter`, `Ooxml::WorkbookWriter`, and `StreamWriter`.
39
+ - Row and column styles persistence: Expose `style_index` on `Row`, `Column`, and `StreamRow`, and persist row and column styles (`<row s="..." customFormat="1">` and `<col style="..."/>`) during serialization and round-trip parsing.
40
+ - Styles metadata persistence: Support border colors, cell alignment, and protection attributes in stylesheet generation and round-trip parsing.
41
+ - Dual format support for `num_fmts`: Support both `Hash` (`{ 164 => "format" }`) and `Array` (`[{ num_fmt_id: 164, format_code: "format" }]`) structures for custom number formats in `WorkbookWriter`.
42
+ - Initial shared strings table seeding: Support pre-seeding `sst` in `Xlsxrb.write` when `workbook.shared_strings` is provided.
43
+ - Sheet visibility state: Support worksheet visibility state (`state`, `hidden?`, `visible?`) across `StreamSheet`, `Elements::Worksheet`, `WorkbookParser`, `WorkbookBuilder`, and `WorkbookWriter`, preserving `:visible`, `:hidden`, and `:very_hidden` visibility in streaming read, in-memory representations, and XML serialization.
44
+ - Workbook defined names: Parse `<definedNames>` in `WorkbookParser` and expose `defined_names` array and `defined_name(name, sheet: nil)` lookup on `Elements::Workbook`, supporting global and sheet-scoped named ranges and formula constants.
45
+ - Cell formula query and expression methods: Expose `formula?` predicate and `formula_expression` string accessor on `Elements::Cell`, and normalize empty formula expressions to `nil` in streaming and DOM cell parsing.
46
+ - Worksheet and cell hyperlinks: Parse and expose hyperlink metadata across `StreamSheet#hyperlinks`, `Elements::Worksheet#hyperlinks`, and `Elements::Cell#hyperlink`, `#link?`, and `#url`, supporting external targets and internal sheet references in streaming read, DOM models, and builders.
47
+ - Worksheet and cell comments: Parse and expose comment and note metadata across `StreamSheet#comments`, `StreamSheet#comment`, `Elements::Worksheet#comments`, and `Elements::Cell#comment`, `#comment?`, and `#comment_text`, supporting comment annotations and coordinate lookups in streaming read, DOM models, and builders.
48
+ - Number formatting engine and extended cell metadata: Add `Xlsxrb::NumberFormatter` supporting standard and custom OpenXML format codes (currencies, percentages, date/time, fixed-point padding, accounting patterns, scientific notation, and text), expose `formatted_value`, `raw_value`, and `format_code` on `Elements::Cell`, add `formatted_value` coordinate lookups on `Elements::Worksheet`, `StreamSheet`, and `Elements::Workbook`, and expose `raw_values`, `formatted_values`, and `formulas` metadata on `Elements::Row` and `StreamRow`.
49
+ - Fallback serialization for DOM model attributes: Support serializing first-class `ws.state`, `ws.comments`, `ws.hyperlinks`, and `workbook.defined_names` in `Xlsxrb.write(workbook)` when not explicitly packaged inside `unmapped_data[:facade]` metadata.
50
+
51
+ ### Changed
52
+ - Optimize chunked streaming read performance: Hoist `</sheetData>` tag lookup outside row iteration, eliminate byte delimiter array allocations, and terminate entry inflation early once worksheet rows are fully parsed, restoring streaming read throughput faster than in-memory read while preserving $O(1)$ memory consumption.
53
+
54
+ ### Fixed
55
+ - Fallback cached value for formula cells: Emit `<v>0</v>` instead of omitting `<v>` when a formula cell does not specify a cached value, ensuring compatibility with OpenXML parsers (such as Roo and LibreOffice) that discard formula cells lacking `<v>` elements.
56
+ - Shared formula cell parsing: Support self-closing formula tags (e.g. `<f t="shared" si="0"/>`) in `WorksheetParser` regex scanners, preventing cells containing shared formulas from being silently omitted.
57
+ - Fixed missing `dcterms:created` and `dcterms:modified` serialization in `WorkbookWriter` core properties (`docProps/core.xml`).
58
+ - Prevent `Encoding::CompatibilityError` in `Xlsxrb.read` and `Cfb::Reader.cfb?` when inspecting raw string buffers in UTF-8 encoding.
59
+
60
+ ## [0.1.13] - 2026-09-25
61
+
62
+ ### Added
63
+ - Non-destructive template modification: Support modifying existing XLSX templates while preserving unmapped parts, media, charts, drawings, and VBA macros via `Xlsxrb.modify`.
64
+ - StreamSheet structural metadata accessors: Expose `merged_cells`, `auto_filter`, `data_validations`, and `conditional_formats` on `StreamSheet`.
65
+ - Pre-Push Smart Change Detection: Optimized `bin/pre-push --all` to automatically skip runtime RBS validation and pure logic mutation testing when unrelated files are modified, adding `--last` and `--force` controls.
66
+
67
+ ### Changed
68
+ - True O(1) memory streaming read via chunked entry inflation in `ZipReader` and `WorksheetParser`, preventing unbounded memory retention on large worksheets.
69
+ - Extracted pure DSL normalizers (`Xlsxrb::DslHelpers`) and fast XML unescape utilities (`Ooxml::XmlBuilder.unescape`).
70
+ - Constrained `rexml` runtime dependency to `~> 3.0` and excluded documentation, build assets, and developer configurations from the gem package.
71
+
72
+ ### Removed
73
+ - OpenTelemetry runtime dependency and tracer spans.
74
+
1
75
  ## [0.1.12] - 2026-09-20
2
76
 
3
77
  ### Added
4
- - **Mutation Testing Framework (`mutant`)**:
78
+ - Mutation Testing Framework (`mutant`):
5
79
  - Full-scale integration of mutant test suite targeting the Functional Core (10 classes and modules, 38 subjects).
6
- - Achieved **100.00% Kill Rate (1,091 / 1,091 mutations killed, 0 alive)** without exclusions.
80
+ - Achieved 100.00% Kill Rate (1,091 / 1,091 mutations killed, 0 alive) without exclusions.
7
81
  - Added dedicated root configuration [`.mutant.yml`](.mutant.yml) for streamlined CLI execution (`bundle exec mutant run -- '...'`).
8
82
  - Integrated `bundle exec rake mutant:pure` into GitHub Actions CI pipeline (`main.yml`), guaranteeing mutation-tested quality on every push and pull request.
9
- - Added comprehensive architecture and testing guide in [`docs/MUTATION_TESTING.md`](docs/MUTATION_TESTING.md).
10
- - **Pre-Push Quality Verification Script (`bin/pre-push`)**: Added automated pre-push verification script running RuboCop, Steep type checking, RBS sync validation, Unit & Contract tests, RBS runtime type validation, and pure logic mutation testing, with automatic hook installation in `bin/setup`.
83
+ - Added architecture and testing guide in [`docs/MUTATION_TESTING.md`](docs/MUTATION_TESTING.md).
84
+ - Pre-Push Quality Verification Script (`bin/pre-push`): Added automated pre-push verification script running RuboCop, Steep type checking, RBS sync validation, Unit & Contract tests, RBS runtime type validation, and pure logic mutation testing, with automatic hook installation in `bin/setup`.
11
85
 
12
86
  ### Changed
13
- - **Functional Core & Imperative Shell Architecture**:
87
+ - Functional Core & Imperative Shell Architecture:
14
88
  - Purified core business logic and domain entities into deterministic, side-effect-free class methods and predicates:
15
89
  - Extracted coordinate and validation predicates: `Elements::Cell.valid_value?`, `Cell.valid_coordinates?`, `Cell.calculate_column_letter`, `Cell.calculate_column_index`.
16
90
  - Extracted domain validation predicates: `Elements::Row.valid_index?`, `Elements::Column.valid_index?`, `Elements::Worksheet.valid_name?`.
17
- - Replaced date/time serial calculations in `Ooxml::Utils` with pure mathematical conversions (Julian Day math for `date_to_serial`, `divmod` arithmetic for `serial_to_datetime`, and rigorous 1900 leap year bug handling).
91
+ - Replaced date/time serial calculations in `Ooxml::Utils` with pure mathematical conversions (Julian Day math for `date_to_serial`, `divmod` arithmetic for `serial_to_datetime`, and 1900 leap year bug handling).
18
92
  - Extracted binary packing utilities in `Ooxml::ZipGenerator` (`le16`, `le32`, and bitfield arithmetic for `dos_datetime`).
19
93
  - Extracted pure XML character escaping logic in `Ooxml::XmlBuilder.escape`.
20
94
  - Modularized `Ooxml::Writer` into domain-specific mixin modules (`DrawingXml`, `FeaturesXml`, `StylesXml`).
@@ -22,12 +96,12 @@
22
96
  - Modularized reader SAX listeners into domain-specific parser components.
23
97
 
24
98
  ### Fixed
25
- - **Boundary Validation & Error Reporting**:
99
+ - Boundary Validation & Error Reporting:
26
100
  - Strictly validate row number digits in `Elements::Cell.parse_ref` to reject malformed cell coordinate strings (e.g. `A0`, `A01`).
27
101
  - Harmonized supported value type checks and nil/empty handling across `Elements::Cell.validate`.
28
102
  - Corrected maximum row index boundary error messages in `Elements::Row.validate`.
29
103
  - Hardened cryptographic stream boundary checks and corrupted header handling in Standard and Agile encryption modes.
30
- - **RBS Runtime Type Validation**:
104
+ - RBS Runtime Type Validation:
31
105
  - Expanded `Elements::Workbook#initialize`, `#sheet`, and `[]` type annotations to accept `StreamSheet` instances used by streaming readers.
32
106
  - Made update block parameter optional in `Elements::Workbook#update_sheet` to allow testing missing block validation.
33
107
  - Supported `Numeric` types (Float and Integer) in `ChartBuilder` and `SeriesBuilder` kwargs (`width`, etc.).
@@ -36,15 +110,15 @@
36
110
  ## [0.1.11] - 2026-08-19
37
111
 
38
112
  ### Performance
39
- - **Sub-Second Streaming Write (< 1.0s / 1,000,000 cells)**:
40
- - Achieved sub-second streaming write performance (**0.97 s** median for 1M cells) while maintaining 100% full SST (Shared String Table) XML deduplication and ISO/IEC 29500 compatibility.
113
+ - Sub-Second Streaming Write (< 1.0s / 1,000,000 cells):
114
+ - Achieved sub-second streaming write performance (0.97 s median for 1M cells) while maintaining 100% full SST (Shared String Table) XML deduplication and ISO/IEC 29500 compatibility.
41
115
  - Implemented 1-Pass Direct ZIP streaming pipeline, eliminating all intermediate tempfiles, disk seek-backs, and extra deflate flush cycles.
42
116
  - Added dedicated unstyled fast-path in `WorksheetWriter#write_row_values` with precomputed coordinate and integer lookup tables.
43
117
  - Pre-registered default date/time formatting styles, completely eliminating redundant per-row scan loops.
44
118
  - Streamlined `ZipWriter` instance variable lookups to eliminate per-chunk hash overhead.
45
119
  - Added `alias << row` to `WorksheetProxy` and `StreamWriter` for idiomatic Ruby streaming.
46
- - **High-Speed Streaming Read (1.61s / 1,000,000 cells)**:
47
- - Reduced streaming read execution time from **3.40 s** down to **1.61 s** (over 2x speedup).
120
+ - High-Speed Streaming Read (1.61s / 1,000,000 cells):
121
+ - Reduced streaming read execution time from 3.40 s down to 1.61 s (over 2x speedup).
48
122
  - Refactored `Elements::Cell` into an optimized lightweight class with `Cell.fast_create`, reducing 1M cell instantiation overhead by 5.2x (0.92s → 0.17s) while maintaining full pattern matching (`deconstruct`, `deconstruct_keys`) and immutability.
49
123
  - Replaced per-byte Ruby scanning in `WorksheetParser.fast_scan_cells_direct` with an Onigmo C-level regular expression scanner (`CELL_FAST_RE`), eliminating 100,000 intermediate XML substring allocations and nested byte loops.
50
124
  - Optimized `StreamRow#cells` to populate cell arrays via direct block traversal, eliminating Enumerator object allocations.
@@ -55,56 +129,56 @@
55
129
  ## [0.1.10] - 2026-08-19
56
130
 
57
131
  ### Added
58
- - **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.
59
- - **Visual Assets & Screen Previews**:
132
+ - 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.
133
+ - Visual Assets & Screen Previews:
60
134
  - Embedded interactive WebAssembly Playground live demo preview in `README.md`.
61
135
  - Added real-world Ruby LSP autocompletion and RBS type hint preview in `README.md`.
62
136
  - Created accurate, neutral linear-scale SVG benchmark performance chart.
63
- - **Enterprise-Grade Test Suite Expansion**:
64
- - **ECMA-376 XSD Schema Validation**: Comprehensive XML schema validation suite ensuring strict element ordering and ISO/IEC 29500 compliance.
65
- - **Contract Testing Suite**: Comprehensive parity verification between Streaming (`Xlsxrb.write`) and In-Memory (`Xlsxrb.build`) APIs.
66
- - **Property-Based Testing (PBT)**: Expanded automated random generation tests for Row/Column invariants, styles, and edge cases.
67
- - **Visual Regression Testing (VRT)**: Added new visual baselines for table styles, drawing shapes, and pivot tables.
68
- - **E2E Interoperability Suite**: Added tests for namespace-prefixed XML streaming, conditional formatting, and table structures.
137
+ - Multi-Layered Test Suite Expansion:
138
+ - ECMA-376 XSD Schema Validation: XML schema validation suite ensuring strict element ordering and ISO/IEC 29500 compliance.
139
+ - Contract Testing Suite: Parity verification between Streaming (`Xlsxrb.write`) and In-Memory (`Xlsxrb.build`) APIs.
140
+ - Property-Based Testing (PBT): Expanded automated random generation tests for Row/Column invariants, styles, and edge cases.
141
+ - Visual Regression Testing (VRT): Added new visual baselines for table styles, drawing shapes, and pivot tables.
142
+ - E2E Interoperability Suite: Added tests for namespace-prefixed XML streaming, conditional formatting, and table structures.
69
143
 
70
144
  ### Fixed
71
- - **WebAssembly (ruby.wasm) Compatibility**: Bundled `pp` and `prettyprint` standard libraries in `ruby.wasm` package to resolve REXML LoadError during browser-based evaluation.
145
+ - WebAssembly (ruby.wasm) Compatibility: Bundled `pp` and `prettyprint` standard libraries in `ruby.wasm` package to resolve REXML LoadError during browser-based evaluation.
72
146
 
73
147
  ### Changed
74
- - **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.
148
+ - 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.
75
149
 
76
150
  ## [0.1.9] - 2026-08-18
77
151
 
78
152
  ### Added
79
- - **Password Protection & Document Encryption ([MS-OFFCRYPTO] / [MS-CFB])**: Full native Pure-Ruby support for reading, writing, and modifying password-protected Excel spreadsheets without any external C-extension dependencies.
80
- - **Standard Encryption**: AES-128-ECB and SHA-1 Key Derivation with CryptoAPI 50,000-spin hashing, fully interoperable across Microsoft Excel, LibreOffice, and Google Sheets.
81
- - **Agile Encryption**: Modern AES-256-CBC, PBKDF2/SHA-512, and HMAC-SHA512 data integrity verification.
82
- - **Compound File Binary (CFB) Engine**: Pure-Ruby reader and writer for OLE structured storage containers with Mini Stream, FAT/MiniFAT sectors, and Red-Black tree directory management.
83
- - **Transparent Public API Integration**: Added `password:` and `encryption_mode:` arguments to `Xlsxrb.read`, `Xlsxrb.write`, and `Xlsxrb.modify`.
84
- - **Security & Threat Model Hardening**:
153
+ - Password Protection & Document Encryption ([MS-OFFCRYPTO] / [MS-CFB]): Full native Pure-Ruby support for reading, writing, and modifying password-protected Excel spreadsheets without any external C-extension dependencies.
154
+ - Standard Encryption: AES-128-ECB and SHA-1 Key Derivation with CryptoAPI 50,000-spin hashing, fully interoperable across Microsoft Excel, LibreOffice, and Google Sheets.
155
+ - Agile Encryption: Modern AES-256-CBC, PBKDF2/SHA-512, and HMAC-SHA512 data integrity verification.
156
+ - Compound File Binary (CFB) Engine: Pure-Ruby reader and writer for OLE structured storage containers with Mini Stream, FAT/MiniFAT sectors, and Red-Black tree directory management.
157
+ - Transparent Public API Integration: Added `password:` and `encryption_mode:` arguments to `Xlsxrb.read`, `Xlsxrb.write`, and `Xlsxrb.modify`.
158
+ - Security & Threat Model Hardening:
85
159
  - Constant-time hash verification via `OpenSSL.secure_compare` to prevent timing attacks (CWE-208).
86
160
  - CSPRNG-backed salt, IV, and session key generation via `SecureRandom` (CWE-330).
87
- - Robust DoS defense: spinCount limit ($\le 10\text{M}$), CFB circular sector chain loop detection in directory/FAT parsing, and `total_size` bounds validation (CWE-400, CWE-835).
161
+ - DoS defense: spinCount limit ($\le 10\text{M}$), CFB circular sector chain loop detection in directory/FAT parsing, and `total_size` bounds validation (CWE-400, CWE-835).
88
162
  - Strict exception hierarchy (`EncryptedFileError`, `InvalidPasswordError`, `DecryptionError`).
89
- - **Cross-Platform & Interoperability Validation**: Bidirectional validation with Microsoft .NET OpenXML SDK and LibreOffice Calc.
90
- - **WebAssembly (ruby.wasm) Support**: Pre-packaged `docs/wasm/ruby.wasm` updated with document encryption support for browser playground.
163
+ - Cross-Platform & Interoperability Validation: Bidirectional validation with Microsoft .NET OpenXML SDK and LibreOffice Calc.
164
+ - WebAssembly (ruby.wasm) Support: Pre-packaged `docs/wasm/ruby.wasm` updated with document encryption support for browser playground.
91
165
 
92
166
  ## [0.1.8] - 2026-08-18
93
167
 
94
168
  ### Changed
95
- - **Unified Symmetric Entrypoints**: Consolidated reading into `Xlsxrb.read` (supporting file path, IO, and raw binary string) and writing into `Xlsxrb.write` (supporting streaming blocks or in-memory Workbooks). Removed legacy `open`, `foreach`, and `generate` methods.
96
- - **Streaming-First Defaults**: `Xlsxrb.read` yields and returns lightweight `StreamSheet` instances with $O(1)$ constant-memory consumption by default.
97
- - **Explicit In-Memory Materialization (`#load`)**: Stripped accidental random-access memory traps from `StreamSheet`; introduced explicit `StreamSheet#load` / `Workbook#load` (inspired by ActiveRecord Relations) to transition from lazy streaming to in-memory `Elements::Worksheet` / `Elements::Workbook`.
98
- - **`CoordinateAccess` Module**: Extracted coordinate lookup methods (`[]`, `cell_value`, `row_at`, `first_row`, `last_row`, `cells`, `cells_hash`) into a dedicated `Xlsxrb::Elements::CoordinateAccess` mixin module included in `Elements::Worksheet`.
169
+ - Unified Symmetric Entrypoints: Consolidated reading into `Xlsxrb.read` (supporting file path, IO, and raw binary string) and writing into `Xlsxrb.write` (supporting streaming blocks or in-memory Workbooks). Removed legacy `open`, `foreach`, and `generate` methods.
170
+ - Streaming-First Defaults: `Xlsxrb.read` yields and returns lightweight `StreamSheet` instances with $O(1)$ constant-memory consumption by default.
171
+ - Explicit In-Memory Materialization (`#load`): Stripped accidental random-access memory traps from `StreamSheet`; introduced explicit `StreamSheet#load` / `Workbook#load` (inspired by ActiveRecord Relations) to transition from lazy streaming to in-memory `Elements::Worksheet` / `Elements::Workbook`.
172
+ - `CoordinateAccess` Module: Extracted coordinate lookup methods (`[]`, `cell_value`, `row_at`, `first_row`, `last_row`, `cells`, `cells_hash`) into a dedicated `Xlsxrb::Elements::CoordinateAccess` mixin module included in `Elements::Worksheet`.
99
173
 
100
174
  ### Added
101
- - **Default Cell Streaming (`Xlsxrb::StreamRow`)**: Enabled streaming along both row and cell dimensions via `row.each_cell` and `sheet.each_cell`, parsing cells on-demand to handle sheets with thousands of columns in $O(1)$ constant memory.
175
+ - Default Cell Streaming (`Xlsxrb::StreamRow`): Enabled streaming along both row and cell dimensions via `row.each_cell` and `sheet.each_cell`, parsing cells on-demand to handle sheets with thousands of columns in $O(1)$ constant memory.
102
176
 
103
177
  ## [0.1.7] - 2026-08-16
104
178
 
105
179
  ### Added
106
180
  - Bundled native Ruby LSP Add-on (`RubyLsp::Xlsxrb::Addon`) for zero-configuration, context-aware method autocompletion and rich markdown documentation in VS Code and LSP-enabled editors for block arguments (`wb.`, `s.`, `sheet.`, `stream_writer.`, `stream_sheet.`).
107
- - Comprehensive YARD documentation (`@param`, `@return`, `@example`) across all public APIs, builders, proxies, and elements.
181
+ - YARD documentation (`@param`, `@return`, `@example`) across all public APIs, builders, proxies, and elements.
108
182
 
109
183
  ### Developer Experience
110
184
  - Enhanced `rbs-inline` type signatures across all facade methods and builder objects with automated RBS generation.
@@ -119,10 +193,10 @@
119
193
  - Hash-compatible symbol indexing in `Elements::Row#[]` (`:cells`, `:index`, `:height`, `:attrs`) and `Elements::Cell#[]` (`:value`, `:ref`, `:style_index`).
120
194
 
121
195
  ### Performance
122
- - **Streaming Write**: Replaced per-cell micro IO calls with row-level string buffering and fast-path serialization for unstyled cells (1,000,000 cells in 1.62s with standard SST).
123
- - **In-Memory Write**: Optimized DOM serialization (`WorksheetWriter#write_row`) with row-level buffer aggregation, boosting 1,000,000 cells write from 22.33s to 4.06s (5.5x faster) and reducing GC count by 83%.
124
- - **Streaming Read**: Implemented zero-allocation byte scanning for cell attributes (`r="..."`, `t="..."`, `s="..."`) and direct integer conversion for SST indexes, cutting GC count by ~70% (129 -> 40) and boosting 1,000,000 cells read to 3.38s.
125
- - **In-Memory Read**: Reduced peak memory footprint by 57% (582 MB -> 250 MB).
196
+ - Streaming Write: Replaced per-cell micro IO calls with row-level string buffering and fast-path serialization for unstyled cells (1,000,000 cells in 1.62s with standard SST).
197
+ - In-Memory Write: Optimized DOM serialization (`WorksheetWriter#write_row`) with row-level buffer aggregation, boosting 1,000,000 cells write from 22.33s to 4.06s (5.5x faster) and reducing GC count by 83%.
198
+ - Streaming Read: Implemented zero-allocation byte scanning for cell attributes (`r="..."`, `t="..."`, `s="..."`) and direct integer conversion for SST indexes, cutting GC count by ~70% (129 -> 40) and boosting 1,000,000 cells read to 3.38s.
199
+ - In-Memory Read: Reduced peak memory footprint by 57% (582 MB -> 250 MB).
126
200
 
127
201
  ### Documentation
128
202
  - Overhauled `benchmark.rb` with `bundler/inline` for deterministic, zero-setup benchmark reproduction pinning peer gem versions.
@@ -142,7 +216,7 @@
142
216
  - Full `RBS::Test` runtime type validation enabled for the entire test suite.
143
217
  - Extensive Excel limit warnings documented via YARD tags.
144
218
  - Formal SemVer API contract with `@api public` tags for user-facing methods.
145
- - Comprehensive mutation testing (Mutant) and test coverage (SimpleCov) integrations.
219
+ - Mutation testing (Mutant) and test coverage (SimpleCov) integrations.
146
220
 
147
221
  ### Changed
148
222
  - Replaced `method_missing` with statically defined, fully typed methods in `WorksheetProxy`, `ChartBuilder`, and `SeriesBuilder`.
data/README.md CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  A Ruby library for reading and writing XLSX files with streaming support.
4
4
 
5
+ Drop-in compatibility adapters for peer Ruby XLSX libraries are available at [xlsxrb-adapters](https://github.com/niku/xlsxrb-adapters).
6
+
5
7
  ## Motivation
6
8
 
7
- The Ruby ecosystem already has great XLSX libraries, each designed for specific tradeoffs:
9
+ The Ruby ecosystem has several XLSX libraries designed for specific tradeoffs:
8
10
 
9
11
  | Library | Read | Write | Streaming | In-Memory |
10
12
  | :--- | :---: | :---: | :---: | :---: |
@@ -17,23 +19,27 @@ The Ruby ecosystem already has great XLSX libraries, each designed for specific
17
19
  | [xlsxtream](https://rubygems.org/gems/xlsxtream) | ❌ | ✅ | ✅ | ❌ |
18
20
  | [fast_excel](https://rubygems.org/gems/fast_excel) | ❌ | ✅ | ✅ | ❌ |
19
21
  | [rubyXL](https://rubygems.org/gems/rubyXL) | ✅ | ✅ | ❌ | ✅ |
20
- | **[xlsxrb](https://github.com/niku/xlsxrb)** | **✅** | **✅** | **✅** | **✅** |
22
+ | [xlsxrb](https://github.com/niku/xlsxrb) | ✅ | ✅ | ✅ | ✅ |
21
23
 
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.
24
+ These libraries make different architectural tradeoffs:
25
+ * Streaming Model: Writes or reads rows sequentially on-the-fly to maintain a constant $O(1)$, low-memory footprint regardless of dataset size.
26
+ * 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
27
 
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.
28
+ Maintaining a gem that supports both reading and writing across streaming and in-memory models, alongside OOXML features and specification compatibility, involves significant ongoing maintenance overhead.
27
29
 
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.
30
+ `xlsxrb` addresses this through automated workflows: AI coding agents handle routine maintenance tasks such as end-to-end testing, visual regression testing, schema compliance checks, and documentation synchronization, enabling sustainable maintenance of both streaming and in-memory architectures.
29
31
 
30
32
  ### Design Principles
31
33
 
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.
34
+ - Zero Runtime Dependencies: Built strictly on the Ruby standard library and bundled gems (`zlib`, `rexml`, etc.) with zero third-party runtime dependencies.
35
+ - Streaming Support: Constant $O(1)$ memory streaming for reading and writing spreadsheets.
36
+ - OpenXML Interoperability: Compliant with ISO/IEC 29500 (ECMA-376) and validated against the Microsoft [Open XML SDK](https://github.com/dotnet/Open-XML-SDK).
37
+ - AI-Assisted Maintenance: Uses AI coding agents for automated quality assurance and verification workflows. Operational guidelines are defined in [AGENTS.md](AGENTS.md).
38
+ - Ruby 4.0+: Requires Ruby 4.0 or higher.
39
+
40
+ ### Autonomous Quality Assurance
41
+
42
+ To maintain its multi-layered QA suite (Steep static typing, 100% mutation kill rate across 38 subjects, ECMA-376 XSD validation, and $O(1)$ streaming memory) without high manual maintenance overhead, xlsxrb defines remediation workflows for AI coding agents ([AGENTS.md](AGENTS.md), [docs/QA_AGENTS.md](docs/QA_AGENTS.md)).
37
43
 
38
44
  ## Installation
39
45
 
@@ -134,7 +140,7 @@ end
134
140
 
135
141
  ### IDE Autocompletion & Ruby LSP Support
136
142
 
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:
143
+ 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:
138
144
 
139
145
  <p align="center">
140
146
  <img src="docs/assets/lsp_autocompletion.png" width="100%" alt="Ruby LSP Autocompletion & Type Signature Hints in VS Code"/>
@@ -142,9 +148,9 @@ Includes a native **Ruby LSP Add-on** and full **RBS signatures** for zero-confi
142
148
 
143
149
  ## Feature Support & ECMA-376 Compliance
144
150
 
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).
151
+ `xlsxrb` supports standard spreadsheet features:
152
+ * Layout & Structure: Formulas, Hyperlinks, Merge Cells, Freeze/Split Panes, Page Setup, Auto Filters, Data Validations, Sheet/Workbook Protection.
153
+ * Styling & Media: Rich Text, Cell Styles & Fills, Conditional Formatting (color scales, data bars), Embedded Images, Charts (Line, Bar, Pie, Radar, Scatter).
148
154
 
149
155
  For full details, see [docs/SPEC_SOURCES.md](docs/SPEC_SOURCES.md).
150
156
 
@@ -162,12 +168,12 @@ To reproduce locally: `ruby benchmark.rb 100000 10`
162
168
 
163
169
  ## Quality Assurance & Testing
164
170
 
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
- * **Mutation Testing (Mutant)**: 100% mutant kill rate (1,091/1,091 mutations across 38 subjects) on pure algorithms, predicates, and coordinates ([docs/MUTATION_TESTING.md](docs/MUTATION_TESTING.md)).
170
- * **Security & DoS Protection**: Formula injection mitigation and ZIP bomb protection.
171
+ Verified by a multi-layered QA architecture:
172
+ * Microsoft Open XML SDK Validation: Validates generated OOXML structures against Microsoft's official SDK.
173
+ * Visual Regression Testing (VRT): Headless LibreOffice Calc pixel-by-pixel rendering checks.
174
+ * Contract & Round-Trip Tests: Verifies parity between Streaming and In-Memory APIs and round-trip read/write accuracy.
175
+ * Mutation Testing (Mutant): 100% mutant kill rate (1,091/1,091 mutations across 38 subjects) on pure algorithms, predicates, and coordinates ([docs/MUTATION_TESTING.md](docs/MUTATION_TESTING.md)).
176
+ * Security & DoS Protection: Formula injection mitigation and ZIP bomb protection.
171
177
 
172
178
  For full architectural details, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [docs/QUALITY_ASSURANCE.md](docs/QUALITY_ASSURANCE.md).
173
179
 
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module Xlsxrb
6
+ # ECMA-376 Part 4 §18.8.27 Standard Indexed Color Palette and utilities.
7
+ module Colors
8
+ # Standard ECMA-376 indexed colors (indices 0 through 63 as 6-character RRGGBB hex strings).
9
+ # Indices 8 through 63 represent the classic 56-color palette.
10
+ INDEXED_COLORS = [
11
+ "000000", # 0
12
+ "FFFFFF", # 1
13
+ "FF0000", # 2
14
+ "00FF00", # 3
15
+ "0000FF", # 4
16
+ "FFFF00", # 5
17
+ "FF00FF", # 6
18
+ "00FFFF", # 7
19
+ "000000", # 8: Black
20
+ "FFFFFF", # 9: White
21
+ "FF0000", # 10: Red
22
+ "00FF00", # 11: Green
23
+ "0000FF", # 12: Blue
24
+ "FFFF00", # 13: Yellow
25
+ "FF00FF", # 14: Magenta
26
+ "00FFFF", # 15: Cyan
27
+ "800000", # 16: Brown / Dark Red
28
+ "008000", # 17: Olive / Dark Green
29
+ "000080", # 18: Navy / Dark Blue
30
+ "808000", # 19: Dark Yellow
31
+ "800080", # 20: Purple
32
+ "008080", # 21: Teal
33
+ "C0C0C0", # 22: Silver / Light Gray
34
+ "808080", # 23: Gray
35
+ "9999FF", # 24
36
+ "993366", # 25
37
+ "FFFFCC", # 26
38
+ "CCFFFF", # 27
39
+ "660066", # 28
40
+ "FF8080", # 29
41
+ "0066CC", # 30
42
+ "CCCCFF", # 31
43
+ "000080", # 32
44
+ "FF00FF", # 33
45
+ "FFFF00", # 34
46
+ "00FFFF", # 35
47
+ "800080", # 36
48
+ "800000", # 37
49
+ "008080", # 38
50
+ "0000FF", # 39
51
+ "00CCFF", # 40
52
+ "CCFFFF", # 41
53
+ "CCFFCC", # 42
54
+ "FFFFC0", # 43
55
+ "CCE5FF", # 44
56
+ "99CCFF", # 45
57
+ "FF99CC", # 46
58
+ "CC99FF", # 47
59
+ "FFCC99", # 48
60
+ "3366FF", # 49
61
+ "33CCCC", # 50
62
+ "99CC00", # 51
63
+ "FFCC00", # 52
64
+ "FF9900", # 53
65
+ "FF6600", # 54
66
+ "666699", # 55
67
+ "969696", # 56
68
+ "003366", # 57
69
+ "339966", # 58
70
+ "003300", # 59
71
+ "333300", # 60
72
+ "993300", # 61
73
+ "993366", # 62
74
+ "333333" # 63
75
+ ].freeze
76
+
77
+ # Resolves a palette index (0..63) to an RRGGBB hex string (or AARRGGBB if alpha: true).
78
+ #
79
+ # @example
80
+ # Xlsxrb::Colors.palette_color(12) #=> "0000FF"
81
+ # Xlsxrb::Colors.palette_color(12, alpha: true) #=> "FF0000FF"
82
+ #
83
+ # @param index [Object, nil] Palette index (0..63).
84
+ # @param alpha [Boolean] Whether to prefix "FF" alpha channel.
85
+ # @return [String, nil]
86
+ # @api public
87
+ #: (untyped index, ?alpha: bool) -> String?
88
+ def self.palette_color(index, alpha: false)
89
+ return nil unless index.is_a?(Integer) && index >= 0 && index < INDEXED_COLORS.size
90
+
91
+ hex = INDEXED_COLORS[index]
92
+ alpha ? "FF#{hex}" : hex
93
+ end
94
+
95
+ # Converts any supported color representation (palette index 0..63, CSS name, #RGB, #RRGGBB, #AARRGGBB, integer RGB)
96
+ # to a normalized hex string.
97
+ #
98
+ # @example
99
+ # Xlsxrb::Colors.to_hex(12) #=> "0000FF"
100
+ # Xlsxrb::Colors.to_hex(12, alpha: true) #=> "FF0000FF"
101
+ # Xlsxrb::Colors.to_hex(:navy) #=> "000080"
102
+ # Xlsxrb::Colors.to_hex("#FF0000") #=> "FF0000"
103
+ # Xlsxrb::Colors.to_hex(0xFF0000) #=> "FF0000"
104
+ #
105
+ # @param color [Integer, String, Symbol, nil]
106
+ # @param alpha [Boolean] Whether to include the alpha channel (default false -> "RRGGBB").
107
+ # @return [String, nil]
108
+ # @api public
109
+ #: (Integer | String | Symbol | nil color, ?alpha: bool) -> String?
110
+ def self.to_hex(color, alpha: false)
111
+ return nil if color.nil?
112
+
113
+ return palette_color(color, alpha: alpha) if color.is_a?(Integer) && color >= 0 && color < INDEXED_COLORS.size
114
+
115
+ StyleBuilder.normalize_color(color, alpha: alpha)
116
+ end
117
+ end
118
+ end
@@ -112,5 +112,64 @@ module Xlsxrb
112
112
  tbl.merge!(opts)
113
113
  tbl
114
114
  end
115
+
116
+ # Converts a cell range (e.g. "A1:B2" or "A1") to an absolute reference format (e.g. "$A$1:$B$2" or "$A$1").
117
+ #
118
+ # @param range [String]
119
+ # @return [String]
120
+ #: (String range) -> String
121
+ def self.absolute_range(range)
122
+ range.gsub(/(?:\$)?([A-Z]+)(?:\$)?(\d+)\b/, '$\1$\2')
123
+ end
124
+
125
+ # Normalizes row values: if given a column-keyed Hash, converts to a sparse Array.
126
+ # If already an Array or other non-Hash enumerable, returns as-is without allocations.
127
+ #
128
+ # @param values [Array<Object>, Hash{String, Integer, Symbol => Object}, nil]
129
+ # @return [Array<Object>, nil]
130
+ #: (untyped values) -> untyped
131
+ def self.normalize_row_values(values)
132
+ return values unless values.is_a?(Hash)
133
+
134
+ cells_array = []
135
+ values.each do |k, v|
136
+ cells_array[Elements::Cell.column_index(k)] = v
137
+ end
138
+ cells_array
139
+ end
140
+
141
+ # Normalizes row styles: if given a Hash of column-keyed styles (including Ranges/Arrays),
142
+ # expands to a sparse Array of styles. If already an Array or nil, returns as-is without allocations.
143
+ #
144
+ # @param styles [untyped]
145
+ # @return [untyped]
146
+ #: (untyped styles) -> untyped
147
+ def self.normalize_row_styles(styles)
148
+ return styles unless styles.is_a?(Hash)
149
+
150
+ styles_array = []
151
+ styles.each do |k, v|
152
+ if k.is_a?(Range) || k.is_a?(Array)
153
+ k.each { |idx| styles_array[Elements::Cell.column_index(idx)] = v }
154
+ else
155
+ styles_array[Elements::Cell.column_index(k)] = v
156
+ end
157
+ end
158
+ styles_array
159
+ end
160
+
161
+ # Validates row index and height against Excel limits in strict mode.
162
+ #
163
+ # @param row_index [Integer]
164
+ # @param height [Float, Integer, nil]
165
+ # @param strict_excel_mode [Boolean]
166
+ # @return [void]
167
+ #: (Integer row_index, Float | Integer | nil height, ?strict_excel_mode: bool) -> void
168
+ def self.validate_row_bounds!(row_index, height, strict_excel_mode: true)
169
+ return unless strict_excel_mode
170
+
171
+ raise ArgumentError, "Row index #{row_index} exceeds Excel limit of 1,048,576 rows" if row_index >= 1_048_576
172
+ raise ArgumentError, "Row height #{height} must be between 0 and 409 points (Excel limitation)" if height && (height.negative? || height > 409)
173
+ end
115
174
  end
116
175
  end