Class: Xlsxrb::WorkbookBuilder
- Inherits:
-
Object
- Object
- Xlsxrb::WorkbookBuilder
- Defined in:
- lib/xlsxrb.rb,
sig/generated/xlsxrb.rbs
Overview
DSL context for Xlsxrb.build.
Instance Method Summary collapse
-
#absolute_range(range) ⇒ Object
: (untyped range) -> untyped.
-
#app_property(name, value) ⇒ void
Set an app document property.
-
#build ⇒ Object
: () -> untyped.
-
#core_property(name, value) ⇒ void
Set a core document property.
-
#custom_property(name, value, type: :string) ⇒ void
Add a custom document property.
-
#defined_name(name, value, sheet: nil, hidden: false) ⇒ void
Add a defined name.
-
#extract_styles_from_writer(writer) ⇒ { fonts: untyped, fills: untyped, borders: untyped, xf_entries: untyped, num_fmts: untyped }
: (untyped writer) -> { fonts: untyped, fills: untyped, borders: untyped, xf_entries: untyped, num_fmts: untyped }.
-
#initialize(strict_excel_mode: true) ⇒ WorkbookBuilder
constructor
: (?strict_excel_mode: bool) -> void.
-
#print_area(range, sheet: nil) ⇒ void
Set the print area for a sheet.
-
#print_titles(rows: nil, cols: nil, sheet: nil) ⇒ void
Set print titles for a sheet.
-
#process_styles(sheets) ⇒ ::Array[untyped | ::Hash[untyped, untyped]], ::Array[untyped]
: (untyped sheets) -> (::Array[untyped | ::Hash[untyped, untyped]] | ::Array).
-
#properties(core: nil, app: nil) ⇒ void
Set multiple core and/or app properties.
-
#protect_workbook(**opts) ⇒ void
Set workbook protection.
-
#resolve_defined_names(names, sheets) ⇒ Object
: (untyped names, untyped sheets) -> untyped.
-
#sheet(name = nil, **opts) {|sheet_builder| ... } ⇒ void
(also: #[])
Add a new sheet.
-
#workbook_property(name, value) ⇒ void
Set a workbook property.
Constructor Details
#initialize(strict_excel_mode: true) ⇒ WorkbookBuilder
: (?strict_excel_mode: bool) -> void
589 590 591 592 593 594 595 596 597 598 599 |
# File 'lib/xlsxrb.rb', line 589 def initialize(strict_excel_mode: true) @strict_excel_mode = strict_excel_mode @sheets = [] @sheet_builders = [] # Keep track of sheet builders for style processing @defined_names = [] @core_properties = {} @app_properties = {} @custom_properties = [] @workbook_protection = nil @workbook_properties = { update_links: "never" } end |
Instance Method Details
#absolute_range(range) ⇒ Object
: (untyped range) -> untyped
771 772 773 |
# File 'lib/xlsxrb.rb', line 771 def absolute_range(range) range.gsub(/([A-Z]+)(\d+)/, '$\1$\2') end |
#app_property(name, value) ⇒ void
This method returns an undefined value.
Set an app document property.
: (Symbol name, String | Integer | Time value) -> void
716 717 718 |
# File 'lib/xlsxrb.rb', line 716 def app_property(name, value) @app_properties[name] = value end |
#build ⇒ Object
: () -> untyped
746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 |
# File 'lib/xlsxrb.rb', line 746 def build raise ArgumentError, "Workbook must contain at least one sheet (Excel limitation)" if @strict_excel_mode && @sheets.empty? # Process styles from all sheets and collect style definitions processed_sheets, styles_definition = process_styles(@sheets) # Store workbook-level metadata in unmapped_data = {} [:defined_names] = resolve_defined_names(@defined_names, processed_sheets) unless @defined_names.empty? [:core_properties] = @core_properties unless @core_properties.empty? [:app_properties] = @app_properties unless @app_properties.empty? [:custom_properties] = @custom_properties unless @custom_properties.empty? [:workbook_protection] = @workbook_protection if @workbook_protection [:workbook_properties] = @workbook_properties unless @workbook_properties.empty? Elements::Workbook.new( sheets: processed_sheets, styles: styles_definition, unmapped_data: .empty? ? {} : { facade: } ) end |
#core_property(name, value) ⇒ void
This method returns an undefined value.
Set a core document property.
: (Symbol name, String | Integer | Time value) -> void
705 706 707 |
# File 'lib/xlsxrb.rb', line 705 def core_property(name, value) @core_properties[name] = value end |
#custom_property(name, value, type: :string) ⇒ void
This method returns an undefined value.
Add a custom document property.
: (String name, String | Integer | Float | bool | Time value, ?type: ::Symbol) -> void
740 741 742 |
# File 'lib/xlsxrb.rb', line 740 def custom_property(name, value, type: :string) @custom_properties << { name: name, value: value, type: type } end |
#defined_name(name, value, sheet: nil, hidden: false) ⇒ void
This method returns an undefined value.
Add a defined name.
: (String name, String value, ?sheet: String?, ?hidden: bool) -> void
650 651 652 653 654 |
# File 'lib/xlsxrb.rb', line 650 def defined_name(name, value, sheet: nil, hidden: false) entry = { name: name, value: value, hidden: hidden } entry[:local_sheet_name] = sheet if sheet @defined_names << entry end |
#extract_styles_from_writer(writer) ⇒ { fonts: untyped, fills: untyped, borders: untyped, xf_entries: untyped, num_fmts: untyped }
: (untyped writer) -> { fonts: untyped, fills: untyped, borders: untyped, xf_entries: untyped, num_fmts: untyped }
856 857 858 859 860 861 862 863 864 865 866 |
# File 'lib/xlsxrb.rb', line 856 def extract_styles_from_writer(writer) # Extract style definitions from the writer that can be reused # This captures the fonts, fills, borders, and xf entries that were created { fonts: writer.fonts.dup, fills: writer.fills.dup, borders: writer.borders.dup, xf_entries: writer.xf_entries.dup, num_fmts: writer.num_fmts.dup } end |
#print_area(range, sheet: nil) ⇒ void
This method returns an undefined value.
Set the print area for a sheet.
: (String range, ?sheet: String?) -> void
663 664 665 666 667 668 |
# File 'lib/xlsxrb.rb', line 663 def print_area(range, sheet: nil) sheet_name = sheet || @sheets.last&.name || "Sheet1" value = "'#{sheet_name}'!#{absolute_range(range)}" @defined_names.reject! { |dn| dn[:name] == "_xlnm.Print_Area" && dn[:local_sheet_name] == sheet_name } defined_name("_xlnm.Print_Area", value, sheet: sheet_name) end |
#print_titles(rows: nil, cols: nil, sheet: nil) ⇒ void
This method returns an undefined value.
Set print titles for a sheet.
: (?rows: String?, ?cols: String?, ?sheet: String?) -> void
678 679 680 681 682 683 684 685 686 |
# File 'lib/xlsxrb.rb', line 678 def print_titles(rows: nil, cols: nil, sheet: nil) sheet_name = sheet || @sheets.last&.name || "Sheet1" parts = [] parts << "'#{sheet_name}'!$#{cols.sub(":", ":$")}" if cols parts << "'#{sheet_name}'!$#{rows.sub(":", ":$")}" if rows value = parts.join(",") @defined_names.reject! { |dn| dn[:name] == "_xlnm.Print_Titles" && dn[:local_sheet_name] == sheet_name } defined_name("_xlnm.Print_Titles", value, sheet: sheet_name) end |
#process_styles(sheets) ⇒ ::Array[untyped | ::Hash[untyped, untyped]], ::Array[untyped]
: (untyped sheets) -> (::Array[untyped | ::Hash[untyped, untyped]] | ::Array)
790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 |
# File 'lib/xlsxrb.rb', line 790 def process_styles(sheets) # Collect all unique StyleBuilders from all sheets all_style_builders = {} @sheet_builders.each do |sheet_builder| sheet_builder.styles.each do |style_name, style_builder| all_style_builders[style_name] = style_builder end end return [sheets, {}] if all_style_builders.empty? # Create a temporary writer to register styles and get numeric IDs temp_writer = Ooxml::Writer.new style_name_to_id = {} all_style_builders.each do |name, builder| style_id = builder.register_with(temp_writer) style_name_to_id[name] = style_id end # Capture the style definitions from the temporary writer styles_definition = extract_styles_from_writer(temp_writer) # Update all cells with their resolved style IDs updated_sheets = sheets.map do |sheet| new_rows = sheet.rows.map do |row| new_cells = row.cells.map do |cell| # If style_index is a string (style name), resolve it to a numeric ID if cell.style_index.is_a?(String) && style_name_to_id.key?(cell.style_index) Elements::Cell.new( row_index: cell.row_index, column_index: cell.column_index, value: cell.value, formula: cell.formula, style_index: style_name_to_id[cell.style_index], unmapped_data: cell.unmapped_data, errors: cell.errors ) else cell end end Elements::Row.new( index: row.index, cells: new_cells, height: row.height, hidden: row.hidden, custom_height: row.custom_height, outline_level: row.outline_level, unmapped_data: row.unmapped_data, errors: row.errors ) end Elements::Worksheet.new( name: sheet.name, rows: new_rows, columns: sheet.columns, charts: sheet.charts, unmapped_data: sheet.unmapped_data, errors: sheet.errors ) end [updated_sheets, styles_definition] end |
#properties(core: nil, app: nil) ⇒ void
This method returns an undefined value.
Set multiple core and/or app properties.
: (?core: Hash[Symbol, String | Integer | Time]?, ?app: Hash[Symbol, String | Integer | Time]?) -> void
727 728 729 730 |
# File 'lib/xlsxrb.rb', line 727 def properties(core: nil, app: nil) core&.each { |k, v| core_property(k, v) } app&.each { |k, v| app_property(k, v) } end |
#protect_workbook(**opts) ⇒ void
This method returns an undefined value.
Set workbook protection.
: (**String | Integer | bool | nil opts) -> void
694 695 696 |
# File 'lib/xlsxrb.rb', line 694 def protect_workbook(**opts) @workbook_protection = opts end |
#resolve_defined_names(names, sheets) ⇒ Object
: (untyped names, untyped sheets) -> untyped
776 777 778 779 780 781 782 783 784 785 786 787 |
# File 'lib/xlsxrb.rb', line 776 def resolve_defined_names(names, sheets) sheet_names = sheets.map(&:name) names.map do |dn| resolved = dn.dup if dn[:local_sheet_name] idx = sheet_names.index(dn[:local_sheet_name]) resolved[:local_sheet_id] = idx if idx resolved.delete(:local_sheet_name) end resolved end end |
#sheet(name = nil, **opts) {|sheet_builder| ... } ⇒ void Also known as: []
This method returns an undefined value.
Add a new sheet.
: (?String? name, **untyped opts) ?{ (WorksheetBuilder) -> void } -> untyped
625 626 627 628 629 630 631 632 633 634 635 636 |
# File 'lib/xlsxrb.rb', line 625 def sheet(name = nil, **opts) name ||= "Sheet#{@sheets.size + 1}" raise ArgumentError, "Sheet name '#{name}' must be <= 31 characters (Excel limitation)" if @strict_excel_mode && name.length > 31 raise ArgumentError, "Sheet name '#{name}' contains invalid characters (ECMA-376 OOXML specification)" if name.match?(%r{[\[\]*?/\\]}) raise ArgumentError, "Sheet name '#{name}' is already used. Excel requires unique sheet names." if @strict_excel_mode && @sheets.map { |s| s.respond_to?(:name) ? s.name.downcase : s.to_s.downcase }.include?(name.downcase) sheet_builder = WorksheetBuilder.new(name, strict_excel_mode: @strict_excel_mode) opts.each { |k, v| sheet_builder.sheet_properties(k, v) } yield sheet_builder if block_given? @sheet_builders << sheet_builder @sheets << sheet_builder.build end |
#workbook_property(name, value) ⇒ void
SECURITY WARNING: If you set :update_links to anything other than "never",
you may expose end-users to malicious external reference vulnerabilities (e.g., CSV/DDE Injection)
when they open the generated Excel file. Ensure you fully trust the exported data.
This method returns an undefined value.
Set a workbook property.
: (Symbol name, String | Integer | bool value) -> (String | Integer | bool)
612 613 614 |
# File 'lib/xlsxrb.rb', line 612 def workbook_property(name, value) @workbook_properties[name] = value end |