rbxl 1.2.0 → 1.4.0

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.
@@ -30,6 +30,16 @@ module Rbxl
30
30
  # Namespace used by the OPC package relationships layer.
31
31
  PACKAGE_REL_NS = "http://schemas.openxmlformats.org/package/2006/relationships"
32
32
 
33
+ # First 8 bytes of the OLE Compound File Binary format (legacy .xls,
34
+ # .doc, .ppt). Sniffed to short-circuit into a typed error before
35
+ # rubyzip bubbles up an opaque "end of central directory" failure.
36
+ OLE_CFB_MAGIC = "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1".b.freeze
37
+ private_constant :OLE_CFB_MAGIC
38
+
39
+ # ZIP local file header signature — the first bytes of every .xlsx.
40
+ ZIP_LOCAL_MAGIC = "PK\x03\x04".b.freeze
41
+ private_constant :ZIP_LOCAL_MAGIC
42
+
33
43
  # @return [String] filesystem path the workbook was opened from
34
44
  attr_reader :path
35
45
 
@@ -39,14 +49,28 @@ module Rbxl
39
49
  # Convenience constructor equivalent to
40
50
  # <tt>new(path, streaming:, date_conversion:)</tt>.
41
51
  #
52
+ # When a block is given, the workbook is yielded to the block and
53
+ # {#close} is called automatically when the block returns (or raises).
54
+ # The block's return value is returned to the caller, matching the
55
+ # +File.open+ / +Zip::File.open+ idiom.
56
+ #
42
57
  # @param path [String, #to_path] path to the <tt>.xlsx</tt> file
43
58
  # @param streaming [Boolean] feed worksheet XML to the native parser in
44
59
  # chunks (see {Rbxl.open})
45
60
  # @param date_conversion [Boolean] convert numeric cells backed by a
46
61
  # date/time +numFmt+ to Ruby date/time objects (see {Rbxl.open})
47
- # @return [Rbxl::ReadOnlyWorkbook]
62
+ # @yieldparam book [Rbxl::ReadOnlyWorkbook] the opened workbook
63
+ # @return [Rbxl::ReadOnlyWorkbook, Object] the workbook when no block is
64
+ # given, otherwise the block's return value
48
65
  def self.open(path, streaming: false, date_conversion: false)
49
- new(path, streaming: streaming, date_conversion: date_conversion)
66
+ book = new(path, streaming: streaming, date_conversion: date_conversion)
67
+ return book unless block_given?
68
+
69
+ begin
70
+ yield book
71
+ ensure
72
+ book.close
73
+ end
50
74
  end
51
75
 
52
76
  # Opens the ZIP archive, pre-loads shared strings, and indexes the
@@ -58,10 +82,11 @@ module Rbxl
58
82
  # date-style lookup table to produced worksheets
59
83
  def initialize(path, streaming: false, date_conversion: false)
60
84
  @path = path
85
+ ensure_xlsx_format!(path)
61
86
  @zip = Zip::File.open(path)
62
87
  @streaming = streaming
63
88
  @date_conversion = date_conversion
64
- @shared_strings = load_shared_strings
89
+ @shared_strings = SharedStringsLoader.load(@zip)
65
90
  @sheet_entries = load_sheet_entries
66
91
  @sheet_names = @sheet_entries.keys.freeze
67
92
  @date_styles = nil
@@ -69,18 +94,23 @@ module Rbxl
69
94
  @closed = false
70
95
  end
71
96
 
72
- # Returns a row-by-row worksheet by visible sheet name.
97
+ # Returns a row-by-row worksheet by visible sheet name or by 0-based
98
+ # index into {#sheet_names}. Negative indexes count from the end, so
99
+ # <tt>sheet(-1)</tt> returns the last sheet.
73
100
  #
74
101
  # The returned object shares the workbook's ZIP handle. Closing the
75
102
  # workbook invalidates any worksheets produced by prior calls.
