Class: Xlsxrb::Ooxml::Reader
- Inherits:
-
Object
- Object
- Xlsxrb::Ooxml::Reader
- Defined in:
- lib/xlsxrb/ooxml/reader.rb,
sig/generated/xlsxrb/ooxml/reader.rbs
Overview
Reads cells from an XLSX file.
Defined Under Namespace
Classes: AppPropertiesListener, AutoFilterListener, CalcChainListener, CellStyleListener, CellWatchesListener, ChartTypeListener, ColumnsListener, CommentsListener, ConditionalFormattingListener, CorePropertiesListener, CustomPropertiesListener, DataConsolidateListener, DataValidationsListener, DimensionListener, DrawingChartsListener, DrawingImagesListener, DrawingShapesListener, ExternalLinkListener, HyperlinksListener, IgnoredErrorsListener, MergeCellsListener, PhoneticPrListener, PivotCacheDefinitionListener, PivotCacheRecordsListener, PivotTableListener, PrintPageListener, ProtectedRangesListener, RelsListener, ScenariosListener, SharedStringsListener, SheetFormatListener, SheetPropertiesListener, SheetProtectionListener, SheetViewListener, SortStateListener, StylesListener, TableListener, WorkbookListener, WorksheetListener
Constant Summary collapse
- STRICT_SSML_NS =
"http://purl.oclc.org/ooxml/spreadsheetml/main/2006/main"- TRANSITIONAL_SSML_NS =
"http://schemas.openxmlformats.org/spreadsheetml/2006/main"
Instance Method Summary collapse
-
#app_properties ⇒ Object
Returns app properties as a hash.
-
#auto_filter(sheet: nil) ⇒ Object
Returns the autoFilter range string (e.g. "A1:B10") or nil.
-
#borders ⇒ Object
Returns array of border entries from the styles.
-
#calc_chain ⇒ Object
Returns the calc chain as an array of { ref:, sheet_id: } hashes, or empty array.
-
#calc_properties ⇒ Object
Returns calc properties (e.g. { calc_id: 191029 }).
-
#cell_formats(sheet: nil) ⇒ Object
Returns cell format codes as { "A1" => "0.00" } for cells with custom numFmt.
-
#cell_phonetic(sheet: nil) ⇒ Object
Returns cell addresses marked as phonetic: { "A1" => true }.
-
#cell_style_xfs ⇒ Object
Returns array of cellStyleXfs entries (base style format definitions).
-
#cell_styles(sheet: nil) ⇒ Object
Returns expanded cell style info: { "A1" => { font:, fill:, border:, num_fmt: } }.
-
#cell_watches(sheet: nil) ⇒ Object
Returns cell watches for the given sheet as an array of cell references.
-
#cell_xfs ⇒ Object
Returns array of cellXfs entries (cell format definitions).
-
#cells(sheet: nil) ⇒ Object
Returns cells for the given sheet (by name or 0-based index).
-
#charts(sheet: nil) ⇒ Object
Returns charts for the given sheet as an array of hashes.
-
#col_breaks(sheet: nil) ⇒ Object
Returns column breaks for the given sheet.
-
#column_attributes(sheet: nil) ⇒ Object
Returns column attributes as { "A" => { hidden: true, outline_level: 1 } }.
- #column_index_to_letter(index) ⇒ Object
-
#columns(sheet: nil) ⇒ Object
Returns column widths as { "A" => 20.0, "B" => 15.5 } for the given sheet.
-
#comments(sheet: nil) ⇒ Object
Returns comments for the given sheet as an array of hashes.
-
#conditional_formats(sheet: nil) ⇒ Object
Returns conditional formatting rules for the given sheet.
-
#conformance ⇒ Object
Returns the workbook conformance class ("transitional" or "strict"), or nil if not set.
-
#core_properties ⇒ Object
Returns core properties as a hash (e.g. { title: "...", creator: "..." }).
-
#custom_properties ⇒ Object
Returns custom document properties as an array of { name:, value:, type: }.
-
#data_consolidate(sheet: nil) ⇒ Object
Returns data consolidation settings for the given sheet.
-
#data_validations(sheet: nil) ⇒ Object
Returns data validations as an array of hashes.
-
#data_validations_options(sheet: nil) ⇒ Object
Returns data validations container options (disablePrompts, xWindow, yWindow).
- #date_format?(fmt_id, custom_num_fmts) ⇒ Boolean
- #date_pattern?(code) ⇒ Boolean
-
#defined_names ⇒ Object
Returns defined names as an array of hashes.
-
#dimension(sheet: nil) ⇒ Object
Returns the dimension ref string (e.g. "A1:B10") for the given sheet.
- #discover_sheets ⇒ Object
-
#dxfs ⇒ Object
Returns array of differential formats (dxfs) from the styles.
-
#entry_names ⇒ Object
Returns all ZIP entry paths in the file.
-
#external_links ⇒ Object
Returns external links from the workbook as an array of hashes.
- #extract_zip_entry(entry_name) ⇒ Object
-
#file_recovery_properties ⇒ Object
Returns file recovery properties hash.
-
#file_sharing ⇒ Object
Returns file sharing properties (e.g. { read_only_recommended: true, user_name: "John" }).
-
#file_version ⇒ Object
Returns file version properties (e.g. { app_name: "xl", last_edited: "7" }).
-
#fills ⇒ Object
Returns array of fill entries from the styles.
-
#filter_columns(sheet: nil) ⇒ Object
Returns filter columns as { col_id => filter_hash }.
- #find_sheet_rel_target(sheet_index, type_suffix) ⇒ Object
- #find_sheet_rel_targets(sheet_index, type_suffix) ⇒ Object
-
#fonts ⇒ Object
Returns array of font entries from the styles.
-
#format_variant ⇒ Object
Returns :strict or :transitional based on the namespace of the workbook XML.
-
#freeze_pane(sheet: nil) ⇒ Object
Returns freeze pane settings for the given sheet.
-
#header_footer(sheet: nil) ⇒ Object
Returns header/footer for the given sheet.
-
#hyperlinks(sheet: nil) ⇒ Object
Returns hyperlinks as { "A1" => "https://example.com" }.
-
#ignored_errors(sheet: nil) ⇒ Object
Returns ignored errors for the given sheet.
-
#images(sheet: nil) ⇒ Object
Returns images for the given sheet as an array of hashes.
-
#indexed_colors ⇒ Object
Returns indexed colors palette (array of ARGB hex strings).
-
#initialize(filepath) ⇒ Reader
constructor
A new instance of Reader.
- #load_drawing_rels(sheet_index) ⇒ Object
- #load_drawing_xml(sheet) ⇒ Object
- #load_pivot_cache_definition(pivot_table_path) ⇒ Object
- #load_shared_strings ⇒ Object
- #load_styles ⇒ Object
- #load_tables(sheet_index) ⇒ Object
- #load_worksheet_xml(sheet) ⇒ Object
-
#macros? ⇒ Object
Returns true if the file contains VBA macros (vbaProject.bin).
-
#merged_cells(sheet: nil) ⇒ Object
Returns merged cell ranges as ["A1:B2", "C3:D4"].
-
#mru_colors ⇒ Object
Returns MRU (most recently used) colors (array of color hashes).
-
#named_cell_styles ⇒ Object
Returns array of named cell styles (cellStyle elements).
- #normalize_xl_path(target, base_dir) ⇒ Object
-
#num_fmts ⇒ Object
Returns custom number formats as { numFmtId => formatCode }.
-
#page_margins(sheet: nil) ⇒ Object
Returns page margins for the given sheet.
-
#page_setup(sheet: nil) ⇒ Object
Returns page setup for the given sheet.
- #parse_cell_style_indices(xml) ⇒ Object
- #parse_rels(rels_xml) ⇒ Object
- #parse_rels_with_types(rels_xml) ⇒ Object
- #parse_table_xml(xml) ⇒ Object
- #parse_workbook_metadata ⇒ Object
- #parse_worksheet_auto_filter(xml) ⇒ Object
- #parse_worksheet_cell_phonetic(xml) ⇒ Object
- #parse_worksheet_cell_watches(xml) ⇒ Object
- #parse_worksheet_cells(xml, shared_strings) ⇒ Object
- #parse_worksheet_column_attributes(xml) ⇒ Object
- #parse_worksheet_columns(xml) ⇒ Object
- #parse_worksheet_conditional_formats(xml) ⇒ Object
- #parse_worksheet_data_consolidate(xml) ⇒ Object
- #parse_worksheet_data_validations(xml) ⇒ Object
- #parse_worksheet_data_validations_options(xml) ⇒ Object
- #parse_worksheet_dimension(xml) ⇒ Object
- #parse_worksheet_filter_columns(xml) ⇒ Object
- #parse_worksheet_ignored_errors(xml) ⇒ Object
- #parse_worksheet_merge_cells(xml) ⇒ Object
- #parse_worksheet_phonetic_pr(xml) ⇒ Object
- #parse_worksheet_print_page(xml) ⇒ Object
- #parse_worksheet_properties(xml) ⇒ Object
- #parse_worksheet_protected_ranges(xml) ⇒ Object
- #parse_worksheet_row_attributes(xml) ⇒ Object
- #parse_worksheet_scenarios(xml) ⇒ Object
- #parse_worksheet_sheet_format(xml) ⇒ Object
- #parse_worksheet_sheet_protection(xml) ⇒ Object
- #parse_worksheet_sheet_view(xml) ⇒ Object
- #parse_worksheet_sort_state(xml) ⇒ Object
-
#phonetic_properties(sheet: nil) ⇒ Object
Returns phonetic properties for the given sheet, or nil if not present.
-
#pivot_tables(sheet: nil) ⇒ Object
Returns pivot tables for the given sheet as an array of hashes.
-
#print_area(sheet: nil) ⇒ Object
Returns the print area for the given sheet, or nil if not set.
-
#print_options(sheet: nil) ⇒ Object
Returns print options for the given sheet.
-
#print_titles(sheet: nil) ⇒ Object
Returns the print titles for the given sheet, or nil if not set.
-
#protected_ranges(sheet: nil) ⇒ Object
Returns protected ranges for the given sheet as an array of hashes.
-
#raw_entry(name) ⇒ Object
Returns raw bytes for a ZIP entry by path.
- #resolve_date_cells(raw_cells, cell_style_map, styles) ⇒ Object
- #resolve_drawing_relative_path(target, sheet_index) ⇒ Object
- #resolve_effective_xf(xf_data, cell_style_xfs) ⇒ Object
- #resolve_num_fmt_code(fmt_id, custom_num_fmts) ⇒ Object
- #resolve_sheet_for_defined_name(sheet) ⇒ Object
- #resolve_sheet_index(sheet) ⇒ Object
- #resolve_sheet_target(sheets, sheet) ⇒ Object
-
#row_attributes(sheet: nil) ⇒ Object
Returns row attributes as { 1 => { height: 25.0 }, 3 => { hidden: true } }.
-
#row_breaks(sheet: nil) ⇒ Object
Returns row breaks for the given sheet.
-
#scenarios(sheet: nil) ⇒ Object
Returns scenarios for the given sheet.
-
#selection(sheet: nil) ⇒ Object
Returns selection for the given sheet.
-
#shapes(sheet: nil) ⇒ Object
Returns shapes for the given sheet as an array of hashes.
-
#sheet_format(sheet: nil) ⇒ Object
Returns sheet format properties (defaultRowHeight, defaultColWidth, baseColWidth).
-
#sheet_names ⇒ Object
Returns ordered sheet names.
-
#sheet_properties(sheet: nil) ⇒ Object
Returns sheet-level properties (tabColor, outlinePr) for the given sheet.
-
#sheet_protection(sheet: nil) ⇒ Object
Returns sheet protection settings as a hash, or nil if unprotected.
-
#sheet_states ⇒ Object
Returns sheet states as { "Sheet1" => :visible, "Hidden" => :hidden }.
-
#sheet_view(sheet: nil) ⇒ Object
Returns sheet view properties for the given sheet.
-
#sort_state(sheet: nil) ⇒ Object
Returns sort state as { ref: "A1:B10", sort_conditions: [...] } or nil.
-
#table_styles ⇒ Object
Returns table styles configuration hash.
-
#tables(sheet: nil) ⇒ Object
Returns tables for the given sheet as an array of { id:, name:, display_name:, ref:, columns: }.
-
#workbook_properties ⇒ Object
Returns workbook properties (e.g. { date1904: false, default_theme_version: 166925 }).
-
#workbook_protection ⇒ Object
Returns workbook protection settings as a hash, or nil if unprotected.
-
#workbook_views ⇒ Object
Returns workbook view properties (e.g. { active_tab: 0 }).
Constructor Details
#initialize(filepath) ⇒ Reader
Returns a new instance of Reader.
13 14 15 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 13 def initialize(filepath) @filepath = filepath end |
Instance Method Details
#app_properties ⇒ Object
Returns app properties as a hash.
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 504 def app_properties # Try standard path first, then discover via rels xml = extract_zip_entry("docProps/app.xml") if xml.nil? || xml.empty? rels_xml = extract_zip_entry("_rels/.rels") return {} if rels_xml.nil? || rels_xml.empty? rels = parse_rels_with_types(rels_xml) app_rel = rels.find { |r| r[:type]&.end_with?("/extended-properties") } return {} unless app_rel target = app_rel[:target] entry_path = target.start_with?("/") ? target.delete_prefix("/") : target xml = extract_zip_entry(entry_path) end return {} if xml.nil? || xml.empty? parser = REXML::Parsers::SAX2Parser.new(xml) listener = AppPropertiesListener.new parser.listen(listener) parser.parse listener.properties end |
#auto_filter(sheet: nil) ⇒ Object
Returns the autoFilter range string (e.g. "A1:B10") or nil.
275 276 277 278 279 280 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 275 def auto_filter(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return nil if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_auto_filter(worksheet_xml) end |
#borders ⇒ Object
Returns array of border entries from the styles.
211 212 213 214 215 216 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 211 def borders styles = load_styles return [] if styles.empty? styles[:borders] || [] end |
#calc_chain ⇒ Object
Returns the calc chain as an array of { ref:, sheet_id: } hashes, or empty array.
593 594 595 596 597 598 599 600 601 602 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 593 def calc_chain xml = extract_zip_entry("xl/calcChain.xml") return [] if xml.nil? || xml.empty? parser = REXML::Parsers::SAX2Parser.new(xml) listener = CalcChainListener.new parser.listen(listener) parser.parse listener.entries end |
#calc_properties ⇒ Object
Returns calc properties (e.g. { calc_id: 191029 }).
583 584 585 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 583 def calc_properties [:calc_properties] end |
#cell_formats(sheet: nil) ⇒ Object
Returns cell format codes as { "A1" => "0.00" } for cells with custom numFmt.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 127 def cell_formats(sheet: nil) # Load styles. styles = load_styles return {} if styles.empty? # Parse worksheet to get cell style indices. worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? parser = REXML::Parsers::SAX2Parser.new(worksheet_xml) listener = CellStyleListener.new parser.listen(listener) parser.parse result = {} listener.cell_style_indices.each do |cell_ref, xf_index| xf = resolve_effective_xf(styles[:cell_xfs][xf_index], styles[:cell_style_xfs]) next unless xf fmt_id = xf[:num_fmt_id] next unless fmt_id && fmt_id != 0 format_code = resolve_num_fmt_code(fmt_id, styles[:num_fmts]) result[cell_ref] = format_code if format_code end result end |
#cell_phonetic(sheet: nil) ⇒ Object
Returns cell addresses marked as phonetic: { "A1" => true }.
59 60 61 62 63 64 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 59 def cell_phonetic(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_cell_phonetic(worksheet_xml) end |
#cell_style_xfs ⇒ Object
Returns array of cellStyleXfs entries (base style format definitions).
251 252 253 254 255 256 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 251 def cell_style_xfs styles = load_styles return [] if styles.empty? styles[:cell_style_xfs] || [] end |
#cell_styles(sheet: nil) ⇒ Object
Returns expanded cell style info: { "A1" => { font:, fill:, border:, num_fmt: } }.
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 156 def cell_styles(sheet: nil) styles = load_styles return {} if styles.empty? worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? indices = parse_cell_style_indices(worksheet_xml) result = {} indices.each do |cell_ref, xf_index| xf = resolve_effective_xf(styles[:cell_xfs][xf_index], styles[:cell_style_xfs]) next unless xf entry = {} entry[:font] = styles[:fonts][xf[:font_id]] if xf[:font_id]&.positive? && styles[:fonts][xf[:font_id]] entry[:fill] = styles[:fills][xf[:fill_id]] if xf[:fill_id]&.positive? && styles[:fills][xf[:fill_id]] entry[:border] = styles[:borders][xf[:border_id]] if xf[:border_id]&.positive? && styles[:borders][xf[:border_id]] if xf[:num_fmt_id]&.positive? code = resolve_num_fmt_code(xf[:num_fmt_id], styles[:num_fmts]) entry[:num_fmt] = code if code end entry[:alignment] = xf[:alignment] if xf[:alignment] entry[:protection] = xf[:protection] if xf[:protection] entry[:quote_prefix] = true if xf[:quote_prefix] entry[:pivot_button] = true if xf[:pivot_button] result[cell_ref] = entry unless entry.empty? end result end |
#cell_watches(sheet: nil) ⇒ Object
Returns cell watches for the given sheet as an array of cell references.
362 363 364 365 366 367 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 362 def cell_watches(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return [] if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_cell_watches(worksheet_xml) end |
#cell_xfs ⇒ Object
Returns array of cellXfs entries (cell format definitions).
259 260 261 262 263 264 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 259 def cell_xfs styles = load_styles return [] if styles.empty? styles[:cell_xfs] || [] end |
#cells(sheet: nil) ⇒ Object
Returns cells for the given sheet (by name or 0-based index). Defaults to the first sheet. Numeric cells with date numFmt are converted to Date.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 19 def cells(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? shared_strings = load_shared_strings raw_cells = parse_worksheet_cells(worksheet_xml, shared_strings) # Resolve date-formatted cells. styles = load_styles return raw_cells if styles.empty? cell_style_map = parse_cell_style_indices(worksheet_xml) resolve_date_cells(raw_cells, cell_style_map, styles) end |
#charts(sheet: nil) ⇒ Object
Returns charts for the given sheet as an array of hashes. Each hash: { name:, rid:, target:, chart_type:, title: }
708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 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 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 708 def charts(sheet: nil) drawing_xml = load_drawing_xml(sheet) return [] if drawing_xml.nil? || drawing_xml.empty? sheet_index = resolve_sheet_index(sheet) drawing_rels = load_drawing_rels(sheet_index) parser = REXML::Parsers::SAX2Parser.new(drawing_xml) listener = DrawingChartsListener.new parser.listen(listener) parser.parse listener.charts.each do |chart| target = drawing_rels[chart[:rid]] next unless target chart[:target] = target chart_path = resolve_drawing_relative_path(target, sheet_index) chart_xml = extract_zip_entry(chart_path) next if chart_xml.nil? || chart_xml.empty? cp = REXML::Parsers::SAX2Parser.new(chart_xml) cl = ChartTypeListener.new cp.listen(cl) cp.parse chart[:chart_type] = cl.chart_type chart[:title] = cl.title chart[:title_overlay] = cl. unless cl..nil? chart[:title_font] = cl.title_font if cl.title_font chart[:title_fill_color] = cl.title_fill_color if cl.title_fill_color chart[:title_no_fill] = cl.title_no_fill if cl.title_no_fill chart[:title_line_color] = cl.title_line_color if cl.title_line_color chart[:title_line_width] = cl.title_line_width if cl.title_line_width chart[:title_line_dash] = cl.title_line_dash if cl.title_line_dash chart[:series] = cl.series unless cl.series.empty? chart[:legend] = cl.legend unless cl.legend.empty? if cl.legend_font chart[:legend] ||= {} chart[:legend][:font] = cl.legend_font end chart[:data_labels] = cl.data_labels unless cl.data_labels.empty? chart[:cat_axis_title] = cl.cat_axis_title if cl.cat_axis_title chart[:val_axis_title] = cl.val_axis_title if cl.val_axis_title chart[:cat_axis_title_font] = cl.cat_axis_title_font if cl.cat_axis_title_font chart[:val_axis_title_font] = cl.val_axis_title_font if cl.val_axis_title_font chart[:cat_axis_title_fill] = cl.cat_axis_title_fill if cl.cat_axis_title_fill chart[:cat_axis_title_no_fill] = cl.cat_axis_title_no_fill if cl.cat_axis_title_no_fill chart[:cat_axis_title_line_color] = cl.cat_axis_title_line_color if cl.cat_axis_title_line_color chart[:cat_axis_title_line_width] = cl.cat_axis_title_line_width if cl.cat_axis_title_line_width chart[:cat_axis_title_line_dash] = cl.cat_axis_title_line_dash if cl.cat_axis_title_line_dash chart[:val_axis_title_fill] = cl.val_axis_title_fill if cl.val_axis_title_fill chart[:val_axis_title_no_fill] = cl.val_axis_title_no_fill if cl.val_axis_title_no_fill chart[:val_axis_title_line_color] = cl.val_axis_title_line_color if cl.val_axis_title_line_color chart[:val_axis_title_line_width] = cl.val_axis_title_line_width if cl.val_axis_title_line_width chart[:val_axis_title_line_dash] = cl.val_axis_title_line_dash if cl.val_axis_title_line_dash chart[:title_layout] = cl.title_layout if cl.title_layout chart[:cat_axis_title_layout] = cl.cat_axis_title_layout if cl.cat_axis_title_layout chart[:val_axis_title_layout] = cl.val_axis_title_layout if cl.val_axis_title_layout chart[:title_rotation] = cl.title_rotation if cl.title_rotation chart[:cat_axis_title_rotation] = cl.cat_axis_title_rotation if cl.cat_axis_title_rotation chart[:val_axis_title_rotation] = cl.val_axis_title_rotation if cl.val_axis_title_rotation chart[:grouping] = cl.grouping if cl.grouping chart[:bar_dir] = cl. if cl. chart[:vary_colors] = cl.vary_colors unless cl.vary_colors.nil? chart[:plot_vis_only] = cl.plot_vis_only unless cl.plot_vis_only.nil? chart[:disp_blanks_as] = cl.disp_blanks_as if cl.disp_blanks_as chart[:style] = cl.style if cl.style chart[:auto_title_deleted] = cl.auto_title_deleted unless cl.auto_title_deleted.nil? chart[:rounded_corners] = cl.rounded_corners unless cl.rounded_corners.nil? chart[:cat_axis_tick_lbl_pos] = cl.cat_axis_tick_lbl_pos if cl.cat_axis_tick_lbl_pos chart[:val_axis_tick_lbl_pos] = cl.val_axis_tick_lbl_pos if cl.val_axis_tick_lbl_pos chart[:cat_axis_major_gridlines] = cl.cat_axis_major_gridlines if cl.cat_axis_major_gridlines chart[:val_axis_major_gridlines] = cl.val_axis_major_gridlines if cl.val_axis_major_gridlines chart[:cat_axis_minor_gridlines] = cl.cat_axis_minor_gridlines if cl.cat_axis_minor_gridlines chart[:val_axis_minor_gridlines] = cl.val_axis_minor_gridlines if cl.val_axis_minor_gridlines chart[:show_d_lbls_over_max] = cl.show_d_lbls_over_max unless cl.show_d_lbls_over_max.nil? chart[:cat_axis_delete] = cl.cat_axis_delete unless cl.cat_axis_delete.nil? chart[:val_axis_delete] = cl.val_axis_delete unless cl.val_axis_delete.nil? chart[:cat_axis_orientation] = cl.cat_axis_orientation if cl.cat_axis_orientation chart[:val_axis_orientation] = cl.val_axis_orientation if cl.val_axis_orientation chart[:gap_width] = cl.gap_width if cl.gap_width chart[:overlap] = cl.overlap if cl.overlap chart[:gap_depth] = cl.gap_depth if cl.gap_depth chart[:bar_shape] = cl. if cl. chart[:bubble_3d] = cl.bubble_3d unless cl.bubble_3d.nil? chart[:bubble_scale] = cl.bubble_scale if cl.bubble_scale chart[:show_neg_bubbles] = cl.show_neg_bubbles unless cl.show_neg_bubbles.nil? chart[:size_represents] = cl.size_represents if cl.size_represents chart[:view_3d] = cl.view_3d if cl.view_3d chart[:cat_axis_num_fmt] = cl.cat_axis_num_fmt if cl.cat_axis_num_fmt chart[:val_axis_num_fmt] = cl.val_axis_num_fmt if cl.val_axis_num_fmt chart[:cat_axis_major_tick_mark] = cl.cat_axis_major_tick_mark if cl.cat_axis_major_tick_mark chart[:cat_axis_minor_tick_mark] = cl.cat_axis_minor_tick_mark if cl.cat_axis_minor_tick_mark chart[:val_axis_major_tick_mark] = cl.val_axis_major_tick_mark if cl.val_axis_major_tick_mark chart[:val_axis_minor_tick_mark] = cl.val_axis_minor_tick_mark if cl.val_axis_minor_tick_mark chart[:cat_axis_crosses] = cl.cat_axis_crosses if cl.cat_axis_crosses chart[:val_axis_crosses] = cl.val_axis_crosses if cl.val_axis_crosses chart[:cat_axis_crosses_at] = cl.cat_axis_crosses_at if cl.cat_axis_crosses_at chart[:val_axis_crosses_at] = cl.val_axis_crosses_at if cl.val_axis_crosses_at chart[:cat_axis_tick_lbl_skip] = cl.cat_axis_tick_lbl_skip if cl.cat_axis_tick_lbl_skip chart[:cat_axis_tick_mark_skip] = cl.cat_axis_tick_mark_skip if cl.cat_axis_tick_mark_skip chart[:cat_axis_lbl_offset] = cl.cat_axis_lbl_offset if cl.cat_axis_lbl_offset chart[:cat_axis_auto] = cl.cat_axis_auto unless cl.cat_axis_auto.nil? chart[:cat_axis_lbl_algn] = cl.cat_axis_lbl_algn if cl.cat_axis_lbl_algn chart[:cat_axis_no_multi_lvl_lbl] = cl.cat_axis_no_multi_lvl_lbl unless cl.cat_axis_no_multi_lvl_lbl.nil? chart[:val_axis_cross_between] = cl.val_axis_cross_between if cl.val_axis_cross_between chart[:val_axis_major_unit] = cl.val_axis_major_unit if cl.val_axis_major_unit chart[:val_axis_minor_unit] = cl.val_axis_minor_unit if cl.val_axis_minor_unit chart[:val_axis_disp_units] = cl.val_axis_disp_units if cl.val_axis_disp_units chart[:cat_axis_scaling_max] = cl.cat_axis_scaling_max if cl.cat_axis_scaling_max chart[:cat_axis_scaling_min] = cl.cat_axis_scaling_min if cl.cat_axis_scaling_min chart[:val_axis_scaling_max] = cl.val_axis_scaling_max if cl.val_axis_scaling_max chart[:val_axis_scaling_min] = cl.val_axis_scaling_min if cl.val_axis_scaling_min chart[:cat_axis_log_base] = cl.cat_axis_log_base if cl.cat_axis_log_base chart[:val_axis_log_base] = cl.val_axis_log_base if cl.val_axis_log_base chart[:first_slice_ang] = cl.first_slice_ang if cl.first_slice_ang chart[:hole_size] = cl.hole_size if cl.hole_size chart[:smooth] = cl.smooth unless cl.smooth.nil? chart[:marker] = cl.marker unless cl.marker.nil? chart[:drop_lines] = cl.drop_lines unless cl.drop_lines.nil? chart[:hi_low_lines] = cl.hi_low_lines unless cl.hi_low_lines.nil? chart[:ser_lines] = cl.ser_lines unless cl.ser_lines.nil? chart[:up_down_bars] = cl. if cl. chart[:scatter_style] = cl.scatter_style if cl.scatter_style chart[:radar_style] = cl.radar_style if cl.radar_style chart[:cat_axis_pos] = cl.cat_axis_pos if cl.cat_axis_pos chart[:val_axis_pos] = cl.val_axis_pos if cl.val_axis_pos chart[:wireframe] = cl.wireframe unless cl.wireframe.nil? chart[:band_fmts] = cl.band_fmts if cl.band_fmts chart[:of_pie_type] = cl.of_pie_type if cl.of_pie_type chart[:split_type] = cl.split_type if cl.split_type chart[:split_pos] = cl.split_pos if cl.split_pos chart[:cust_split] = cl.cust_split if cl.cust_split&.any? chart[:second_pie_size] = cl.second_pie_size if cl.second_pie_size chart[:data_table] = cl.data_table if cl.data_table chart[:plot_area_fill] = cl.plot_area_fill if cl.plot_area_fill chart[:plot_area_line_color] = cl.plot_area_line_color if cl.plot_area_line_color chart[:plot_area_line_width] = cl.plot_area_line_width if cl.plot_area_line_width chart[:plot_area_line_dash] = cl.plot_area_line_dash if cl.plot_area_line_dash chart[:plot_area_no_fill] = cl.plot_area_no_fill if cl.plot_area_no_fill chart[:plot_area_layout] = cl.plot_area_layout if cl.plot_area_layout chart[:cat_axis_label_rotation] = cl.cat_axis_label_rotation if cl.cat_axis_label_rotation chart[:val_axis_label_rotation] = cl.val_axis_label_rotation if cl.val_axis_label_rotation chart[:cat_axis_font] = cl.cat_axis_font if cl.cat_axis_font chart[:val_axis_font] = cl.val_axis_font if cl.val_axis_font chart[:cat_axis_fill] = cl.cat_axis_fill if cl.cat_axis_fill chart[:cat_axis_no_fill] = cl.cat_axis_no_fill if cl.cat_axis_no_fill chart[:val_axis_fill] = cl.val_axis_fill if cl.val_axis_fill chart[:val_axis_no_fill] = cl.val_axis_no_fill if cl.val_axis_no_fill chart[:cat_axis_line_color] = cl.cat_axis_line_color if cl.cat_axis_line_color chart[:cat_axis_line_width] = cl.cat_axis_line_width if cl.cat_axis_line_width chart[:cat_axis_line_dash] = cl.cat_axis_line_dash if cl.cat_axis_line_dash chart[:val_axis_line_color] = cl.val_axis_line_color if cl.val_axis_line_color chart[:val_axis_line_width] = cl.val_axis_line_width if cl.val_axis_line_width chart[:val_axis_line_dash] = cl.val_axis_line_dash if cl.val_axis_line_dash chart[:floor] = cl.floor if cl.floor chart[:side_wall] = cl.side_wall if cl.side_wall chart[:back_wall] = cl.back_wall if cl.back_wall chart[:cat_axis_type] = cl.cat_axis_type if cl.cat_axis_type chart[:cat_axis_base_time_unit] = cl.cat_axis_base_time_unit if cl.cat_axis_base_time_unit chart[:cat_axis_major_time_unit] = cl.cat_axis_major_time_unit if cl.cat_axis_major_time_unit chart[:cat_axis_minor_time_unit] = cl.cat_axis_minor_time_unit if cl.cat_axis_minor_time_unit chart[:cat_axis_major_unit] = cl.cat_axis_major_unit if cl.cat_axis_major_unit chart[:cat_axis_minor_unit] = cl.cat_axis_minor_unit if cl.cat_axis_minor_unit chart[:chart_fill] = cl.chart_fill if cl.chart_fill chart[:chart_no_fill] = cl.chart_no_fill if cl.chart_no_fill chart[:chart_line_color] = cl.chart_line_color if cl.chart_line_color chart[:chart_line_width] = cl.chart_line_width if cl.chart_line_width chart[:chart_line_dash] = cl.chart_line_dash if cl.chart_line_dash chart[:protection] = cl.protection if cl.protection chart[:print_settings] = cl.print_settings if cl.print_settings chart[:chart_font] = cl.chart_font if cl.chart_font end listener.charts end |
#col_breaks(sheet: nil) ⇒ Object
Returns column breaks for the given sheet.
474 475 476 477 478 479 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 474 def col_breaks(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return [] if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_print_page(worksheet_xml)[:col_breaks] end |
#column_attributes(sheet: nil) ⇒ Object
Returns column attributes as { "A" => { hidden: true, outline_level: 1 } }.
43 44 45 46 47 48 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 43 def column_attributes(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_column_attributes(worksheet_xml) end |
#column_index_to_letter(index) ⇒ Object
1537 1538 1539 1540 1541 1542 1543 1544 1545 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1537 def column_index_to_letter(index) result = +"" while index.positive? index -= 1 result.prepend(("A".ord + (index % 26)).chr) index /= 26 end result end |
#columns(sheet: nil) ⇒ Object
Returns column widths as { "A" => 20.0, "B" => 15.5 } for the given sheet.
35 36 37 38 39 40 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 35 def columns(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_columns(worksheet_xml) end |
#comments(sheet: nil) ⇒ Object
Returns comments for the given sheet as an array of hashes. Each hash: { ref:, author:, text: }
899 900 901 902 903 904 905 906 907 908 909 910 911 912 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 899 def comments(sheet: nil) sheet_index = resolve_sheet_index(sheet) comments_path = find_sheet_rel_target(sheet_index, "/comments") return [] unless comments_path xml = extract_zip_entry(comments_path) return [] if xml.nil? || xml.empty? parser = REXML::Parsers::SAX2Parser.new(xml) listener = CommentsListener.new parser.listen(listener) parser.parse listener.comments end |
#conditional_formats(sheet: nil) ⇒ Object
Returns conditional formatting rules for the given sheet.
321 322 323 324 325 326 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 321 def conditional_formats(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return [] if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_conditional_formats(worksheet_xml) end |
#conformance ⇒ Object
Returns the workbook conformance class ("transitional" or "strict"), or nil if not set.
558 559 560 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 558 def conformance [:conformance] end |
#core_properties ⇒ Object
Returns core properties as a hash (e.g. { title: "...", creator: "..." }).
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 482 def core_properties # Discover core properties path from _rels/.rels rels_xml = extract_zip_entry("_rels/.rels") return {} if rels_xml.nil? || rels_xml.empty? rels = parse_rels_with_types(rels_xml) core_rel = rels.find { |r| r[:type]&.end_with?("/metadata/core-properties") } return {} unless core_rel target = core_rel[:target] entry_path = target.start_with?("/") ? target.delete_prefix("/") : target xml = extract_zip_entry(entry_path) return {} if xml.nil? || xml.empty? parser = REXML::Parsers::SAX2Parser.new(xml) listener = CorePropertiesListener.new parser.listen(listener) parser.parse listener.properties end |
#custom_properties ⇒ Object
Returns custom document properties as an array of { name:, value:, type: }.
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 529 def custom_properties xml = extract_zip_entry("docProps/custom.xml") if xml.nil? || xml.empty? rels_xml = extract_zip_entry("_rels/.rels") return [] if rels_xml.nil? || rels_xml.empty? rels = parse_rels_with_types(rels_xml) custom_rel = rels.find { |r| r[:type]&.end_with?("/custom-properties") } return [] unless custom_rel target = custom_rel[:target] entry_path = target.start_with?("/") ? target.delete_prefix("/") : target xml = extract_zip_entry(entry_path) end return [] if xml.nil? || xml.empty? parser = REXML::Parsers::SAX2Parser.new(xml) listener = CustomPropertiesListener.new parser.listen(listener) parser.parse listener.properties end |
#data_consolidate(sheet: nil) ⇒ Object
Returns data consolidation settings for the given sheet.
378 379 380 381 382 383 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 378 def data_consolidate(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return nil if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_data_consolidate(worksheet_xml) end |
#data_validations(sheet: nil) ⇒ Object
Returns data validations as an array of hashes.
305 306 307 308 309 310 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 305 def data_validations(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return [] if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_data_validations(worksheet_xml) end |
#data_validations_options(sheet: nil) ⇒ Object
Returns data validations container options (disablePrompts, xWindow, yWindow).
313 314 315 316 317 318 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 313 def (sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? (worksheet_xml) end |
#date_format?(fmt_id, custom_num_fmts) ⇒ Boolean
1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1251 def date_format?(fmt_id, custom_num_fmts) return false unless fmt_id # Built-in date format IDs. return true if Xlsxrb::Ooxml::Utils::BUILTIN_DATE_FMT_IDS.include?(fmt_id) # Check custom format code for date-like patterns. code = custom_num_fmts[fmt_id] return false unless code date_pattern?(code) end |
#date_pattern?(code) ⇒ Boolean
1284 1285 1286 1287 1288 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1284 def date_pattern?(code) # Strip quoted strings to avoid false matches. stripped = code.gsub(/"[^"]*"/, "").gsub(/\\[.]/, "") stripped.match?(/[ymdhsYMDHS]/) end |
#defined_names ⇒ Object
Returns defined names as an array of hashes.
605 606 607 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 605 def defined_names [:defined_names] end |
#dimension(sheet: nil) ⇒ Object
Returns the dimension ref string (e.g. "A1:B10") for the given sheet.
394 395 396 397 398 399 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 394 def dimension(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return nil if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_dimension(worksheet_xml) end |
#discover_sheets ⇒ Object
1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1047 def discover_sheets workbook_xml = extract_zip_entry("xl/workbook.xml") return [{ name: "Sheet1", rid: "rId1", target: "worksheets/sheet1.xml" }] if workbook_xml.nil? || workbook_xml.empty? rels_xml = extract_zip_entry("xl/_rels/workbook.xml.rels") rid_to_target = parse_rels(rels_xml) sheets = [] parser = REXML::Parsers::SAX2Parser.new(workbook_xml) listener = WorkbookListener.new parser.listen(listener) parser.parse listener.sheets.each do |s| target = rid_to_target[s[:rid]] sheets << { name: s[:name], rid: s[:rid], target: target, state: s[:state] } if target end sheets end |
#dxfs ⇒ Object
Returns array of differential formats (dxfs) from the styles.
187 188 189 190 191 192 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 187 def dxfs styles = load_styles return [] if styles.empty? styles[:dxfs] || [] end |
#entry_names ⇒ Object
Returns all ZIP entry paths in the file.
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 649 def entry_names names = [] File.open(@filepath, "rb") do |file| loop do sig = file.read(4) break if sig.nil? || sig.bytesize < 4 sig_val = sig.unpack1("V") break if [0x02014b50, 0x06054b50].include?(sig_val) break unless sig_val == 0x04034b50 header = file.read(26) break if header.nil? || header.bytesize < 26 _ver, flags, _cm, _mt, _md, _crc, comp_size, _unc, fname_len, extra_len = header.unpack("v v v v v V V V v v") break if flags.anybits?(0x0008) fname = file.read(fname_len) file.read(extra_len) file.read(comp_size) names << fname end end names end |
#external_links ⇒ Object
Returns external links from the workbook as an array of hashes. Each hash: { target:, sheet_names: [] }
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 972 973 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 941 def external_links wb_rels_xml = extract_zip_entry("xl/_rels/workbook.xml.rels") return [] if wb_rels_xml.nil? || wb_rels_xml.empty? # Find external link rels. el_targets = [] wb_rels_xml.scan(/<Relationship\s[^>]*>/) do |rel_tag| next unless rel_tag.include?("/externalLink") target = rel_tag[/Target="([^"]+)"/, 1] el_targets << target if target end return [] if el_targets.empty? el_targets.filter_map do |target| path = target.start_with?("/") ? target[1..] : "xl/#{target}" xml = extract_zip_entry(path) next if xml.nil? || xml.empty? parser = REXML::Parsers::SAX2Parser.new(xml) listener = ExternalLinkListener.new parser.listen(listener) parser.parse # Resolve the external book target from rels. rels_path = path.sub(%r{([^/]+)\.xml$}, '_rels/\1.xml.rels') rels_xml = extract_zip_entry(rels_path) ext_target = nil rels_xml&.scan(/<Relationship[^>]+Target="([^"]+)"/) { |t,| ext_target = t } { target: ext_target, sheet_names: listener.sheet_names } end end |
#extract_zip_entry(entry_name) ⇒ Object
1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1290 def extract_zip_entry(entry_name) File.open(@filepath, "rb") do |file| loop do signature = file.read(4) break if signature.nil? || signature.bytesize < 4 signature_value = signature.unpack1("V") break if [0x02014b50, 0x06054b50].include?(signature_value) raise ZipError, "invalid ZIP local header signature" unless signature_value == 0x04034b50 header = file.read(26) raise ZipError, "truncated ZIP local header" if header.nil? || header.bytesize < 26 _version, flags, compression_method, _mtime, _mdate, _crc32, compressed_size, _uncompressed_size, file_name_length, extra_field_length = header.unpack("v v v v v V V V v v") raise ZipError, "ZIP data descriptor is not supported" if flags.anybits?(0x0008) file_name = file.read(file_name_length) raise ZipError, "truncated ZIP file name" if file_name.nil? || file_name.bytesize < file_name_length file.read(extra_field_length) compressed_data = file.read(compressed_size) raise ZipError, "truncated ZIP entry data" if compressed_data.nil? || compressed_data.bytesize < compressed_size next unless file_name == entry_name case compression_method when 0 return compressed_data when 8 inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) begin return inflater.inflate(compressed_data) ensure inflater.close end else raise ZipError, "unsupported ZIP compression method: #{compression_method}" end end end nil end |
#file_recovery_properties ⇒ Object
Returns file recovery properties hash.
588 589 590 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 588 def file_recovery_properties [:file_recovery_properties] end |
#file_sharing ⇒ Object
Returns file sharing properties (e.g. { read_only_recommended: true, user_name: "John" }).
568 569 570 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 568 def file_sharing [:file_sharing] end |
#file_version ⇒ Object
Returns file version properties (e.g. { app_name: "xl", last_edited: "7" }).
563 564 565 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 563 def file_version [:file_version] end |
#fills ⇒ Object
Returns array of fill entries from the styles.
203 204 205 206 207 208 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 203 def fills styles = load_styles return [] if styles.empty? styles[:fills] || [] end |
#filter_columns(sheet: nil) ⇒ Object
Returns filter columns as { col_id => filter_hash }.
289 290 291 292 293 294 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 289 def filter_columns(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_filter_columns(worksheet_xml) end |
#find_sheet_rel_target(sheet_index, type_suffix) ⇒ Object
1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1566 def find_sheet_rel_target(sheet_index, type_suffix) rels_path = "xl/worksheets/_rels/sheet#{sheet_index + 1}.xml.rels" rels_xml = extract_zip_entry(rels_path) return nil if rels_xml.nil? || rels_xml.empty? rels = parse_rels_with_types(rels_xml) rel = rels.find { |r| r[:type]&.end_with?(type_suffix) } return nil unless rel normalize_xl_path(rel[:target], "xl/worksheets") end |
#find_sheet_rel_targets(sheet_index, type_suffix) ⇒ Object
1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1578 def find_sheet_rel_targets(sheet_index, type_suffix) rels_path = "xl/worksheets/_rels/sheet#{sheet_index + 1}.xml.rels" rels_xml = extract_zip_entry(rels_path) return [] if rels_xml.nil? || rels_xml.empty? rels = parse_rels_with_types(rels_xml) rels.select { |r| r[:type]&.end_with?(type_suffix) }.map do |r| normalize_xl_path(r[:target], "xl/worksheets") end end |
#fonts ⇒ Object
Returns array of font entries from the styles.
195 196 197 198 199 200 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 195 def fonts styles = load_styles return [] if styles.empty? styles[:fonts] || [] end |
#format_variant ⇒ Object
Returns :strict or :transitional based on the namespace of the workbook XML.
979 980 981 982 983 984 985 986 987 988 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 979 def format_variant workbook_xml = extract_zip_entry("xl/workbook.xml") return :transitional if workbook_xml.nil? || workbook_xml.empty? if workbook_xml.include?(STRICT_SSML_NS) :strict else :transitional end end |
#freeze_pane(sheet: nil) ⇒ Object
Returns freeze pane settings for the given sheet.
418 419 420 421 422 423 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 418 def freeze_pane(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return nil if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_sheet_view(worksheet_xml)[:pane] end |
#header_footer(sheet: nil) ⇒ Object
Returns header/footer for the given sheet.
458 459 460 461 462 463 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 458 def (sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_print_page(worksheet_xml)[:header_footer] end |
#hyperlinks(sheet: nil) ⇒ Object
Returns hyperlinks as { "A1" => "https://example.com" }.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 75 def hyperlinks(sheet: nil) sheets = discover_sheets raise ArgumentError, "workbook has no sheets" if sheets.empty? target = resolve_sheet_target(sheets, sheet) raise ArgumentError, "sheet not found: #{sheet.inspect}" if target.nil? entry_path = if target.start_with?("/") target.delete_prefix("/") else "xl/#{target}" end worksheet_xml = extract_zip_entry(entry_path) return {} if worksheet_xml.nil? || worksheet_xml.empty? # Parse hyperlink elements from worksheet. links = [] WorksheetParser.each_event(worksheet_xml, part_name: entry_path) do |event| next unless event.type == :hyperlink ref, rid, display, tooltip, location = event.args link = { ref: ref } link[:rid] = rid if rid link[:display] = display if display link[:tooltip] = tooltip if tooltip link[:location] = location if location links << link end # Parse rels to resolve rId -> URL. rels_path = entry_path.sub(%r{([^/]+)$}, '_rels/\1.rels') rels_xml = extract_zip_entry(rels_path) rid_to_url = {} rid_to_url = parse_rels(rels_xml).transform_values { |v| v } if rels_xml && !rels_xml.empty? result = {} links.each do |link| entry = {} if link[:rid] url = rid_to_url[link[:rid]] entry[:url] = url if url end entry[:display] = link[:display] if link[:display] entry[:tooltip] = link[:tooltip] if link[:tooltip] entry[:location] = link[:location] if link[:location] result[link[:ref]] = entry unless entry.empty? end result end |
#ignored_errors(sheet: nil) ⇒ Object
Returns ignored errors for the given sheet.
370 371 372 373 374 375 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 370 def ignored_errors(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return [] if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_ignored_errors(worksheet_xml) end |
#images(sheet: nil) ⇒ Object
Returns images for the given sheet as an array of hashes. Each hash: { name:, embed_rid:, target:, from_col:, from_row:, to_col:, to_row:, cx:, cy: }
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 687 def images(sheet: nil) drawing_xml = load_drawing_xml(sheet) return [] if drawing_xml.nil? || drawing_xml.empty? sheet_index = resolve_sheet_index(sheet) drawing_rels = load_drawing_rels(sheet_index) parser = REXML::Parsers::SAX2Parser.new(drawing_xml) listener = DrawingImagesListener.new parser.listen(listener) parser.parse listener.images.each do |img| target = drawing_rels[img[:embed_rid]] img[:target] = target if target end listener.images end |
#indexed_colors ⇒ Object
Returns indexed colors palette (array of ARGB hex strings).
227 228 229 230 231 232 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 227 def indexed_colors styles = load_styles return [] if styles.empty? styles[:indexed_colors] || [] end |
#load_drawing_rels(sheet_index) ⇒ Object
1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1555 def load_drawing_rels(sheet_index) drawing_path = find_sheet_rel_target(sheet_index, "/drawing") return {} unless drawing_path dir = drawing_path.sub(%r{([^/]+)$}, '_rels/\1.rels') rels_xml = extract_zip_entry(dir) return {} if rels_xml.nil? || rels_xml.empty? parse_rels(rels_xml) end |
#load_drawing_xml(sheet) ⇒ Object
1547 1548 1549 1550 1551 1552 1553 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1547 def load_drawing_xml(sheet) sheet_index = resolve_sheet_index(sheet) drawing_path = find_sheet_rel_target(sheet_index, "/drawing") return nil unless drawing_path extract_zip_entry(drawing_path) end |
#load_pivot_cache_definition(pivot_table_path) ⇒ Object
1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1162 def load_pivot_cache_definition(pivot_table_path) rels_path = pivot_table_path.sub(%r{([^/]+)$}, '_rels/\1.rels') rels_xml = extract_zip_entry(rels_path) return nil if rels_xml.nil? || rels_xml.empty? rels = parse_rels_with_types(rels_xml) cache_rel = rels.find { |r| r[:type]&.end_with?("/pivotCacheDefinition") } return nil unless cache_rel cache_path = normalize_xl_path(cache_rel[:target], File.dirname(pivot_table_path)) cache_xml = extract_zip_entry(cache_path) return nil if cache_xml.nil? || cache_xml.empty? parser = REXML::Parsers::SAX2Parser.new(cache_xml) listener = PivotCacheDefinitionListener.new parser.listen(listener) parser.parse cache_def = listener.cache_definition # Load pivotCacheRecords via cache definition rels. cache_rels_path = cache_path.sub(%r{([^/]+)$}, '_rels/\1.rels') cache_rels_xml = extract_zip_entry(cache_rels_path) if cache_rels_xml && !cache_rels_xml.empty? cache_rels = parse_rels_with_types(cache_rels_xml) rec_rel = cache_rels.find { |r| r[:type]&.end_with?("/pivotCacheRecords") } if rec_rel rec_path = normalize_xl_path(rec_rel[:target], File.dirname(cache_path)) rec_xml = extract_zip_entry(rec_path) if rec_xml && !rec_xml.empty? rec_parser = REXML::Parsers::SAX2Parser.new(rec_xml) rec_listener = PivotCacheRecordsListener.new rec_parser.listen(rec_listener) rec_parser.parse cache_def[:records] = rec_listener.records unless rec_listener.records.empty? end end end cache_def end |
#load_shared_strings ⇒ Object
1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1102 def load_shared_strings sst_xml = extract_zip_entry("xl/sharedStrings.xml") return [] if sst_xml.nil? || sst_xml.empty? parser = REXML::Parsers::SAX2Parser.new(sst_xml) listener = SharedStringsListener.new parser.listen(listener) parser.parse listener.strings end |
#load_styles ⇒ Object
1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1203 def load_styles styles_xml = extract_zip_entry("xl/styles.xml") return {} if styles_xml.nil? || styles_xml.empty? parser = REXML::Parsers::SAX2Parser.new(styles_xml) listener = StylesListener.new parser.listen(listener) parser.parse { num_fmts: listener.num_fmts, cell_xfs: listener.cell_xfs, cell_style_xfs: listener.cell_style_xfs, cell_styles: listener.cell_styles, fonts: listener.fonts, fills: listener.fills, borders: listener.borders, dxfs: listener.dxfs, indexed_colors: listener.indexed_colors, mru_colors: listener.mru_colors, table_styles: listener.table_styles } end |
#load_tables(sheet_index) ⇒ Object
1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1125 def load_tables(sheet_index) rels_path = "xl/worksheets/_rels/sheet#{sheet_index + 1}.xml.rels" rels_xml = extract_zip_entry(rels_path) return [] if rels_xml.nil? || rels_xml.empty? table_paths = [] parser = REXML::Parsers::SAX2Parser.new(rels_xml) listener = RelsListener.new parser.listen(listener) parser.parse listener.relationships.each do |rel| table_paths << rel[:target] if rel[:type]&.end_with?("/table") end table_paths.map do |rel_target| path = if rel_target.start_with?("/") rel_target[1..] # strip leading / elsif rel_target.start_with?("..") "xl/#{rel_target.sub("../", "")}" else "xl/worksheets/#{rel_target}" end tbl_xml = extract_zip_entry(path) next if tbl_xml.nil? || tbl_xml.empty? parse_table_xml(tbl_xml) end.compact end |
#load_worksheet_xml(sheet) ⇒ Object
1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1030 def load_worksheet_xml(sheet) sheets = discover_sheets raise ArgumentError, "workbook has no sheets" if sheets.empty? target = resolve_sheet_target(sheets, sheet) raise ArgumentError, "sheet not found: #{sheet.inspect}" if target.nil? # Target may be absolute (/xl/worksheets/sheet1.xml) or relative (worksheets/sheet1.xml). entry_path = if target.start_with?("/") target.delete_prefix("/") else "xl/#{target}" end extract_zip_entry(entry_path) end |
#macros? ⇒ Object
Returns true if the file contains VBA macros (vbaProject.bin).
681 682 683 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 681 def macros? entry_names.any? { |n| n.include?("vbaProject.bin") } end |
#merged_cells(sheet: nil) ⇒ Object
Returns merged cell ranges as ["A1:B2", "C3:D4"].
67 68 69 70 71 72 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 67 def merged_cells(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return [] if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_merge_cells(worksheet_xml) end |
#mru_colors ⇒ Object
Returns MRU (most recently used) colors (array of color hashes).
235 236 237 238 239 240 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 235 def mru_colors styles = load_styles return [] if styles.empty? styles[:mru_colors] || [] end |
#named_cell_styles ⇒ Object
Returns array of named cell styles (cellStyle elements).
267 268 269 270 271 272 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 267 def named_cell_styles styles = load_styles return [] if styles.empty? styles[:cell_styles] || [] end |
#normalize_xl_path(target, base_dir) ⇒ Object
1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1589 def normalize_xl_path(target, base_dir) if target.start_with?("/") target[1..] elsif target.start_with?("..") # Resolve relative to base parts = base_dir.split("/") + target.split("/") resolved = [] parts.each { |p| p == ".." ? resolved.pop : resolved << p } resolved.join("/") else "#{base_dir}/#{target}" end end |
#num_fmts ⇒ Object
Returns custom number formats as { numFmtId => formatCode }.
219 220 221 222 223 224 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 219 def num_fmts styles = load_styles return {} if styles.empty? styles[:num_fmts] || {} end |
#page_margins(sheet: nil) ⇒ Object
Returns page margins for the given sheet.
442 443 444 445 446 447 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 442 def page_margins(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return nil if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_print_page(worksheet_xml)[:page_margins] end |
#page_setup(sheet: nil) ⇒ Object
Returns page setup for the given sheet.
450 451 452 453 454 455 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 450 def page_setup(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_print_page(worksheet_xml)[:page_setup] end |
#parse_cell_style_indices(xml) ⇒ Object
1221 1222 1223 1224 1225 1226 1227 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1221 def parse_cell_style_indices(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = CellStyleListener.new parser.listen(listener) parser.parse listener.cell_style_indices end |
#parse_rels(rels_xml) ⇒ Object
1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1067 def parse_rels(rels_xml) return {} if rels_xml.nil? || rels_xml.empty? mapping = {} parser = REXML::Parsers::SAX2Parser.new(rels_xml) listener = RelsListener.new parser.listen(listener) parser.parse listener.relationships.each { |r| mapping[r[:id]] = r[:target] } mapping end |
#parse_rels_with_types(rels_xml) ⇒ Object
1079 1080 1081 1082 1083 1084 1085 1086 1087 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1079 def parse_rels_with_types(rels_xml) return [] if rels_xml.nil? || rels_xml.empty? parser = REXML::Parsers::SAX2Parser.new(rels_xml) listener = RelsListener.new parser.listen(listener) parser.parse listener.relationships end |
#parse_table_xml(xml) ⇒ Object
1154 1155 1156 1157 1158 1159 1160 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1154 def parse_table_xml(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = TableListener.new parser.listen(listener) parser.parse listener.table end |
#parse_workbook_metadata ⇒ Object
1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1006 def workbook_xml = extract_zip_entry("xl/workbook.xml") if workbook_xml.nil? || workbook_xml.empty? return { workbook_properties: {}, workbook_views: {}, calc_properties: {}, file_recovery_properties: {}, workbook_protection: nil } end parser = REXML::Parsers::SAX2Parser.new(workbook_xml) listener = WorkbookListener.new parser.listen(listener) parser.parse { workbook_properties: listener.workbook_properties, workbook_views: listener.workbook_views, calc_properties: listener.calc_properties, defined_names: listener.defined_names, workbook_protection: listener.workbook_protection, file_version: listener.file_version, file_sharing: listener.file_sharing, conformance: listener.conformance, file_recovery_properties: listener.file_recovery_properties } end |
#parse_worksheet_auto_filter(xml) ⇒ Object
1386 1387 1388 1389 1390 1391 1392 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1386 def parse_worksheet_auto_filter(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = AutoFilterListener.new parser.listen(listener) parser.parse listener.ref end |
#parse_worksheet_cell_phonetic(xml) ⇒ Object
1370 1371 1372 1373 1374 1375 1376 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1370 def parse_worksheet_cell_phonetic(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = WorksheetListener.new([]) parser.listen(listener) parser.parse listener.cell_phonetic end |
#parse_worksheet_cell_watches(xml) ⇒ Object
1466 1467 1468 1469 1470 1471 1472 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1466 def parse_worksheet_cell_watches(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = CellWatchesListener.new parser.listen(listener) parser.parse listener.watches end |
#parse_worksheet_cells(xml, shared_strings) ⇒ Object
1338 1339 1340 1341 1342 1343 1344 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1338 def parse_worksheet_cells(xml, shared_strings) parser = REXML::Parsers::SAX2Parser.new(xml) listener = WorksheetListener.new(shared_strings) parser.listen(listener) parser.parse listener.cells end |
#parse_worksheet_column_attributes(xml) ⇒ Object
1354 1355 1356 1357 1358 1359 1360 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1354 def parse_worksheet_column_attributes(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = ColumnsListener.new parser.listen(listener) parser.parse listener.raw_column_attrs.transform_keys { |idx| column_index_to_letter(idx) } end |
#parse_worksheet_columns(xml) ⇒ Object
1346 1347 1348 1349 1350 1351 1352 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1346 def parse_worksheet_columns(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = ColumnsListener.new parser.listen(listener) parser.parse listener.raw_columns.transform_keys { |idx| column_index_to_letter(idx) } end |
#parse_worksheet_conditional_formats(xml) ⇒ Object
1426 1427 1428 1429 1430 1431 1432 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1426 def parse_worksheet_conditional_formats(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = ConditionalFormattingListener.new parser.listen(listener) parser.parse listener.rules end |
#parse_worksheet_data_consolidate(xml) ⇒ Object
1482 1483 1484 1485 1486 1487 1488 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1482 def parse_worksheet_data_consolidate(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = DataConsolidateListener.new parser.listen(listener) parser.parse listener.result end |
#parse_worksheet_data_validations(xml) ⇒ Object
1410 1411 1412 1413 1414 1415 1416 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1410 def parse_worksheet_data_validations(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = DataValidationsListener.new parser.listen(listener) parser.parse listener.validations end |
#parse_worksheet_data_validations_options(xml) ⇒ Object
1418 1419 1420 1421 1422 1423 1424 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1418 def (xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = DataValidationsListener.new parser.listen(listener) parser.parse listener. end |
#parse_worksheet_dimension(xml) ⇒ Object
1498 1499 1500 1501 1502 1503 1504 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1498 def parse_worksheet_dimension(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = DimensionListener.new parser.listen(listener) parser.parse listener.ref end |
#parse_worksheet_filter_columns(xml) ⇒ Object
1394 1395 1396 1397 1398 1399 1400 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1394 def parse_worksheet_filter_columns(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = AutoFilterListener.new parser.listen(listener) parser.parse listener.filter_columns end |
#parse_worksheet_ignored_errors(xml) ⇒ Object
1474 1475 1476 1477 1478 1479 1480 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1474 def parse_worksheet_ignored_errors(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = IgnoredErrorsListener.new parser.listen(listener) parser.parse listener.errors end |
#parse_worksheet_merge_cells(xml) ⇒ Object
1378 1379 1380 1381 1382 1383 1384 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1378 def parse_worksheet_merge_cells(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = MergeCellsListener.new parser.listen(listener) parser.parse listener.ranges end |
#parse_worksheet_phonetic_pr(xml) ⇒ Object
1442 1443 1444 1445 1446 1447 1448 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1442 def parse_worksheet_phonetic_pr(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = PhoneticPrListener.new parser.listen(listener) parser.parse listener.phonetic_pr end |
#parse_worksheet_print_page(xml) ⇒ Object
1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1522 def parse_worksheet_print_page(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = PrintPageListener.new parser.listen(listener) parser.parse { print_options: listener., page_margins: listener.page_margins, page_setup: listener.page_setup, header_footer: listener., row_breaks: listener.row_breaks, col_breaks: listener.col_breaks } end |
#parse_worksheet_properties(xml) ⇒ Object
1434 1435 1436 1437 1438 1439 1440 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1434 def parse_worksheet_properties(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = SheetPropertiesListener.new parser.listen(listener) parser.parse listener.properties end |
#parse_worksheet_protected_ranges(xml) ⇒ Object
1458 1459 1460 1461 1462 1463 1464 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1458 def parse_worksheet_protected_ranges(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = ProtectedRangesListener.new parser.listen(listener) parser.parse listener.ranges end |
#parse_worksheet_row_attributes(xml) ⇒ Object
1362 1363 1364 1365 1366 1367 1368 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1362 def parse_worksheet_row_attributes(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = WorksheetListener.new([]) parser.listen(listener) parser.parse listener.row_attributes end |
#parse_worksheet_scenarios(xml) ⇒ Object
1490 1491 1492 1493 1494 1495 1496 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1490 def parse_worksheet_scenarios(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = ScenariosListener.new parser.listen(listener) parser.parse listener.result end |
#parse_worksheet_sheet_format(xml) ⇒ Object
1506 1507 1508 1509 1510 1511 1512 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1506 def parse_worksheet_sheet_format(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = SheetFormatListener.new parser.listen(listener) parser.parse listener.properties end |
#parse_worksheet_sheet_protection(xml) ⇒ Object
1450 1451 1452 1453 1454 1455 1456 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1450 def parse_worksheet_sheet_protection(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = SheetProtectionListener.new parser.listen(listener) parser.parse listener.protection end |
#parse_worksheet_sheet_view(xml) ⇒ Object
1514 1515 1516 1517 1518 1519 1520 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1514 def parse_worksheet_sheet_view(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = SheetViewListener.new parser.listen(listener) parser.parse { view: listener.view, pane: listener.pane, selection: listener.selection } end |
#parse_worksheet_sort_state(xml) ⇒ Object
1402 1403 1404 1405 1406 1407 1408 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1402 def parse_worksheet_sort_state(xml) parser = REXML::Parsers::SAX2Parser.new(xml) listener = SortStateListener.new parser.listen(listener) parser.parse listener.sort_state end |
#phonetic_properties(sheet: nil) ⇒ Object
Returns phonetic properties for the given sheet, or nil if not present.
338 339 340 341 342 343 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 338 def phonetic_properties(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return nil if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_phonetic_pr(worksheet_xml) end |
#pivot_tables(sheet: nil) ⇒ Object
Returns pivot tables for the given sheet as an array of hashes. Each hash: { name:, ref:, cache_id:, fields:, row_fields:, col_fields:, data_fields:, cache: }
916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 916 def pivot_tables(sheet: nil) sheet_index = resolve_sheet_index(sheet) pivot_paths = find_sheet_rel_targets(sheet_index, "/pivotTable") return [] if pivot_paths.empty? pivot_paths.filter_map do |path| xml = extract_zip_entry(path) next if xml.nil? || xml.empty? parser = REXML::Parsers::SAX2Parser.new(xml) listener = PivotTableListener.new parser.listen(listener) parser.parse pt = listener.pivot_table next unless pt # Resolve pivotCacheDefinition via pivot table rels. cache_info = load_pivot_cache_definition(path) pt[:cache] = cache_info if cache_info pt end end |
#print_area(sheet: nil) ⇒ Object
Returns the print area for the given sheet, or nil if not set.
610 611 612 613 614 615 616 617 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 610 def print_area(sheet: nil) _sheet_name, idx = resolve_sheet_for_defined_name(sheet) dn = defined_names.find { |d| d[:name] == "_xlnm.Print_Area" && d[:local_sheet_id] == idx } return nil unless dn # Strip the sheet prefix (e.g. "'Sheet1'!$A$1:$D$20" → "$A$1:$D$20") dn[:value]&.sub(/\A'[^']*'!/, "") end |
#print_options(sheet: nil) ⇒ Object
Returns print options for the given sheet.
434 435 436 437 438 439 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 434 def (sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_print_page(worksheet_xml)[:print_options] end |
#print_titles(sheet: nil) ⇒ Object
Returns the print titles for the given sheet, or nil if not set.
620 621 622 623 624 625 626 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 620 def print_titles(sheet: nil) _sheet_name, idx = resolve_sheet_for_defined_name(sheet) dn = defined_names.find { |d| d[:name] == "_xlnm.Print_Titles" && d[:local_sheet_id] == idx } return nil unless dn dn[:value] end |
#protected_ranges(sheet: nil) ⇒ Object
Returns protected ranges for the given sheet as an array of hashes.
354 355 356 357 358 359 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 354 def protected_ranges(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return [] if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_protected_ranges(worksheet_xml) end |
#raw_entry(name) ⇒ Object
Returns raw bytes for a ZIP entry by path.
676 677 678 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 676 def raw_entry(name) extract_zip_entry(name) end |
#resolve_date_cells(raw_cells, cell_style_map, styles) ⇒ Object
1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1229 def resolve_date_cells(raw_cells, cell_style_map, styles) raw_cells.each do |cell_ref, value| next unless value.is_a?(Numeric) xf_index = cell_style_map[cell_ref] next unless xf_index xf = resolve_effective_xf(styles[:cell_xfs][xf_index], styles[:cell_style_xfs]) next unless xf fmt_id = xf[:num_fmt_id] next unless date_format?(fmt_id, styles[:num_fmts]) raw_cells[cell_ref] = if value.is_a?(Float) && (value % 1).positive? Xlsxrb::Ooxml::Utils.serial_to_datetime(value) else Xlsxrb::Ooxml::Utils.serial_to_date(value.to_i) end end raw_cells end |
#resolve_drawing_relative_path(target, sheet_index) ⇒ Object
1603 1604 1605 1606 1607 1608 1609 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1603 def resolve_drawing_relative_path(target, sheet_index) drawing_path = find_sheet_rel_target(sheet_index, "/drawing") return target unless drawing_path base_dir = File.dirname(drawing_path) normalize_xl_path(target, base_dir) end |
#resolve_effective_xf(xf_data, cell_style_xfs) ⇒ Object
1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1268 def resolve_effective_xf(xf_data, cell_style_xfs) return nil unless xf_data effective = xf_data.dup style_xf = nil style_xf = cell_style_xfs[effective[:xf_id]] if effective[:xf_id] return effective unless style_xf %i[num_fmt_id font_id fill_id border_id].each do |k| effective[k] = style_xf[k] if (effective[k].nil? || effective[k].zero?) && style_xf.key?(k) end effective[:alignment] ||= style_xf[:alignment] effective[:protection] ||= style_xf[:protection] effective end |
#resolve_num_fmt_code(fmt_id, custom_num_fmts) ⇒ Object
1264 1265 1266 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1264 def resolve_num_fmt_code(fmt_id, custom_num_fmts) custom_num_fmts[fmt_id] || Xlsxrb::Ooxml::Utils::BUILTIN_NUM_FMT_CODES[fmt_id] end |
#resolve_sheet_for_defined_name(sheet) ⇒ Object
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 992 def resolve_sheet_for_defined_name(sheet) sheets = discover_sheets if sheet idx = if sheet.is_a?(Integer) sheet else sheets.index { |s| s[:name] == sheet } end [sheets[idx][:name], idx] else [sheets.first[:name], 0] end end |
#resolve_sheet_index(sheet) ⇒ Object
1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1113 def resolve_sheet_index(sheet) sheets = discover_sheets if sheet.nil? 0 elsif sheet.is_a?(Integer) sheet else idx = sheets.index { |s| s[:name] == sheet } idx || 0 end end |
#resolve_sheet_target(sheets, sheet) ⇒ Object
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 1089 def resolve_sheet_target(sheets, sheet) case sheet when nil sheets.first&.dig(:target) when Integer sheets[sheet]&.dig(:target) when String sheets.find { |s| s[:name] == sheet }&.dig(:target) else raise ArgumentError, "sheet must be a String name or Integer index" end end |
#row_attributes(sheet: nil) ⇒ Object
Returns row attributes as { 1 => { height: 25.0 }, 3 => { hidden: true } }.
51 52 53 54 55 56 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 51 def row_attributes(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_row_attributes(worksheet_xml) end |
#row_breaks(sheet: nil) ⇒ Object
Returns row breaks for the given sheet.
466 467 468 469 470 471 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 466 def row_breaks(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return [] if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_print_page(worksheet_xml)[:row_breaks] end |
#scenarios(sheet: nil) ⇒ Object
Returns scenarios for the given sheet.
386 387 388 389 390 391 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 386 def scenarios(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return nil if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_scenarios(worksheet_xml) end |
#selection(sheet: nil) ⇒ Object
Returns selection for the given sheet.
426 427 428 429 430 431 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 426 def selection(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return nil if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_sheet_view(worksheet_xml)[:selection] end |
#shapes(sheet: nil) ⇒ Object
Returns shapes for the given sheet as an array of hashes. Each hash: { name:, id:, preset:, text:, from_col:, from_row:, to_col:, to_row: }
886 887 888 889 890 891 892 893 894 895 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 886 def shapes(sheet: nil) drawing_xml = load_drawing_xml(sheet) return [] if drawing_xml.nil? || drawing_xml.empty? parser = REXML::Parsers::SAX2Parser.new(drawing_xml) listener = DrawingShapesListener.new parser.listen(listener) parser.parse listener.shapes end |
#sheet_format(sheet: nil) ⇒ Object
Returns sheet format properties (defaultRowHeight, defaultColWidth, baseColWidth).
402 403 404 405 406 407 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 402 def sheet_format(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_sheet_format(worksheet_xml) end |
#sheet_names ⇒ Object
Returns ordered sheet names.
644 645 646 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 644 def sheet_names discover_sheets.map { |s| s[:name] } end |
#sheet_properties(sheet: nil) ⇒ Object
Returns sheet-level properties (tabColor, outlinePr) for the given sheet. Returns sheet-level properties (tabColor, outlinePr) for the given sheet.
330 331 332 333 334 335 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 330 def sheet_properties(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_properties(worksheet_xml) end |
#sheet_protection(sheet: nil) ⇒ Object
Returns sheet protection settings as a hash, or nil if unprotected.
346 347 348 349 350 351 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 346 def sheet_protection(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return nil if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_sheet_protection(worksheet_xml) end |
#sheet_states ⇒ Object
Returns sheet states as { "Sheet1" => :visible, "Hidden" => :hidden }.
629 630 631 632 633 634 635 636 637 638 639 640 641 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 629 def sheet_states sheets = discover_sheets result = {} sheets.each do |s| state = case s[:state] when "hidden" then :hidden when "veryHidden" then :very_hidden else :visible end result[s[:name]] = state end result end |
#sheet_view(sheet: nil) ⇒ Object
Returns sheet view properties for the given sheet.
410 411 412 413 414 415 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 410 def sheet_view(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return {} if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_sheet_view(worksheet_xml)[:view] end |
#sort_state(sheet: nil) ⇒ Object
Returns sort state as { ref: "A1:B10", sort_conditions: [...] } or nil.
297 298 299 300 301 302 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 297 def sort_state(sheet: nil) worksheet_xml = load_worksheet_xml(sheet) return nil if worksheet_xml.nil? || worksheet_xml.empty? parse_worksheet_sort_state(worksheet_xml) end |
#table_styles ⇒ Object
Returns table styles configuration hash.
243 244 245 246 247 248 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 243 def table_styles styles = load_styles return {} if styles.empty? styles[:table_styles] || {} end |
#tables(sheet: nil) ⇒ Object
Returns tables for the given sheet as an array of { id:, name:, display_name:, ref:, columns: }.
283 284 285 286 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 283 def tables(sheet: nil) sheet_index = resolve_sheet_index(sheet) load_tables(sheet_index) end |
#workbook_properties ⇒ Object
Returns workbook properties (e.g. { date1904: false, default_theme_version: 166925 }).
553 554 555 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 553 def workbook_properties [:workbook_properties] end |
#workbook_protection ⇒ Object
Returns workbook protection settings as a hash, or nil if unprotected.
578 579 580 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 578 def workbook_protection [:workbook_protection] end |
#workbook_views ⇒ Object
Returns workbook view properties (e.g. { active_tab: 0 }).
573 574 575 |
# File 'lib/xlsxrb/ooxml/reader.rb', line 573 def workbook_views [:workbook_views] end |