Class: Xlsxrb::Ooxml::WorksheetParser

Inherits:
Object
  • Object
show all
Defined in:
lib/xlsxrb/ooxml/worksheet_parser.rb,
sig/xlsxrb.rbs

Overview

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.

Defined Under Namespace

Classes: ColumnsListener

Constant Summary collapse

EMPTY_ARRAY =
[].freeze
EMPTY_HASH =
{}.freeze
XML_ENTITIES =
{ "&amp;" => "&", "&lt;" => "<", "&gt;" => ">", "&quot;" => '"', "&apos;" => "'" }.freeze

Class Method Summary collapse

Class Method Details

.each_event(arg0, shared_strings:, part_name:) ⇒ void .each_event(arg0, shared_strings:, part_name:) ⇒ Enumerator[Event, void]

Yields Event objects for columns, rows, cells, and hyperlinks.

Overloads:

  • .each_event(arg0, shared_strings:, part_name:) ⇒ void

    This method returns an undefined value.

    Parameters:

    • arg0 (String)
    • shared_strings: (Array[String])
    • part_name: (String)
  • .each_event(arg0, shared_strings:, part_name:) ⇒ Enumerator[Event, void]

    Parameters:

    • arg0 (String)
    • shared_strings: (Array[String])
    • part_name: (String)

    Returns:

    • (Enumerator[Event, void])


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
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
# File 'lib/xlsxrb/ooxml/worksheet_parser.rb', line 55

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

.each_row(xml_string, shared_strings: [], part_name: "xl/worksheets/sheet1.xml", &block) ⇒ Object

Streaming parse: yields one raw row hash at a time.



26
27
28
29
30
31
# File 'lib/xlsxrb/ooxml/worksheet_parser.rb', line 26

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

.parse(xml_string, shared_strings: []) ⇒ Object

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: }


17
18
19
20
21
22
23
# File 'lib/xlsxrb/ooxml/worksheet_parser.rb', line 17

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

.parse_columns(xml_string, part_name: "xl/worksheets/sheet1.xml") ⇒ Object

Parses column definitions () from a worksheet.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/xlsxrb/ooxml/worksheet_parser.rb', line 34

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