76
103
  #
77
- # @param name [String] visible sheet name as listed in {#sheet_names}
104
+ # @param name_or_index [String, Integer] visible sheet name as listed in
105
+ # {#sheet_names}, or an integer index into that list
78
106
  # @return [Rbxl::ReadOnlyWorksheet]
79
- # @raise [Rbxl::SheetNotFoundError] if +name+ is not present
107
+ # @raise [Rbxl::SheetNotFoundError] if +name_or_index+ does not resolve
108
+ # to a sheet
80
109
  # @raise [Rbxl::ClosedWorkbookError] if the workbook has been closed
81
- def sheet(name)
110
+ def sheet(name_or_index)
82
111
  ensure_open!
83
112
 
113
+ name = resolve_sheet_name(name_or_index)
84
114
  entry_path = @sheet_entries.fetch(name) do
85
115
  raise SheetNotFoundError, "sheet not found: #{name}"
86
116
  end
@@ -97,6 +127,23 @@ module Rbxl
97
127
  )
98
128
  end
99
129
 
130
+ # Iterates the workbook's sheets in workbook order. Each worksheet is
131
+ # constructed on demand, so <tt>sheets.first</tt> allocates only the
132
+ # first sheet and <tt>sheets.lazy.find { ... }</tt> stops as soon as a
133
+ # match is found. Returned objects share the same ZIP handle and
134
+ # cached shared-strings / date-style tables as {#sheet}.
135
+ #
136
+ # @yieldparam worksheet [Rbxl::ReadOnlyWorksheet]
137
+ # @return [Enumerator<Rbxl::ReadOnlyWorksheet>] when no block is given
138
+ # @return [void] when a block is given
139
+ # @raise [Rbxl::ClosedWorkbookError] if the workbook has been closed
140
+ def sheets
141
+ ensure_open!
142
+ return enum_for(:sheets) unless block_given?
143
+
144
+ @sheet_names.each { |name| yield sheet(name) }
145
+ end
146
+
100
147
  # Releases the underlying ZIP file handle. Idempotent; subsequent calls
101
148
  # are no-ops.
102
149
  #
@@ -119,6 +166,30 @@ module Rbxl
119
166
  raise ClosedWorkbookError, "workbook has been closed" if closed?
120
167
  end
121
168
 
169
+ def resolve_sheet_name(key)
170
+ return key unless key.is_a?(Integer)
171
+
172
+ name = @sheet_names[key]
173
+ return name if name
174
+
175
+ raise SheetNotFoundError, "sheet index out of range: #{key} (#{@sheet_names.length} sheet(s))"
176
+ end
177
+
178
+ def ensure_xlsx_format!(path)
179
+ header = File.binread(path, 8)
180
+ return if header.start_with?(ZIP_LOCAL_MAGIC)
181
+
182
+ if header.start_with?(OLE_CFB_MAGIC)
183
+ raise UnsupportedFormatError,
184
+ "#{path} looks like a legacy .xls (BIFF/CFB). " \
185
+ "rbxl supports .xlsx (OOXML) only; convert first, e.g. " \
186
+ "`libreoffice --headless --convert-to xlsx #{File.basename(path.to_s)}`."
187
+ end
188
+
189
+ raise UnsupportedFormatError,
190
+ "#{path} is not a valid .xlsx (no ZIP signature at offset 0)."
191
+ end
192
+
122
193
  # Built-in numFmtId values that Excel resolves to date/time formats.
123
194
  # Ids outside this set are dates only when the workbook provides a
124
195
  # matching custom +<numFmt>+ entry whose format code contains date
@@ -185,87 +256,6 @@ module Rbxl
185
256
  stripped.match?(/[ymdhs]/i)
186
257
  end
187
258
 
