xlsxrb 0.1.13 → 0.1.14
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 +57 -0
- data/README.md +2 -0
- data/lib/xlsxrb/colors.rb +118 -0
- data/lib/xlsxrb/elements/cell.rb +205 -16
- data/lib/xlsxrb/elements/column.rb +6 -4
- data/lib/xlsxrb/elements/coordinate_access.rb +32 -0
- data/lib/xlsxrb/elements/image.rb +128 -0
- data/lib/xlsxrb/elements/row.rb +206 -9
- data/lib/xlsxrb/elements/styles.rb +157 -0
- data/lib/xlsxrb/elements/types.rb +95 -1
- data/lib/xlsxrb/elements/workbook.rb +85 -4
- data/lib/xlsxrb/elements/worksheet.rb +424 -29
- data/lib/xlsxrb/elements.rb +2 -0
- data/lib/xlsxrb/number_formatter.rb +429 -0
- data/lib/xlsxrb/ooxml/cfb.rb +1 -1
- data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +7 -5
- data/lib/xlsxrb/ooxml/reader/listeners/drawing_listeners.rb +11 -4
- data/lib/xlsxrb/ooxml/reader.rb +12 -14
- data/lib/xlsxrb/ooxml/shared_strings_parser.rb +7 -1
- data/lib/xlsxrb/ooxml/styles_parser.rb +39 -9
- data/lib/xlsxrb/ooxml/utils.rb +34 -18
- data/lib/xlsxrb/ooxml/workbook_parser.rb +53 -15
- data/lib/xlsxrb/ooxml/workbook_writer.rb +65 -14
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +319 -79
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +206 -61
- data/lib/xlsxrb/ooxml/writer/features_xml.rb +4 -2
- data/lib/xlsxrb/ooxml/writer.rb +14 -0
- data/lib/xlsxrb/ooxml/zip_reader.rb +109 -25
- data/lib/xlsxrb/stream_row.rb +243 -16
- data/lib/xlsxrb/stream_sheet.rb +414 -15
- data/lib/xlsxrb/stream_writer.rb +213 -17
- data/lib/xlsxrb/style_builder.rb +308 -22
- data/lib/xlsxrb/utils.rb +158 -0
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +73 -3
- data/lib/xlsxrb/worksheet_builder.rb +227 -16
- data/lib/xlsxrb.rb +578 -79
- data/sig/generated/xlsxrb/colors.rbs +40 -0
- data/sig/generated/xlsxrb/elements/cell.rbs +113 -8
- data/sig/generated/xlsxrb/elements/column.rbs +3 -2
- data/sig/generated/xlsxrb/elements/coordinate_access.rbs +18 -0
- data/sig/generated/xlsxrb/elements/image.rbs +82 -0
- data/sig/generated/xlsxrb/elements/row.rbs +83 -4
- data/sig/generated/xlsxrb/elements/styles.rbs +79 -0
- data/sig/generated/xlsxrb/elements/types.rbs +43 -0
- data/sig/generated/xlsxrb/elements/workbook.rbs +51 -2
- data/sig/generated/xlsxrb/elements/worksheet.rbs +231 -18
- data/sig/generated/xlsxrb/number_formatter.rbs +132 -0
- data/sig/generated/xlsxrb/ooxml/reader.rbs +6 -0
- data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +5 -2
- data/sig/generated/xlsxrb/ooxml/utils.rbs +17 -8
- data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +9 -3
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +28 -6
- data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +3 -1
- data/sig/generated/xlsxrb/ooxml/writer.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +10 -0
- data/sig/generated/xlsxrb/stream_row.rbs +100 -6
- data/sig/generated/xlsxrb/stream_sheet.rbs +247 -13
- data/sig/generated/xlsxrb/stream_writer.rbs +120 -14
- data/sig/generated/xlsxrb/style_builder.rbs +55 -18
- data/sig/generated/xlsxrb/utils.rbs +119 -0
- data/sig/generated/xlsxrb/workbook_builder.rbs +26 -0
- data/sig/generated/xlsxrb/worksheet_builder.rbs +107 -11
- data/sig/generated/xlsxrb.rbs +186 -27
- metadata +11 -1
|
@@ -17,23 +17,105 @@ module Xlsxrb
|
|
|
17
17
|
class Worksheet
|
|
18
18
|
[Enumerable, CoordinateAccess].each { |m| include m }
|
|
19
19
|
|
|
20
|
-
attr_reader :name, :rows, :columns, :charts, :unmapped_data, :errors
|
|
20
|
+
attr_reader :name, :rows, :columns, :charts, :conditional_formatting, :data_validations, :unmapped_data, :errors, :state, :hyperlinks, :comments, :images, :styles, :date1904, :print_area, :print_titles, :sparkline_groups
|
|
21
|
+
attr_writer :dimension
|
|
22
|
+
alias conditional_formats conditional_formatting
|
|
21
23
|
|
|
22
24
|
# @param name [String] The worksheet name (max 31 characters).
|
|
23
25
|
# @param rows [Array<Elements::Row>] Rows in the sheet.
|
|
24
26
|
# @param columns [Array<Elements::Column>] Column definitions.
|
|
25
27
|
# @param charts [Array<Hash>] Charts in the sheet.
|
|
28
|
+
# @param conditional_formatting [Array<Hash>, nil] Conditional formatting rules.
|
|
29
|
+
# @param data_validations [Array<Hash>] Data validation rules.
|
|
26
30
|
# @param unmapped_data [Hash] Additional metadata for round-tripping.
|
|
27
31
|
# @param errors [Array<String>, nil] Validation errors.
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
# @param conditional_formats [Array<Hash>, nil] Alias for conditional_formatting.
|
|
33
|
+
# @param state [Symbol] Sheet visibility state (:visible, :hidden, or :very_hidden).
|
|
34
|
+
# @param hyperlinks [Hash{String => Hash}] Hyperlink mappings by cell reference.
|
|
35
|
+
# @param comments [Array<Hash>] Comments and notes in the sheet.
|
|
36
|
+
# @param images [Array<Elements::Image>] Embedded images in the sheet.
|
|
37
|
+
# @param styles [Hash, nil] Optional parsed styles hash.
|
|
38
|
+
# @param dimension [String, nil] Sheet dimension reference string (e.g. "A1:Z50000").
|
|
39
|
+
# @param trim_empty_rows [Boolean] Whether to omit trailing empty rows during iteration.
|
|
40
|
+
# @param pad_empty_rows [Boolean] Whether to yield empty rows for skipped row numbers.
|
|
41
|
+
# @param pad_empty_cells [Boolean] Whether to pad missing cells within rows.
|
|
42
|
+
# @param print_area [String, nil] Sheet print area range (e.g. "A1:H50").
|
|
43
|
+
# @param print_titles [Hash, nil] Repeating print title rows/cols (e.g. { rows: "1:2", cols: "A:B" }).
|
|
44
|
+
# @param sparkline_groups [Array<Hash>] Sparkline groups in the sheet.
|
|
45
|
+
#: (name: String?, ?rows: Array[Elements::Row], ?columns: Array[Elements::Column], ?charts: Array[Hash[Symbol, untyped]], ?conditional_formatting: Array[Hash[Symbol, untyped]]?, ?data_validations: Array[Hash[Symbol, untyped]], ?unmapped_data: Hash[untyped, untyped], ?errors: Array[String]?, ?conditional_formats: Array[Hash[Symbol, untyped]]?, ?state: Symbol, ?hyperlinks: Hash[String, Hash[Symbol, untyped]], ?comments: Array[Hash[Symbol, untyped]], ?images: Array[Elements::Image], ?styles: Hash[untyped, untyped]?, ?date1904: bool, ?dimension: String?, ?trim_empty_rows: bool, ?pad_empty_rows: bool, ?pad_empty_cells: bool, ?print_area: String?, ?print_titles: Hash[Symbol, untyped]?, ?sparkline_groups: Array[Hash[Symbol, untyped]]) -> void
|
|
46
|
+
def initialize(name:, rows: [], columns: [], charts: [], conditional_formatting: nil, data_validations: [], unmapped_data: {}, errors: nil, conditional_formats: nil, state: :visible, hyperlinks: {}, comments: [], images: [], styles: nil, date1904: false, dimension: nil, trim_empty_rows: false, pad_empty_rows: false, pad_empty_cells: false, print_area: nil, print_titles: nil, sparkline_groups: [])
|
|
30
47
|
@name = name
|
|
31
48
|
@rows = (rows || []).freeze
|
|
32
49
|
@columns = (columns || []).freeze
|
|
33
50
|
@charts = (charts || []).freeze
|
|
51
|
+
cf = conditional_formatting || conditional_formats || []
|
|
52
|
+
@conditional_formatting = cf.freeze
|
|
53
|
+
@data_validations = (data_validations || []).freeze
|
|
34
54
|
@unmapped_data = (unmapped_data || {}).freeze
|
|
35
55
|
computed_errors = errors || self.class.validate(@name, @rows)
|
|
36
56
|
@errors = computed_errors.freeze
|
|
57
|
+
@state = state ? state.to_sym : :visible
|
|
58
|
+
@hyperlinks = (hyperlinks || {}).freeze
|
|
59
|
+
@comments = (comments || []).freeze
|
|
60
|
+
@images = (images || []).freeze
|
|
61
|
+
@styles = styles
|
|
62
|
+
@date1904 = date1904 ? true : false
|
|
63
|
+
@dimension = dimension
|
|
64
|
+
@print_area = print_area
|
|
65
|
+
@print_titles = print_titles&.freeze
|
|
66
|
+
@sparkline_groups = (sparkline_groups || []).freeze
|
|
67
|
+
@trim_empty_rows = trim_empty_rows ? true : false
|
|
68
|
+
@pad_empty_rows = pad_empty_rows ? true : false
|
|
69
|
+
@pad_empty_cells = pad_empty_cells ? true : false
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns whether trailing empty rows are omitted during iteration.
|
|
73
|
+
#
|
|
74
|
+
# @return [Boolean]
|
|
75
|
+
# @api public
|
|
76
|
+
#: () -> bool
|
|
77
|
+
def trim_empty_rows?
|
|
78
|
+
@trim_empty_rows ? true : false
|
|
79
|
+
end
|
|
80
|
+
alias trim_empty_rows trim_empty_rows?
|
|
81
|
+
|
|
82
|
+
# Returns whether skipped rows are padded during iteration.
|
|
83
|
+
#
|
|
84
|
+
# @return [Boolean]
|
|
85
|
+
# @api public
|
|
86
|
+
#: () -> bool
|
|
87
|
+
def pad_empty_rows?
|
|
88
|
+
@pad_empty_rows ? true : false
|
|
89
|
+
end
|
|
90
|
+
alias pad_empty_rows pad_empty_rows?
|
|
91
|
+
|
|
92
|
+
# Returns whether missing cells within rows are padded during iteration.
|
|
93
|
+
#
|
|
94
|
+
# @return [Boolean]
|
|
95
|
+
# @api public
|
|
96
|
+
#: () -> bool
|
|
97
|
+
def pad_empty_cells?
|
|
98
|
+
@pad_empty_cells ? true : false
|
|
99
|
+
end
|
|
100
|
+
alias pad_empty_cells pad_empty_cells?
|
|
101
|
+
|
|
102
|
+
# Returns whether the worksheet uses the 1904 date system.
|
|
103
|
+
#
|
|
104
|
+
# @return [Boolean]
|
|
105
|
+
# @api public
|
|
106
|
+
#: () -> bool
|
|
107
|
+
def date1904?
|
|
108
|
+
@date1904
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Returns the worksheet dimension reference string (e.g. "A1:Z50000"),
|
|
112
|
+
# or computes it dynamically from rows if not explicitly set.
|
|
113
|
+
#
|
|
114
|
+
# @return [String, nil]
|
|
115
|
+
# @api public
|
|
116
|
+
#: () -> String?
|
|
117
|
+
def dimension
|
|
118
|
+
@dimension ||= compute_dimension
|
|
37
119
|
end
|
|
38
120
|
|
|
39
121
|
# Iterate over rows in the worksheet.
|
|
@@ -43,16 +125,27 @@ module Xlsxrb
|
|
|
43
125
|
# puts row.to_a.inspect
|
|
44
126
|
# end
|
|
45
127
|
#
|
|
46
|
-
# @
|
|
47
|
-
#
|
|
48
|
-
#
|
|
128
|
+
# @overload each(trim_empty_rows: nil, pad_empty_rows: nil, pad_empty_cells: nil, &block)
|
|
129
|
+
# @param trim_empty_rows [Boolean, nil] Whether to omit trailing empty rows.
|
|
130
|
+
# @param pad_empty_rows [Boolean, nil] Whether to yield empty rows for skipped row numbers.
|
|
131
|
+
# @param pad_empty_cells [Boolean, nil] Whether to pad missing cells within rows.
|
|
132
|
+
# @yield [row]
|
|
133
|
+
# @yieldparam row [Elements::Row]
|
|
134
|
+
# @return [void]
|
|
135
|
+
#
|
|
136
|
+
# @overload each(trim_empty_rows: nil, pad_empty_rows: nil, pad_empty_cells: nil)
|
|
137
|
+
# @param trim_empty_rows [Boolean, nil] Whether to omit trailing empty rows.
|
|
138
|
+
# @param pad_empty_rows [Boolean, nil] Whether to yield empty rows for skipped row numbers.
|
|
139
|
+
# @param pad_empty_cells [Boolean, nil] Whether to pad missing cells within rows.
|
|
140
|
+
# @return [Enumerator<Elements::Row, void>]
|
|
141
|
+
#
|
|
49
142
|
# @api public
|
|
50
|
-
#: () { (Elements::Row) -> void } -> void
|
|
51
|
-
#: () -> Enumerator[Elements::Row, void]
|
|
52
|
-
def each(&)
|
|
53
|
-
return to_enum(:each) unless block_given?
|
|
143
|
+
#: (?trim_empty_rows: bool?, ?pad_empty_rows: bool?, ?pad_empty_cells: bool?) { (Elements::Row) -> void } -> void
|
|
144
|
+
#: (?trim_empty_rows: bool?, ?pad_empty_rows: bool?, ?pad_empty_cells: bool?) -> Enumerator[Elements::Row, void]
|
|
145
|
+
def each(trim_empty_rows: nil, pad_empty_rows: nil, pad_empty_cells: nil, &)
|
|
146
|
+
return to_enum(:each, trim_empty_rows: trim_empty_rows, pad_empty_rows: pad_empty_rows, pad_empty_cells: pad_empty_cells) unless block_given?
|
|
54
147
|
|
|
55
|
-
|
|
148
|
+
each_row(trim_empty_rows: trim_empty_rows, pad_empty_rows: pad_empty_rows, pad_empty_cells: pad_empty_cells, &)
|
|
56
149
|
end
|
|
57
150
|
|
|
58
151
|
# Iterate over rows in the worksheet.
|
|
@@ -62,16 +155,104 @@ module Xlsxrb
|
|
|
62
155
|
# puts "Row #{row.index}: #{row.to_a.inspect}"
|
|
63
156
|
# end
|
|
64
157
|
#
|
|
65
|
-
# @
|
|
66
|
-
#
|
|
67
|
-
#
|
|
158
|
+
# @overload each_row(trim_empty_rows: nil, pad_empty_rows: nil, pad_empty_cells: nil, &block)
|
|
159
|
+
# @param trim_empty_rows [Boolean, nil] Whether to omit trailing empty rows.
|
|
160
|
+
# @param pad_empty_rows [Boolean, nil] Whether to yield empty rows for skipped row numbers.
|
|
161
|
+
# @param pad_empty_cells [Boolean, nil] Whether to pad missing cells within rows.
|
|
162
|
+
# @yield [row]
|
|
163
|
+
# @yieldparam row [Elements::Row]
|
|
164
|
+
# @return [void]
|
|
165
|
+
#
|
|
166
|
+
# @overload each_row(trim_empty_rows: nil, pad_empty_rows: nil, pad_empty_cells: nil)
|
|
167
|
+
# @param trim_empty_rows [Boolean, nil] Whether to omit trailing empty rows.
|
|
168
|
+
# @param pad_empty_rows [Boolean, nil] Whether to yield empty rows for skipped row numbers.
|
|
169
|
+
# @param pad_empty_cells [Boolean, nil] Whether to pad missing cells within rows.
|
|
170
|
+
# @return [Enumerator<Elements::Row, void>]
|
|
171
|
+
#
|
|
172
|
+
# @api public
|
|
173
|
+
#: (?trim_empty_rows: bool?, ?pad_empty_rows: bool?, ?pad_empty_cells: bool?) { (Elements::Row) -> void } -> void
|
|
174
|
+
#: (?trim_empty_rows: bool?, ?pad_empty_rows: bool?, ?pad_empty_cells: bool?) -> Enumerator[Elements::Row, void]
|
|
175
|
+
def each_row(trim_empty_rows: nil, pad_empty_rows: nil, pad_empty_cells: nil, &block)
|
|
176
|
+
return to_enum(:each_row, trim_empty_rows: trim_empty_rows, pad_empty_rows: pad_empty_rows, pad_empty_cells: pad_empty_cells) unless block
|
|
177
|
+
|
|
178
|
+
trim = if trim_empty_rows.nil?
|
|
179
|
+
@trim_empty_rows
|
|
180
|
+
else
|
|
181
|
+
(trim_empty_rows ? true : false)
|
|
182
|
+
end
|
|
183
|
+
pad_rows = if pad_empty_rows.nil?
|
|
184
|
+
@pad_empty_rows
|
|
185
|
+
else
|
|
186
|
+
(pad_empty_rows ? true : false)
|
|
187
|
+
end
|
|
188
|
+
pad_cells = if pad_empty_cells.nil?
|
|
189
|
+
@pad_empty_cells
|
|
190
|
+
else
|
|
191
|
+
(pad_empty_cells ? true : false)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
row_consumer = if trim
|
|
195
|
+
pending_empty = []
|
|
196
|
+
lambda do |row|
|
|
197
|
+
if row.empty?
|
|
198
|
+
pending_empty << row
|
|
199
|
+
else
|
|
200
|
+
pending_empty.each(&block)
|
|
201
|
+
pending_empty.clear
|
|
202
|
+
block.call(row)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
else
|
|
206
|
+
block
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
if pad_rows
|
|
210
|
+
current_expected_idx = 0
|
|
211
|
+
inner_consumer = row_consumer
|
|
212
|
+
row_consumer = lambda do |row|
|
|
213
|
+
while current_expected_idx < row.index
|
|
214
|
+
empty_row = Row.new(index: current_expected_idx)
|
|
215
|
+
inner_consumer.call(pad_cells ? empty_row.pad_empty_cells : empty_row)
|
|
216
|
+
current_expected_idx += 1
|
|
217
|
+
end
|
|
218
|
+
inner_consumer.call(row)
|
|
219
|
+
current_expected_idx = row.index + 1
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
rows.each do |row|
|
|
224
|
+
row_to_yield = pad_cells ? row.pad_empty_cells : row
|
|
225
|
+
row_consumer.call(row_to_yield)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Iterates over row values as Arrays.
|
|
230
|
+
#
|
|
231
|
+
# @overload each_row_values(type_cast: false, trim_empty_rows: nil, pad_empty_rows: nil, pad_empty_cells: nil, &block)
|
|
232
|
+
# @param type_cast [Boolean] Whether to coerce date/time serial numbers into Date/Time instances.
|
|
233
|
+
# @param trim_empty_rows [Boolean, nil] Whether to omit trailing empty rows.
|
|
234
|
+
# @param pad_empty_rows [Boolean, nil] Whether to yield empty rows for skipped row numbers.
|
|
235
|
+
# @param pad_empty_cells [Boolean, nil] Whether to pad missing cells within rows.
|
|
236
|
+
# @yield [values]
|
|
237
|
+
# @yieldparam values [Array<Object>] Row values array.
|
|
238
|
+
# @return [void]
|
|
239
|
+
#
|
|
240
|
+
# @overload each_row_values(type_cast: false, trim_empty_rows: nil, pad_empty_rows: nil, pad_empty_cells: nil)
|
|
241
|
+
# @param type_cast [Boolean] Whether to coerce date/time serial numbers into Date/Time instances.
|
|
242
|
+
# @param trim_empty_rows [Boolean, nil] Whether to omit trailing empty rows.
|
|
243
|
+
# @param pad_empty_rows [Boolean, nil] Whether to yield empty rows for skipped row numbers.
|
|
244
|
+
# @param pad_empty_cells [Boolean, nil] Whether to pad missing cells within rows.
|
|
245
|
+
# @return [Enumerator<Array<Object>, void>]
|
|
246
|
+
#
|
|
68
247
|
# @api public
|
|
69
|
-
#: () { (
|
|
70
|
-
#: () -> Enumerator[
|
|
71
|
-
def
|
|
72
|
-
return to_enum(:
|
|
248
|
+
#: (?type_cast: bool, ?trim_empty_rows: bool?, ?pad_empty_rows: bool?, ?pad_empty_cells: bool?) { (Array[untyped]) -> void } -> void
|
|
249
|
+
#: (?type_cast: bool, ?trim_empty_rows: bool?, ?pad_empty_rows: bool?, ?pad_empty_cells: bool?) -> Enumerator[Array[untyped], void]
|
|
250
|
+
def each_row_values(type_cast: false, trim_empty_rows: nil, pad_empty_rows: nil, pad_empty_cells: nil, &block)
|
|
251
|
+
return to_enum(:each_row_values, type_cast: type_cast, trim_empty_rows: trim_empty_rows, pad_empty_rows: pad_empty_rows, pad_empty_cells: pad_empty_cells) unless block
|
|
73
252
|
|
|
74
|
-
|
|
253
|
+
each_row(trim_empty_rows: trim_empty_rows, pad_empty_rows: pad_empty_rows, pad_empty_cells: pad_empty_cells) do |row|
|
|
254
|
+
block.call(row.values(type_cast: type_cast))
|
|
255
|
+
end
|
|
75
256
|
end
|
|
76
257
|
|
|
77
258
|
# Iterate over all cells across rows.
|
|
@@ -88,6 +269,39 @@ module Xlsxrb
|
|
|
88
269
|
cells.each(&)
|
|
89
270
|
end
|
|
90
271
|
|
|
272
|
+
# Returns all images anchored at a specific cell reference or 0-based coordinate.
|
|
273
|
+
#
|
|
274
|
+
# @param ref_or_row [String, Symbol, Integer] Cell reference (e.g. "A1") or 0-based row index.
|
|
275
|
+
# @param col [Integer, nil] Optional 0-based column index.
|
|
276
|
+
# @return [Array<Elements::Image>]
|
|
277
|
+
# @api public
|
|
278
|
+
#: (String | Symbol | Integer ref_or_row, ?Integer? col) -> Array[Elements::Image]
|
|
279
|
+
def images_at(ref_or_row, col = nil)
|
|
280
|
+
ref = if col
|
|
281
|
+
"#{Cell.column_letter(col)}#{ref_or_row.to_i + 1}"
|
|
282
|
+
else
|
|
283
|
+
ref_or_row.to_s.upcase
|
|
284
|
+
end
|
|
285
|
+
images.select { |img| img.cell_ref == ref }
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Yields self with images loaded or returns self.
|
|
289
|
+
#
|
|
290
|
+
# @yield [sheet]
|
|
291
|
+
# @yieldparam sheet [Elements::Worksheet]
|
|
292
|
+
# @return [Elements::Worksheet, void]
|
|
293
|
+
# @api public
|
|
294
|
+
#: () { (Elements::Worksheet) -> void } -> void
|
|
295
|
+
#: () -> Elements::Worksheet
|
|
296
|
+
def with_images
|
|
297
|
+
if block_given?
|
|
298
|
+
yield self
|
|
299
|
+
nil
|
|
300
|
+
else
|
|
301
|
+
self
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
91
305
|
# Returns whether the worksheet is valid according to OOXML specifications.
|
|
92
306
|
#
|
|
93
307
|
# @return [Boolean]
|
|
@@ -96,6 +310,95 @@ module Xlsxrb
|
|
|
96
310
|
errors.empty?
|
|
97
311
|
end
|
|
98
312
|
|
|
313
|
+
# Returns whether the worksheet is hidden (:hidden or :very_hidden).
|
|
314
|
+
#
|
|
315
|
+
# @return [Boolean]
|
|
316
|
+
# @api public
|
|
317
|
+
#: () -> bool
|
|
318
|
+
def hidden?
|
|
319
|
+
@state == :hidden || @state == :very_hidden
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Returns whether the worksheet is visible.
|
|
323
|
+
#
|
|
324
|
+
# @return [Boolean]
|
|
325
|
+
# @api public
|
|
326
|
+
#: () -> bool
|
|
327
|
+
def visible?
|
|
328
|
+
@state == :visible
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Returns hyperlink metadata for the given cell reference, or nil.
|
|
332
|
+
#
|
|
333
|
+
# @param ref_or_row [String, Symbol, Integer] Cell reference (e.g. "A1") or 0-based row index.
|
|
334
|
+
# @param col [Integer, nil] Optional 0-based column index if ref_or_row is a row index.
|
|
335
|
+
# @return [Hash, nil]
|
|
336
|
+
# @api public
|
|
337
|
+
#: (String | Symbol | Integer ref_or_row, ?Integer? col) -> Hash[Symbol, untyped]?
|
|
338
|
+
def hyperlink(ref_or_row, col = nil)
|
|
339
|
+
ref = if col
|
|
340
|
+
"#{Cell.column_letter(col)}#{ref_or_row.to_i + 1}"
|
|
341
|
+
else
|
|
342
|
+
ref_or_row.to_s.upcase
|
|
343
|
+
end
|
|
344
|
+
@hyperlinks[ref]
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Returns comment metadata for the given cell reference, or nil.
|
|
348
|
+
#
|
|
349
|
+
# @param ref_or_row [String, Symbol, Integer] Cell reference (e.g. "A1") or 0-based row index.
|
|
350
|
+
# @param col [Integer, nil] Optional 0-based column index if ref_or_row is a row index.
|
|
351
|
+
# @return [Hash, nil]
|
|
352
|
+
# @api public
|
|
353
|
+
#: (String | Symbol | Integer ref_or_row, ?Integer? col) -> Hash[Symbol, untyped]?
|
|
354
|
+
def comment(ref_or_row, col = nil)
|
|
355
|
+
ref = if col
|
|
356
|
+
"#{Cell.column_letter(col)}#{ref_or_row.to_i + 1}"
|
|
357
|
+
else
|
|
358
|
+
ref_or_row.to_s.upcase
|
|
359
|
+
end
|
|
360
|
+
comments_by_ref[ref]
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Returns comments indexed by cell reference.
|
|
364
|
+
#
|
|
365
|
+
# @return [Hash<String, Hash[Symbol, untyped]>]
|
|
366
|
+
# @api public
|
|
367
|
+
#: () -> Hash[String, Hash[Symbol, untyped]]
|
|
368
|
+
def comments_by_ref
|
|
369
|
+
@comments_by_ref ||= @comments.each_with_object({}) do |c, acc|
|
|
370
|
+
r = c[:ref] || c[:cell]
|
|
371
|
+
acc[r.to_s.upcase] = c if r
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Access a cell by reference or 0-based coordinates.
|
|
376
|
+
#
|
|
377
|
+
# @param ref_or_row [String, Symbol, Integer] Cell reference (e.g. "A1") or 0-based row index.
|
|
378
|
+
# @param col [Integer, nil] Optional 0-based column index.
|
|
379
|
+
# @return [Elements::Cell, nil]
|
|
380
|
+
# @api public
|
|
381
|
+
#: (String | Symbol | Integer ref_or_row, ?Integer? col) -> Elements::Cell?
|
|
382
|
+
def cell(ref_or_row, col = nil)
|
|
383
|
+
if col
|
|
384
|
+
row = row_at(ref_or_row.to_i)
|
|
385
|
+
row&.cell_at(col.to_i)
|
|
386
|
+
else
|
|
387
|
+
self[ref_or_row]
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# Returns the formatted string representation of a cell's value.
|
|
392
|
+
#
|
|
393
|
+
# @param ref_or_row [String, Symbol, Integer] Cell reference (e.g. "A1") or 0-based row index.
|
|
394
|
+
# @param col [Integer, nil] Optional 0-based column index.
|
|
395
|
+
# @return [String, nil]
|
|
396
|
+
# @api public
|
|
397
|
+
#: (String | Symbol | Integer ref_or_row, ?Integer? col) -> String?
|
|
398
|
+
def formatted_value(ref_or_row, col = nil)
|
|
399
|
+
cell(ref_or_row, col)&.formatted_value
|
|
400
|
+
end
|
|
401
|
+
|
|
99
402
|
# Returns a new Worksheet with the specified cell updated.
|
|
100
403
|
#
|
|
101
404
|
# @example
|
|
@@ -105,10 +408,12 @@ module Xlsxrb
|
|
|
105
408
|
# @param value [Object] The new cell value.
|
|
106
409
|
# @param style_index [Integer, String, nil] Optional new style index.
|
|
107
410
|
# @param formula [Elements::Formula, nil] Optional new formula.
|
|
411
|
+
# @param hyperlink [Hash, String, nil] Optional new hyperlink.
|
|
412
|
+
# @param comment [Hash, String, nil] Optional new comment.
|
|
108
413
|
# @return [Worksheet] A new Worksheet instance.
|
|
109
414
|
# @api public
|
|
110
|
-
#: (String ref, ?value: untyped, ?style_index: Integer | String | nil, ?formula: Elements::Formula?) -> Elements::Worksheet
|
|
111
|
-
def update_cell(ref, value: nil, style_index: nil, formula: nil)
|
|
415
|
+
#: (String ref, ?value: untyped, ?style_index: Integer | String | nil, ?formula: Elements::Formula?, ?hyperlink: (Hash[Symbol, untyped] | String)?, ?comment: (Hash[Symbol, untyped] | String)?) -> Elements::Worksheet
|
|
416
|
+
def update_cell(ref, value: nil, style_index: nil, formula: nil, hyperlink: nil, comment: nil)
|
|
112
417
|
parsed = Cell.parse_ref(ref)
|
|
113
418
|
raise ArgumentError, "invalid cell reference: #{ref}" unless parsed
|
|
114
419
|
|
|
@@ -121,10 +426,12 @@ module Xlsxrb
|
|
|
121
426
|
existing_cell.with(
|
|
122
427
|
value: value || existing_cell.value,
|
|
123
428
|
style_index: style_index || existing_cell.style_index,
|
|
124
|
-
formula: formula || existing_cell.formula
|
|
429
|
+
formula: formula || existing_cell.formula,
|
|
430
|
+
hyperlink: hyperlink || existing_cell.hyperlink,
|
|
431
|
+
comment: comment || existing_cell.comment
|
|
125
432
|
)
|
|
126
433
|
else
|
|
127
|
-
Cell.new(row_index: row_idx, column_index: col_idx, value: value, style_index: style_index, formula: formula)
|
|
434
|
+
Cell.new(row_index: row_idx, column_index: col_idx, value: value, style_index: style_index, formula: formula, hyperlink: hyperlink, comment: comment)
|
|
128
435
|
end
|
|
129
436
|
|
|
130
437
|
# Replace cell in the existing row
|
|
@@ -136,11 +443,18 @@ module Xlsxrb
|
|
|
136
443
|
new_rows = rows.map { |r| r.index == row_idx ? new_row : r }
|
|
137
444
|
else
|
|
138
445
|
# Row doesn't exist, create it
|
|
139
|
-
new_cell = Cell.new(row_index: row_idx, column_index: col_idx, value: value, style_index: style_index, formula: formula)
|
|
446
|
+
new_cell = Cell.new(row_index: row_idx, column_index: col_idx, value: value, style_index: style_index, formula: formula, hyperlink: hyperlink, comment: comment)
|
|
140
447
|
new_row = Row.new(index: row_idx, cells: [new_cell])
|
|
141
448
|
new_rows = (rows + [new_row]).sort_by!(&:index)
|
|
142
449
|
end
|
|
143
|
-
|
|
450
|
+
new_hyperlinks = hyperlink ? hyperlinks.merge(ref.upcase => hyperlink) : hyperlinks
|
|
451
|
+
new_comments = if comment
|
|
452
|
+
entry = comment.is_a?(Hash) ? comment.merge(ref: ref.upcase) : { ref: ref.upcase, text: comment }
|
|
453
|
+
@comments.reject { |c| (c[:ref] || c[:cell])&.to_s&.upcase == ref.upcase } + [entry]
|
|
454
|
+
else
|
|
455
|
+
@comments
|
|
456
|
+
end
|
|
457
|
+
with(rows: new_rows, hyperlinks: new_hyperlinks, comments: new_comments)
|
|
144
458
|
end
|
|
145
459
|
|
|
146
460
|
# Returns a new Worksheet with attributes replaced (Data-like behavior).
|
|
@@ -154,23 +468,73 @@ module Xlsxrb
|
|
|
154
468
|
new_rows = changes.key?(:rows) ? changes[:rows] : rows
|
|
155
469
|
new_cols = changes.key?(:columns) ? changes[:columns] : columns
|
|
156
470
|
new_charts = changes.key?(:charts) ? changes[:charts] : charts
|
|
471
|
+
new_cf = if changes.key?(:conditional_formatting)
|
|
472
|
+
changes[:conditional_formatting]
|
|
473
|
+
elsif changes.key?(:conditional_formats)
|
|
474
|
+
changes[:conditional_formats]
|
|
475
|
+
else
|
|
476
|
+
conditional_formatting
|
|
477
|
+
end
|
|
478
|
+
new_dv = changes.key?(:data_validations) ? changes[:data_validations] : data_validations
|
|
157
479
|
new_unmapped = changes.key?(:unmapped_data) ? changes[:unmapped_data] : unmapped_data
|
|
158
480
|
new_errors = changes.key?(:errors) ? changes[:errors] : errors
|
|
481
|
+
new_state = changes.key?(:state) ? changes[:state] : state
|
|
482
|
+
new_hyperlinks = changes.key?(:hyperlinks) ? changes[:hyperlinks] : hyperlinks
|
|
483
|
+
new_comments = changes.key?(:comments) ? changes[:comments] : comments
|
|
484
|
+
new_styles = changes.key?(:styles) ? changes[:styles] : styles
|
|
485
|
+
new_date1904 = changes.key?(:date1904) ? changes[:date1904] : date1904
|
|
486
|
+
new_dim = changes.key?(:dimension) ? changes[:dimension] : @dimension
|
|
487
|
+
new_print_area = changes.key?(:print_area) ? changes[:print_area] : print_area
|
|
488
|
+
new_print_titles = changes.key?(:print_titles) ? changes[:print_titles] : print_titles
|
|
489
|
+
new_sparklines = changes.key?(:sparkline_groups) ? changes[:sparkline_groups] : sparkline_groups
|
|
490
|
+
new_trim = changes.key?(:trim_empty_rows) ? changes[:trim_empty_rows] : @trim_empty_rows
|
|
159
491
|
|
|
160
492
|
self.class.new(
|
|
161
493
|
name: new_name,
|
|
162
494
|
rows: new_rows,
|
|
163
495
|
columns: new_cols,
|
|
164
496
|
charts: new_charts,
|
|
497
|
+
conditional_formatting: new_cf,
|
|
498
|
+
data_validations: new_dv,
|
|
165
499
|
unmapped_data: new_unmapped,
|
|
166
|
-
errors: new_errors
|
|
500
|
+
errors: new_errors,
|
|
501
|
+
state: new_state,
|
|
502
|
+
hyperlinks: new_hyperlinks,
|
|
503
|
+
comments: new_comments,
|
|
504
|
+
styles: new_styles,
|
|
505
|
+
date1904: new_date1904,
|
|
506
|
+
dimension: new_dim,
|
|
507
|
+
print_area: new_print_area,
|
|
508
|
+
print_titles: new_print_titles,
|
|
509
|
+
sparkline_groups: new_sparklines,
|
|
510
|
+
trim_empty_rows: new_trim
|
|
167
511
|
)
|
|
168
512
|
end
|
|
169
513
|
|
|
170
514
|
# Support pattern matching.
|
|
171
515
|
#: (Array[Symbol]?) -> Hash[Symbol, untyped]
|
|
172
516
|
def deconstruct_keys(_keys)
|
|
173
|
-
{
|
|
517
|
+
{
|
|
518
|
+
name: name,
|
|
519
|
+
rows: rows,
|
|
520
|
+
columns: columns,
|
|
521
|
+
charts: charts,
|
|
522
|
+
conditional_formatting: conditional_formatting,
|
|
523
|
+
conditional_formats: conditional_formatting,
|
|
524
|
+
data_validations: data_validations,
|
|
525
|
+
unmapped_data: unmapped_data,
|
|
526
|
+
errors: errors,
|
|
527
|
+
state: state,
|
|
528
|
+
hyperlinks: hyperlinks,
|
|
529
|
+
comments: comments,
|
|
530
|
+
styles: styles,
|
|
531
|
+
date1904: date1904,
|
|
532
|
+
dimension: dimension,
|
|
533
|
+
print_area: print_area,
|
|
534
|
+
print_titles: print_titles,
|
|
535
|
+
sparkline_groups: sparkline_groups,
|
|
536
|
+
trim_empty_rows: @trim_empty_rows
|
|
537
|
+
}
|
|
174
538
|
end
|
|
175
539
|
|
|
176
540
|
# Compare worksheets for equality.
|
|
@@ -178,13 +542,15 @@ module Xlsxrb
|
|
|
178
542
|
def ==(other)
|
|
179
543
|
return false unless other.is_a?(Worksheet)
|
|
180
544
|
|
|
181
|
-
name == other.name && rows == other.rows && columns == other.columns && charts == other.charts
|
|
545
|
+
name == other.name && rows == other.rows && columns == other.columns && charts == other.charts &&
|
|
546
|
+
conditional_formatting == other.conditional_formatting && data_validations == other.data_validations &&
|
|
547
|
+
state == other.state && hyperlinks == other.hyperlinks && comments == other.comments
|
|
182
548
|
end
|
|
183
549
|
alias eql? ==
|
|
184
550
|
|
|
185
551
|
#: () -> Integer
|
|
186
552
|
def hash
|
|
187
|
-
[self.class, name, rows, columns, charts].hash
|
|
553
|
+
[self.class, name, rows, columns, charts, conditional_formatting, data_validations, state, hyperlinks, comments].hash
|
|
188
554
|
end
|
|
189
555
|
|
|
190
556
|
# Returns self when load is called on an already in-memory Worksheet.
|
|
@@ -237,6 +603,35 @@ module Xlsxrb
|
|
|
237
603
|
end
|
|
238
604
|
errs
|
|
239
605
|
end
|
|
606
|
+
|
|
607
|
+
private
|
|
608
|
+
|
|
609
|
+
#: () -> String?
|
|
610
|
+
def compute_dimension
|
|
611
|
+
return nil if rows.empty?
|
|
612
|
+
|
|
613
|
+
min_r = nil
|
|
614
|
+
max_r = nil
|
|
615
|
+
min_c = nil
|
|
616
|
+
max_c = nil
|
|
617
|
+
|
|
618
|
+
rows.each do |r|
|
|
619
|
+
next if r.cells.empty?
|
|
620
|
+
|
|
621
|
+
min_r = r.index if min_r.nil? || r.index < min_r
|
|
622
|
+
max_r = r.index if max_r.nil? || r.index > max_r
|
|
623
|
+
r.cells.each do |c|
|
|
624
|
+
min_c = c.column_index if min_c.nil? || c.column_index < min_c
|
|
625
|
+
max_c = c.column_index if max_c.nil? || c.column_index > max_c
|
|
626
|
+
end
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
return nil if min_r.nil? || min_c.nil?
|
|
630
|
+
|
|
631
|
+
start_cell = "#{Elements::Cell.column_letter(min_c)}#{min_r + 1}"
|
|
632
|
+
end_cell = "#{Elements::Cell.column_letter(max_c)}#{max_r + 1}"
|
|
633
|
+
start_cell == end_cell ? start_cell : "#{start_cell}:#{end_cell}"
|
|
634
|
+
end
|
|
240
635
|
end
|
|
241
636
|
end
|
|
242
637
|
end
|
data/lib/xlsxrb/elements.rb
CHANGED
|
@@ -9,6 +9,8 @@ require_relative "elements/column"
|
|
|
9
9
|
require_relative "elements/coordinate_access"
|
|
10
10
|
require_relative "elements/worksheet"
|
|
11
11
|
require_relative "elements/workbook"
|
|
12
|
+
require_relative "elements/styles"
|
|
13
|
+
require_relative "elements/image"
|
|
12
14
|
|
|
13
15
|
module Xlsxrb
|
|
14
16
|
# High-level domain model layer.
|