xlsxrb 0.1.5 → 0.1.6
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/.gem_rbs_collection/nokogiri/1.11/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/nokogiri/1.11/nokogiri.rbs +2332 -0
- data/.gem_rbs_collection/nokogiri/1.11/patch.rbs +4 -0
- data/.gem_rbs_collection/rubyzip/3.2/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/rubyzip/3.2/manifest.yaml +8 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/central_directory.rbs +42 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/compressor.rbs +5 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/constants.rbs +47 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/aes_encryption.rbs +30 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/decrypted_io.rbs +9 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/encryption.rbs +7 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/null_encryption.rbs +19 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/traditional_encryption.rbs +31 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/decompressor.rbs +18 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/deflater.rbs +12 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/dirtyable.rbs +11 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/dos_time.rbs +13 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/entry.rbs +95 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/entry_set.rbs +31 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/errors.rbs +58 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/aes.rbs +24 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/generic.rbs +17 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/ntfs.rbs +23 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/old_unix.rbs +22 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/universal_time.rbs +30 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/unix.rbs +20 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/unknown.rbs +15 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/zip64.rbs +26 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field.rbs +21 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/file.rbs +131 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/file_split.rbs +14 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/dir.rbs +33 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/directory_iterator.rbs +21 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/file.rbs +63 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/file_stat.rbs +55 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/zip_file_name_mapper.rbs +35 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem.rbs +7 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/inflater.rbs +10 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/input_stream.rbs +22 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras/abstract_input_stream.rbs +29 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras/abstract_output_stream.rbs +17 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras.rbs +13 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/null_compressor.rbs +10 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/null_decompressor.rbs +8 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/null_input_stream.rbs +6 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/output_stream.rbs +30 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/pass_thru_compressor.rbs +10 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/pass_thru_decompressor.rbs +10 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/streamable_directory.rbs +5 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/streamable_stream.rbs +15 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip/version.rbs +3 -0
- data/.gem_rbs_collection/rubyzip/3.2/zip.rbs +40 -0
- data/CHANGELOG.md +18 -0
- data/README.md +39 -32
- data/Rakefile +2 -1
- data/Steepfile +0 -2
- data/benchmark.rb +376 -0
- data/docs/ARCHITECTURE.md +12 -5
- data/docs/wasm/ruby.wasm +0 -0
- data/lib/xlsxrb/elements/cell.rb +39 -7
- data/lib/xlsxrb/elements/row.rb +17 -4
- data/lib/xlsxrb/elements/types.rb +4 -0
- data/lib/xlsxrb/ooxml/shared_strings_parser.rb +104 -40
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +144 -23
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +160 -145
- data/lib/xlsxrb/ooxml/xml_builder.rb +16 -10
- data/lib/xlsxrb/ooxml/zip_reader.rb +104 -18
- data/lib/xlsxrb/ooxml/zip_writer.rb +63 -25
- data/lib/xlsxrb/style_builder.rb +158 -30
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb.rb +443 -236
- data/rbs_collection.lock.yaml +28 -0
- data/sig/generated/xlsxrb/elements/cell.rbs +30 -0
- data/sig/generated/xlsxrb/elements/column.rbs +30 -0
- data/sig/generated/xlsxrb/elements/row.rbs +32 -0
- data/sig/generated/xlsxrb/elements/types.rbs +74 -0
- data/sig/generated/xlsxrb/elements/workbook.rbs +25 -0
- data/sig/generated/xlsxrb/elements/worksheet.rbs +27 -0
- data/sig/generated/xlsxrb/ooxml/reader.rbs +1412 -0
- data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +21 -0
- data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +44 -0
- data/sig/generated/xlsxrb/ooxml/utils.rbs +40 -0
- data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +46 -0
- data/sig/generated/xlsxrb/ooxml/workbook_writer.rbs +66 -0
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +67 -0
- data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +92 -0
- data/sig/generated/xlsxrb/ooxml/writer.rbs +880 -0
- data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +45 -0
- data/sig/generated/xlsxrb/ooxml/xml_parser.rbs +30 -0
- data/sig/generated/xlsxrb/ooxml/zip_generator.rbs +38 -0
- data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +45 -0
- data/sig/generated/xlsxrb/ooxml/zip_writer.rbs +45 -0
- data/sig/generated/xlsxrb/style_builder.rbs +212 -50
- data/sig/generated/xlsxrb.rbs +1276 -0
- data/sig/rexml.rbs +4 -0
- metadata +76 -1
|
@@ -16,6 +16,7 @@ module Xlsxrb
|
|
|
16
16
|
def initialize(io)
|
|
17
17
|
@io = io
|
|
18
18
|
@builder = XmlBuilder.new(@io)
|
|
19
|
+
@row_buffer = String.new(capacity: 65_536)
|
|
19
20
|
@started = false
|
|
20
21
|
@finished = false
|
|
21
22
|
end
|
|
@@ -47,27 +48,27 @@ module Xlsxrb
|
|
|
47
48
|
|
|
48
49
|
row_num = row_index + 1
|
|
49
50
|
row_num_str = row_num.to_s
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
io.write('"')
|
|
51
|
+
buf = @row_buffer
|
|
52
|
+
buf.clear
|
|
53
|
+
buf << '<row r="' << row_num_str << '"'
|
|
54
54
|
if attrs[:height]
|
|
55
|
-
|
|
56
|
-
io.write(attrs[:height].to_s)
|
|
57
|
-
io.write('" customHeight="1"')
|
|
55
|
+
buf << ' ht="' << attrs[:height].to_s << '" customHeight="1"'
|
|
58
56
|
elsif attrs[:custom_height]
|
|
59
|
-
|
|
57
|
+
buf << ' customHeight="1"'
|
|
60
58
|
end
|
|
61
|
-
|
|
62
|
-
if attrs[:outline_level]
|
|
63
|
-
|
|
64
|
-
io.write(attrs[:outline_level].to_s)
|
|
65
|
-
io.write('"')
|
|
66
|
-
end
|
|
67
|
-
io.write(">")
|
|
59
|
+
buf << ' hidden="1"' if attrs[:hidden]
|
|
60
|
+
buf << ' outlineLevel="' << attrs[:outline_level].to_s << '"' if attrs[:outline_level]
|
|
61
|
+
buf << ">"
|
|
68
62
|
|
|
69
63
|
cells.each do |cell|
|
|
70
|
-
if cell.is_a?(
|
|
64
|
+
if cell.is_a?(Elements::Cell)
|
|
65
|
+
value = cell.value
|
|
66
|
+
style_id = cell.style_index
|
|
67
|
+
col_ref = cell.ref || "#{column_letter(cell.column_index)}#{row_num_str}"
|
|
68
|
+
formula = cell.formula
|
|
69
|
+
formula_ca = false
|
|
70
|
+
cell_type_val = nil
|
|
71
|
+
elsif cell.is_a?(Hash)
|
|
71
72
|
value = cell[:value]
|
|
72
73
|
style_id = cell[:style_index]
|
|
73
74
|
col_ref = cell[:ref] || "#{column_letter(cell[:column_index])}#{row_num_str}"
|
|
@@ -75,24 +76,45 @@ module Xlsxrb
|
|
|
75
76
|
formula_ca = cell[:formula_ca]
|
|
76
77
|
cell_type_val = cell[:type]
|
|
77
78
|
else
|
|
78
|
-
value = cell
|
|
79
|
-
style_id =
|
|
80
|
-
col_ref =
|
|
81
|
-
formula =
|
|
79
|
+
value = cell
|
|
80
|
+
style_id = nil
|
|
81
|
+
col_ref = nil
|
|
82
|
+
formula = nil
|
|
82
83
|
formula_ca = false
|
|
83
84
|
cell_type_val = nil
|
|
84
85
|
end
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
# Fast path for common unstyled cells
|
|
88
|
+
if !formula && !style_id && !cell_type_val
|
|
89
|
+
case value
|
|
90
|
+
when Integer, Float
|
|
91
|
+
buf << '<c r="' << col_ref << '"><v>' << value.to_s << "</v></c>"
|
|
92
|
+
next
|
|
93
|
+
when String
|
|
94
|
+
if !value.start_with?("=") && sst_index && (idx = sst_index[value])
|
|
95
|
+
buf << '<c r="' << col_ref << '" t="s"><v>' << idx.to_s << "</v></c>"
|
|
96
|
+
next
|
|
97
|
+
end
|
|
98
|
+
when true
|
|
99
|
+
buf << '<c r="' << col_ref << '" t="b"><v>1</v></c>'
|
|
100
|
+
next
|
|
101
|
+
when false
|
|
102
|
+
buf << '<c r="' << col_ref << '" t="b"><v>0</v></c>'
|
|
103
|
+
next
|
|
104
|
+
when nil
|
|
105
|
+
buf << '<c r="' << col_ref << '"/>'
|
|
106
|
+
next
|
|
107
|
+
when Date
|
|
108
|
+
serial = Xlsxrb::Ooxml::Utils.date_to_serial(value)
|
|
109
|
+
buf << '<c r="' << col_ref << '"><v>' << serial.to_s << "</v></c>"
|
|
110
|
+
next
|
|
94
111
|
end
|
|
95
|
-
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
if value.nil? && formula.nil?
|
|
115
|
+
buf << '<c r="' << col_ref << '"'
|
|
116
|
+
buf << ' s="' << style_id.to_s << '"' if style_id
|
|
117
|
+
buf << "/>"
|
|
96
118
|
next
|
|
97
119
|
end
|
|
98
120
|
|
|
@@ -148,86 +170,70 @@ module Xlsxrb
|
|
|
148
170
|
end
|
|
149
171
|
end
|
|
150
172
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if style_id
|
|
155
|
-
io.write(' s="')
|
|
156
|
-
io.write(style_id.to_s)
|
|
157
|
-
io.write('"')
|
|
158
|
-
end
|
|
159
|
-
if type
|
|
160
|
-
io.write(' t="')
|
|
161
|
-
io.write(type)
|
|
162
|
-
io.write('"')
|
|
163
|
-
end
|
|
173
|
+
buf << '<c r="' << col_ref << '"'
|
|
174
|
+
buf << ' s="' << style_id.to_s << '"' if style_id
|
|
175
|
+
buf << ' t="' << type << '"' if type
|
|
164
176
|
|
|
165
177
|
if type == "inlineStr"
|
|
166
178
|
if xml_val.is_a?(Xlsxrb::Elements::RichText)
|
|
167
|
-
|
|
179
|
+
buf << "><is>"
|
|
168
180
|
xml_val.runs.each do |run|
|
|
169
181
|
font = run[:font]
|
|
170
182
|
if font && !font.empty?
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
183
|
+
buf << "<r><rPr>"
|
|
184
|
+
buf << "<b/>" if font[:bold]
|
|
185
|
+
buf << "<i/>" if font[:italic]
|
|
186
|
+
buf << "<strike/>" if font[:strike]
|
|
175
187
|
if font[:underline]
|
|
176
188
|
if font[:underline] == true
|
|
177
|
-
|
|
189
|
+
buf << "<u/>"
|
|
178
190
|
else
|
|
179
|
-
|
|
191
|
+
buf << '<u val="' << font[:underline].to_s << '"/>'
|
|
180
192
|
end
|
|
181
193
|
end
|
|
182
|
-
|
|
183
|
-
|
|
194
|
+
buf << '<vertAlign val="' << font[:vert_align].to_s << '"/>' if font[:vert_align]
|
|
195
|
+
buf << '<sz val="' << font[:sz].to_s << '"/>' if font[:sz]
|
|
184
196
|
if font[:color]
|
|
185
|
-
|
|
197
|
+
buf << '<color rgb="' << font[:color].to_s << '"/>'
|
|
186
198
|
elsif font[:theme]
|
|
187
199
|
tint_attr = font[:tint] ? " tint=\"#{font[:tint]}\"" : ""
|
|
188
|
-
|
|
200
|
+
buf << '<color theme="' << font[:theme].to_s << '"' << tint_attr << "/>"
|
|
189
201
|
end
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
202
|
+
buf << '<rFont val="' << escape_xml(font[:name]) << '"/>' if font[:name]
|
|
203
|
+
buf << '<family val="' << font[:family].to_s << '"/>' if font[:family]
|
|
204
|
+
buf << '<scheme val="' << font[:scheme].to_s << '"/>' if font[:scheme]
|
|
205
|
+
buf << "</rPr><t>"
|
|
194
206
|
else
|
|
195
|
-
|
|
207
|
+
buf << "<r><t>"
|
|
196
208
|
end
|
|
197
|
-
|
|
198
|
-
io.write("</t></r>")
|
|
209
|
+
buf << escape_xml(run[:text]) << "</t></r>"
|
|
199
210
|
end
|
|
200
|
-
|
|
211
|
+
buf << "</is></c>"
|
|
201
212
|
else
|
|
202
|
-
|
|
203
|
-
io.write(escape_xml(xml_val.to_s))
|
|
204
|
-
io.write("</t></is></c>")
|
|
213
|
+
buf << "><is><t>" << escape_xml(xml_val.to_s) << "</t></is></c>"
|
|
205
214
|
end
|
|
206
215
|
elsif formula_expr
|
|
207
|
-
if formula_ca
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
io.write("</f>")
|
|
216
|
+
buf << if formula_ca
|
|
217
|
+
'><f ca="1">'
|
|
218
|
+
else
|
|
219
|
+
"><f>"
|
|
220
|
+
end
|
|
221
|
+
buf << escape_xml(formula_expr) << "</f>"
|
|
214
222
|
if xml_val
|
|
215
|
-
|
|
216
|
-
io.write(xml_val.to_s)
|
|
217
|
-
io.write("</v></c>")
|
|
223
|
+
buf << "<v>" << xml_val.to_s << "</v></c>"
|
|
218
224
|
else
|
|
219
|
-
|
|
225
|
+
buf << "</c>"
|
|
220
226
|
end
|
|
221
227
|
else
|
|
222
|
-
|
|
223
|
-
io.write(xml_val.to_s)
|
|
224
|
-
io.write("</v></c>")
|
|
228
|
+
buf << "><v>" << xml_val.to_s << "</v></c>"
|
|
225
229
|
end
|
|
226
230
|
end
|
|
227
231
|
|
|
228
|
-
|
|
232
|
+
buf << "</row>"
|
|
233
|
+
@io.write(buf)
|
|
234
|
+
buf.clear
|
|
229
235
|
|
|
230
|
-
|
|
236
|
+
unmapped.each { |node| @builder.write_unmapped(node) }
|
|
231
237
|
end
|
|
232
238
|
|
|
233
239
|
# Highly optimized row writing for StreamWriter that avoids allocating intermediate Hashes.
|
|
@@ -239,24 +245,15 @@ module Xlsxrb
|
|
|
239
245
|
is_styles_collection = styles && (styles.is_a?(Array) || styles.is_a?(Hash))
|
|
240
246
|
single_style_id = nil
|
|
241
247
|
single_style_id = style_map[styles] if styles && style_map && !is_styles_collection
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
io.write('"')
|
|
248
|
+
|
|
249
|
+
buf = @row_buffer ||= String.new(capacity: 65_536)
|
|
250
|
+
buf << '<row r="' << row_num_str << '"'
|
|
246
251
|
if attrs
|
|
247
|
-
if attrs[:height]
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
io.write('" customHeight="1"')
|
|
251
|
-
end
|
|
252
|
-
io.write(' hidden="1"') if attrs[:hidden]
|
|
253
|
-
if attrs[:outline_level]
|
|
254
|
-
io.write(' outlineLevel="')
|
|
255
|
-
io.write(attrs[:outline_level].to_s)
|
|
256
|
-
io.write('"')
|
|
257
|
-
end
|
|
252
|
+
buf << ' ht="' << attrs[:height].to_s << '" customHeight="1"' if attrs[:height]
|
|
253
|
+
buf << ' hidden="1"' if attrs[:hidden]
|
|
254
|
+
buf << ' outlineLevel="' << attrs[:outline_level].to_s << '"' if attrs[:outline_level]
|
|
258
255
|
end
|
|
259
|
-
|
|
256
|
+
buf << ">"
|
|
260
257
|
|
|
261
258
|
max_len = values.length
|
|
262
259
|
if is_styles_collection
|
|
@@ -280,29 +277,54 @@ module Xlsxrb
|
|
|
280
277
|
col_ref = column_letter(col_index)
|
|
281
278
|
|
|
282
279
|
if value.nil?
|
|
283
|
-
if style_id
|
|
284
|
-
io.write('<c r="')
|
|
285
|
-
io.write(col_ref)
|
|
286
|
-
io.write(row_num_str)
|
|
287
|
-
io.write('" s="')
|
|
288
|
-
io.write(style_id.to_s)
|
|
289
|
-
io.write('"/>')
|
|
290
|
-
end
|
|
280
|
+
buf << '<c r="' << col_ref << row_num_str << '" s="' << style_id.to_s << '"/>' if style_id
|
|
291
281
|
col_index += 1
|
|
292
282
|
next
|
|
293
283
|
end
|
|
294
284
|
|
|
295
|
-
#
|
|
296
|
-
if value.is_a?(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
285
|
+
# Fast path: unstyled numbers, booleans, and simple strings (majority of cells)
|
|
286
|
+
if style_id.nil? && !value.is_a?(Xlsxrb::Elements::Formula) && !value.is_a?(Hash)
|
|
287
|
+
case value
|
|
288
|
+
when Integer, Float
|
|
289
|
+
buf << '<c r="' << col_ref << row_num_str << '"><v>' << value.to_s << "</v></c>"
|
|
290
|
+
col_index += 1
|
|
291
|
+
next
|
|
292
|
+
when String
|
|
293
|
+
unless value.start_with?("=")
|
|
294
|
+
idx = sst_index[value]
|
|
295
|
+
unless idx
|
|
296
|
+
sst << value
|
|
297
|
+
idx = sst.size - 1
|
|
298
|
+
sst_index[value] = idx
|
|
299
|
+
end
|
|
300
|
+
buf << '<c r="' << col_ref << row_num_str << '" t="s"><v>' << idx.to_s << "</v></c>"
|
|
301
|
+
col_index += 1
|
|
302
|
+
next
|
|
303
|
+
end
|
|
304
|
+
when true
|
|
305
|
+
buf << '<c r="' << col_ref << row_num_str << '" t="b"><v>1</v></c>'
|
|
306
|
+
col_index += 1
|
|
307
|
+
next
|
|
308
|
+
when false
|
|
309
|
+
buf << '<c r="' << col_ref << row_num_str << '" t="b"><v>0</v></c>'
|
|
310
|
+
col_index += 1
|
|
311
|
+
next
|
|
312
|
+
when Date
|
|
313
|
+
buf << '<c r="' << col_ref << row_num_str << '"><v>' << Xlsxrb::Ooxml::Utils.date_to_serial(value).to_s << "</v></c>"
|
|
314
|
+
col_index += 1
|
|
315
|
+
next
|
|
316
|
+
when Time
|
|
317
|
+
buf << '<c r="' << col_ref << row_num_str << '"><v>' << Xlsxrb::Ooxml::Utils.datetime_to_serial(value).to_s << "</v></c>"
|
|
318
|
+
col_index += 1
|
|
319
|
+
next
|
|
320
|
+
end
|
|
302
321
|
end
|
|
303
322
|
|
|
304
|
-
|
|
323
|
+
# General path: styled, formula, rich text, or complex cell
|
|
324
|
+
formula_expr = nil
|
|
305
325
|
formula_ca = false
|
|
326
|
+
xml_val = value
|
|
327
|
+
type = nil
|
|
306
328
|
|
|
307
329
|
case value
|
|
308
330
|
when Xlsxrb::Elements::Formula
|
|
@@ -336,8 +358,9 @@ module Xlsxrb
|
|
|
336
358
|
end
|
|
337
359
|
end
|
|
338
360
|
when String
|
|
339
|
-
if value.start_with?("=")
|
|
340
|
-
|
|
361
|
+
if value.start_with?("=") && value.length > 1
|
|
362
|
+
formula_expr = value
|
|
363
|
+
xml_val = nil
|
|
341
364
|
else
|
|
342
365
|
idx = sst_index[value]
|
|
343
366
|
unless idx
|
|
@@ -376,45 +399,31 @@ module Xlsxrb
|
|
|
376
399
|
|
|
377
400
|
formula_expr = formula_expr[1..] if formula_expr&.start_with?("=")
|
|
378
401
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
io.write('"')
|
|
383
|
-
if style_id
|
|
384
|
-
io.write(' s="')
|
|
385
|
-
io.write(style_id.to_s)
|
|
386
|
-
io.write('"')
|
|
387
|
-
end
|
|
388
|
-
if type
|
|
389
|
-
io.write(' t="')
|
|
390
|
-
io.write(type)
|
|
391
|
-
io.write('"')
|
|
392
|
-
end
|
|
402
|
+
buf << '<c r="' << col_ref << row_num_str << '"'
|
|
403
|
+
buf << ' s="' << style_id.to_s << '"' if style_id
|
|
404
|
+
buf << ' t="' << type << '"' if type
|
|
393
405
|
if formula_expr
|
|
394
|
-
if formula_ca
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
io.write("</f>")
|
|
406
|
+
buf << if formula_ca
|
|
407
|
+
'><f ca="1">'
|
|
408
|
+
else
|
|
409
|
+
"><f>"
|
|
410
|
+
end
|
|
411
|
+
buf << escape_xml(formula_expr) << "</f>"
|
|
401
412
|
if xml_val
|
|
402
|
-
|
|
403
|
-
io.write(xml_val.to_s)
|
|
404
|
-
io.write("</v></c>")
|
|
413
|
+
buf << "<v>" << xml_val.to_s << "</v></c>"
|
|
405
414
|
else
|
|
406
|
-
|
|
415
|
+
buf << "</c>"
|
|
407
416
|
end
|
|
408
417
|
else
|
|
409
|
-
|
|
410
|
-
io.write(xml_val.to_s)
|
|
411
|
-
io.write("</v></c>")
|
|
418
|
+
buf << "><v>" << xml_val.to_s << "</v></c>"
|
|
412
419
|
end
|
|
413
420
|
|
|
414
421
|
col_index += 1
|
|
415
422
|
end
|
|
416
423
|
|
|
417
|
-
|
|
424
|
+
buf << "</row>"
|
|
425
|
+
@io.write(buf)
|
|
426
|
+
buf.clear
|
|
418
427
|
end
|
|
419
428
|
|
|
420
429
|
# Write the worksheet footer. Call once after all rows.
|
|
@@ -430,6 +439,10 @@ module Xlsxrb
|
|
|
430
439
|
|
|
431
440
|
start unless @started
|
|
432
441
|
@finished = true
|
|
442
|
+
if @row_buffer && !@row_buffer.empty?
|
|
443
|
+
@io.write(@row_buffer)
|
|
444
|
+
@row_buffer.clear
|
|
445
|
+
end
|
|
433
446
|
@builder.close_tag("sheetData")
|
|
434
447
|
|
|
435
448
|
# Elements must appear in OOXML specification order after sheetData
|
|
@@ -1035,12 +1048,14 @@ module Xlsxrb
|
|
|
1035
1048
|
Xlsxrb::Elements::Cell.column_letter(index)
|
|
1036
1049
|
end
|
|
1037
1050
|
|
|
1051
|
+
INVALID_XML_CHARS_RE = /[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/
|
|
1038
1052
|
ESCAPE_RE = /[&<>"']/
|
|
1039
1053
|
ESCAPE_MAP = { "&" => "&", "<" => "<", ">" => ">", '"' => """, "'" => "'" }.freeze
|
|
1040
|
-
private_constant :ESCAPE_RE, :ESCAPE_MAP
|
|
1054
|
+
private_constant :INVALID_XML_CHARS_RE, :ESCAPE_RE, :ESCAPE_MAP
|
|
1041
1055
|
|
|
1042
1056
|
def escape_xml(value)
|
|
1043
|
-
str = value.to_s
|
|
1057
|
+
str = value.to_s
|
|
1058
|
+
str = str.gsub(INVALID_XML_CHARS_RE, "") if str.match?(INVALID_XML_CHARS_RE)
|
|
1044
1059
|
str.match?(ESCAPE_RE) ? str.gsub(ESCAPE_RE, ESCAPE_MAP) : str
|
|
1045
1060
|
end
|
|
1046
1061
|
end
|
|
@@ -27,8 +27,10 @@ module Xlsxrb
|
|
|
27
27
|
self
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
EMPTY_HASH = {}.freeze
|
|
31
|
+
|
|
30
32
|
# Opens a tag, yields for children, then closes the tag.
|
|
31
|
-
def tag(name, attrs =
|
|
33
|
+
def tag(name, attrs = EMPTY_HASH, &block)
|
|
32
34
|
if block
|
|
33
35
|
open_tag(name, attrs)
|
|
34
36
|
yield self
|
|
@@ -39,22 +41,26 @@ module Xlsxrb
|
|
|
39
41
|
self
|
|
40
42
|
end
|
|
41
43
|
|
|
42
|
-
def open_tag(name, attrs =
|
|
43
|
-
@io
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
def open_tag(name, attrs = EMPTY_HASH)
|
|
45
|
+
@io.write("<")
|
|
46
|
+
@io.write(name)
|
|
47
|
+
write_attrs(attrs) unless attrs.empty?
|
|
48
|
+
@io.write(">")
|
|
46
49
|
self
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
def close_tag(name)
|
|
50
|
-
@io
|
|
53
|
+
@io.write("</")
|
|
54
|
+
@io.write(name)
|
|
55
|
+
@io.write(">")
|
|
51
56
|
self
|
|
52
57
|
end
|
|
53
58
|
|
|
54
|
-
def empty_tag(name, attrs =
|
|
55
|
-
@io
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
def empty_tag(name, attrs = EMPTY_HASH)
|
|
60
|
+
@io.write("<")
|
|
61
|
+
@io.write(name)
|
|
62
|
+
write_attrs(attrs) unless attrs.empty?
|
|
63
|
+
@io.write("/>")
|
|
58
64
|
self
|
|
59
65
|
end
|
|
60
66
|
|