188
- def load_shared_strings
189
- entry = @zip.find_entry("xl/sharedStrings.xml")
190
- return [] unless entry
191
-
192
- max_count = Rbxl.max_shared_strings
193
- max_bytes = Rbxl.max_shared_string_bytes
194
-
195
- # Reject zip-bomb style entries up front using the ZIP directory's
196
- # declared uncompressed size, before allocating any decompression buffer.
197
- if max_bytes && entry.size && entry.size > max_bytes
198
- raise SharedStringsTooLargeError,
199
- "shared strings uncompressed size #{entry.size} exceeds limit #{max_bytes}"
200
- end
201
-
202
- strings = []
203
- total_bytes = 0
204
- io = entry.get_input_stream
205
- reader = Nokogiri::XML::Reader(io)
206
-
207
- in_si = false
208
- in_run = false
209
- in_phonetic = false
210
- collecting_text = false
211
- buffer = +""
212
- current_fragments = []
213
-
214
- reader.each do |node|
215
- case node.node_type
216
- when Nokogiri::XML::Reader::TYPE_ELEMENT
217
- case node.local_name
218
- when "si"
219
- in_si = true
220
- current_fragments = []
221
- when "r"
222
- in_run = true if in_si
223
- when "rPh"
224
- in_phonetic = true if in_si
225
- when "t"
226
- next unless in_si && !in_phonetic
227
-
228
- collecting_text = !in_run || node.depth.positive?
229
- buffer.clear if collecting_text
230
- end
231
- when Nokogiri::XML::Reader::TYPE_TEXT, Nokogiri::XML::Reader::TYPE_CDATA
232
- buffer << node.value if collecting_text
233
- when Nokogiri::XML::Reader::TYPE_END_ELEMENT
234
- case node.local_name
235
- when "t"
236
- if collecting_text
237
- current_fragments << buffer.dup
238
- collecting_text = false
239
- end
240
- when "r"
241
- in_run = false
242
- when "rPh"
243
- in_phonetic = false
244
- when "si"
245
- value = current_fragments.join.freeze
246
- total_bytes += value.bytesize
247
- if max_bytes && total_bytes > max_bytes
248
- raise SharedStringsTooLargeError,
249
- "shared strings total size exceeds limit #{max_bytes}"
250
- end
251
- strings << value
252
- if max_count && strings.size > max_count
253
- raise SharedStringsTooLargeError,
254
- "shared strings count exceeds limit #{max_count}"
255
- end
256
- in_si = false
257
- in_run = false
258
- in_phonetic = false
259
- collecting_text = false
260
- end
261
- end
262
- end
263
-
264
- strings
265
- ensure
266
- io&.close
267
- end
268
-
269
259
  def load_sheet_entries
270
260
  relationships = load_relationship_targets("xl/_rels/workbook.xml.rels")
271
261
  sheets = {}
