xlsxrb 0.1.15 → 0.1.16
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +61 -0
- data/README.md +22 -3
- data/benchmark/micro_ips.rb +70 -0
- data/lib/xlsxrb/elements/styles.rb +427 -15
- data/lib/xlsxrb/elements/workbook.rb +31 -4
- data/lib/xlsxrb/elements/worksheet.rb +22 -7
- data/lib/xlsxrb/ooxml/cfb.rb +10 -0
- data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +56 -38
- data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +7 -1
- data/lib/xlsxrb/ooxml/reader/listeners/style_listeners.rb +12 -7
- data/lib/xlsxrb/ooxml/reader.rb +165 -136
- data/lib/xlsxrb/ooxml/shared_strings_parser.rb +83 -28
- data/lib/xlsxrb/ooxml/styles_parser.rb +57 -14
- data/lib/xlsxrb/ooxml/utils.rb +59 -2
- data/lib/xlsxrb/ooxml/workbook_parser.rb +42 -3
- data/lib/xlsxrb/ooxml/workbook_writer.rb +215 -50
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +192 -123
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +238 -108
- data/lib/xlsxrb/ooxml/writer/drawing_xml.rb +16 -12
- data/lib/xlsxrb/ooxml/writer/styles_xml.rb +64 -15
- data/lib/xlsxrb/ooxml/writer.rb +31 -28
- data/lib/xlsxrb/ooxml/xml_builder.rb +12 -4
- data/lib/xlsxrb/ooxml/xml_parser.rb +45 -0
- data/lib/xlsxrb/ooxml/zip_reader.rb +48 -29
- data/lib/xlsxrb/stream_row.rb +4 -3
- data/lib/xlsxrb/stream_sheet.rb +26 -6
- data/lib/xlsxrb/stream_writer.rb +35 -14
- data/lib/xlsxrb/utils.rb +15 -0
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +110 -10
- data/lib/xlsxrb/worksheet_builder.rb +15 -7
- data/lib/xlsxrb.rb +416 -108
- data/sig/generated/xlsxrb/elements/styles.rbs +271 -5
- data/sig/generated/xlsxrb/elements/workbook.rbs +26 -3
- data/sig/generated/xlsxrb/elements/worksheet.rbs +12 -2
- data/sig/generated/xlsxrb/ooxml/reader/listeners/style_listeners.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/reader.rbs +12 -0
- data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +3 -1
- data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/utils.rbs +22 -0
- data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +7 -0
- data/sig/generated/xlsxrb/ooxml/workbook_writer.rbs +12 -0
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +18 -2
- data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +3 -1
- data/sig/generated/xlsxrb/ooxml/writer/styles_xml.rbs +5 -2
- data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +4 -1
- data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +6 -6
- data/sig/generated/xlsxrb/stream_row.rbs +3 -2
- data/sig/generated/xlsxrb/stream_sheet.rbs +21 -5
- data/sig/generated/xlsxrb/stream_writer.rbs +18 -11
- data/sig/generated/xlsxrb/utils.rbs +13 -0
- data/sig/generated/xlsxrb/worksheet_builder.rbs +3 -2
- data/sig/generated/xlsxrb.rbs +47 -10
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7edcf69207f83781642d4628e25802bd9f25e6db4caac46c4419cc803c225d4c
|
|
4
|
+
data.tar.gz: 6af71208f72dd6f2b94f4127faca05bce3046cd40c5a1ca9c605935eac754bad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b52768fc57d885db35b14c793e451c99c60045d56e5dad8ff549108fd1b25c825b8ad49dfb98d70e8f17a4512542129aeec0d793f3ed1d148b5cbcd228493523
|
|
7
|
+
data.tar.gz: d701b2f511498b462377ab7dcfcbe3288f331bd5e3df2c1c25c9a326abee7e5b1c6b583973f9afb5359a8affd158a33350aee186c4949055ea4d711e5db1a5fc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.1.16] - 2026-10-24
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Encapsulate `Elements::Styles` with dedicated collection readers (`fonts`, `fills`, `borders`, `cell_xfs`, `num_fmts`, `dxfs`, `cell_style_xfs`, `cell_styles`, `table_styles`, `indexed_colors`, `mru_colors`, `colors`) and item lookups (`font`, `fill`, `border`, `cell_xf` / `xf`, `num_fmt`, `dxf`, `cell_style_xf`, `cell_style`), removing public Hash delegation (`[]`, `[]=`, `fetch`, `dig`, `keys`, `values`, `each`, `merge!`), and provide builder helpers (`add_font`, `add_fill`, `add_border`, `add_num_fmt`, `add_xf`, `add_dxf`, `add_cell_style_xf`, `add_cell_style`, `add_style`) that append entries, invalidate memoized caches, and return allocated IDs.
|
|
7
|
+
- Enforce binary encoding (`ASCII-8BIT`) on raw XML buffers in `StreamRow` and document byte vs character indexing consistency policy in `AGENTS.md` to prevent terminal row truncation or byte-offset drift when scanning documents with multibyte UTF-8 characters.
|
|
8
|
+
- Clarify streaming write memory profile and date/time timezone semantics in README and YARD documentation.
|
|
9
|
+
- Reading a file that is not a ZIP archive (including an empty file) raises `Xlsxrb::ZipError` instead of `ArgumentError`.
|
|
10
|
+
- Strict numeric cell values: an explicit numeric cell (`t="n"`) with an invalid number raises `Xlsxrb::ParseError`, while an empty `<v>` reads as `nil` instead of `0`, exponent notation such as `1E-3` reads as a Float, and non-numeric values in cells without `t` fall back to their string payload. Inline string and formula string cells without text read as `nil`, and `t="d"` cells read as their ISO 8601 text.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Compound File Binary (CFB) header validation: Validate major version (3 or 4) and sector shifts in `Ooxml::Cfb::Reader#parse_header` and bounds-check sector reading against file size in `read_sector`, preventing `RangeError` integer overflows on corrupted or tampered inputs and raising `Xlsxrb::Error` gracefully.
|
|
14
|
+
- Unstyled empty cells and column ranges: Suppress style attribute (`s="..."`) emission on unstyled empty cells in `WorksheetWriter#write_row`, `write_cell`, and `write_row_values` to prevent unintended style inheritance explosion when rows or columns have styles, and preserve column `min`..`max` ranges on round-trip across `build_worksheet`, `Elements::Column`, and `WorksheetWriter#write_columns`.
|
|
15
|
+
- Data validation `showDropDown` and `imeMode` attributes: Serialize `showDropDown="1"` and `imeMode` on `<dataValidation>` elements in `WorksheetWriter#write_data_validations`, matching `Ooxml::Writer` and `DataValidationsListener`.
|
|
16
|
+
- Sheet views and selection serialization: Omit hardcoded `tabSelected="1"` in `WorksheetWriter#write_sheet_views` unless `tab_selected` is true, preventing multiple sheets from opening simultaneously selected in Excel, serialize full sheet view properties (`top_left_cell`, `zoom_scale_normal`, `view`, etc.), and preserve active cell selection across `WorksheetWriter`, `Elements::Worksheet`, `StreamSheet`, and `Xlsxrb.write`.
|
|
17
|
+
- Differential format (`<dxfs>`) parsing and boolean font properties: Parse differential formats (`<dxf>`) in `Ooxml::StylesParser` and serialize font, `numFmt`, fill, alignment, border, and protection in `WorkbookWriter`, parse and serialize ECMA-376 `CT_BooleanProperty` attributes (`<b val="0"/>`, `<i val="0"/>`) for font properties, and preserve `<color auto="1"/>` in `StylesParser`.
|
|
18
|
+
- Column and row style inheritance and serialization: Resolve named and inline column and row styles to integer style indices and propagate style inheritance to unstyled cells in `WorkbookBuilder#process_styles`, serialize row style attribute `s` on streaming `<row>` elements in `WorksheetWriter#write_row_values`, and guard `<col style>` emission against non-integer identifiers.
|
|
19
|
+
- Resolve boolean (`t="b"`) formula `cached_value` payloads (`"1"`, `"0"`, `"true"`, `"false"`) to `true` and `false` in `Ooxml::Reader::WorksheetListener#store_cell_value`, matching streaming and DOM cell parsers.
|
|
20
|
+
- Forward `:type`, `:ref`, and `:shared_index` (`:si`) formula attributes when building cells from a formula Hash (`{ formula: ..., type: :array, ref: ... }`) in `WorksheetBuilder#row`, `Xlsxrb.build_raw_cell`, and `Xlsxrb.build_raw_cell_from_value`.
|
|
21
|
+
- Unify `Ooxml::Reader#extract_zip_entry` and `Ooxml::Reader#entry_names` with `Ooxml::ZipReader` to support case-insensitive OPC part name resolution (per ECMA-376 Part 2 §9.1.1.2), ZIP64 archives, and data-descriptor streams uniformly across all reader APIs.
|
|
22
|
+
- Exclude non-worksheet relationship parts (`chartsheet`, `dialogsheet`, `macrosheet`, `xlMacrosheet`, `xlIntlMacrosheet`) and `xl/chartsheets/`, `xl/dialogsheets/`, `xl/macrosheets/` targets from standard worksheet enumeration in `Xlsxrb.read`, `Xlsxrb.modify`, and `Ooxml::Reader#discover_sheets`.
|
|
23
|
+
- Excel date boundary guards: Raise `ArgumentError` when writing dates before the date system epoch (`1899-12-31` / `1900-01-01` in 1900 date system or `1904-01-01` in 1904 date system) or after `9999-12-31` in `date_to_serial`, and raise `ArgumentError` when reading negative serials or serials exceeding `9999-12-31` (`serial >= 2_958_466` in 1900 system or `serial >= 2_957_004` in 1904 system) in `serial_to_date`, adhering to ECMA-376 date constraints and preventing corrupted Julian day calculations or negative date representation errors.
|
|
24
|
+
- Formula cached value falsy resolution and boolean typing: Fix falsy checks on formula `cached_value` across `Xlsxrb`, `build_raw_cell_from_value`, and `WorksheetWriter` so that boolean `false` and numeric `0` are not skipped or converted to `nil`, and serialize boolean cached values with cell type `t="b"` and `<v>1</v>` / `<v>0</v>` per ECMA-376 Part 4 §18.3.1.4.
|
|
25
|
+
- Boolean cell parsing tolerance: Support text payloads `"true"` and `"false"` (case-insensitive) in addition to numeric `"1"` and `"0"` for cells with boolean type attribute `t="b"`, conforming to ECMA-376 `xs:boolean` specification and preventing boolean `true` cells from evaluating to `false`.
|
|
26
|
+
- Template modification in `Xlsxrb.modify`: Support case-insensitive and slash-normalized archive entry replacement per ECMA-376 Part 2 §7.3, preventing shadowed or duplicate entries when archives use mixed casing (such as `xl/SharedStrings.xml`) or Windows backslashes (such as `xl\worksheets\sheet1.xml`). Preserve XML namespace prefixes (such as `<x:worksheet>`, `<x:sst>`, `<x:si>`) during sheetData replacement and shared strings appending, prevent duplicate `xmlns:r` attribute definitions in namespaced and ISO/IEC 29500 Strict documents, and correctly anchor `<hyperlinks>` and `<legacyDrawing>` after preceding root container elements to avoid illegal nesting inside `<customSheetViews>` or extension blocks.
|
|
27
|
+
- Resolve Open Packaging Conventions (OPC) workbook part path dynamically via root relationships (`_rels/.rels`), enabling reading and modifying workbooks with non-standard part names or root locations (such as `xl/workbook2.xml` or `workbook.xml`), and fall back to standard sheet and part locations when workbook relationships are missing or corrupted.
|
|
28
|
+
- Handle multibyte UTF-8 characters encoded as ASCII-8BIT (binary) strings during XML escaping and buffer concatenation in worksheet writer, workbook writer, and streaming sheet, preventing `Encoding::CompatibilityError` when cell values or formulas contain multibyte text.
|
|
29
|
+
- ECMA-376 schema conformance during workbook serialization: Format hash-based colors (`{ theme: ... }`, `{ indexed: ... }`, `{ auto: ... }`) into schema-compliant `<color>` elements in stylesheets and workbooks instead of emitting unparseable Hash string representations (`rgb="{...}"`), strictly validate 6/8-character hex colors against `ST_UnsignedIntHex`, reorder DrawingML line and character properties (`a:ln`, `a:rPr`) to satisfy ECMA-376 `xs:sequence` child constraints, preserve leading and trailing whitespace using `xml:space="preserve"` on `<si><t>` elements in `sharedStrings.xml`, drop data validations missing required `sqref` references, and omit negative `firstPageNumber` values in worksheet `page_setup`.
|
|
30
|
+
- String fallback when cell type attribute `t` is omitted: Parse numeric strings (integers, floats, exponents) when cell type attribute is omitted, while treating non-numeric text payloads in `<v>` as raw strings rather than raising `Xlsxrb::ParseError`, allowing parsing of cells generated by Open XML SDK and other spreadsheet producers without crashing.
|
|
31
|
+
- XML namespace prefix tolerance in shared strings parser (`Ooxml::SharedStringsParser`): Locate `<sst>`, `<si>`, `<t>`, and `<rPh>` tags dynamically regardless of XML namespace prefixes (such as `<x:sst>`, `<x:si>`, `<x:t>`), preventing dropped shared strings in spreadsheets produced by Microsoft Open XML SDK, ClosedXML, and other XML-prefixed generators.
|
|
32
|
+
- Consolidate cell value interpretation across streaming fast-scan direct, row values, events, and SAX listener parsers into a centralized `Ooxml::WorksheetParser.resolve_cell_value` helper, eliminating redundant logic and preventing parser divergence.
|
|
33
|
+
- Preserve worksheet and workbook attributes during in-memory writes (`Xlsxrb.write(workbook)`): accurately map and serialize `freeze_pane`, `split_pane`, `sheet_view`, `page_setup`, `page_margins`, `header_footer`, `print_options`, `sheet_protection`, `tables`, `merged_cells`, `auto_filter`, `row_breaks`, `col_breaks`, and embedded images from `Elements::Worksheet`, and propagate workbook properties (`core_properties`, `app_properties`, `custom_properties`, `workbook_protection`).
|
|
34
|
+
- Support arbitrary XML attribute ordering on cells (such as `t` before `s` or `r` after `s`) in streaming fast-scan parsers, ensuring cell types and styles are never dropped regardless of producer attribute sequence.
|
|
35
|
+
- Empty self-closing string cells without `<v>` in fast scan parsers evaluate to `nil` instead of empty string `""`.
|
|
36
|
+
- In-memory SAX listeners (`WorksheetListener` and `SharedStringsListener`) skip phonetic readings (`<rPh>`) and treat empty self-closing string cells (`<c ... t="s"/>`) as nil, matching streaming reader behavior.
|
|
37
|
+
- Rich text parsing in shared strings parser (`Ooxml::SharedStringsParser`): Handle self-closing `<t/>` tags when the first run is empty without dropping subsequent runs, while bounding phonetic tag scans to maintain linear throughput.
|
|
38
|
+
- `extract_inline_text` and `extract_multi_t` strictly distinguish `<t>` elements from other XML tags containing 't' (such as `<rPr>`, `<vertAlign>`, or `<table>`), preventing corrupted inline string parsing in rich text runs.
|
|
39
|
+
- Formulas are serialized strictly when explicitly defined via `Xlsxrb.formula(...)` or `{ formula: ... }`. Plain string cells starting with `=` are always serialized as text cells, preventing formula injection vulnerabilities and unintended 0-value coercions on reload.
|
|
40
|
+
- Streaming reads no longer drop the cell that follows a self-closing empty cell (`<c r="A1" s="1"/>`) in `StreamRow#values` and `StreamRow#cells`.
|
|
41
|
+
- Streaming reads place a row-leading cell with a multi-letter column reference (`AB1`) in its own column instead of column A.
|
|
42
|
+
- Phonetic readings (`<rPh>`) are excluded from shared strings and inline strings, so cells with furigana no longer concatenate phonetic readings to display text.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
- Extract and preserve `activeTab` (`<workbookView>`) and `fullCalcOnLoad` (`<calcPr>`) in `Ooxml::WorkbookParser`, `Xlsxrb.read`, and `Xlsxrb.modify`, and expose `workbook_properties`, `active_tab`, and `full_calc_on_load?` (`full_calc_on_load`) readers on `Elements::Workbook`.
|
|
46
|
+
- Support self-closing row tags (`<row r="N" .../>`): Extract row indices and custom row attributes (`ht`, `hidden`, `customHeight`, `outlineLevel`, `collapsed`, `s`) and emit `:row_start` / `:row_end` events and empty `StreamRow` instances across direct and streaming worksheet parsers.
|
|
47
|
+
- ISO/IEC 29500-1 ST_Xstring control character decoding: Support decoding `_xHHHH_` hexadecimal escape sequences (such as `_x000D_` carriage returns and `_x005F_` literal underscores) in `XmlBuilder.unescape`, `SharedStringsParser`, and `SharedStringsListener`, correctly normalizing newlines and preserving control character fidelity in cell strings.
|
|
48
|
+
- Record Architecture Decision Record [ADR 0001: Excel Date System Boundary Handling](docs/adr/0001-excel-date-system-boundary-handling.md) documenting date conversion semantics, ecosystem survey, and design rationale across 1900 and 1904 date systems.
|
|
49
|
+
- Date validation and boundary checks: Add `Xlsxrb::Ooxml::Utils.valid_excel_date?` (and delegates on `Xlsxrb::Utils` and `Xlsxrb`) to check if a numeric serial value falls within the supported Excel date boundaries [0, 2,958,465] (1900 system) or [0, 2,957,003] (1904 system).
|
|
50
|
+
- `ZipReader#open_entry_io` and `ZipReader#each_entry_chunk` support an `encoding:` parameter and automatically resolve `.xml` and `.rels` entries to UTF-8 encoding, preventing `Encoding::CompatibilityError` in downstream text and regular expression operations.
|
|
51
|
+
- `Xlsxrb::Ooxml::Utils.booleanize` and `Xlsxrb::Ooxml::Utils.camelize` helper methods for OOXML attribute serialization and name casing conversion.
|
|
52
|
+
- Round-trip contract tests in `test/contract/round_trip_fidelity_test.rb` verifying that `read -> write -> read` preserves worksheet and workbook attributes across streaming and loaded in-memory representations.
|
|
53
|
+
- Alias `conditional_formatting` to `conditional_formats` on `StreamSheet` for complete API symmetry with `Elements::Worksheet`.
|
|
54
|
+
- Property-based tests for cell XML parsing in `test/pbt/cell_xml_parser_pbt_test.rb`, validating 100% equivalence between streaming fast-scan and SAX listener parsers across random attribute permutations, namespaces, rich text, and phonetic elements.
|
|
55
|
+
- Dedicated exception `Xlsxrb::ZipBombError < Xlsxrb::ZipError` for archive expansion limit violations.
|
|
56
|
+
- Workbook view and calculation properties in in-memory writes: `Xlsxrb.write(workbook)` emits `<bookViews><workbookView activeTab="n"/></bookViews>` and `<calcPr fullCalcOnLoad="1"/>` when the workbook properties carry `active_tab:` and `full_calc_on_load: true`.
|
|
57
|
+
- Autonomous QA agent squad skill definitions and documentation: Added skill playbooks in `.agents/skills/` (`throughput-performance`, `security-resilience`, `api-contract`, `concurrency-safety`, `visual-fidelity`), and updated `docs/QA_AGENTS.md`, `docs/QUALITY_ASSURANCE.md`, `.agents/rules/qa_agents.md`, and `AGENTS.md`.
|
|
58
|
+
- Visual fidelity and Wasm compatibility verification agent (`visual-fidelity`): Formulated autonomous skill playbook covering headless LibreOffice Calc visual regression testing (`rake test:visual`) and WebAssembly playground asset verification (`rake doc:check`).
|
|
59
|
+
- API symmetry and contract verification agent (`api-contract`): Added reflective method signature and parameter arity parity tests between `WorksheetBuilder`, `StreamWriter`, and `WorksheetProxy` in `test/contract/api_symmetry_test.rb`, added `very_hidden?` predicate on both `Elements::Worksheet` and `StreamSheet`, and enforced lossless round-trip serialization contracts between streaming and in-memory workflows.
|
|
60
|
+
- Security and resilience verification agent (`security-resilience`): Added regular expression ReDoS audit tests in `test/security_resilience_test.rb` verifying linear processing under 10k+ character adversarial inputs, supported `max_uncompressed_size` option on `Xlsxrb.read` block form to protect streaming decompression from ZIP bombs, and added `escape_formulas: true` option on `Xlsxrb.write`, `StreamWriter`, and `WorksheetWriter` to prevent formula and CSV injection vulnerabilities.
|
|
61
|
+
- Throughput and complexity verification agent (`throughput-performance`): Added deterministic allocation bounds testing (< 25 objects/row write, < 15 objects/row read for 10k rows) and scaling ratio tests in `test/performance/throughput_test.rb`, hot-path microbenchmarks with `benchmark-ips` (`benchmark/micro_ips.rb`), and corresponding `benchmark:ips` rake task.
|
|
62
|
+
- Concurrency and Ractor safety verification agent (`concurrency-safety`): Added automated reflection scan testing that 100% of constants in the `Xlsxrb` namespace are `Ractor.shareable?`, deeply froze `EPOCH_1900`, `EPOCH_1904`, `INTEGER_STRINGS`, and `LOCAL_HEADER_SIG`, added Ractor-safe accessors for REXML security settings in `XmlParser`, and added full parallel write/read/modify pipeline verification across concurrent Ractors in `test/concurrent_test.rb`.
|
|
63
|
+
|
|
3
64
|
## [0.1.15] - 2026-10-17
|
|
4
65
|
|
|
5
66
|
### Added
|
data/README.md
CHANGED
|
@@ -23,7 +23,7 @@ The Ruby ecosystem has several XLSX libraries designed for specific tradeoffs:
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
These libraries make different architectural tradeoffs:
|
|
26
|
-
* Streaming Model: Writes or reads rows sequentially on-the-fly to maintain a
|
|
26
|
+
* Streaming Model: Writes or reads rows sequentially on-the-fly to maintain a low-memory footprint regardless of dataset size.
|
|
27
27
|
* 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.
|
|
28
28
|
|
|
29
29
|
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.
|
|
@@ -33,7 +33,7 @@ Maintaining a gem that supports both reading and writing across streaming and in
|
|
|
33
33
|
### Design Principles
|
|
34
34
|
|
|
35
35
|
- Zero Runtime Dependencies: Built strictly on the Ruby standard library and bundled gems (`zlib`, `rexml`, etc.) with zero third-party runtime dependencies.
|
|
36
|
-
- Streaming Support:
|
|
36
|
+
- Streaming Support: Low-memory row streaming for reading and writing spreadsheets ($O(1)$ memory on reads; writes buffer unique shared strings).
|
|
37
37
|
- OpenXML Interoperability: Compliant with ISO/IEC 29500 (ECMA-376) and validated against the Microsoft [Open XML SDK](https://github.com/dotnet/Open-XML-SDK).
|
|
38
38
|
- AI-Assisted Maintenance: Uses AI coding agents for automated quality assurance and verification workflows. Operational guidelines are defined in [AGENTS.md](AGENTS.md).
|
|
39
39
|
- Ruby 4.0+: Requires Ruby 4.0 or higher.
|
|
@@ -65,7 +65,7 @@ You can also browse 80+ rendered visual examples across all features in the [Vis
|
|
|
65
65
|
|
|
66
66
|
### Quick Start: Streaming (Recommended for Large Files)
|
|
67
67
|
|
|
68
|
-
#### Streaming Write (
|
|
68
|
+
#### Streaming Write (Low Memory)
|
|
69
69
|
```ruby
|
|
70
70
|
require "xlsxrb"
|
|
71
71
|
|
|
@@ -78,6 +78,9 @@ Xlsxrb.write("large_output.xlsx") do |writer|
|
|
|
78
78
|
end
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
> [!NOTE]
|
|
82
|
+
> Streaming writes pipe row and cell XML directly to the archive without buffering rows, keeping cell data memory constant. Because shared strings are indexed across the workbook to minimize output file size, memory scales with the count of distinct strings.
|
|
83
|
+
|
|
81
84
|
#### Streaming Read ($O(1)$ Memory)
|
|
82
85
|
```ruby
|
|
83
86
|
require "xlsxrb"
|
|
@@ -139,6 +142,22 @@ Xlsxrb.read("confidential.xlsx", password: "SecretPassword123") do |sheet|
|
|
|
139
142
|
end
|
|
140
143
|
```
|
|
141
144
|
|
|
145
|
+
### Date and Time Handling
|
|
146
|
+
|
|
147
|
+
Excel stores dates and times as floating-point serial numbers without timezone information. When reading spreadsheet files, `xlsxrb` returns date-formatted cells as UTC `Time` objects (`Time.utc`).
|
|
148
|
+
|
|
149
|
+
To interpret an Excel timestamp in a specific application timezone (such as JST or America/New_York), construct a local time using the year, month, day, hour, min, and sec components:
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
# With ActiveSupport / Rails:
|
|
153
|
+
local_time = Time.zone.local(cell.value.year, cell.value.month, cell.value.day,
|
|
154
|
+
cell.value.hour, cell.value.min, cell.value.sec)
|
|
155
|
+
|
|
156
|
+
# With Ruby standard library:
|
|
157
|
+
local_time = Time.local(cell.value.year, cell.value.month, cell.value.day,
|
|
158
|
+
cell.value.hour, cell.value.min, cell.value.sec)
|
|
159
|
+
```
|
|
160
|
+
|
|
142
161
|
### IDE Autocompletion & Ruby LSP Support
|
|
143
162
|
|
|
144
163
|
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:
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "benchmark/ips"
|
|
4
|
+
require_relative "../lib/xlsxrb"
|
|
5
|
+
|
|
6
|
+
puts "=" * 70
|
|
7
|
+
puts "xlsxrb Hot-Path Microbenchmarks (benchmark-ips)"
|
|
8
|
+
puts "Ruby: #{RUBY_DESCRIPTION}"
|
|
9
|
+
puts "=" * 70
|
|
10
|
+
|
|
11
|
+
ips_time = (ENV["IPS_TIME"] || "2").to_f
|
|
12
|
+
ips_warmup = (ENV["IPS_WARMUP"] || "1").to_f
|
|
13
|
+
|
|
14
|
+
# 1. XML escaping hot path
|
|
15
|
+
sample_escape_string = "Customer & Company <sales@example.com> \"Q1 '2026'\""
|
|
16
|
+
puts "\n--- 1. XmlBuilder.escape ---"
|
|
17
|
+
Benchmark.ips do |x|
|
|
18
|
+
x.config(time: ips_time, warmup: ips_warmup)
|
|
19
|
+
x.report("XmlBuilder.escape") do
|
|
20
|
+
Xlsxrb::Ooxml::XmlBuilder.escape(sample_escape_string)
|
|
21
|
+
end
|
|
22
|
+
x.compare!
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# 2. Coordinate conversion hot path
|
|
26
|
+
puts "\n--- 2. Cell.calculate_column_index ---"
|
|
27
|
+
Benchmark.ips do |x|
|
|
28
|
+
x.config(time: ips_time, warmup: ips_warmup)
|
|
29
|
+
x.report("Cell.calculate_column_index ('XFD1048576')") do
|
|
30
|
+
Xlsxrb::Elements::Cell.calculate_column_index("XFD1048576")
|
|
31
|
+
end
|
|
32
|
+
x.report("Cell.calculate_column_letter (16384)") do
|
|
33
|
+
Xlsxrb::Elements::Cell.calculate_column_letter(16_384)
|
|
34
|
+
end
|
|
35
|
+
x.compare!
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# 3. Number formatting hot path
|
|
39
|
+
sample_numbers = [1234.5678, -42.0, 0.05, 1_000_000.99]
|
|
40
|
+
sample_format_code = "#,##0.00;(#,##0.00);\"-\""
|
|
41
|
+
puts "\n--- 3. NumberFormatter.format ---"
|
|
42
|
+
Benchmark.ips do |x|
|
|
43
|
+
x.config(time: ips_time, warmup: ips_warmup)
|
|
44
|
+
x.report("NumberFormatter.format (custom pattern)") do
|
|
45
|
+
sample_numbers.each do |num|
|
|
46
|
+
Xlsxrb::NumberFormatter.format(num, sample_format_code)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
x.report("NumberFormatter.format (currency)") do
|
|
50
|
+
sample_numbers.each do |num|
|
|
51
|
+
Xlsxrb::NumberFormatter.format(num, "$#,##0.00")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
x.compare!
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# 4. WorksheetParser row scanning hot path
|
|
58
|
+
sample_row_xml = '<row r="1" spans="1:4"><c r="A1" t="s"><v>0</v></c><c r="B1"><v>123.45</v></c><c r="C1" s="1"><v>44927</v></c><c r="D1" t="inlineStr"><is><t>Inline text</t></is></c></row>'
|
|
59
|
+
shared_strings = ["First String", "Second String"]
|
|
60
|
+
styles = {}
|
|
61
|
+
puts "\n--- 4. WorksheetParser Row Scanning ---"
|
|
62
|
+
Benchmark.ips do |x|
|
|
63
|
+
x.config(time: ips_time, warmup: ips_warmup)
|
|
64
|
+
x.report("WorksheetParser.fast_scan_row_values") do
|
|
65
|
+
Xlsxrb::Ooxml::WorksheetParser.fast_scan_row_values(sample_row_xml, 0, sample_row_xml.bytesize, shared_strings, styles)
|
|
66
|
+
end
|
|
67
|
+
x.compare!
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
puts "\nMicrobenchmarks complete."
|