Class: Xlsxrb::Ooxml::WorksheetParser

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

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(xml_string, shared_strings: [], part_name: "xl/worksheets/sheet1.xml", &block) ⇒ Object

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



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
117
118
# 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

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

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



28
29
30
31
32
33
# 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

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


19
20
21
22
23
24
25
# 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

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

Parses column definitions () from a worksheet.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 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