@@ -0,0 +1,100 @@
1
+ module Rbxl
2
+ # Streams +xl/sharedStrings.xml+ out of an opened +.xlsx+ ZIP and decodes
3
+ # the table to an immutable +Array<String>+.
4
+ #
5
+ # Both the read-only and edit modes need this same view of the SST. The
6
+ # logic is identical — phonetic guides are skipped, +<r>+/+<t>+ runs inside
7
+ # an +<si>+ are concatenated, the count and byte caps configured on
8
+ # {Rbxl} are enforced — so it lives here as a single source of truth
9
+ # rather than being inlined twice.
10
+ #
11
+ # @api private
12
+ module SharedStringsLoader
13
+ module_function
14
+
15
+ # @param zip [Zip::File] the open package
16
+ # @return [Array<String>] frozen, index-aligned shared strings table
17
+ # @raise [Rbxl::SharedStringsTooLargeError] if the table exceeds the
18
+ # configured count or byte limits
19
+ def load(zip)
20
+ entry = zip.find_entry("xl/sharedStrings.xml")
21
+ return [].freeze unless entry
22
+
23
+ max_count = Rbxl.max_shared_strings
24
+ max_bytes = Rbxl.max_shared_string_bytes
25
+
26
+ # Reject zip-bomb style entries up front using the ZIP directory's
27
+ # declared uncompressed size, before allocating any decompression buffer.
28
+ if max_bytes && entry.size && entry.size > max_bytes
29
+ raise SharedStringsTooLargeError,
30
+ "shared strings uncompressed size #{entry.size} exceeds limit #{max_bytes}"
31
+ end
32
+
33
+ strings = []
34
+ total_bytes = 0
35
+ io = entry.get_input_stream
36
+ reader = Nokogiri::XML::Reader(io)
37
+
38
+ in_si = false
39
+ in_run = false
40
+ in_phonetic = false
41
+ collecting_text = false
42
+ buffer = +""
43
+ current_fragments = []
44
+
45
+ reader.each do |node|
46
+ case node.node_type
47
+ when Nokogiri::XML::Reader::TYPE_ELEMENT
48
+ case node.local_name
49
+ when "si"
50
+ in_si = true
51
+ current_fragments = []
52
+ when "r"
53
+ in_run = true if in_si
54
+ when "rPh"
55
+ in_phonetic = true if in_si
56
+ when "t"
57
+ next unless in_si && !in_phonetic
58
+
59
+ collecting_text = !in_run || node.depth.positive?
60
+ buffer.clear if collecting_text
61
+ end
62
+ when Nokogiri::XML::Reader::TYPE_TEXT, Nokogiri::XML::Reader::TYPE_CDATA
63
+ buffer << node.value if collecting_text
64
+ when Nokogiri::XML::Reader::TYPE_END_ELEMENT
65
+ case node.local_name
66
+ when "t"
67
+ if collecting_text
68
+ current_fragments << buffer.dup
69
+ collecting_text = false
70
+ end
71
+ when "r"
72
+ in_run = false
73
+ when "rPh"
74
+ in_phonetic = false
75
+ when "si"
76
+ value = current_fragments.join.freeze
77
+ total_bytes += value.bytesize
78
+ if max_bytes && total_bytes > max_bytes
79
+ raise SharedStringsTooLargeError,
80
+ "shared strings total size exceeds limit #{max_bytes}"
81
+ end
82
+ strings << value
83
+ if max_count && strings.size > max_count
84
+ raise SharedStringsTooLargeError,
85
+ "shared strings count exceeds limit #{max_count}"
86
+ end
87
+ in_si = false
88
+ in_run = false
89
+ in_phonetic = false
90
+ collecting_text = false
91
+ end
92
+ end
93
+ end
94
+
95
+ strings.freeze
96
+ ensure
97
+ io&.close
98
+ end
99
+ end
100
+ end
data/lib/rbxl/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Rbxl
2
2
  # Gem version string, tracked with semantic versioning.
3
- VERSION = "1.2.0"
3
+ VERSION = "1.4.0"
4
4
  end
data/lib/rbxl.rb CHANGED
@@ -8,9 +8,13 @@ require "zip"
8
8
  require_relative "rbxl/cell"
9
9
  require_relative "rbxl/empty_cell"
10
10
  require_relative "rbxl/errors"
11
+ require_relative "rbxl/shared_strings_loader"
11
12
  require_relative "rbxl/read_only_cell"
12
13
  require_relative "rbxl/read_only_workbook"
13
14
  require_relative "rbxl/read_only_worksheet"
15
+ require_relative "rbxl/editable_cell"
16
+ require_relative "rbxl/editable_worksheet"
17
+ require_relative "rbxl/editable_workbook"
14
18
  require_relative "rbxl/row"
15
19
  require_relative "rbxl/version"
16
20
  require_relative "rbxl/write_only_cell"
@@ -19,9 +23,13 @@ require_relative "rbxl/write_only_worksheet"
19
23
 
20
24
  # Minimal, memory-friendly XLSX reader/writer inspired by +openpyxl+.
