xlsxrb 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +110 -43
- data/Rakefile +16 -5
- data/Steepfile +1 -0
- data/benchmark.rb +32 -4
- data/docs/ARCHITECTURE.md +32 -30
- data/docs/visual/VisualGallery.md +165 -157
- data/docs/wasm/ruby.wasm +0 -0
- data/lib/ruby_lsp/xlsxrb/addon.rb +43 -0
- data/lib/ruby_lsp/xlsxrb/completion_listener.rb +767 -0
- data/lib/xlsxrb/elements/cell.rb +98 -4
- data/lib/xlsxrb/elements/column.rb +22 -0
- data/lib/xlsxrb/elements/coordinate_access.rb +101 -0
- data/lib/xlsxrb/elements/row.rb +81 -1
- data/lib/xlsxrb/elements/types.rb +36 -5
- data/lib/xlsxrb/elements/workbook.rb +92 -6
- data/lib/xlsxrb/elements/worksheet.rb +138 -59
- data/lib/xlsxrb/elements.rb +1 -0
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +21 -15
- data/lib/xlsxrb/stream_row.rb +181 -0
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb.rb +654 -247
- data/sig/generated/xlsxrb/elements/cell.rbs +10 -1
- data/sig/generated/xlsxrb/elements/column.rbs +5 -0
- data/sig/generated/xlsxrb/elements/coordinate_access.rbs +70 -0
- data/sig/generated/xlsxrb/elements/row.rbs +8 -1
- data/sig/generated/xlsxrb/elements/types.rbs +13 -5
- data/sig/generated/xlsxrb/elements/workbook.rbs +8 -1
- data/sig/generated/xlsxrb/elements/worksheet.rbs +121 -12
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +2 -0
- data/sig/generated/xlsxrb/stream_row.rbs +125 -0
- data/sig/generated/xlsxrb.rbs +674 -262
- metadata +7 -1
|
@@ -4,91 +4,110 @@
|
|
|
4
4
|
|
|
5
5
|
module Xlsxrb
|
|
6
6
|
module Elements
|
|
7
|
-
# Represents a single worksheet in a workbook.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
# Represents a single fully parsed, in-memory worksheet in a workbook.
|
|
8
|
+
# Provides coordinate random access (sheet["A1"]), row lookups (row_at),
|
|
9
|
+
# and immutable cell updates (update_cell).
|
|
10
|
+
#
|
|
11
|
+
# @example Access cells and rows
|
|
12
|
+
# sheet = workbook.sheet(0).load
|
|
13
|
+
# cell = sheet["A1"]
|
|
14
|
+
# row = sheet.row_at(0)
|
|
15
|
+
#
|
|
16
|
+
# @api public
|
|
17
|
+
class Worksheet
|
|
18
|
+
[Enumerable, CoordinateAccess].each { |m| include m }
|
|
19
|
+
|
|
20
|
+
attr_reader :name, :rows, :columns, :charts, :unmapped_data, :errors
|
|
21
|
+
|
|
22
|
+
# @param name [String] The worksheet name (max 31 characters).
|
|
23
|
+
# @param rows [Array<Elements::Row>] Rows in the sheet.
|
|
24
|
+
# @param columns [Array<Elements::Column>] Column definitions.
|
|
25
|
+
# @param charts [Array<Hash>] Charts in the sheet.
|
|
26
|
+
# @param unmapped_data [Hash] Additional metadata for round-tripping.
|
|
27
|
+
# @param errors [Array<String>, nil] Validation errors.
|
|
28
|
+
#: (name: String, ?rows: Array[Elements::Row], ?columns: Array[Elements::Column], ?charts: Array[Hash[Symbol, untyped]], ?unmapped_data: Hash[untyped, untyped], ?errors: Array[String]?) -> void
|
|
11
29
|
def initialize(name:, rows: [], columns: [], charts: [], unmapped_data: {}, errors: nil)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
rows.each do |r|
|
|
20
|
-
r.cells.each do |c|
|
|
21
|
-
ref = "#{Cell.column_letter(c.column_index)}#{c.row_index + 1}"
|
|
22
|
-
h[ref] = c
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
h
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def cells
|
|
29
|
-
# Ensure ordered traversal
|
|
30
|
-
cells_hash.values.sort_by { |c| [c.row_index, c.column_index] }
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def [](ref)
|
|
34
|
-
cells_hash[ref.to_s.upcase]
|
|
30
|
+
@name = name
|
|
31
|
+
@rows = (rows || []).freeze
|
|
32
|
+
@columns = (columns || []).freeze
|
|
33
|
+
@charts = (charts || []).freeze
|
|
34
|
+
@unmapped_data = (unmapped_data || {}).freeze
|
|
35
|
+
computed_errors = errors || self.class.validate(@name, @rows)
|
|
36
|
+
@errors = computed_errors.freeze
|
|
35
37
|
end
|
|
36
38
|
|
|
39
|
+
# Iterate over rows in the worksheet.
|
|
40
|
+
#
|
|
41
|
+
# @example
|
|
42
|
+
# sheet.each do |row|
|
|
43
|
+
# puts row.to_a.inspect
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# @yield [row]
|
|
47
|
+
# @yieldparam row [Elements::Row]
|
|
48
|
+
# @return [Enumerator, void]
|
|
49
|
+
# @api public
|
|
50
|
+
#: () { (Elements::Row) -> void } -> void
|
|
51
|
+
#: | () -> Enumerator[Elements::Row, void]
|
|
37
52
|
def each(&)
|
|
38
53
|
return to_enum(:each) unless block_given?
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def each_cell(&)
|
|
44
|
-
return to_enum(:each_cell) unless block_given?
|
|
45
|
-
|
|
46
|
-
cells.each(&)
|
|
55
|
+
rows.each(&)
|
|
47
56
|
end
|
|
48
57
|
|
|
58
|
+
# Iterate over rows in the worksheet.
|
|
59
|
+
#
|
|
60
|
+
# @example
|
|
61
|
+
# sheet.each_row do |row|
|
|
62
|
+
# puts "Row #{row.index}: #{row.to_a.inspect}"
|
|
63
|
+
# end
|
|
64
|
+
#
|
|
65
|
+
# @yield [row]
|
|
66
|
+
# @yieldparam row [Elements::Row]
|
|
67
|
+
# @return [Enumerator, void]
|
|
68
|
+
# @api public
|
|
69
|
+
#: () { (Elements::Row) -> void } -> void
|
|
70
|
+
#: | () -> Enumerator[Elements::Row, void]
|
|
49
71
|
def each_row(&)
|
|
50
72
|
return to_enum(:each_row) unless block_given?
|
|
51
73
|
|
|
52
74
|
rows.each(&)
|
|
53
75
|
end
|
|
54
76
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
#
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
rows.min_by(&:index)
|
|
66
|
-
end
|
|
77
|
+
# Iterate over all cells across rows.
|
|
78
|
+
#
|
|
79
|
+
# @yield [cell]
|
|
80
|
+
# @yieldparam cell [Elements::Cell]
|
|
81
|
+
# @return [Enumerator, void]
|
|
82
|
+
# @api public
|
|
83
|
+
#: () { (Elements::Cell) -> void } -> void
|
|
84
|
+
#: | () -> Enumerator[Elements::Cell, void]
|
|
85
|
+
def each_cell(&)
|
|
86
|
+
return to_enum(:each_cell) unless block_given?
|
|
67
87
|
|
|
68
|
-
|
|
69
|
-
rows.max_by(&:index)
|
|
88
|
+
cells.each(&)
|
|
70
89
|
end
|
|
71
90
|
|
|
72
|
-
# Returns
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
row = row_at(row_idx)
|
|
79
|
-
return nil unless row
|
|
80
|
-
|
|
81
|
-
cell = row.cell_at(col_idx)
|
|
82
|
-
cell&.value
|
|
91
|
+
# Returns whether the worksheet is valid according to OOXML specifications.
|
|
92
|
+
#
|
|
93
|
+
# @return [Boolean]
|
|
94
|
+
#: () -> bool
|
|
95
|
+
def valid?
|
|
96
|
+
errors.empty?
|
|
83
97
|
end
|
|
84
98
|
|
|
85
99
|
# Returns a new Worksheet with the specified cell updated.
|
|
86
100
|
#
|
|
101
|
+
# @example
|
|
102
|
+
# new_sheet = sheet.update_cell("B1", value: "Updated")
|
|
103
|
+
#
|
|
87
104
|
# @param ref [String] The cell reference (e.g. "B1").
|
|
88
105
|
# @param value [Object] The new cell value.
|
|
89
106
|
# @param style_index [Integer, String, nil] Optional new style index.
|
|
90
107
|
# @param formula [Elements::Formula, nil] Optional new formula.
|
|
91
108
|
# @return [Worksheet] A new Worksheet instance.
|
|
109
|
+
# @api public
|
|
110
|
+
#: (String ref, ?value: untyped, ?style_index: Integer | String | nil, ?formula: Elements::Formula?) -> Elements::Worksheet
|
|
92
111
|
def update_cell(ref, value: nil, style_index: nil, formula: nil)
|
|
93
112
|
parsed = Cell.parse_ref(ref)
|
|
94
113
|
raise ArgumentError, "invalid cell reference: #{ref}" unless parsed
|
|
@@ -124,6 +143,66 @@ module Xlsxrb
|
|
|
124
143
|
with(rows: new_rows)
|
|
125
144
|
end
|
|
126
145
|
|
|
146
|
+
# Returns a new Worksheet with attributes replaced (Data-like behavior).
|
|
147
|
+
#
|
|
148
|
+
# @param changes [Hash]
|
|
149
|
+
# @return [Worksheet]
|
|
150
|
+
# @api public
|
|
151
|
+
#: (**untyped) -> Elements::Worksheet
|
|
152
|
+
def with(**changes)
|
|
153
|
+
new_name = changes.key?(:name) ? changes[:name] : name
|
|
154
|
+
new_rows = changes.key?(:rows) ? changes[:rows] : rows
|
|
155
|
+
new_cols = changes.key?(:columns) ? changes[:columns] : columns
|
|
156
|
+
new_charts = changes.key?(:charts) ? changes[:charts] : charts
|
|
157
|
+
new_unmapped = changes.key?(:unmapped_data) ? changes[:unmapped_data] : unmapped_data
|
|
158
|
+
new_errors = changes.key?(:errors) ? changes[:errors] : errors
|
|
159
|
+
|
|
160
|
+
self.class.new(
|
|
161
|
+
name: new_name,
|
|
162
|
+
rows: new_rows,
|
|
163
|
+
columns: new_cols,
|
|
164
|
+
charts: new_charts,
|
|
165
|
+
unmapped_data: new_unmapped,
|
|
166
|
+
errors: new_errors
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Support pattern matching.
|
|
171
|
+
#: (Array[Symbol]?) -> Hash[Symbol, untyped]
|
|
172
|
+
def deconstruct_keys(_keys)
|
|
173
|
+
{ name: name, rows: rows, columns: columns, charts: charts, unmapped_data: unmapped_data, errors: errors }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Compare worksheets for equality.
|
|
177
|
+
#: (untyped other) -> bool
|
|
178
|
+
def ==(other)
|
|
179
|
+
return false unless other.is_a?(Worksheet)
|
|
180
|
+
|
|
181
|
+
name == other.name && rows == other.rows && columns == other.columns && charts == other.charts
|
|
182
|
+
end
|
|
183
|
+
alias eql? ==
|
|
184
|
+
|
|
185
|
+
#: () -> Integer
|
|
186
|
+
def hash
|
|
187
|
+
[self.class, name, rows, columns, charts].hash
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Returns self when load is called on an already in-memory Worksheet.
|
|
191
|
+
#
|
|
192
|
+
# @return [Elements::Worksheet]
|
|
193
|
+
# @api public
|
|
194
|
+
#: () -> Elements::Worksheet
|
|
195
|
+
def load
|
|
196
|
+
self
|
|
197
|
+
end
|
|
198
|
+
alias to_worksheet load
|
|
199
|
+
|
|
200
|
+
# Validates worksheet name and rows against OOXML limits.
|
|
201
|
+
#
|
|
202
|
+
# @param name [String]
|
|
203
|
+
# @param rows [Array<Elements::Row>]
|
|
204
|
+
# @return [Array<String>] List of errors.
|
|
205
|
+
#: (untyped name, untyped rows) -> Array[String]
|
|
127
206
|
def self.validate(name, rows)
|
|
128
207
|
errs = []
|
|
129
208
|
if name.nil? || !name.is_a?(String) || name.empty?
|
data/lib/xlsxrb/elements.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative "elements/types"
|
|
|
6
6
|
require_relative "elements/cell"
|
|
7
7
|
require_relative "elements/row"
|
|
8
8
|
require_relative "elements/column"
|
|
9
|
+
require_relative "elements/coordinate_access"
|
|
9
10
|
require_relative "elements/worksheet"
|
|
10
11
|
require_relative "elements/workbook"
|
|
11
12
|
|
|
@@ -515,17 +515,17 @@ module Xlsxrb
|
|
|
515
515
|
row_end = xml.index("</row>", tag_end + 1)
|
|
516
516
|
break unless row_end
|
|
517
517
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
row_obj = Elements::Row.new(
|
|
518
|
+
{ part: part_name, row: row_index }
|
|
519
|
+
row_obj = StreamRow.new(
|
|
522
520
|
index: row_index,
|
|
523
|
-
|
|
521
|
+
xml_bytes: xml,
|
|
522
|
+
from: tag_end + 1,
|
|
523
|
+
to: row_end,
|
|
524
|
+
shared_strings: shared_strings,
|
|
524
525
|
height: attrs[:height],
|
|
525
526
|
hidden: attrs[:hidden] || false,
|
|
526
527
|
custom_height: attrs[:custom_height] || false,
|
|
527
|
-
outline_level: attrs[:outline_level]
|
|
528
|
-
errors: Elements::EMPTY_ERRORS
|
|
528
|
+
outline_level: attrs[:outline_level]
|
|
529
529
|
)
|
|
530
530
|
block.call(row_obj)
|
|
531
531
|
|
|
@@ -537,7 +537,13 @@ module Xlsxrb
|
|
|
537
537
|
|
|
538
538
|
def self.fast_parse_cells_direct(xml, from, to, shared_strings, row_source)
|
|
539
539
|
cells = []
|
|
540
|
+
fast_scan_cells_direct(xml, from, to, shared_strings, row_source) { |c| cells << c }
|
|
541
|
+
cells
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
def self.fast_scan_cells_direct(xml, from, to, shared_strings, row_source, &block)
|
|
540
545
|
pos = from
|
|
546
|
+
cell_count = 0
|
|
541
547
|
|
|
542
548
|
while pos < to
|
|
543
549
|
c_start = xml.index("<c", pos)
|
|
@@ -589,7 +595,6 @@ module Xlsxrb
|
|
|
589
595
|
|
|
590
596
|
row_idx = (row_idx * 10) + (cb - 48)
|
|
591
597
|
ai += 1
|
|
592
|
-
|
|
593
598
|
end
|
|
594
599
|
row_idx -= 1
|
|
595
600
|
ai += 1 if xml.getbyte(ai) == 34
|
|
@@ -619,7 +624,6 @@ module Xlsxrb
|
|
|
619
624
|
|
|
620
625
|
style_index = (style_index * 10) + (cb - 48)
|
|
621
626
|
ai += 1
|
|
622
|
-
|
|
623
627
|
end
|
|
624
628
|
ai += 1 if xml.getbyte(ai) == 34
|
|
625
629
|
else
|
|
@@ -629,13 +633,15 @@ module Xlsxrb
|
|
|
629
633
|
|
|
630
634
|
# Self-closing <c ... />
|
|
631
635
|
if xml.getbyte(c_tag_end - 1) == 47
|
|
632
|
-
|
|
636
|
+
cell_obj = Elements::Cell.new(
|
|
633
637
|
row_index: row_idx || row_source[:row],
|
|
634
|
-
column_index: col_idx ||
|
|
638
|
+
column_index: col_idx || cell_count,
|
|
635
639
|
value: nil,
|
|
636
640
|
style_index: style_index,
|
|
637
641
|
errors: Elements::EMPTY_ERRORS
|
|
638
642
|
)
|
|
643
|
+
cell_count += 1
|
|
644
|
+
block.call(cell_obj)
|
|
639
645
|
pos = c_tag_end + 1
|
|
640
646
|
next
|
|
641
647
|
end
|
|
@@ -721,19 +727,19 @@ module Xlsxrb
|
|
|
721
727
|
end
|
|
722
728
|
|
|
723
729
|
val_to_use = inline_str || value
|
|
724
|
-
|
|
730
|
+
cell_obj = Elements::Cell.new(
|
|
725
731
|
row_index: row_idx || row_source[:row],
|
|
726
|
-
column_index: col_idx ||
|
|
732
|
+
column_index: col_idx || cell_count,
|
|
727
733
|
value: val_to_use,
|
|
728
734
|
formula: formula,
|
|
729
735
|
style_index: style_index,
|
|
730
736
|
errors: Elements::EMPTY_ERRORS
|
|
731
737
|
)
|
|
738
|
+
cell_count += 1
|
|
739
|
+
block.call(cell_obj)
|
|
732
740
|
|
|
733
741
|
pos = c_end + 4
|
|
734
742
|
end
|
|
735
|
-
|
|
736
|
-
cells
|
|
737
743
|
end
|
|
738
744
|
|
|
739
745
|
private_class_method :fast_parse_cells_direct
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rbs_inline: enabled
|
|
4
|
+
|
|
5
|
+
module Xlsxrb
|
|
6
|
+
# Streaming row implementation that parses cells on-demand / lazily.
|
|
7
|
+
# Provides O(1) memory consumption even for rows with tens of thousands of columns.
|
|
8
|
+
#
|
|
9
|
+
# @example Streaming cells one-by-one (O(1) memory)
|
|
10
|
+
# row.each_cell do |cell|
|
|
11
|
+
# puts "#{cell.ref}: #{cell.value}"
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# @example Random access or array conversion (cached on-demand)
|
|
15
|
+
# cell = row[0]
|
|
16
|
+
# values = row.to_a
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
class StreamRow
|
|
20
|
+
[Enumerable].each { |m| include m }
|
|
21
|
+
|
|
22
|
+
attr_reader :index, :height, :hidden, :custom_height, :outline_level
|
|
23
|
+
|
|
24
|
+
# @param index [Integer] 0-based row index.
|
|
25
|
+
# @param xml_bytes [String] Raw ASCII-8BIT XML bytes.
|
|
26
|
+
# @param from [Integer] Byte offset where cells start.
|
|
27
|
+
# @param to [Integer] Byte offset where cells end.
|
|
28
|
+
# @param shared_strings [Array<String>] Shared strings table.
|
|
29
|
+
# @param height [Float, Integer, nil] Row height in points.
|
|
30
|
+
# @param hidden [Boolean] Whether the row is hidden.
|
|
31
|
+
# @param custom_height [Boolean] Whether custom height is set.
|
|
32
|
+
# @param outline_level [Integer, nil] Grouping/outline level.
|
|
33
|
+
#: (index: Integer, xml_bytes: String, from: Integer, to: Integer, shared_strings: Array[String], ?height: Float | Integer | nil, ?hidden: bool, ?custom_height: bool, ?outline_level: Integer | nil) -> void
|
|
34
|
+
def initialize(index:, xml_bytes:, from:, to:, shared_strings:, height: nil, hidden: false,
|
|
35
|
+
custom_height: false, outline_level: nil)
|
|
36
|
+
@index = index
|
|
37
|
+
@xml = xml_bytes
|
|
38
|
+
@from = from
|
|
39
|
+
@to = to
|
|
40
|
+
@shared_strings = shared_strings
|
|
41
|
+
@height = height
|
|
42
|
+
@hidden = hidden
|
|
43
|
+
@custom_height = custom_height
|
|
44
|
+
@outline_level = outline_level
|
|
45
|
+
@cells_cache = nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Iterate over cells in this streaming row one by one.
|
|
49
|
+
#
|
|
50
|
+
# @yield [cell]
|
|
51
|
+
# @yieldparam cell [Elements::Cell]
|
|
52
|
+
# @return [Enumerator, void]
|
|
53
|
+
# @api public
|
|
54
|
+
#: () { (Elements::Cell) -> void } -> void
|
|
55
|
+
#: | () -> Enumerator[Elements::Cell, void]
|
|
56
|
+
def each_cell(&)
|
|
57
|
+
return enum_for(:each_cell) unless block_given?
|
|
58
|
+
|
|
59
|
+
if @cells
|
|
60
|
+
@cells.each(&)
|
|
61
|
+
else
|
|
62
|
+
Ooxml::WorksheetParser.fast_scan_cells_direct(@xml, @from, @to, @shared_strings, { row: @index }, &)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Iterate over cells in this streaming row.
|
|
67
|
+
#
|
|
68
|
+
# @yield [cell]
|
|
69
|
+
# @yieldparam cell [Elements::Cell]
|
|
70
|
+
# @return [Enumerator, void]
|
|
71
|
+
# @api public
|
|
72
|
+
#: () { (Elements::Cell) -> void } -> void
|
|
73
|
+
#: | () -> Enumerator[Elements::Cell, void]
|
|
74
|
+
def each(&)
|
|
75
|
+
each_cell(&)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Returns all cells as an Array. Cached on first access.
|
|
79
|
+
#
|
|
80
|
+
# @return [Array<Elements::Cell>]
|
|
81
|
+
# @api public
|
|
82
|
+
#: () -> Array[Elements::Cell]
|
|
83
|
+
def cells
|
|
84
|
+
@cells ||= each_cell.to_a.freeze
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Access a cell by 0-based column index, or access row attributes via Symbol.
|
|
88
|
+
#
|
|
89
|
+
# @param col_index [Integer, Symbol] Column index or attribute symbol.
|
|
90
|
+
# @return [Elements::Cell, Object, nil]
|
|
91
|
+
# @api public
|
|
92
|
+
#: (Integer | Symbol col_index) -> untyped
|
|
93
|
+
def [](col_index)
|
|
94
|
+
case col_index
|
|
95
|
+
when Symbol
|
|
96
|
+
case col_index
|
|
97
|
+
when :cells then cells
|
|
98
|
+
when :index then index
|
|
99
|
+
when :height then height
|
|
100
|
+
when :hidden then hidden
|
|
101
|
+
when :custom_height then custom_height
|
|
102
|
+
when :outline_level then outline_level
|
|
103
|
+
when :attrs then { height: height, hidden: hidden, custom_height: custom_height, outline_level: outline_level }
|
|
104
|
+
end
|
|
105
|
+
else
|
|
106
|
+
cells[col_index]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Access a cell by 0-based column index.
|
|
111
|
+
#
|
|
112
|
+
# @param col_index [Integer] 0-based column index.
|
|
113
|
+
# @return [Elements::Cell, nil]
|
|
114
|
+
# @api public
|
|
115
|
+
#: (Integer col_index) -> Elements::Cell?
|
|
116
|
+
def cell_at(col_index)
|
|
117
|
+
cells.find { |c| c.column_index == col_index }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Convert row cells to an Array of raw values (sparse columns get nil).
|
|
121
|
+
#
|
|
122
|
+
# @return [Array<Object>]
|
|
123
|
+
# @api public
|
|
124
|
+
#: () -> Array[untyped]
|
|
125
|
+
def to_a
|
|
126
|
+
return [] if cells.empty?
|
|
127
|
+
|
|
128
|
+
max_col = cells.map(&:column_index).max || 0
|
|
129
|
+
arr = Array.new(max_col + 1)
|
|
130
|
+
cells.each do |cell|
|
|
131
|
+
arr[cell.column_index] = cell.value
|
|
132
|
+
end
|
|
133
|
+
arr
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Returns cell values as an Array.
|
|
137
|
+
#
|
|
138
|
+
# @return [Array<Object>]
|
|
139
|
+
# @api public
|
|
140
|
+
#: () -> Array[untyped]
|
|
141
|
+
def values
|
|
142
|
+
to_a
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Returns whether the row is valid according to OOXML specifications.
|
|
146
|
+
#
|
|
147
|
+
# @return [Boolean]
|
|
148
|
+
# @api public
|
|
149
|
+
#: () -> bool
|
|
150
|
+
def valid?
|
|
151
|
+
true
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Unmapped metadata for compatibility with Elements::Row.
|
|
155
|
+
#
|
|
156
|
+
# @return [Hash]
|
|
157
|
+
# @api public
|
|
158
|
+
#: () -> Hash[untyped, untyped]
|
|
159
|
+
def unmapped_data
|
|
160
|
+
Elements::EMPTY_HASH
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Validation errors for compatibility with Elements::Row.
|
|
164
|
+
#
|
|
165
|
+
# @return [Array<String>]
|
|
166
|
+
# @api public
|
|
167
|
+
#: () -> Array[String]
|
|
168
|
+
def errors
|
|
169
|
+
Elements::EMPTY_ERRORS
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Human-readable representation.
|
|
173
|
+
#
|
|
174
|
+
# @return [String]
|
|
175
|
+
# @api public
|
|
176
|
+
#: () -> String
|
|
177
|
+
def inspect
|
|
178
|
+
"#<#{self.class.name} index=#{index} height=#{height.inspect} hidden=#{hidden}>"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
data/lib/xlsxrb/version.rb
CHANGED