class Xlsxrb::Ooxml::WorksheetParser
Streaming parser for xl/worksheets/sheetN.xml. Uses a fast string-scanning approach for row/cell extraction, falling back to REXML SAX only for column definitions and unmapped data.
Constants
- ATTR_R
- ATTR_S
- ATTR_T
- CELL_FAST_RE
- CELL_GENERIC_RE
- COL_LETTERS
- COL_MAP
- EMPTY_ARRAY
- EMPTY_HASH
- XML_ENTITIES
Public Class Methods
(untyped xml_string, ?shared_strings: untyped, ?part_name: untyped) ?{ (?) → untyped } → untyped
Source
# File lib/xlsxrb/ooxml/worksheet_parser.rb, line 57 def self.each_event(xml_string, shared_strings: [], part_name: "xl/worksheets/sheet1.xml", &block) return enum_for(:each_event, xml_string, shared_strings: shared_strings, part_name: part_name) unless block return if xml_string.nil? || xml_string.empty? # 1. Parse and yield column events if xml_string.include?("<cols") listener = ColumnsListener.new XmlParser.parse(xml_string, listener) listener.columns.each do |col| block.call(Event.new( type: :column, args: [col[:min], col[:max], col[:width], col[:hidden], col[:custom_width], col[:outline_level]], source: { part: part_name } )) end end # 2. Parse and yield row/cell events fast_scan_events(xml_string, shared_strings, part_name, &block) # 3. Parse and yield hyperlink events return unless xml_string.include?("hyperlink") xml = xml_string.b hpos_term = xml.index("hyperlinks") return unless hpos_term hpos = xml.rindex("<", hpos_term) return unless hpos h_end_term = xml.index("/hyperlinks", hpos) h_end = h_end_term ? xml.index(">", h_end_term) : xml.size h_end ||= xml.size pos = hpos while pos < h_end hl_start_term = xml.index("hyperlink", pos) break unless hl_start_term && hl_start_term < h_end hl_start = xml.rindex("<", hl_start_term) break unless hl_start && hl_start < h_end hl_tag_end = xml.index(">", hl_start + 1) break unless hl_tag_end hl_tag = xml.byteslice(hl_start, hl_tag_end - hl_start) ref = tag_attr(hl_tag, ' ref="') rid = tag_attr(hl_tag, ' r:id="') || tag_attr(hl_tag, ' id="') display = tag_attr(hl_tag, ' display="') tooltip = tag_attr(hl_tag, ' tooltip="') location = tag_attr(hl_tag, ' location="') if ref block.call(Event.new( type: :hyperlink, args: [ref, rid, display, tooltip, location], source: { part: part_name, cell: ref } )) end pos = hl_tag_end + 1 end end
Yields Event objects for columns, rows, cells, and hyperlinks.
(untyped xml_string, ?shared_strings: untyped, ?part_name: untyped) ?{ (?) → untyped } → untyped
Source
# File lib/xlsxrb/ooxml/worksheet_parser.rb, line 28 def self.each_row(xml_string, shared_strings: [], part_name: "xl/worksheets/sheet1.xml", &block) return enum_for(:each_row, xml_string, shared_strings: shared_strings, part_name: part_name) unless block return if xml_string.nil? || xml_string.empty? fast_scan_rows_direct(xml_string, shared_strings, part_name, &block) end
Streaming parse: yields one raw row hash at a time.
(untyped xml, untyped from, untyped to, untyped shared_strings, untyped row_source, ?untyped _prefix) ?{ (?) → untyped } → untyped
Source
# File lib/xlsxrb/ooxml/worksheet_parser.rb, line 604 def self.fast_scan_cells_direct(xml, from, to, shared_strings, row_source, _prefix = "", &block) chunk = xml.byteslice(from, to - from) row_idx = row_source.is_a?(Hash) ? row_source[:row] : row_source col_idx = 0 matched_any = false chunk.scan(CELL_FAST_RE) do |r, s, t, f, v, is, self_r, self_s| matched_any = true ref = r || self_r s_val = s || self_s style_idx = s_val&.to_i c_idx = if ref expected_letter = COL_LETTERS[col_idx] if expected_letter && ref.start_with?(expected_letter) col_idx else c_len = 0 c_len += 1 while (b = ref.getbyte(c_len)) && (b.between?(65, 90) || b.between?(97, 122)) col_letters = ref.byteslice(0, c_len).upcase COL_MAP[col_letters] || col_idx end else col_idx end col_idx = c_idx + 1 val = if t == "s" shared_strings[v.to_i] || "" elsif t == "b" v == "1" elsif %w[inlineStr str e].include?(t) if is extract_inline_text(is, 0, is.bytesize) elsif v v.include?("&") ? decode_xml_entities(v) : v else "" end elsif v v.include?(".") ? v.to_f : v.to_i end formula_expr = f&.include?("&") ? decode_xml_entities(f) : f cell = Elements::Cell.fast_create(row_idx, c_idx, val, style_idx, formula_expr) block.call(cell) end return if matched_any || (!chunk.include?("<c") && !chunk.include?(":c")) # Fallback for non-standard attribute ordering chunk.scan(CELL_GENERIC_RE) do |attrs, f, v, is| r = attrs[ATTR_R, 1] t = attrs[ATTR_T, 1] s = attrs[ATTR_S, 1] c_idx = if r expected_letter = COL_LETTERS[col_idx] if expected_letter && r.start_with?(expected_letter) col_idx else c_len = 0 c_len += 1 while (b = r.getbyte(c_len)) && (b.between?(65, 90) || b.between?(97, 122)) col_letters = r.byteslice(0, c_len).upcase COL_MAP[col_letters] || col_idx end else col_idx end col_idx = c_idx + 1 val = if t == "s" shared_strings[v.to_i] || "" elsif t == "b" v == "1" elsif %w[inlineStr str e].include?(t) if is extract_inline_text(is, 0, is.bytesize) elsif v v.include?("&") ? decode_xml_entities(v) : v else "" end elsif v v.include?(".") ? v.to_f : v.to_i end style_idx = s&.to_i formula_expr = f&.include?("&") ? decode_xml_entities(f) : f cell = Elements::Cell.fast_create(row_idx, c_idx, val, style_idx, formula_expr) block.call(cell) end end
(untyped xml_string, ?shared_strings: untyped) → untyped
Source
# File lib/xlsxrb/ooxml/worksheet_parser.rb, line 19 def self.parse(xml_string, shared_strings: []) return [] if xml_string.nil? || xml_string.empty? rows = [] each_row(xml_string, shared_strings: shared_strings) { |row| rows << row } rows end
Parses all rows from a worksheet XML string. Returns an Array of raw row hashes:
{ index:, cells: [{ ref:, type:, style_index:, value:, formula: }], attrs:, unmapped: }
(untyped xml_string, ?part_name: untyped) → untyped
Source
# File lib/xlsxrb/ooxml/worksheet_parser.rb, line 36 def self.parse_columns(xml_string, part_name: "xl/worksheets/sheet1.xml") return [] if xml_string.nil? || xml_string.empty? columns = [] each_event(xml_string, part_name: part_name) do |event| next unless event.type == :column min, max, width, hidden, custom_width, outline_level = event.args columns << { min: min, max: max, width: width, hidden: hidden, custom_width: custom_width, outline_level: outline_level } end columns end
Parses column definitions (<cols>) from a worksheet.