21
25
  #
22
- # Rbxl exposes two explicit, non-overlapping modes:
26
+ # Rbxl exposes three explicit, non-overlapping modes, each picked up by
27
+ # {Rbxl.open} / {Rbxl.new}:
23
28
  #
24
29
  # * {Rbxl.open} returns a {Rbxl::ReadOnlyWorkbook} for row-by-row reads
30
+ # * {Rbxl.open} with <tt>edit: true</tt> returns a {Rbxl::EditableWorkbook}
31
+ # for surgical read-modify-save passes that round-trip every untouched
32
+ # part byte-for-byte
25
33
  # * {Rbxl.new} returns a {Rbxl::WriteOnlyWorkbook} for one-shot writes
26
34
  #
27
35
  # The API is intentionally narrow so that memory usage stays predictable
@@ -86,11 +94,32 @@ module Rbxl
86
94
  # @return [Integer, nil] per-worksheet streaming byte cap
87
95
  attr_accessor :max_worksheet_bytes
88
96
 
89
- # Opens an existing workbook in read-only row-by-row mode.
97
+ # Opens an existing workbook.
98
+ #
99
+ # By default opens in read-only row-by-row mode and returns a
100
+ # {Rbxl::ReadOnlyWorkbook}. Pass <tt>edit: true</tt> to open in
101
+ # read-modify-save mode and receive a {Rbxl::EditableWorkbook} instead.
102
+ # The two modes are wired up here at the module level so call sites pick
103
+ # a mode by keyword without juggling backend classes directly.
90
104
  #
91
105
  # The +read_only+ keyword defaults to +true+ and exists to mark the
92
- # intent explicitly at the call site. Passing +read_only: false+ raises
93
- # {NotImplementedError}; a read/write mode is not available.
106
+ # intent explicitly at the call site. Passing +read_only: false+ without
107
+ # also passing +edit: true+ raises {NotImplementedError} there is no
108
+ # promiscuous read/write mode that mixes streaming reads with surgical
109
+ # writes.
110
+ #
111
+ # When a block is given, the workbook is yielded and automatically
112
+ # closed when the block returns (or raises), mirroring the +File.open+
113
+ # and +Zip::File.open+ idiom:
114
+ #
115
+ # Rbxl.open("report.xlsx") do |book|
116
+ # book.sheet("Report").each_row(values_only: true) { |row| p row }
117
+ # end
118
+ #
119
+ # Rbxl.open("template.xlsx", edit: true) do |book|
120
+ # book.sheet("Sheet1")["B5"].value = "Acme Inc."
121
+ # book.save
122
+ # end
94
123
  #
95
124
  # With <tt>streaming: true</tt>, the native backend (when loaded) feeds
96
125
  # worksheet XML to the parser in chunks pulled from the ZIP input stream
@@ -110,19 +139,41 @@ module Rbxl
110
139
  # disables the native fast path and routes reads through the Ruby
111
140
  # worksheet parser.
112
141
  #
142
+ # +streaming:+ and +date_conversion:+ are read-mode options and are
143
+ # rejected when paired with +edit: true+, since the editable backend
144
+ # does not run worksheets through the streaming parser.
145
+ #
113
146
  # @param path [String, #to_path] filesystem path to an <tt>.xlsx</tt> file
114
- # @param read_only [Boolean] retained for call-site clarity; must be +true+
147
+ # @param read_only [Boolean] retained for call-site clarity; must be
148
+ # +true+ unless +edit: true+ is also passed
149
+ # @param edit [Boolean] open in read-modify-save mode; returns an
150
+ # {Rbxl::EditableWorkbook}
115
151
  # @param streaming [Boolean] feed worksheet XML to the native parser in
116
152
  # chunks instead of fully inflating the entry in advance. Ignored when
117
153
  # the native extension is not loaded.
118
154
  # @param date_conversion [Boolean] convert numeric cells backed by a
