xlsxrb 0.1.6 → 0.1.7
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 +10 -0
- data/README.md +27 -12
- data/Steepfile +1 -0
- data/docs/visual/VisualGallery.md +8 -0
- data/docs/wasm/ruby.wasm +0 -0
- data/lib/ruby_lsp/xlsxrb/addon.rb +43 -0
- data/lib/ruby_lsp/xlsxrb/completion_listener.rb +769 -0
- data/lib/xlsxrb/elements/cell.rb +98 -4
- data/lib/xlsxrb/elements/column.rb +22 -0
- data/lib/xlsxrb/elements/row.rb +81 -1
- data/lib/xlsxrb/elements/types.rb +36 -5
- data/lib/xlsxrb/elements/workbook.rb +73 -5
- data/lib/xlsxrb/elements/worksheet.rb +110 -1
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb.rb +530 -181
- data/sig/generated/xlsxrb/elements/cell.rbs +10 -1
- data/sig/generated/xlsxrb/elements/column.rbs +5 -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 +7 -0
- data/sig/generated/xlsxrb.rbs +606 -236
- metadata +3 -1
data/lib/xlsxrb/elements/cell.rb
CHANGED
|
@@ -5,8 +5,25 @@
|
|
|
5
5
|
module Xlsxrb
|
|
6
6
|
module Elements
|
|
7
7
|
# Represents a single cell in a worksheet.
|
|
8
|
-
# All indices are 0-based.
|
|
8
|
+
# All row and column indices are 0-based.
|
|
9
|
+
#
|
|
10
|
+
# @example Access cell properties
|
|
11
|
+
# cell = sheet["A1"]
|
|
12
|
+
# cell.value # raw value
|
|
13
|
+
# cell.ref # "A1"
|
|
14
|
+
# cell.to_i # integer value
|
|
15
|
+
# cell.to_date # Date value
|
|
16
|
+
#
|
|
17
|
+
# @api public
|
|
9
18
|
Cell = Data.define(:row_index, :column_index, :value, :formula, :style_index, :unmapped_data, :errors) do
|
|
19
|
+
# @param row_index [Integer] 0-based row index.
|
|
20
|
+
# @param column_index [Integer] 0-based column index.
|
|
21
|
+
# @param value [Object, nil] The cell's value.
|
|
22
|
+
# @param formula [Elements::Formula, nil] Optional formula.
|
|
23
|
+
# @param style_index [Integer, String, nil] Style identifier.
|
|
24
|
+
# @param unmapped_data [Hash] Additional metadata.
|
|
25
|
+
# @param errors [Array<String>, nil] Validation errors.
|
|
26
|
+
#: (row_index: Integer, column_index: Integer, ?value: untyped, ?formula: Elements::Formula?, ?style_index: Integer | String | nil, ?unmapped_data: Hash[untyped, untyped], ?errors: Array[String]?) -> void
|
|
10
27
|
def initialize(row_index:, column_index:, value: nil, formula: nil, style_index: nil, unmapped_data: EMPTY_HASH, errors: nil)
|
|
11
28
|
computed_errors = errors || self.class.validate(row_index, column_index, value)
|
|
12
29
|
computed_errors = computed_errors.freeze unless computed_errors.frozen?
|
|
@@ -14,15 +31,29 @@ module Xlsxrb
|
|
|
14
31
|
style_index: style_index, unmapped_data: unmapped_data, errors: computed_errors)
|
|
15
32
|
end
|
|
16
33
|
|
|
34
|
+
# Returns whether the cell is valid according to OOXML specifications.
|
|
35
|
+
#
|
|
36
|
+
# @return [Boolean]
|
|
37
|
+
#: () -> bool
|
|
17
38
|
def valid?
|
|
18
39
|
errors.empty?
|
|
19
40
|
end
|
|
20
41
|
|
|
21
|
-
# Excel-style reference (e.g. "A1").
|
|
42
|
+
# Returns the Excel-style reference (e.g. "A1", "B2").
|
|
43
|
+
#
|
|
44
|
+
# @return [String]
|
|
45
|
+
# @api public
|
|
46
|
+
#: () -> String
|
|
22
47
|
def ref
|
|
23
48
|
"#{self.class.column_letter(column_index)}#{row_index + 1}"
|
|
24
49
|
end
|
|
25
50
|
|
|
51
|
+
# Access cell attributes by Symbol key.
|
|
52
|
+
#
|
|
53
|
+
# @param key [Symbol] Attribute key (:value, :formula, :style_index, :ref, :column_index, :row_index, :type).
|
|
54
|
+
# @return [Object, nil]
|
|
55
|
+
# @api public
|
|
56
|
+
#: (Symbol key) -> untyped
|
|
26
57
|
def [](key)
|
|
27
58
|
case key
|
|
28
59
|
when :value then value
|
|
@@ -39,22 +70,47 @@ module Xlsxrb
|
|
|
39
70
|
end
|
|
40
71
|
end
|
|
41
72
|
|
|
73
|
+
# Returns the cell value.
|
|
74
|
+
#
|
|
75
|
+
# @return [Object, nil]
|
|
76
|
+
# @api public
|
|
77
|
+
#: () -> untyped
|
|
42
78
|
def content
|
|
43
79
|
value
|
|
44
80
|
end
|
|
45
81
|
|
|
82
|
+
# Returns the string representation of the cell value.
|
|
83
|
+
#
|
|
84
|
+
# @return [String]
|
|
85
|
+
# @api public
|
|
86
|
+
#: () -> String
|
|
46
87
|
def to_s
|
|
47
88
|
value.to_s
|
|
48
89
|
end
|
|
49
90
|
|
|
91
|
+
# Converts the cell value to Integer.
|
|
92
|
+
#
|
|
93
|
+
# @return [Integer]
|
|
94
|
+
# @api public
|
|
95
|
+
#: () -> Integer
|
|
50
96
|
def to_i
|
|
51
97
|
value.to_i
|
|
52
98
|
end
|
|
53
99
|
|
|
100
|
+
# Converts the cell value to Float.
|
|
101
|
+
#
|
|
102
|
+
# @return [Float]
|
|
103
|
+
# @api public
|
|
104
|
+
#: () -> Float
|
|
54
105
|
def to_f
|
|
55
106
|
value.to_f
|
|
56
107
|
end
|
|
57
108
|
|
|
109
|
+
# Converts the cell value (numeric serial date or date string) to Date.
|
|
110
|
+
#
|
|
111
|
+
# @return [Date, nil]
|
|
112
|
+
# @api public
|
|
113
|
+
#: () -> Date?
|
|
58
114
|
def to_date
|
|
59
115
|
return value if value.is_a?(Date)
|
|
60
116
|
|
|
@@ -70,6 +126,11 @@ module Xlsxrb
|
|
|
70
126
|
end
|
|
71
127
|
end
|
|
72
128
|
|
|
129
|
+
# Converts the cell value (numeric serial datetime or datetime string) to Time.
|
|
130
|
+
#
|
|
131
|
+
# @return [Time, nil]
|
|
132
|
+
# @api public
|
|
133
|
+
#: () -> Time?
|
|
73
134
|
def to_time
|
|
74
135
|
return value if value.is_a?(Time)
|
|
75
136
|
|
|
@@ -98,7 +159,16 @@ module Xlsxrb
|
|
|
98
159
|
result.freeze
|
|
99
160
|
end.freeze
|
|
100
161
|
|
|
101
|
-
# Converts a 0-based column index to
|
|
162
|
+
# Converts a 0-based column index to an Excel letter (0 -> "A", 25 -> "Z", 26 -> "AA").
|
|
163
|
+
#
|
|
164
|
+
# @example
|
|
165
|
+
# Cell.column_letter(0) #=> "A"
|
|
166
|
+
# Cell.column_letter(26) #=> "AA"
|
|
167
|
+
#
|
|
168
|
+
# @param index [Integer] 0-based column index.
|
|
169
|
+
# @return [String] Excel column letter.
|
|
170
|
+
# @api public
|
|
171
|
+
#: (Integer index) -> String
|
|
102
172
|
def self.column_letter(index)
|
|
103
173
|
raise ArgumentError, "Column index must be a non-negative Integer, got #{index.inspect}" unless index.is_a?(Integer) && index >= 0
|
|
104
174
|
|
|
@@ -115,7 +185,15 @@ module Xlsxrb
|
|
|
115
185
|
end
|
|
116
186
|
|
|
117
187
|
# Converts a column letter (e.g. "A", :AA) to a 0-based column index.
|
|
118
|
-
#
|
|
188
|
+
#
|
|
189
|
+
# @example
|
|
190
|
+
# Cell.column_index("A") #=> 0
|
|
191
|
+
# Cell.column_index(:AA) #=> 26
|
|
192
|
+
#
|
|
193
|
+
# @param letter [String, Symbol, Integer] Column letter or integer index.
|
|
194
|
+
# @return [Integer] 0-based column index.
|
|
195
|
+
# @api public
|
|
196
|
+
#: (String | Symbol | Integer letter) -> Integer
|
|
119
197
|
def self.column_index(letter)
|
|
120
198
|
if letter.is_a?(Integer)
|
|
121
199
|
raise ArgumentError, "Column index must be >= 0, got #{letter}" if letter.negative?
|
|
@@ -137,6 +215,15 @@ module Xlsxrb
|
|
|
137
215
|
end
|
|
138
216
|
|
|
139
217
|
# Parses an Excel-style reference to [row_index, col_index] (both 0-based).
|
|
218
|
+
#
|
|
219
|
+
# @example
|
|
220
|
+
# Cell.parse_ref("A1") #=> [0, 0]
|
|
221
|
+
# Cell.parse_ref("C10") #=> [9, 2]
|
|
222
|
+
#
|
|
223
|
+
# @param ref [String, nil] Excel cell reference.
|
|
224
|
+
# @return [Array(Integer, Integer), nil] 0-based [row_index, col_index].
|
|
225
|
+
# @api public
|
|
226
|
+
#: (String? ref) -> [Integer, Integer]?
|
|
140
227
|
def self.parse_ref(ref)
|
|
141
228
|
return nil unless ref
|
|
142
229
|
|
|
@@ -162,6 +249,13 @@ module Xlsxrb
|
|
|
162
249
|
[row, col - 1]
|
|
163
250
|
end
|
|
164
251
|
|
|
252
|
+
# Validates cell coordinates and value type against OOXML specifications.
|
|
253
|
+
#
|
|
254
|
+
# @param row_index [Integer]
|
|
255
|
+
# @param column_index [Integer]
|
|
256
|
+
# @param value [Object]
|
|
257
|
+
# @return [Array<String>] List of errors.
|
|
258
|
+
#: (untyped row_index, untyped column_index, untyped value) -> Array[String]
|
|
165
259
|
def self.validate(row_index, column_index, value)
|
|
166
260
|
if row_index.is_a?(Integer) && row_index >= 0 && row_index < 1_048_576 &&
|
|
167
261
|
column_index.is_a?(Integer) && column_index >= 0 && column_index < 16_384 &&
|
|
@@ -6,7 +6,20 @@ module Xlsxrb
|
|
|
6
6
|
module Elements
|
|
7
7
|
# Represents column formatting in a worksheet.
|
|
8
8
|
# index is 0-based.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# col = Elements::Column.new(index: 0, width: 25.0)
|
|
12
|
+
#
|
|
13
|
+
# @api public
|
|
9
14
|
Column = Data.define(:index, :width, :hidden, :custom_width, :outline_level, :unmapped_data, :errors) do
|
|
15
|
+
# @param index [Integer] 0-based column index.
|
|
16
|
+
# @param width [Float, Integer, nil] Column width in characters.
|
|
17
|
+
# @param hidden [Boolean] Whether the column is hidden.
|
|
18
|
+
# @param custom_width [Boolean] Whether custom width flag is set.
|
|
19
|
+
# @param outline_level [Integer, nil] Grouping/outline level.
|
|
20
|
+
# @param unmapped_data [Hash] Additional metadata.
|
|
21
|
+
# @param errors [Array<String>, nil] Validation errors.
|
|
22
|
+
#: (index: Integer, ?width: Float | Integer | nil, ?hidden: bool, ?custom_width: bool, ?outline_level: Integer | nil, ?unmapped_data: Hash[untyped, untyped], ?errors: Array[String]?) -> void
|
|
10
23
|
def initialize(index:, width: nil, hidden: false, custom_width: false, outline_level: nil,
|
|
11
24
|
unmapped_data: {}, errors: nil)
|
|
12
25
|
computed_errors = errors || self.class.validate(index)
|
|
@@ -15,10 +28,19 @@ module Xlsxrb
|
|
|
15
28
|
errors: computed_errors.freeze)
|
|
16
29
|
end
|
|
17
30
|
|
|
31
|
+
# Returns whether the column definition is valid according to OOXML specifications.
|
|
32
|
+
#
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
#: () -> bool
|
|
18
35
|
def valid?
|
|
19
36
|
errors.empty?
|
|
20
37
|
end
|
|
21
38
|
|
|
39
|
+
# Validates column index against OOXML limits.
|
|
40
|
+
#
|
|
41
|
+
# @param index [Integer]
|
|
42
|
+
# @return [Array<String>] List of errors.
|
|
43
|
+
#: (untyped index) -> Array[String]
|
|
22
44
|
def self.validate(index)
|
|
23
45
|
errs = []
|
|
24
46
|
errs << "index must be a non-negative Integer (got #{index.inspect})" if !index.is_a?(Integer) || index.negative?
|
data/lib/xlsxrb/elements/row.rb
CHANGED
|
@@ -5,10 +5,26 @@
|
|
|
5
5
|
module Xlsxrb
|
|
6
6
|
module Elements
|
|
7
7
|
# Represents a single row in a worksheet.
|
|
8
|
-
#
|
|
8
|
+
# All row and column indices are 0-based.
|
|
9
|
+
#
|
|
10
|
+
# @example Access cell by index or symbol
|
|
11
|
+
# row = sheet.row_at(0)
|
|
12
|
+
# cell = row[0] # cell at column 0
|
|
13
|
+
# row.to_a # array of cell values
|
|
14
|
+
#
|
|
15
|
+
# @api public
|
|
9
16
|
Row = Data.define(:index, :cells, :height, :hidden, :custom_height, :outline_level, :unmapped_data, :errors) do
|
|
10
17
|
include Enumerable
|
|
11
18
|
|
|
19
|
+
# @param index [Integer] 0-based row index.
|
|
20
|
+
# @param cells [Array<Elements::Cell>] Cells in this row.
|
|
21
|
+
# @param height [Float, Integer, nil] Row height in points.
|
|
22
|
+
# @param hidden [Boolean] Whether the row is hidden.
|
|
23
|
+
# @param custom_height [Boolean] Whether custom height is set.
|
|
24
|
+
# @param outline_level [Integer, nil] Grouping/outline level.
|
|
25
|
+
# @param unmapped_data [Hash] Additional metadata.
|
|
26
|
+
# @param errors [Array<String>, nil] Validation errors.
|
|
27
|
+
#: (index: Integer, ?cells: Array[Elements::Cell], ?height: Float | Integer | nil, ?hidden: bool, ?custom_height: bool, ?outline_level: Integer | nil, ?unmapped_data: Hash[untyped, untyped], ?errors: Array[String]?) -> void
|
|
12
28
|
def initialize(index:, cells: EMPTY_CELLS, height: nil, hidden: false, custom_height: false, outline_level: nil,
|
|
13
29
|
unmapped_data: EMPTY_HASH, errors: nil)
|
|
14
30
|
computed_errors = errors || self.class.validate(index, cells)
|
|
@@ -19,6 +35,17 @@ module Xlsxrb
|
|
|
19
35
|
unmapped_data: unmapped_data, errors: computed_errors)
|
|
20
36
|
end
|
|
21
37
|
|
|
38
|
+
# Access a cell by 0-based column index, or access row attributes via Symbol.
|
|
39
|
+
#
|
|
40
|
+
# @example
|
|
41
|
+
# row[0] #=> Cell at column 0
|
|
42
|
+
# row[:height] #=> 25.0
|
|
43
|
+
# row[:cells] #=> [Cell, Cell, ...]
|
|
44
|
+
#
|
|
45
|
+
# @param col_index [Integer, Symbol] Column index or attribute symbol.
|
|
46
|
+
# @return [Elements::Cell, Object, nil]
|
|
47
|
+
# @api public
|
|
48
|
+
#: (Integer | Symbol col_index) -> untyped
|
|
22
49
|
def [](col_index)
|
|
23
50
|
case col_index
|
|
24
51
|
when Symbol
|
|
@@ -36,18 +63,52 @@ module Xlsxrb
|
|
|
36
63
|
end
|
|
37
64
|
end
|
|
38
65
|
|
|
66
|
+
# Iterate over cells in this row.
|
|
67
|
+
#
|
|
68
|
+
# @example
|
|
69
|
+
# row.each do |cell|
|
|
70
|
+
# puts cell.value
|
|
71
|
+
# end
|
|
72
|
+
#
|
|
73
|
+
# @yield [cell]
|
|
74
|
+
# @yieldparam cell [Elements::Cell]
|
|
75
|
+
# @return [Enumerator, void]
|
|
76
|
+
# @api public
|
|
77
|
+
#: () { (Elements::Cell) -> void } -> void
|
|
78
|
+
#: | () -> Enumerator[Elements::Cell, void]
|
|
39
79
|
def each(&)
|
|
40
80
|
return to_enum(:each) unless block_given?
|
|
41
81
|
|
|
42
82
|
cells.each(&)
|
|
43
83
|
end
|
|
44
84
|
|
|
85
|
+
# Iterate over cells in this row.
|
|
86
|
+
#
|
|
87
|
+
# @example
|
|
88
|
+
# row.each_cell do |cell|
|
|
89
|
+
# puts "#{cell.ref}: #{cell.value}"
|
|
90
|
+
# end
|
|
91
|
+
#
|
|
92
|
+
# @yield [cell]
|
|
93
|
+
# @yieldparam cell [Elements::Cell]
|
|
94
|
+
# @return [Enumerator, void]
|
|
95
|
+
# @api public
|
|
96
|
+
#: () { (Elements::Cell) -> void } -> void
|
|
97
|
+
#: | () -> Enumerator[Elements::Cell, void]
|
|
45
98
|
def each_cell(&)
|
|
46
99
|
return to_enum(:each_cell) unless block_given?
|
|
47
100
|
|
|
48
101
|
cells.each(&)
|
|
49
102
|
end
|
|
50
103
|
|
|
104
|
+
# Convert row cells to an Array of raw values.
|
|
105
|
+
#
|
|
106
|
+
# @example
|
|
107
|
+
# row.to_a #=> ["ID", "Name", "Total"]
|
|
108
|
+
#
|
|
109
|
+
# @return [Array<Object>]
|
|
110
|
+
# @api public
|
|
111
|
+
#: () -> Array[untyped]
|
|
51
112
|
def to_a
|
|
52
113
|
return [] if cells.empty?
|
|
53
114
|
|
|
@@ -59,16 +120,29 @@ module Xlsxrb
|
|
|
59
120
|
arr
|
|
60
121
|
end
|
|
61
122
|
|
|
123
|
+
# Returns whether the row is valid according to OOXML specifications.
|
|
124
|
+
#
|
|
125
|
+
# @return [Boolean]
|
|
126
|
+
#: () -> bool
|
|
62
127
|
def valid?
|
|
63
128
|
errors.empty?
|
|
64
129
|
end
|
|
65
130
|
|
|
66
131
|
# Returns the cell at the given 0-based column index, or nil.
|
|
132
|
+
#
|
|
133
|
+
# @param column_index [Integer] 0-based column index.
|
|
134
|
+
# @return [Elements::Cell, nil]
|
|
135
|
+
# @api public
|
|
136
|
+
#: (Integer column_index) -> Elements::Cell?
|
|
67
137
|
def cell_at(column_index)
|
|
68
138
|
cells.find { |c| c.column_index == column_index }
|
|
69
139
|
end
|
|
70
140
|
|
|
71
141
|
# Returns cell values as an Array (sparse columns get nil).
|
|
142
|
+
#
|
|
143
|
+
# @return [Array<Object>]
|
|
144
|
+
# @api public
|
|
145
|
+
#: () -> Array[untyped]
|
|
72
146
|
def values
|
|
73
147
|
return [] if cells.empty?
|
|
74
148
|
|
|
@@ -78,6 +152,12 @@ module Xlsxrb
|
|
|
78
152
|
result
|
|
79
153
|
end
|
|
80
154
|
|
|
155
|
+
# Validates row index and cells against OOXML limits.
|
|
156
|
+
#
|
|
157
|
+
# @param index [Integer]
|
|
158
|
+
# @param cells [Array<Elements::Cell>]
|
|
159
|
+
# @return [Array<String>] List of errors.
|
|
160
|
+
#: (untyped index, untyped cells) -> Array[String]
|
|
81
161
|
def self.validate(index, cells)
|
|
82
162
|
return EMPTY_ERRORS if index.is_a?(Integer) && index >= 0 && index < 1_048_576 && cells.is_a?(Array)
|
|
83
163
|
|
|
@@ -8,32 +8,63 @@ module Xlsxrb
|
|
|
8
8
|
EMPTY_HASH = {}.freeze
|
|
9
9
|
EMPTY_CELLS = [].freeze
|
|
10
10
|
|
|
11
|
-
# Represents
|
|
12
|
-
#
|
|
11
|
+
# Represents an Excel formula with an optional cached value and calculation properties.
|
|
12
|
+
#
|
|
13
|
+
# @example Create a formula
|
|
14
|
+
# formula = Elements::Formula.new(expression: "SUM(A1:A10)")
|
|
15
|
+
#
|
|
16
|
+
# @api public
|
|
13
17
|
Formula = Data.define(:expression, :cached_value, :type, :ref, :shared_index, :calculate_always, :aca, :bx, :dt2d, :dtr, :r1, :r2) do
|
|
18
|
+
# @param expression [String] Excel formula expression without leading '=' (e.g. "SUM(A1:A10)").
|
|
19
|
+
# @param cached_value [Object, nil] Optional precomputed value.
|
|
20
|
+
# @param type [Symbol, String, nil] Formula type (:shared, :array, etc.).
|
|
21
|
+
# @param ref [String, nil] Target cell or range reference.
|
|
22
|
+
# @param shared_index [Integer, nil] Shared formula index.
|
|
23
|
+
# @param calculate_always [Boolean, nil] Force Excel to recalculate on open.
|
|
24
|
+
# @param aca [Boolean, nil] Always calculate array attribute.
|
|
25
|
+
# @param bx [Boolean, nil] Assigns to array formula.
|
|
26
|
+
# @param dt2d [Boolean, nil] 2D data table reference.
|
|
27
|
+
# @param dtr [Boolean, nil] 1D data table reference.
|
|
28
|
+
# @param r1 [String, nil] First table reference.
|
|
29
|
+
# @param r2 [String, nil] Second table reference.
|
|
30
|
+
#: (expression: String, ?cached_value: untyped, ?type: untyped, ?ref: String?, ?shared_index: Integer?, ?calculate_always: bool?, ?aca: bool?, ?bx: bool?, ?dt2d: bool?, ?dtr: bool?, ?r1: String?, ?r2: String?) -> void
|
|
14
31
|
def initialize(expression:, cached_value: nil, type: nil, ref: nil, shared_index: nil, calculate_always: nil, aca: nil, bx: nil, dt2d: nil, dtr: nil, r1: nil, r2: nil) # rubocop:disable Naming/MethodParameterName
|
|
15
32
|
super
|
|
16
33
|
end
|
|
17
34
|
end
|
|
18
35
|
|
|
19
36
|
# Represents a cell error value (e.g. #N/A, #REF!, #DIV/0!).
|
|
37
|
+
# @api public
|
|
20
38
|
VALID_ERROR_CODES = %w[#NULL! #DIV/0! #VALUE! #REF! #NAME? #NUM! #N/A #GETTING_DATA].freeze
|
|
21
39
|
CellError = Data.define(:code) do
|
|
40
|
+
# @param code [String] Valid error code string (e.g. "#N/A").
|
|
41
|
+
#: (code: String) -> void
|
|
22
42
|
def initialize(code:)
|
|
23
43
|
raise ArgumentError, "invalid error code: #{code.inspect} (must be one of #{VALID_ERROR_CODES.join(", ")})" unless VALID_ERROR_CODES.include?(code)
|
|
24
44
|
|
|
25
45
|
super
|
|
26
46
|
end
|
|
27
47
|
|
|
48
|
+
# @return [String]
|
|
49
|
+
# @api public
|
|
50
|
+
#: () -> String
|
|
28
51
|
def to_s
|
|
29
52
|
code
|
|
30
53
|
end
|
|
31
54
|
end
|
|
32
55
|
|
|
33
|
-
# Represents a rich text string with formatting runs.
|
|
34
|
-
#
|
|
35
|
-
#
|
|
56
|
+
# Represents a rich text string with multiple formatting runs.
|
|
57
|
+
#
|
|
58
|
+
# @example
|
|
59
|
+
# rt = Elements::RichText.new(runs: [{ text: "Hello ", font: { bold: true } }, { text: "World" }])
|
|
60
|
+
#
|
|
61
|
+
# @api public
|
|
36
62
|
RichText = Data.define(:runs) do
|
|
63
|
+
# Returns concatenated plain text of all runs.
|
|
64
|
+
#
|
|
65
|
+
# @return [String]
|
|
66
|
+
# @api public
|
|
67
|
+
#: () -> String
|
|
37
68
|
def to_s
|
|
38
69
|
runs.map { |r| r[:text] }.join
|
|
39
70
|
end
|
|
@@ -4,25 +4,63 @@
|
|
|
4
4
|
|
|
5
5
|
module Xlsxrb
|
|
6
6
|
module Elements
|
|
7
|
-
# Represents an entire XLSX workbook.
|
|
7
|
+
# Represents an entire in-memory XLSX workbook.
|
|
8
|
+
#
|
|
9
|
+
# @example Access sheets
|
|
10
|
+
# workbook = Xlsxrb.read("report.xlsx")
|
|
11
|
+
# sheet = workbook.sheet(0) # or workbook["Sheet1"]
|
|
12
|
+
# workbook.each { |s| puts s.name }
|
|
13
|
+
#
|
|
14
|
+
# @api public
|
|
8
15
|
Workbook = Data.define(:sheets, :shared_strings, :styles, :unmapped_data, :errors) do
|
|
9
16
|
include Enumerable
|
|
10
17
|
|
|
18
|
+
# @param sheets [Array<Elements::Worksheet>] Worksheets in the workbook.
|
|
19
|
+
# @param shared_strings [Array<String>] Shared strings table.
|
|
20
|
+
# @param styles [Hash] Styles definition.
|
|
21
|
+
# @param unmapped_data [Hash] Additional metadata for round-tripping.
|
|
22
|
+
# @param errors [Array<String>, nil] Validation errors.
|
|
23
|
+
#: (?sheets: Array[Elements::Worksheet], ?shared_strings: Array[String], ?styles: Hash[untyped, untyped], ?unmapped_data: Hash[untyped, untyped], ?errors: Array[String]?) -> void
|
|
11
24
|
def initialize(sheets: [], shared_strings: [], styles: {}, unmapped_data: {}, errors: nil)
|
|
12
25
|
computed_errors = errors || self.class.validate(sheets)
|
|
13
26
|
super(sheets: sheets.freeze, shared_strings: shared_strings.freeze, styles: styles,
|
|
14
27
|
unmapped_data: unmapped_data, errors: computed_errors.freeze)
|
|
15
28
|
end
|
|
16
29
|
|
|
30
|
+
# Iterate over worksheets.
|
|
31
|
+
#
|
|
32
|
+
# @example
|
|
33
|
+
# workbook.each do |sheet|
|
|
34
|
+
# puts sheet.name
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# @yield [sheet]
|
|
38
|
+
# @yieldparam sheet [Elements::Worksheet]
|
|
39
|
+
# @return [Enumerator, void]
|
|
40
|
+
#: () { (Elements::Worksheet) -> void } -> void
|
|
41
|
+
#: | () -> Enumerator[Elements::Worksheet, void]
|
|
17
42
|
def each(&)
|
|
18
43
|
sheets.each(&)
|
|
19
44
|
end
|
|
20
45
|
|
|
46
|
+
# Returns whether the workbook is valid according to ECMA-376 rules.
|
|
47
|
+
#
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
#: () -> bool
|
|
21
50
|
def valid?
|
|
22
51
|
errors.empty?
|
|
23
52
|
end
|
|
24
53
|
|
|
25
|
-
# Returns the
|
|
54
|
+
# Returns the worksheet at the given 0-based index or by name.
|
|
55
|
+
#
|
|
56
|
+
# @example
|
|
57
|
+
# wb.sheet(0)
|
|
58
|
+
# wb.sheet("Sales")
|
|
59
|
+
#
|
|
60
|
+
# @param identifier [Integer, String] 0-based index or sheet name.
|
|
61
|
+
# @return [Elements::Worksheet, nil]
|
|
62
|
+
# @api public
|
|
63
|
+
#: (?Integer | String identifier) -> Elements::Worksheet?
|
|
26
64
|
def sheet(identifier = 0)
|
|
27
65
|
case identifier
|
|
28
66
|
when Integer
|
|
@@ -34,7 +72,20 @@ module Xlsxrb
|
|
|
34
72
|
alias_method :[], :sheet
|
|
35
73
|
|
|
36
74
|
# Returns a new Workbook with the specified sheet updated.
|
|
37
|
-
#
|
|
75
|
+
# Yields the matched worksheet to the block, which must return a new Worksheet.
|
|
76
|
+
#
|
|
77
|
+
# @example
|
|
78
|
+
# new_wb = wb.update_sheet("Sheet1") do |sheet|
|
|
79
|
+
# sheet.update_cell("A1", value: "New Title")
|
|
80
|
+
# end
|
|
81
|
+
#
|
|
82
|
+
# @param identifier [Integer, String] 0-based index or sheet name.
|
|
83
|
+
# @yield [sheet]
|
|
84
|
+
# @yieldparam sheet [Elements::Worksheet] The worksheet to update.
|
|
85
|
+
# @yieldreturn [Elements::Worksheet] The modified worksheet.
|
|
86
|
+
# @return [Elements::Workbook] A new Workbook instance.
|
|
87
|
+
# @api public
|
|
88
|
+
#: (Integer | String identifier) { (Elements::Worksheet) -> Elements::Worksheet } -> Elements::Workbook
|
|
38
89
|
def update_sheet(identifier)
|
|
39
90
|
raise ArgumentError, "block is required" unless block_given?
|
|
40
91
|
|
|
@@ -48,16 +99,33 @@ module Xlsxrb
|
|
|
48
99
|
with(sheets: new_sheets)
|
|
49
100
|
end
|
|
50
101
|
|
|
51
|
-
# Returns
|
|
102
|
+
# Returns an Array of all worksheet names.
|
|
103
|
+
#
|
|
104
|
+
# @return [Array<String>]
|
|
105
|
+
# @api public
|
|
106
|
+
#: () -> Array[String]
|
|
52
107
|
def sheet_names
|
|
53
108
|
sheets.map(&:name)
|
|
54
109
|
end
|
|
55
110
|
|
|
56
|
-
# Save the workbook to
|
|
111
|
+
# Save the workbook to an XLSX file.
|
|
112
|
+
#
|
|
113
|
+
# @example
|
|
114
|
+
# wb.save("output.xlsx")
|
|
115
|
+
#
|
|
116
|
+
# @param filepath [String, IO] Destination file path or IO stream.
|
|
117
|
+
# @return [void]
|
|
118
|
+
# @api public
|
|
119
|
+
#: (untyped filepath) -> void
|
|
57
120
|
def save(filepath)
|
|
58
121
|
Xlsxrb.write(filepath, self)
|
|
59
122
|
end
|
|
60
123
|
|
|
124
|
+
# Validates workbook structure according to OOXML specifications.
|
|
125
|
+
#
|
|
126
|
+
# @param sheets [Array<Elements::Worksheet>]
|
|
127
|
+
# @return [Array<String>] List of error messages.
|
|
128
|
+
#: (untyped sheets) -> Array[String]
|
|
61
129
|
def self.validate(sheets)
|
|
62
130
|
errs = []
|
|
63
131
|
errs << "sheets must be an Array (got #{sheets.class})" unless sheets.is_a?(Array)
|