Class: Xlsxrb::StreamWriter
- Inherits:
-
Object
- Object
- Xlsxrb::StreamWriter
- Defined in:
- lib/xlsxrb/stream_writer.rb,
sig/generated/xlsxrb/stream_writer.rbs
Overview
High-performance streaming writer that outputs XLSX files with O(1) constant memory.
Defined Under Namespace
Classes: WorksheetProxy
Instance Attribute Summary collapse
-
#current_sheet ⇒ String?
readonly
: String?.
Instance Method Summary collapse
-
#absolute_range(range) ⇒ Object
: (untyped range) -> untyped.
-
#app_property(name, value) ⇒ void
Sets an app document property.
-
#auto_filter(range) ⇒ void
Sets the auto-filter range on the active sheet.
-
#chart(**options) {|builder| ... } ⇒ void
Adds a chart to the current worksheet.
-
#cleanup! ⇒ void
Explicitly cleans up any resources if not already closed.
-
#close ⇒ void
Finalizes and writes all streaming sheet contents to the destination target.
-
#column(index, width: nil, hidden: false, custom_width: false, outline_level: nil) ⇒ void
Sets column formatting and properties for one or multiple columns.
-
#comment(cell, text, author: "Author") ⇒ void
Adds a comment to a cell.
-
#conditional_format(sqref, **opts) ⇒ void
Adds a conditional formatting rule.
-
#core_property(name, value) ⇒ void
Sets a core document metadata property.
-
#custom_property(name, value, type: :string) ⇒ void
Adds a custom document property.
-
#defined_name(name, value, sheet: nil, hidden: false) ⇒ void
Adds a defined name.
-
#filter_column(col_id, filter) ⇒ void
Sets filter criteria on a column in the auto-filter.
-
#flush_current_sheet ⇒ nil, untyped
: () -> (nil | untyped).
-
#freeze_pane(row: 0, col: 0) ⇒ void
Freezes window panes at the given row and column.
-
#header_footer(**opts) ⇒ void
Configures headers and footers for printing.
-
#hyperlink(cell, url = nil, display: nil, tooltip: nil, location: nil) ⇒ void
Adds a hyperlink on a cell.
-
#image(file_data, ext: "png", from_col: 0, from_row: 0, to_col: 5, to_row: 10, **opts) ⇒ void
Inserts an embedded image into the active sheet.
-
#initialize(target, strict_excel_mode: true) ⇒ StreamWriter
constructor
Initializes a streaming writer context.
-
#internal_sheet_setup(name = nil) ⇒ void
Internal: Start or switch to a named sheet (internal helper).
-
#merge(range = nil, row: nil, col_start: nil, col_end: nil, row_start: nil, row_end: nil) ⇒ void
Merges a range of cells into a single cell.
-
#page_break_col(col_index) ⇒ void
Inserts a vertical page break before a column.
-
#page_break_row(row_num) ⇒ void
Inserts a horizontal page break before a row.
-
#page_margins(left: nil, right: nil, top: nil, bottom: nil, header: nil, footer: nil) ⇒ void
Sets page margins in inches for printing.
-
#page_setup(**opts) ⇒ void
Sets page setup configuration.
-
#pivot_table(source_ref, row_fields:, data_fields:, col_fields: [], dest_ref: "E1", name: nil, field_names: nil, items: nil) ⇒ void
Adds a Pivot Table.
-
#print_area(range, sheet: nil) ⇒ void
Sets the print area for the current or named sheet.
-
#print_options(name, value) ⇒ void
Sets a print option (e.g. grid_lines: true).
-
#print_titles(rows: nil, cols: nil, sheet: nil) ⇒ void
Sets print titles for the current or named sheet.
-
#properties(core: nil, app: nil, custom: nil) ⇒ void
Sets multiple core and/or app properties.
-
#protect_sheet(**opts) ⇒ void
Sets sheet-level protection with optional password hashing.
-
#protect_workbook(**opts) ⇒ void
Sets workbook protection.
-
#resolve_defined_names(names, sheets) ⇒ Object
: (untyped names, untyped sheets) -> untyped.
-
#row(values, styles: nil, height: nil, hidden: false, custom_height: false, outline_level: nil) ⇒ void
(also: #<<)
Appends a row of values to the active sheet.
-
#select_cell(active_cell, sqref: nil, pane: nil) ⇒ void
Sets the active cell selection on the current sheet.
-
#shape(preset: "rect", text: nil, from_col: 0, from_row: 0, to_col: 5, to_row: 5, **opts) ⇒ void
Inserts a drawing shape.
-
#sheet(name = nil, **opts) {|sheet_proxy| ... } ⇒ String
Adds a new sheet to the workbook and starts streaming rows into it.
-
#sheet_properties(name, value) ⇒ void
Sets a sheet-level property (e.g. tab_color: "FF0000").
-
#sheet_view(name, value) ⇒ void
Sets a sheet view property (e.g. zoom_scale: 120).
-
#sort_state(ref, sort_conditions, **opts) ⇒ void
Configures column sort state on the active sheet.
-
#sparkline_group(sparklines:, type: nil, **opts) ⇒ void
Adds a sparkline group.
-
#split_pane(x_split: 0, y_split: 0, top_left_cell: nil) ⇒ void
Splits window panes without freezing.
- #start_sheet_entry ⇒ Object
-
#style(name, **opts) {|style_builder| ... } ⇒ StyleBuilder
Defines or configures a named cell style.
-
#table(ref, columns:, name: nil, display_name: nil, style: nil, **opts) ⇒ void
Adds an Excel Table (ListObject).
-
#validate_data(sqref, **opts) ⇒ void
Adds a data validation rule.
-
#workbook_property(name, value) ⇒ void
Sets a workbook property.
Constructor Details
#initialize(target, strict_excel_mode: true) ⇒ StreamWriter
Initializes a streaming writer context.
: (untyped target, ?strict_excel_mode: bool) -> void
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/xlsxrb/stream_writer.rb', line 34 def initialize(target, strict_excel_mode: true) @target = target @strict_excel_mode = strict_excel_mode @io = target.is_a?(String) ? File.open(target, "wb") : target @owns_io = target.is_a?(String) @zip = Ooxml::ZipWriter.new(@io) @sst = [] @sst_index = {} @sheets = [] @current_sheet = nil @current_sheet_index = 0 @current_row_index = 0 @sheet_entry_started = false @current_row_writer = nil @current_columns = [] @current_charts = [] @current_hyperlinks = [] @current_auto_filter = nil @current_filter_columns = {} @current_sort_state = nil @current_data_validations = [] @current_conditional_formats = [] @current_tables = [] @current_pivot_tables = [] @current_sparkline_groups = [] @current_comments = [] @current_merge_cells = [] @current_freeze_pane = nil @current_split_pane = nil @current_selection = nil @current_page_margins = nil @current_page_setup = {} @current_header_footer = {} @current_print_options = {} @current_sheet_protection = nil @current_images = [] @current_shapes = [] @current_sheet_properties = {} @current_sheet_view = {} @current_row_breaks = [] @current_col_breaks = [] @current_cells = {} @styles = {} # { style_name => StyleBuilder } @style_writer = Ooxml::Writer.new @style_name_to_id = {} style("__xlsxrb_date", number_format: "yyyy-mm-dd") style("__xlsxrb_time", number_format: "yyyy-mm-dd hh:mm:ss") # Workbook-level settings @defined_names = [] @core_properties = {} @app_properties = {} @custom_properties = [] @workbook_protection = nil @workbook_properties = { update_links: "never" } end |
Instance Attribute Details
#current_sheet ⇒ String? (readonly)
: String?
27 28 29 |
# File 'lib/xlsxrb/stream_writer.rb', line 27 def current_sheet @current_sheet end |
Instance Method Details
#absolute_range(range) ⇒ Object
: (untyped range) -> untyped
1606 1607 1608 1609 1610 1611 |
# File 'lib/xlsxrb/stream_writer.rb', line 1606 def absolute_range(range) # simplecov:disable # Edge case / untested delegation block range.gsub(/([A-Z]+)(\d+)/, '$\1$\2') # simplecov:enable end |
#app_property(name, value) ⇒ void
This method returns an undefined value.
Sets an app document property.
: (Symbol name, String | Integer | Time value) -> void
1499 1500 1501 1502 1503 1504 |
# File 'lib/xlsxrb/stream_writer.rb', line 1499 def app_property(name, value) # simplecov:disable # Edge case / untested delegation block @app_properties[name] = value # simplecov:enable end |
#auto_filter(range) ⇒ void
This method returns an undefined value.
Sets the auto-filter range on the active sheet.
: (String range) -> void
1047 1048 1049 1050 |
# File 'lib/xlsxrb/stream_writer.rb', line 1047 def auto_filter(range) sheet if @current_sheet.nil? @current_auto_filter = range end |
#chart(**options) {|builder| ... } ⇒ void
This method returns an undefined value.
Adds a chart to the current worksheet.
: (**untyped options) ?{ (ChartBuilder) -> void } -> void
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 |
# File 'lib/xlsxrb/stream_writer.rb', line 1009 def chart(**) sheet if @current_sheet.nil? if block_given? builder = ChartBuilder.new yield builder = builder..merge() end @current_charts << end |
#cleanup! ⇒ void
This method returns an undefined value.
Explicitly cleans up any resources if not already closed.
: () -> void
1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 |
# File 'lib/xlsxrb/stream_writer.rb', line 1586 def cleanup! if @zip && !@zip.instance_variable_get(:@closed) begin @zip.close rescue StandardError nil end end return unless @owns_io && @io && !@io.closed? begin @io.close rescue StandardError nil end end |
#close ⇒ void
This method returns an undefined value.
Finalizes and writes all streaming sheet contents to the destination target.
: () -> untyped
1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 |
# File 'lib/xlsxrb/stream_writer.rb', line 1543 def close raise ArgumentError, "Workbook must contain at least one sheet (Excel limitation)" if @strict_excel_mode && @sheets.empty? && @current_sheet.nil? Xlsxrb.in_span("StreamWriter#close") do flush_current_sheet styles_definition = { fonts: @style_writer.fonts.dup, fills: @style_writer.fills.dup, borders: @style_writer.borders.dup, xf_entries: @style_writer.xf_entries.dup, num_fmts: @style_writer.num_fmts.dup, dxfs: @dxfs || [] } resolved_names = resolve_defined_names(@defined_names, @sheets) wb_writer = Ooxml::WorkbookWriter.new( sheets: @sheets, shared_strings: @sst, shared_strings_index: @sst_index, styles: styles_definition, defined_names: resolved_names.empty? ? nil : resolved_names, core_properties: @core_properties.empty? ? nil : @core_properties, app_properties: @app_properties.empty? ? nil : @app_properties, custom_properties: @custom_properties.empty? ? nil : @custom_properties, workbook_protection: @workbook_protection, workbook_properties: @workbook_properties ) wb_writer.write_package_parts(@zip) @zip.close @io.close if @owns_io && !@io.closed? end ensure cleanup! end |
#column(index, width: nil, hidden: false, custom_width: false, outline_level: nil) ⇒ void
This method returns an undefined value.
Sets column formatting and properties for one or multiple columns.
: (Integer | String | Range[Integer | String] | Array[Integer | String] index, ?width: Float | Integer | nil, ?hidden: bool, ?custom_width: bool, ?outline_level: Integer | nil) -> void
984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 |
# File 'lib/xlsxrb/stream_writer.rb', line 984 def column(index, width: nil, hidden: false, custom_width: false, outline_level: nil) raise ArgumentError, "Column width #{width} must be between 0 and 255 characters (Excel limitation)" if @strict_excel_mode && width && (width.negative? || width > 255) indices = case index when Range, Array index.map { |i| Elements::Cell.column_index(i) } else [Elements::Cell.column_index(index)] end sheet if @current_sheet.nil? indices.each do |idx| @current_columns << { index: idx, width: width, hidden: hidden, custom_width: custom_width || !width.nil?, outline_level: outline_level } end end |
#comment(cell, text, author: "Author") ⇒ void
This method returns an undefined value.
Adds a comment to a cell.
: (String | Integer cell, String text, ?author: ::String) -> void
1155 1156 1157 1158 |
# File 'lib/xlsxrb/stream_writer.rb', line 1155 def comment(cell, text, author: "Author") sheet if @current_sheet.nil? @current_comments << { cell: cell, text: text, author: } end |
#conditional_format(sqref, **opts) ⇒ void
This method returns an undefined value.
Adds a conditional formatting rule.
: (untyped sqref, **untyped opts) -> void
1096 1097 1098 1099 |
# File 'lib/xlsxrb/stream_writer.rb', line 1096 def conditional_format(sqref, **opts) sheet if @current_sheet.nil? @current_conditional_formats << opts.merge(sqref: sqref) end |
#core_property(name, value) ⇒ void
This method returns an undefined value.
Sets a core document metadata property.
: (Symbol name, String | Integer | Time value) -> void
1488 1489 1490 |
# File 'lib/xlsxrb/stream_writer.rb', line 1488 def core_property(name, value) @core_properties[name] = value end |
#custom_property(name, value, type: :string) ⇒ void
This method returns an undefined value.
Adds a custom document property.
: (String name, String | Integer | Float | bool | Time value, ?type: ::Symbol) -> void
1531 1532 1533 1534 1535 1536 |
# File 'lib/xlsxrb/stream_writer.rb', line 1531 def custom_property(name, value, type: :string) # simplecov:disable # Edge case / untested delegation block @custom_properties << { name: name, value: value, type: type } # simplecov:enable end |
#defined_name(name, value, sheet: nil, hidden: false) ⇒ void
This method returns an undefined value.
Adds a defined name.
: (String name, String value, ?sheet: String?, ?hidden: bool) -> void
1427 1428 1429 1430 1431 1432 1433 1434 |
# File 'lib/xlsxrb/stream_writer.rb', line 1427 def defined_name(name, value, sheet: nil, hidden: false) entry = { name: name, value: value, hidden: hidden } if sheet # local_sheet_id will be resolved at close time entry[:local_sheet_name] = sheet end @defined_names << entry end |
#filter_column(col_id, filter) ⇒ void
This method returns an undefined value.
Sets filter criteria on a column in the auto-filter.
: (untyped col_id, untyped filter) -> untyped
1059 1060 1061 1062 |
# File 'lib/xlsxrb/stream_writer.rb', line 1059 def filter_column(col_id, filter) sheet if @current_sheet.nil? @current_filter_columns[col_id] = filter end |
#flush_current_sheet ⇒ nil, untyped
: () -> (nil | untyped)
1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 |
# File 'lib/xlsxrb/stream_writer.rb', line 1628 def flush_current_sheet return unless @current_sheet # Normalize conditional formatting rules and record dxfs in @dxfs unless @current_conditional_formats.empty? @dxfs ||= [] @current_conditional_formats = @current_conditional_formats.map do |rule| next rule if rule[:format_id] normalized = rule.dup font = {} font[:color] = normalized.delete(:font_color) if normalized.key?(:font_color) font[:bold] = normalized.delete(:bold) if normalized.key?(:bold) font[:italic] = normalized.delete(:italic) if normalized.key?(:italic) font[:underline] = normalized.delete(:underline) if normalized.key?(:underline) fill = {} if normalized.key?(:fill_color) fill[:pattern] = "solid" fill[:fg_color] = normalized.delete(:fill_color) end dxf = {} dxf[:font] = font unless font.empty? dxf[:fill] = fill unless fill.empty? next normalized if dxf.empty? dxf_id = @dxfs.index(dxf) unless dxf_id @dxfs << dxf dxf_id = @dxfs.size - 1 end normalized[:format_id] = dxf_id normalized end end start_sheet_entry unless @sheet_entry_started sheet_data = { name: @current_sheet, columns: @current_columns, charts: @current_charts, hyperlinks: @current_hyperlinks, auto_filter: @current_auto_filter, filter_columns: @current_filter_columns, sort_state: @current_sort_state, data_validations: @current_data_validations, conditional_formats: @current_conditional_formats, tables: @current_tables, pivot_tables: @current_pivot_tables, sparkline_groups: @current_sparkline_groups, comments: @current_comments, merge_cells: @current_merge_cells, freeze_pane: @current_freeze_pane, split_pane: @current_split_pane, selection: @current_selection, page_margins: @current_page_margins, page_setup: @current_page_setup, header_footer: @current_header_footer, print_options: @current_print_options, sheet_protection: @current_sheet_protection, images: @current_images, shapes: @current_shapes, sheet_properties: @current_sheet_properties, sheet_view: @current_sheet_view, row_breaks: @current_row_breaks, col_breaks: @current_col_breaks } sheet_data[:cells] = @current_cells if @current_cells && !@current_cells.empty? @current_cells = nil temp_wb_writer = Ooxml::WorkbookWriter.new(sheets: [sheet_data]) info = temp_wb_writer.prepare_sheet_auxiliary(sheet_data, @current_sheet_index - 1) @current_row_writer.finish( drawing_rid: info[:drawing_rid], sheet_protection: @current_sheet_protection, auto_filter: @current_auto_filter, filter_columns: @current_filter_columns.empty? ? nil : @current_filter_columns, sort_state: @current_sort_state, merge_cells: @current_merge_cells.empty? ? nil : @current_merge_cells, conditional_formats: @current_conditional_formats.empty? ? nil : @current_conditional_formats, data_validations: @current_data_validations.empty? ? nil : @current_data_validations, hyperlinks: info[:enriched_hyperlinks].empty? ? nil : info[:enriched_hyperlinks], print_options: @current_print_options.empty? ? nil : @current_print_options, page_margins: @current_page_margins, page_setup: @current_page_setup.empty? ? nil : @current_page_setup, header_footer: @current_header_footer.empty? ? nil : @current_header_footer, row_breaks: @current_row_breaks.empty? ? nil : @current_row_breaks, col_breaks: @current_col_breaks.empty? ? nil : @current_col_breaks, tables: @current_tables.empty? ? nil : @current_tables, table_start_rid: info[:table_start_rid], legacy_drawing_rid: info[:vml_rid], sparkline_groups: @current_sparkline_groups.empty? ? nil : @current_sparkline_groups ) @zip.finish_entry @sheets << sheet_data @current_sheet = nil @current_row_writer = nil @sheet_entry_started = false end |
#freeze_pane(row: 0, col: 0) ⇒ void
This method returns an undefined value.
Freezes window panes at the given row and column.
: (?row: Integer, ?col: (Integer | String)) -> void
1220 1221 1222 1223 1224 |
# File 'lib/xlsxrb/stream_writer.rb', line 1220 def freeze_pane(row: 0, col: 0) col = Elements::Cell.column_index(col) sheet if @current_sheet.nil? @current_freeze_pane = { row: row, col: col } end |
#header_footer(**opts) ⇒ void
This method returns an undefined value.
Configures headers and footers for printing.
: (**untyped opts) -> void
1286 1287 1288 1289 |
# File 'lib/xlsxrb/stream_writer.rb', line 1286 def (**opts) sheet if @current_sheet.nil? @current_header_footer.merge!(opts) end |
#hyperlink(cell, url = nil, display: nil, tooltip: nil, location: nil) ⇒ void
This method returns an undefined value.
Adds a hyperlink on a cell.
: (String | Integer cell, ?String? url, ?display: String?, ?tooltip: String?, ?location: String?) -> void
1031 1032 1033 1034 1035 1036 1037 1038 1039 |
# File 'lib/xlsxrb/stream_writer.rb', line 1031 def hyperlink(cell, url = nil, display: nil, tooltip: nil, location: nil) sheet if @current_sheet.nil? link = { cell: cell } link[:url] = url if url link[:display] = display if display link[:tooltip] = tooltip if tooltip link[:location] = location if location @current_hyperlinks << link end |
#image(file_data, ext: "png", from_col: 0, from_row: 0, to_col: 5, to_row: 10, **opts) ⇒ void
This method returns an undefined value.
Inserts an embedded image into the active sheet.
: (String file_data, ?ext: ::String, ?from_col: ::Integer, ?from_row: ::Integer, ?to_col: ::Integer, ?to_row: ::Integer, **untyped opts) -> void
1336 1337 1338 1339 1340 1341 |
# File 'lib/xlsxrb/stream_writer.rb', line 1336 def image(file_data, ext: "png", from_col: 0, from_row: 0, to_col: 5, to_row: 10, **opts) sheet if @current_sheet.nil? img = { file_data: file_data, ext: ext, from_col: from_col, from_row: from_row, to_col: to_col, to_row: to_row } img.merge!(opts) @current_images << img end |
#internal_sheet_setup(name = nil) ⇒ void
This method returns an undefined value.
Internal: Start or switch to a named sheet (internal helper). : (?String? name) ?{ (WorksheetProxy) -> void } -> (WorksheetProxy | nil)
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 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 |
# File 'lib/xlsxrb/stream_writer.rb', line 822 def internal_sheet_setup(name = nil) flush_current_sheet name ||= "Sheet#{@sheets.size + 1}" @current_sheet = name @current_sheet_index = @sheets.size + 1 @current_row_index = 0 @sheet_entry_started = false @current_row_buffer = String.new(capacity: 65_536) @current_sheet_io = StringIO.new(@current_row_buffer) @current_row_writer = Ooxml::WorksheetWriter.new(@current_sheet_io) @current_row_writer.instance_variable_set(:@started, true) @current_columns = [] @current_charts = [] @current_hyperlinks = [] @current_auto_filter = nil @current_filter_columns = {} @current_sort_state = nil @current_data_validations = [] @current_conditional_formats = [] @current_tables = [] @current_pivot_tables = [] @current_sparkline_groups = [] @current_comments = [] @current_merge_cells = [] @current_freeze_pane = nil @current_split_pane = nil @current_selection = nil @current_page_margins = nil @current_page_setup = {} @current_header_footer = {} @current_print_options = {} @current_sheet_protection = nil @current_images = [] @current_shapes = [] @current_sheet_properties = {} @current_sheet_view = {} @current_row_breaks = [] @current_col_breaks = [] @current_cells = {} return unless block_given? # simplecov:disable # Edge case / untested delegation block yield self flush_current_sheet # simplecov:enable end |
#merge(range = nil, row: nil, col_start: nil, col_end: nil, row_start: nil, row_end: nil) ⇒ void
This method returns an undefined value.
Merges a range of cells into a single cell.
: (?(String | Hash[Symbol, Integer | String])? range, ?row: Integer?, ?col_start: (Integer | String)?, ?col_end: (Integer | String)?, ?row_start: Integer?, ?row_end: Integer?) -> void
1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 |
# File 'lib/xlsxrb/stream_writer.rb', line 1187 def merge(range = nil, row: nil, col_start: nil, col_end: nil, row_start: nil, row_end: nil) sheet if @current_sheet.nil? if range.is_a?(Hash) row = range[:row] row_start = range[:row_start] row_end = range[:row_end] col_start = range[:col_start] col_end = range[:col_end] range = nil end if range raise ArgumentError, "Invalid merge range format: '#{range}'. Expected format like 'A1:B2'." if @strict_excel_mode && !range.match?(/^[A-Za-z]{1,3}\d+(:[A-Za-z]{1,3}\d+)?$/) @current_merge_cells << range else r_start = row || row_start || 0 r_end = row || row_end || 0 c_start = Elements::Cell.column_index(col_start || 0) c_end = Elements::Cell.column_index(col_end || 0) start_ref = "#{Xlsxrb::Elements::Cell.column_letter(c_start)}#{r_start + 1}" end_ref = "#{Xlsxrb::Elements::Cell.column_letter(c_end)}#{r_end + 1}" @current_merge_cells << "#{start_ref}:#{end_ref}" end end |
#page_break_col(col_index) ⇒ void
This method returns an undefined value.
Inserts a vertical page break before a column.
: (Integer col_index) -> void
1407 1408 1409 1410 1411 1412 1413 1414 |
# File 'lib/xlsxrb/stream_writer.rb', line 1407 def page_break_col(col_index) # simplecov:disable # Edge case / untested delegation block col_index = Elements::Cell.column_index(col_index) sheet if @current_sheet.nil? @current_col_breaks << col_index # simplecov:enable end |
#page_break_row(row_num) ⇒ void
This method returns an undefined value.
Inserts a horizontal page break before a row.
: (Integer row_num) -> void
1393 1394 1395 1396 1397 1398 1399 |
# File 'lib/xlsxrb/stream_writer.rb', line 1393 def page_break_row(row_num) # simplecov:disable # Edge case / untested delegation block sheet if @current_sheet.nil? @current_row_breaks << row_num # simplecov:enable end |
#page_margins(left: nil, right: nil, top: nil, bottom: nil, header: nil, footer: nil) ⇒ void
This method returns an undefined value.
Sets page margins in inches for printing.
: (?left: Float?, ?right: Float?, ?top: Float?, ?bottom: Float?, ?header: Float?, ?footer: Float?) -> void
1264 1265 1266 1267 |
# File 'lib/xlsxrb/stream_writer.rb', line 1264 def page_margins(left: nil, right: nil, top: nil, bottom: nil, header: nil, footer: nil) sheet if @current_sheet.nil? @current_page_margins = { left: left, right: right, top: top, bottom: bottom, header: header, footer: }.compact end |
#page_setup(**opts) ⇒ void
This method returns an undefined value.
Sets page setup configuration.
: (**untyped opts) -> void
1275 1276 1277 1278 |
# File 'lib/xlsxrb/stream_writer.rb', line 1275 def page_setup(**opts) sheet if @current_sheet.nil? @current_page_setup.merge!(opts) end |
#pivot_table(source_ref, row_fields:, data_fields:, col_fields: [], dest_ref: "E1", name: nil, field_names: nil, items: nil) ⇒ void
This method returns an undefined value.
Adds a Pivot Table.
: (untyped source_ref, row_fields: untyped, data_fields: untyped, ?col_fields: untyped, ?dest_ref: untyped, ?name: untyped, ?field_names: untyped, ?items: untyped, **untyped opts) -> void
1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 |
# File 'lib/xlsxrb/stream_writer.rb', line 1136 def pivot_table(source_ref, row_fields:, data_fields:, col_fields: [], dest_ref: "E1", name: nil, field_names: nil, items: nil) sheet if @current_sheet.nil? @current_pivot_tables ||= [] @current_pivot_tables << { source_ref: source_ref, row_fields: row_fields, data_fields: data_fields, col_fields: col_fields, dest_ref: dest_ref, name: name, field_names: field_names, items: items } end |
#print_area(range, sheet: nil) ⇒ void
This method returns an undefined value.
Sets the print area for the current or named sheet.
: (String range, ?sheet: String?) -> void
1443 1444 1445 1446 1447 1448 1449 1450 1451 |
# File 'lib/xlsxrb/stream_writer.rb', line 1443 def print_area(range, sheet: nil) # simplecov:disable # Edge case / untested delegation block sheet_name = sheet || @current_sheet || "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) # simplecov:enable end |
#print_options(name, value) ⇒ void
This method returns an undefined value.
Sets a print option (e.g. grid_lines: true).
: (Symbol name, untyped value) -> void
1298 1299 1300 1301 |
# File 'lib/xlsxrb/stream_writer.rb', line 1298 def (name, value) sheet if @current_sheet.nil? @current_print_options[name] = value end |
#print_titles(rows: nil, cols: nil, sheet: nil) ⇒ void
This method returns an undefined value.
Sets print titles for the current or named sheet.
: (?rows: String?, ?cols: String?, ?sheet: String?) -> void
1461 1462 1463 1464 1465 1466 1467 1468 1469 |
# File 'lib/xlsxrb/stream_writer.rb', line 1461 def print_titles(rows: nil, cols: nil, sheet: nil) sheet_name = sheet || @current_sheet || "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 |
#properties(core: nil, app: nil, custom: nil) ⇒ void
This method returns an undefined value.
Sets multiple core and/or app properties.
: (?core: Hash[Symbol, String | Integer | Time]?, ?app: Hash[Symbol, String | Integer | Time]?, ?custom: Hash[String | Symbol, untyped]?) -> void
1514 1515 1516 1517 1518 1519 1520 1521 |
# File 'lib/xlsxrb/stream_writer.rb', line 1514 def properties(core: nil, app: nil, custom: nil) # simplecov:disable # Edge case / untested delegation block core&.each { |k, v| core_property(k, v) } app&.each { |k, v| app_property(k, v) } custom&.each { |k, v| custom_property(k.to_s, v) } # simplecov:enable end |
#protect_sheet(**opts) ⇒ void
This method returns an undefined value.
Sets sheet-level protection with optional password hashing.
: (**untyped opts) -> void
1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 |
# File 'lib/xlsxrb/stream_writer.rb', line 1309 def protect_sheet(**opts) sheet if @current_sheet.nil? normalized = opts.dup plain_password = normalized[:password] needs_hash = plain_password.is_a?(String) && !plain_password.empty? && normalized[:algorithm_name].nil? && normalized[:hash_value].nil? && normalized[:salt_value].nil? && normalized[:spin_count].nil? && !plain_password.match?(/\A[0-9A-Fa-f]{4}\z/) if needs_hash normalized.delete(:password) normalized.merge!(Xlsxrb::Ooxml::Utils.hash_password(plain_password)) end @current_sheet_protection = normalized end |
#protect_workbook(**opts) ⇒ void
This method returns an undefined value.
Sets workbook protection.
: (**String | Integer | bool | nil opts) -> void
1477 1478 1479 |
# File 'lib/xlsxrb/stream_writer.rb', line 1477 def protect_workbook(**opts) @workbook_protection = opts end |
#resolve_defined_names(names, sheets) ⇒ Object
: (untyped names, untyped sheets) -> untyped
1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 |
# File 'lib/xlsxrb/stream_writer.rb', line 1614 def resolve_defined_names(names, sheets) sheet_names = sheets.map { |s| s[: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 |
#row(values, styles: nil, height: nil, hidden: false, custom_height: false, outline_level: nil) ⇒ void Also known as: <<
This method returns an undefined value.
Appends a row of values to the active sheet.
: (Array | Hash[untyped, untyped] values, ?styles: untyped, ?height: Float | Integer | nil, ?hidden: bool, ?custom_height: bool, ?outline_level: Integer | nil) -> void
909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 |
# File 'lib/xlsxrb/stream_writer.rb', line 909 def row(values, styles: nil, height: nil, hidden: false, custom_height: false, outline_level: nil) sheet if @current_sheet.nil? row_index = @current_row_index # See: https://support.microsoft.com/en-us/office/excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3 if @strict_excel_mode raise ArgumentError, "Row index #{row_index} exceeds Excel limit of 1,048,576 rows" if row_index >= 1_048_576 raise ArgumentError, "Row height #{height} must be between 0 and 409 points (Excel limitation)" if height && (height.negative? || height > 409) end @current_row_index += 1 if values.is_a?(Hash) max_col = values.keys.map { |k| Elements::Cell.column_index(k) }.max || -1 cells_array = Array.new(max_col + 1) values.each do |k, v| idx = Elements::Cell.column_index(k) cells_array[idx] = v end values = cells_array end if styles.is_a?(Hash) = {} styles.each do |k, v| if k.is_a?(Range) || k.is_a?(Array) k.each { |idx| [Elements::Cell.column_index(idx)] = v } else [Elements::Cell.column_index(k)] = v end end max_col_style = .keys.max || -1 styles_array = Array.new(max_col_style + 1) .each do |idx, v| styles_array[idx] = v end styles = styles_array end if @current_charts && !@current_charts.empty? @current_cells ||= {} row_num = row_index + 1 values.each_with_index do |val, col_idx| next if val.nil? addr = "#{Elements::Cell.column_letter(col_idx)}#{row_num}" @current_cells[addr] = val end end raise ArgumentError, "Row contains #{values.length} columns, exceeding Excel limit of 16_384 columns" if @strict_excel_mode && values.length > 16_384 attrs = nil if height || hidden || outline_level attrs = {} attrs[:height] = height if height attrs[:hidden] = true if hidden attrs[:custom_height] = custom_height || !height.nil? attrs[:outline_level] = outline_level if outline_level end @current_row_writer.write_row_values(row_index, values, styles: styles, style_map: @style_name_to_id, sst: @sst, sst_index: @sst_index, attrs: attrs) start_sheet_entry if !@sheet_entry_started && @current_row_buffer.bytesize >= 65_536 end |
#select_cell(active_cell, sqref: nil, pane: nil) ⇒ void
This method returns an undefined value.
Sets the active cell selection on the current sheet.
: (String active_cell, ?sqref: String?, ?pane: (String | Symbol)?) -> void
1247 1248 1249 1250 1251 |
# File 'lib/xlsxrb/stream_writer.rb', line 1247 def select_cell(active_cell, sqref: nil, pane: nil) sheet if @current_sheet.nil? @current_selection = { active_cell: active_cell, sqref: sqref || active_cell } @current_selection[:pane] = pane if pane end |
#shape(preset: "rect", text: nil, from_col: 0, from_row: 0, to_col: 5, to_row: 5, **opts) ⇒ void
This method returns an undefined value.
Inserts a drawing shape.
: (**untyped opts) -> void
1355 1356 1357 1358 1359 1360 1361 |
# File 'lib/xlsxrb/stream_writer.rb', line 1355 def shape(preset: "rect", text: nil, from_col: 0, from_row: 0, to_col: 5, to_row: 5, **opts) sheet if @current_sheet.nil? shape = { preset: preset, text: text, from_col: from_col, from_row: from_row, to_col: to_col, to_row: to_row } shape[:name] = opts.delete(:name) || "Shape #{@current_shapes.size + 1}" shape.merge!(opts) @current_shapes << shape end |
#sheet(name = nil, **opts) {|sheet_proxy| ... } ⇒ String
Adds a new sheet to the workbook and starts streaming rows into it.
: (?String? name, **untyped opts) ?{ (WorksheetProxy) -> void } -> untyped
807 808 809 810 811 812 813 814 815 816 817 818 |
# File 'lib/xlsxrb/stream_writer.rb', line 807 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) internal_sheet_setup(name) opts.each { |k, v| sheet_properties(k, v) } yield WorksheetProxy.new(self, @current_sheet) if block_given? @current_sheet end |
#sheet_properties(name, value) ⇒ void
This method returns an undefined value.
Sets a sheet-level property (e.g. tab_color: "FF0000").
: (Symbol name, untyped value) -> void
1370 1371 1372 1373 |
# File 'lib/xlsxrb/stream_writer.rb', line 1370 def sheet_properties(name, value) sheet if @current_sheet.nil? @current_sheet_properties[name] = value end |
#sheet_view(name, value) ⇒ void
This method returns an undefined value.
Sets a sheet view property (e.g. zoom_scale: 120).
: (Symbol name, untyped value) -> void
1382 1383 1384 1385 |
# File 'lib/xlsxrb/stream_writer.rb', line 1382 def sheet_view(name, value) sheet if @current_sheet.nil? @current_sheet_view[name] = value end |
#sort_state(ref, sort_conditions, **opts) ⇒ void
This method returns an undefined value.
Configures column sort state on the active sheet.
: (untyped ref, untyped sort_conditions, **untyped opts) -> untyped
1072 1073 1074 1075 |
# File 'lib/xlsxrb/stream_writer.rb', line 1072 def sort_state(ref, sort_conditions, **opts) sheet if @current_sheet.nil? @current_sort_state = { ref: ref, sort_conditions: sort_conditions }.merge(opts) end |
#sparkline_group(sparklines:, type: nil, **opts) ⇒ void
This method returns an undefined value.
Adds a sparkline group.
: (sparklines: untyped, ?type: untyped, **untyped opts) -> void
1168 1169 1170 1171 1172 1173 1174 |
# File 'lib/xlsxrb/stream_writer.rb', line 1168 def sparkline_group(sparklines:, type: nil, **opts) sheet if @current_sheet.nil? group = { sparklines: sparklines } group[:type] = type if type group.merge!(opts) @current_sparkline_groups << group end |
#split_pane(x_split: 0, y_split: 0, top_left_cell: nil) ⇒ void
This method returns an undefined value.
Splits window panes without freezing.
: (?x_split: ::Integer, ?y_split: ::Integer, ?top_left_cell: String?) -> void
1234 1235 1236 1237 |
# File 'lib/xlsxrb/stream_writer.rb', line 1234 def split_pane(x_split: 0, y_split: 0, top_left_cell: nil) sheet if @current_sheet.nil? @current_split_pane = { x_split: x_split, y_split: y_split, top_left_cell: top_left_cell } end |
#start_sheet_entry ⇒ Object
872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 |
# File 'lib/xlsxrb/stream_writer.rb', line 872 def start_sheet_entry return if @sheet_entry_started @sheet_entry_started = true @zip.start_entry("xl/worksheets/sheet#{@current_sheet_index}.xml") zip_io = Ooxml::WorkbookWriter::ZipEntryIO.new(@zip) row_buf = @current_row_writer.instance_variable_get(:@row_buffer) if row_buf && !row_buf.empty? @current_sheet_io.write(row_buf) row_buf.clear end @current_row_writer = Ooxml::WorksheetWriter.new(zip_io) @current_row_writer.start( columns: @current_columns, sheet_properties: @current_sheet_properties.empty? ? nil : @current_sheet_properties, freeze_pane: @current_freeze_pane, split_pane: @current_split_pane, selection: @current_selection, sheet_view: @current_sheet_view.empty? ? nil : @current_sheet_view ) @zip.write_data(@current_row_buffer) unless @current_row_buffer.empty? @current_row_buffer.clear end |
#style(name, **opts) {|style_builder| ... } ⇒ StyleBuilder
Defines or configures a named cell style.
: (String | Symbol name, **untyped opts) ?{ (StyleBuilder) -> void } -> StyleBuilder
112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/xlsxrb/stream_writer.rb', line 112 def style(name, **opts) style_name = name.to_s style_builder = StyleBuilder.new(style_name) style_builder.(**opts) unless opts.empty? yield style_builder if block_given? @styles[style_name] = style_builder # Register immediately with low-level style writer @style_name_to_id[style_name] = style_builder.register_with(@style_writer) style_builder end |
#table(ref, columns:, name: nil, display_name: nil, style: nil, **opts) ⇒ void
This method returns an undefined value.
Adds an Excel Table (ListObject).
: (untyped ref, columns: untyped, ?name: untyped, ?display_name: untyped, ?style: untyped, **untyped opts) -> void
1112 1113 1114 1115 1116 1117 1118 1119 1120 |
# File 'lib/xlsxrb/stream_writer.rb', line 1112 def table(ref, columns:, name: nil, display_name: nil, style: nil, **opts) sheet if @current_sheet.nil? tbl = { ref: ref, columns: columns } tbl[:name] = name if name tbl[:display_name] = display_name if display_name tbl[:style] = style if style tbl.merge!(opts) @current_tables << tbl end |
#validate_data(sqref, **opts) ⇒ void
This method returns an undefined value.
Adds a data validation rule.
: (untyped sqref, **untyped opts) -> void
1084 1085 1086 1087 |
# File 'lib/xlsxrb/stream_writer.rb', line 1084 def validate_data(sqref, **opts) sheet if @current_sheet.nil? @current_data_validations << opts.merge(sqref: sqref) 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.
Sets a workbook property.
: (Symbol name, String | Integer | bool value) -> void
100 101 102 |
# File 'lib/xlsxrb/stream_writer.rb', line 100 def workbook_property(name, value) @workbook_properties[name] = value end |