119
155
  # date/time +numFmt+ to +Date+ / +Time+ / +DateTime+
120
- # @return [Rbxl::ReadOnlyWorkbook]
121
- # @raise [NotImplementedError] if +read_only+ is not +true+
122
- def open(path, read_only: true, streaming: false, date_conversion: false)
123
- raise NotImplementedError, "read/write mode is not supported; pass read_only: true" unless read_only
156
+ # @yieldparam book [Rbxl::ReadOnlyWorkbook, Rbxl::EditableWorkbook]
157
+ # opened workbook; auto-closed when the block returns
158
+ # @return [Rbxl::ReadOnlyWorkbook, Rbxl::EditableWorkbook, Object] the
159
+ # workbook when no block is given, otherwise the block's return value
160
+ # @raise [NotImplementedError] if +read_only+ is +false+ without
161
+ # +edit: true+
162
+ # @raise [ArgumentError] if +edit: true+ is paired with read-only options
163
+ def open(path, read_only: true, edit: false, streaming: false, date_conversion: false, &block)
164
+ if edit
165
+ if streaming || date_conversion
166
+ raise ArgumentError,
167
+ "edit: true is incompatible with streaming:/date_conversion:; " \
168
+ "those options apply to the read-only mode"
169
+ end
170
+
171
+ return EditableWorkbook.open(path, &block)
172
+ end
173
+
174
+ raise NotImplementedError, "read/write mode is not supported; pass read_only: true or edit: true" unless read_only
124
175
 
125
- ReadOnlyWorkbook.open(path, streaming: streaming, date_conversion: date_conversion)
176
+ ReadOnlyWorkbook.open(path, streaming: streaming, date_conversion: date_conversion, &block)
126
177
  end
127
178
 
128
179
  # Creates a new workbook in write-only mode.
data/sig/rbxl.rbs CHANGED
@@ -9,7 +9,10 @@ module Rbxl
9
9
  type row_cells = Array[row_cell]
10
10
  type dimensions = { ref: String, max_col: Integer, max_row: Integer }
11
11
 
12
- def self.open: (pathish path, ?read_only: bool, ?streaming: bool, ?date_conversion: bool) -> ReadOnlyWorkbook
12
+ type editable_cell_value = String | Integer | Float | bool | nil
13
+
14
+ def self.open: (pathish path, ?read_only: bool, ?edit: bool, ?streaming: bool, ?date_conversion: bool) -> (ReadOnlyWorkbook | EditableWorkbook)
15
+ | [T] (pathish path, ?read_only: bool, ?edit: bool, ?streaming: bool, ?date_conversion: bool) { (ReadOnlyWorkbook | EditableWorkbook) -> T } -> T
13
16
  def self.new: (?write_only: bool) -> WriteOnlyWorkbook
14
17
 
15
18
  attr_accessor self.max_shared_strings: Integer?
@@ -37,6 +40,21 @@ module Rbxl
37
40
  class WorksheetTooLargeError < Error
38
41
  end
39
42
 
43
+ class UnsupportedFormatError < Error
44
+ end
45
+
46
+ class WorkbookFormatError < Error
47
+ end
48
+
49
+ class WorksheetFormatError < Error
50
+ end
51
+
52
+ class CellValueError < WorksheetFormatError
53
+ end
54
+
55
+ class EditableCellTypeError < Error
56
+ end
57
+
40
58
  class Cell
41
59
  attr_accessor value: cell_value
42
60
  attr_accessor coordinate: String?
@@ -84,8 +102,11 @@ module Rbxl
84
102
  attr_reader sheet_names: Array[String]
85
103
 
86
104
  def self.open: (pathish path, ?streaming: bool, ?date_conversion: bool) -> ReadOnlyWorkbook
105
+ | [T] (pathish path, ?streaming: bool, ?date_conversion: bool) { (ReadOnlyWorkbook) -> T } -> T
87
106
  def initialize: (pathish path, ?streaming: bool, ?date_conversion: bool) -> void
