Module: Xlsxrb
- Defined in:
- lib/xlsxrb.rb,
lib/xlsxrb/ooxml.rb,
lib/xlsxrb/version.rb,
lib/xlsxrb/elements.rb,
lib/xlsxrb/ooxml/cfb.rb,
lib/xlsxrb/stream_row.rb,
lib/xlsxrb/ooxml/utils.rb,
lib/xlsxrb/elements/row.rb,
lib/xlsxrb/ooxml/crypto.rb,
lib/xlsxrb/ooxml/reader.rb,
lib/xlsxrb/ooxml/writer.rb,
lib/xlsxrb/elements/cell.rb,
lib/xlsxrb/style_builder.rb,
lib/xlsxrb/elements/types.rb,
lib/xlsxrb/elements/column.rb,
lib/xlsxrb/ooxml/xml_parser.rb,
lib/xlsxrb/ooxml/zip_reader.rb,
lib/xlsxrb/ooxml/zip_writer.rb,
lib/xlsxrb/elements/workbook.rb,
lib/xlsxrb/ooxml/xml_builder.rb,
lib/xlsxrb/elements/worksheet.rb,
lib/xlsxrb/ooxml/crypto/agile.rb,
lib/xlsxrb/ooxml/styles_parser.rb,
lib/xlsxrb/ooxml/zip_generator.rb,
lib/xlsxrb/ooxml/crypto/standard.rb,
lib/xlsxrb/ooxml/workbook_parser.rb,
lib/xlsxrb/ooxml/workbook_writer.rb,
lib/xlsxrb/ooxml/worksheet_parser.rb,
lib/xlsxrb/ooxml/worksheet_writer.rb,
lib/xlsxrb/elements/coordinate_access.rb,
lib/xlsxrb/ooxml/shared_strings_parser.rb,
sig/generated/xlsxrb.rbs,
sig/generated/xlsxrb/ooxml.rbs,
sig/generated/xlsxrb/version.rbs,
sig/generated/xlsxrb/elements.rbs,
sig/generated/xlsxrb/ooxml/cfb.rbs,
sig/generated/xlsxrb/stream_row.rbs,
sig/generated/xlsxrb/ooxml/utils.rbs,
sig/generated/xlsxrb/elements/row.rbs,
sig/generated/xlsxrb/ooxml/crypto.rbs,
sig/generated/xlsxrb/ooxml/reader.rbs,
sig/generated/xlsxrb/ooxml/writer.rbs,
sig/generated/xlsxrb/elements/cell.rbs,
sig/generated/xlsxrb/style_builder.rbs,
sig/generated/xlsxrb/elements/types.rbs,
sig/generated/xlsxrb/elements/column.rbs,
sig/generated/xlsxrb/ooxml/xml_parser.rbs,
sig/generated/xlsxrb/ooxml/zip_reader.rbs,
sig/generated/xlsxrb/ooxml/zip_writer.rbs,
sig/generated/xlsxrb/elements/workbook.rbs,
sig/generated/xlsxrb/ooxml/xml_builder.rbs,
sig/generated/xlsxrb/elements/worksheet.rbs,
sig/generated/xlsxrb/ooxml/crypto/agile.rbs,
sig/generated/xlsxrb/ooxml/styles_parser.rbs,
sig/generated/xlsxrb/ooxml/zip_generator.rbs,
sig/generated/xlsxrb/ooxml/crypto/standard.rbs,
sig/generated/xlsxrb/ooxml/workbook_parser.rbs,
sig/generated/xlsxrb/ooxml/workbook_writer.rbs,
sig/generated/xlsxrb/ooxml/worksheet_parser.rbs,
sig/generated/xlsxrb/ooxml/worksheet_writer.rbs,
sig/generated/xlsxrb/elements/coordinate_access.rbs,
sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs
Overview
Ruby XLSX read/write library.
Defined Under Namespace
Modules: Elements, Ooxml Classes: ChartBuilder, DecryptionError, EncryptedFileError, Error, InvalidPasswordError, ParseError, StreamRow, StreamSheet, StreamWriter, StyleBuilder, ValidationError, WorkbookBuilder, WorksheetBuilder, ZipError
Constant Summary collapse
- TRACER =
OpenTelemetry.tracer_provider.tracer("xlsxrb", Xlsxrb::VERSION)
- VERSION =
"0.1.9"
Class Method Summary collapse
-
.build(strict_excel_mode: true) {|builder| ... } ⇒ Elements::Workbook
Builds an in-memory Elements::Workbook using a declarative DSL.
-
.build_raw_cell_from_value(row_index, col_index, value, sst, sst_index) ⇒ Object
Builds a raw cell hash from a value for streaming writes.
-
.formula(expression, cached_value: nil) ⇒ Elements::Formula
Creates a Formula object for use in row values.
- .in_span(name, attributes: nil) ⇒ void
-
.modify(source, target = nil, password: nil) {|workbook| ... } ⇒ void
Modifies an existing XLSX file.
-
.read(source, password: nil) {|sheet| ... } ⇒ Elements::Workbook, void
Reads an XLSX file (streaming / lazy-loaded by default) from a file path, IO stream, or binary String.
-
.rich_text(*runs, text: nil, **font_props) ⇒ Elements::RichText
Helper to easily create RichText objects.
-
.write(target_or_workbook, workbook_or_nil = nil, password: nil, encryption_mode: :standard, strict_excel_mode: true, &block) ⇒ Object
Writes an XLSX file or IO stream (streaming or in-memory), or returns a binary string.
Class Method Details
.build(strict_excel_mode: true) {|builder| ... } ⇒ Elements::Workbook
Builds an in-memory Elements::Workbook using a declarative DSL.
: (?strict_excel_mode: bool) ?{ (WorkbookBuilder) -> void } -> Elements::Workbook
791 792 793 794 795 796 797 798 799 |
# File 'lib/xlsxrb.rb', line 791 def self.build(strict_excel_mode: true) raise Error, "block is required" unless block_given? Xlsxrb.in_span("Xlsxrb.build") do builder = WorkbookBuilder.new(strict_excel_mode: strict_excel_mode) yield builder builder.build end end |
.build_raw_cell_from_value(row_index, col_index, value, sst, sst_index) ⇒ Object
Builds a raw cell hash from a value for streaming writes. : (untyped row_index, untyped col_index, untyped value, untyped sst, untyped sst_index) -> untyped
3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 |
# File 'lib/xlsxrb.rb', line 3374 def self.build_raw_cell_from_value(row_index, col_index, value, sst, sst_index) # simplecov:disable # Edge case / untested delegation block ref = "#{Elements::Cell.column_letter(col_index)}#{row_index + 1}" result = { ref: ref } case value when Elements::Formula result[:formula] = value.expression result[:formula_ca] = true if value.calculate_always result[:value] = value.cached_value if value.cached_value when String idx = sst_index[value] ||= begin sst << value sst.size - 1 end result[:value] = idx result[:type] = "s" when true, false result[:value] = value result[:type] = "b" when Integer, Float result[:value] = value when Date result[:value] = Xlsxrb::Ooxml::Utils.date_to_serial(value) when Time result[:value] = Xlsxrb::Ooxml::Utils.datetime_to_serial(value) # simplecov:enable when NilClass # empty cell end # simplecov:disable # Edge case / untested delegation block result # simplecov:enable end |
.formula(expression, cached_value: nil) ⇒ Elements::Formula
Creates a Formula object for use in row values.
: (String expression, ?cached_value: String | Numeric | bool | nil) -> Elements::Formula
360 361 362 363 364 365 366 |
# File 'lib/xlsxrb.rb', line 360 def self.formula(expression, cached_value: nil) Elements::Formula.new( expression: expression, cached_value: cached_value, calculate_always: cached_value.nil? || nil ) end |
.in_span(name, attributes: nil) ⇒ void
This method returns an undefined value.
52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/xlsxrb.rb', line 52 def self.in_span(name, attributes: nil, &) if defined?(Ractor) && Ractor.current != Ractor.main # simplecov:disable # Test suite runs in the main Ractor. This branch is for multi-threaded usage via Ractors. yield # simplecov:enable elsif attributes TRACER.in_span(name, attributes: attributes, &) else TRACER.in_span(name, &) end end |
.modify(source, target = nil, password: nil) {|workbook| ... } ⇒ void
This method returns an undefined value.
Modifies an existing XLSX file.
Reads the workbook, passes it to the block, and writes the result.
The block receives an Elements::Workbook and must return a modified one (e.g. via update_sheet).
If no target is given, the source is overwritten.
: (untyped source, ?untyped target, ?password: String?) ?{ (Elements::Workbook) -> untyped } -> void
663 664 665 666 667 668 669 670 671 672 673 |
# File 'lib/xlsxrb.rb', line 663 def self.modify(source, target = nil, password: nil) raise Error, "source is required" if source.nil? raise Error, "block is required" unless block_given? workbook = read(source, password: password).load result_workbook = yield workbook result_workbook = workbook unless result_workbook.is_a?(Elements::Workbook) write_target = target || source write(write_target, result_workbook, password: password) end |
.read(source, password:) ⇒ void .read(source, password:) ⇒ Elements::Workbook
Reads an XLSX file (streaming / lazy-loaded by default) from a file path, IO stream, or binary String.
Sheets and rows are streamed lazily with O(1) constant memory. If a block is given, yields each StreamSheet sequentially.
Call #load on the returned Workbook or Sheet to convert to an in-memory representation for coordinate random access (e.g. sheet).
: (untyped source, ?password: String?) { (StreamSheet) -> void } -> void : (untyped source, ?password: String?) -> Elements::Workbook
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 |
# File 'lib/xlsxrb.rb', line 399 def self.read(source, password: nil, &) if source.is_a?(String) if source.start_with?("PK\x03\x04") || source.include?("\x00") || Ooxml::Cfb::Reader.cfb?(source) if Ooxml::Cfb::Reader.cfb?(source) decrypted_zip = Ooxml::Crypto.decrypt(source, password) source = StringIO.new(decrypted_zip) else source = StringIO.new(source) end elsif File.file?(source) first_bytes = begin File.binread(source, 8) rescue StandardError nil end if Ooxml::Cfb::Reader.cfb?(first_bytes) encrypted_data = File.binread(source) decrypted_zip = Ooxml::Crypto.decrypt(encrypted_data, password) source = StringIO.new(decrypted_zip) end end elsif source.respond_to?(:read) && source.respond_to?(:pos) && source.respond_to?(:seek) begin cur_pos = source.pos first_bytes = source.read(8) source.seek(cur_pos) if Ooxml::Cfb::Reader.cfb?(first_bytes) full_data = source.read decrypted_zip = Ooxml::Crypto.decrypt(full_data, password) source = StringIO.new(decrypted_zip) end rescue StandardError # Fall through to standard reader if seeking fails end end attributes = source.is_a?(String) ? { "filepath" => source } : {} Xlsxrb.in_span("Xlsxrb.read", attributes: attributes) do entries = Ooxml::ZipReader.open(source, &:read_all) shared_strings = Ooxml::SharedStringsParser.parse(entries["xl/sharedStrings.xml"]) workbook_sheets = Ooxml::WorkbookParser.parse(entries["xl/workbook.xml"]) rels = Ooxml::RelationshipsParser.parse(entries["xl/_rels/workbook.xml.rels"]) styles = Ooxml::StylesParser.parse(entries["xl/styles.xml"]) sheets = workbook_sheets.map do |sheet_info| target = rels[sheet_info[:r_id]] next nil unless target sheet_path = target.start_with?("/") ? target.delete_prefix("/") : "xl/#{target}" sheet_xml = entries[sheet_path] next nil if sheet_xml.nil? || sheet_xml.empty? StreamSheet.new( sheet_info[:name], sheet_xml, shared_strings, styles ) end.compact wb = Elements::Workbook.new(sheets: sheets, shared_strings: shared_strings, styles: styles) if block_given? sheets.each(&) nil else wb end end end |
.rich_text(*runs, text: nil, **font_props) ⇒ Elements::RichText
Helper to easily create RichText objects.
Supports both Xlsxrb.rich_text({ text: "A" }, { text: "B" })
and Xlsxrb.rich_text(text: "Hi", bold: true)
: (*untyped runs, ?text: String?, **untyped font_props) -> untyped
75 76 77 78 |
# File 'lib/xlsxrb.rb', line 75 def self.rich_text(*runs, text: nil, **font_props) runs = [{ text: text, font: font_props }] if text Elements::RichText.new(runs: runs) end |
.write(target, password: nil, strict_excel_mode: true) {|stream_writer| ... } ⇒ void .write(workbook, password: nil) ⇒ String .write(target, workbook, password: nil) ⇒ void .write(workbook, password:, encryption_mode:) ⇒ String .write(target, workbook, password:, encryption_mode:) ⇒ void .write(target_or_workbook, workbook_or_nil, password:, encryption_mode:, strict_excel_mode:) ⇒ void
Writes an XLSX file or IO stream (streaming or in-memory), or returns a binary string.
: (Elements::Workbook workbook, ?password: String?, ?encryption_mode: Symbol) -> String : (untyped target, Elements::Workbook | untyped workbook, ?password: String?, ?encryption_mode: Symbol) -> void : (untyped target_or_workbook, ?Elements::Workbook | untyped workbook_or_nil, ?password: String?, ?encryption_mode: Symbol, ?strict_excel_mode: bool) ?{ (StreamWriter) -> void } -> untyped
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 |
# File 'lib/xlsxrb.rb', line 514 def self.write(target_or_workbook, workbook_or_nil = nil, password: nil, encryption_mode: :standard, strict_excel_mode: true, &block) if block_given? target = target_or_workbook raise Error, "target is required" if target.nil? attributes = target.is_a?(String) ? { "filepath" => target } : {} return Xlsxrb.in_span("Xlsxrb.write", attributes: attributes) do if password && !password.empty? buf = StringIO.new buf.binmode stream_writer = StreamWriter.new(buf, strict_excel_mode: strict_excel_mode) begin yield stream_writer stream_writer.close plain_bytes = buf.string.b encrypted_bytes = Ooxml::Crypto.encrypt(plain_bytes, password, mode: encryption_mode) if target.is_a?(String) File.binwrite(target, encrypted_bytes) elsif target.respond_to?(:write) target.write(encrypted_bytes) end ensure stream_writer.cleanup! end else stream_writer = StreamWriter.new(target, strict_excel_mode: strict_excel_mode) begin yield stream_writer stream_writer.close ensure stream_writer.cleanup! end end end end if workbook_or_nil.nil? wb = target_or_workbook raise Error, "workbook must be an Elements::Workbook" unless wb.is_a?(Elements::Workbook) io = StringIO.new io.binmode write(io, wb, password: password, encryption_mode: encryption_mode) return io.string.b end target = target_or_workbook workbook = workbook_or_nil raise Error, "target is required" if target.nil? raise Error, "workbook must be an Elements::Workbook" unless workbook.is_a?(Elements::Workbook) attributes = target.is_a?(String) ? { "filepath" => target } : {} Xlsxrb.in_span("Xlsxrb.write", attributes: attributes) do sst = [] sst_index = {} # Collect shared strings and build index without allocating new Hashes sheet_data = workbook.sheets.map do |raw_ws| ws = raw_ws.respond_to?(:load) ? raw_ws.load : raw_ws ws.rows.each do |row| row.cells.each do |cell| val = cell.value if (val.is_a?(String) || val.is_a?(Elements::RichText)) && !sst_index.key?(val) sst << val sst_index[val] = sst.size - 1 end end end columns = ws.columns.map do |col| # simplecov:disable # Edge case / untested delegation block { index: col.index, width: col.width, hidden: col.hidden, custom_width: col.custom_width, outline_level: col.outline_level } # simplecov:enable end sd = { name: ws.name, rows: ws.rows, columns: columns } sd[:charts] = ws.charts unless ws.charts.empty? # Extract facade metadata from unmapped_data facade = ws.unmapped_data[:facade] facade&.each { |key, val| sd[key] = val } sd end # Extract workbook-level facade metadata wb_facade = workbook.unmapped_data[:facade] || {} if password && !password.empty? buf = StringIO.new buf.binmode Ooxml::WorkbookWriter.write( buf, sheets: sheet_data, shared_strings: sst, shared_strings_index: sst_index, styles: workbook.styles, defined_names: wb_facade[:defined_names], core_properties: wb_facade[:core_properties], app_properties: wb_facade[:app_properties], custom_properties: wb_facade[:custom_properties], workbook_protection: wb_facade[:workbook_protection], workbook_properties: wb_facade[:workbook_properties] ) encrypted_bytes = Ooxml::Crypto.encrypt(buf.string.b, password, mode: encryption_mode) if target.is_a?(String) File.binwrite(target, encrypted_bytes) elsif target.respond_to?(:write) target.write(encrypted_bytes) end else Ooxml::WorkbookWriter.write( target, sheets: sheet_data, shared_strings: sst, shared_strings_index: sst_index, styles: workbook.styles, defined_names: wb_facade[:defined_names], core_properties: wb_facade[:core_properties], app_properties: wb_facade[:app_properties], custom_properties: wb_facade[:custom_properties], workbook_protection: wb_facade[:workbook_protection], workbook_properties: wb_facade[:workbook_properties] ) end end end |