88
- def sheet: (String name) -> ReadOnlyWorksheet
107
+ def sheet: (String | Integer name_or_index) -> ReadOnlyWorksheet
108
+ def sheets: () { (ReadOnlyWorksheet) -> void } -> void
109
+ | () -> Enumerator[ReadOnlyWorksheet, void]
89
110
  def close: () -> void
90
111
  def closed?: () -> bool
91
112
  end
@@ -125,4 +146,51 @@ module Rbxl
125
146
  def append: (row_input values) -> WriteOnlyWorksheet
126
147
  def to_xml: () -> String
127
148
  end
149
+
150
+ class EditableWorkbook
151
+ MAIN_NS: String
152
+ REL_NS: String
153
+ PACKAGE_REL_NS: String
154
+ OFFICE_DOC_REL_TYPE: String
155
+
156
+ attr_reader path: String
157
+ attr_reader sheet_names: Array[String]
158
+
159
+ def self.open: (pathish path) -> EditableWorkbook
160
+ | [T] (pathish path) { (EditableWorkbook) -> T } -> T
161
+ def initialize: (pathish path) -> void
162
+ def sheet: (String | Integer name_or_index) -> EditableWorksheet
163
+ def sheets: () { (EditableWorksheet) -> void } -> void
164
+ | () -> Enumerator[EditableWorksheet, void]
165
+ def save: (?pathish? path) -> String
166
+ def close: () -> bool
167
+ def closed?: () -> bool
168
+ end
169
+
170
+ class EditableWorksheet
171
+ MAIN_NS: String
172
+
173
+ attr_reader name: String
174
+ attr_reader entry_path: String
175
+
176
+ def initialize: (zip: untyped, entry_path: String, workbook_path: String, shared_strings: Array[String], name: String) -> void
177
+ def cell: (String coordinate) -> EditableCell
178
+ def []: (String coordinate) -> EditableCell
179
+ def dirty?: () -> bool
180
+ def to_xml: () -> String
181
+ end
182
+
183
+ class EditableCell
184
+ MAIN_NS: String
185
+
186
+ attr_reader coordinate: String
187
+
188
+ def initialize: (worksheet: EditableWorksheet, coordinate: String) -> void
189
+ def value: () -> editable_cell_value
190
+ def value=: (editable_cell_value new_value) -> editable_cell_value
191
+ end
192
+
193
+ module SharedStringsLoader
194
+ def self.load: (untyped zip) -> Array[String]
195
+ end
128
196
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbxl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taro KOBAYASHI
@@ -60,6 +60,9 @@ files:
60
60
  - ext/rbxl_native/native.c
61
61
  - lib/rbxl.rb
62
62
  - lib/rbxl/cell.rb
63
+ - lib/rbxl/editable_cell.rb
64
+ - lib/rbxl/editable_workbook.rb
65
+ - lib/rbxl/editable_worksheet.rb
63
66
  - lib/rbxl/empty_cell.rb
64
67
  - lib/rbxl/errors.rb
65
68
  - lib/rbxl/native.rb
@@ -67,6 +70,7 @@ files:
67
70
  - lib/rbxl/read_only_workbook.rb
68
71
  - lib/rbxl/read_only_worksheet.rb
69
72
  - lib/rbxl/row.rb
73
+ - lib/rbxl/shared_strings_loader.rb
70
74
  - lib/rbxl/version.rb
71
75
  - lib/rbxl/write_only_cell.rb
72
76
  - lib/rbxl/write_only_workbook.rb
@@ -87,7 +91,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
87
91
  requirements:
88
92
  - - ">="
89
93
  - !ruby/object:Gem::Version
90
- version: '3.1'
94
+ version: '3.2'
91
95
  required_rubygems_version: !ruby/object:Gem::Requirement
92
96
  requirements:
93
97
  - - ">="