xlsxrb 0.1.16 → 0.1.17
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 +64 -0
- data/README.md +2 -2
- data/lib/xlsxrb/chart_builder.rb +41 -2
- data/lib/xlsxrb/dsl_helpers.rb +522 -6
- data/lib/xlsxrb/elements/cell.rb +34 -9
- data/lib/xlsxrb/elements/column.rb +2 -2
- data/lib/xlsxrb/elements/types.rb +1 -0
- data/lib/xlsxrb/elements/workbook.rb +48 -0
- data/lib/xlsxrb/elements/worksheet.rb +158 -11
- data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +22 -1
- data/lib/xlsxrb/ooxml/reader/listeners/drawing_listeners.rb +187 -24
- data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +108 -1
- data/lib/xlsxrb/ooxml/reader/listeners/style_listeners.rb +16 -11
- data/lib/xlsxrb/ooxml/reader.rb +88 -0
- data/lib/xlsxrb/ooxml/styles_parser.rb +19 -11
- data/lib/xlsxrb/ooxml/workbook_parser.rb +18 -3
- data/lib/xlsxrb/ooxml/workbook_writer.rb +266 -46
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +18 -4
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +289 -88
- data/lib/xlsxrb/ooxml/writer/drawing_xml.rb +579 -441
- data/lib/xlsxrb/ooxml/writer/features_xml.rb +26 -9
- data/lib/xlsxrb/ooxml/writer/styles_xml.rb +23 -9
- data/lib/xlsxrb/ooxml/writer.rb +268 -31
- data/lib/xlsxrb/ooxml/zip_writer.rb +15 -5
- data/lib/xlsxrb/stream_sheet.rb +157 -3
- data/lib/xlsxrb/stream_writer.rb +946 -74
- data/lib/xlsxrb/style_builder.rb +59 -6
- data/lib/xlsxrb/utils.rb +65 -0
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +197 -14
- data/lib/xlsxrb/worksheet_builder.rb +1137 -44
- data/lib/xlsxrb.rb +302 -47
- data/sig/generated/xlsxrb/chart_builder.rbs +31 -3
- data/sig/generated/xlsxrb/dsl_helpers.rbs +119 -4
- data/sig/generated/xlsxrb/elements/cell.rbs +20 -3
- data/sig/generated/xlsxrb/elements/column.rbs +3 -3
- data/sig/generated/xlsxrb/elements/types.rbs +2 -0
- data/sig/generated/xlsxrb/elements/workbook.rbs +37 -0
- data/sig/generated/xlsxrb/elements/worksheet.rbs +82 -2
- data/sig/generated/xlsxrb/ooxml/reader/listeners/core_listeners.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/reader/listeners/drawing_listeners.rbs +74 -2
- data/sig/generated/xlsxrb/ooxml/reader/listeners/feature_listeners.rbs +36 -0
- data/sig/generated/xlsxrb/ooxml/reader/listeners/style_listeners.rbs +3 -0
- data/sig/generated/xlsxrb/ooxml/reader.rbs +17 -0
- data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +3 -0
- data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +8 -0
- data/sig/generated/xlsxrb/ooxml/workbook_writer.rbs +8 -4
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +1 -1
- data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +12 -1
- data/sig/generated/xlsxrb/ooxml/writer/drawing_xml.rbs +16 -0
- data/sig/generated/xlsxrb/ooxml/writer.rbs +42 -8
- data/sig/generated/xlsxrb/ooxml/zip_writer.rbs +8 -2
- data/sig/generated/xlsxrb/stream_sheet.rbs +101 -1
- data/sig/generated/xlsxrb/stream_writer.rbs +592 -23
- data/sig/generated/xlsxrb/style_builder.rbs +24 -6
- data/sig/generated/xlsxrb/utils.rbs +20 -0
- data/sig/generated/xlsxrb/workbook_builder.rbs +87 -5
- data/sig/generated/xlsxrb/worksheet_builder.rbs +554 -10
- data/sig/generated/xlsxrb.rbs +77 -16
- metadata +1 -1
|
@@ -32,19 +32,22 @@ module Xlsxrb
|
|
|
32
32
|
|
|
33
33
|
def self.write(target, sheets:, shared_strings: [], shared_strings_index: nil, styles: nil,
|
|
34
34
|
defined_names: nil, core_properties: nil, app_properties: nil,
|
|
35
|
-
custom_properties: nil, workbook_protection: nil, workbook_properties: nil
|
|
35
|
+
custom_properties: nil, workbook_protection: nil, workbook_properties: nil,
|
|
36
|
+
compression_level: Zlib::DEFAULT_COMPRESSION)
|
|
36
37
|
writer = new(sheets: sheets, shared_strings: shared_strings, shared_strings_index: shared_strings_index, styles: styles,
|
|
37
38
|
defined_names: defined_names, core_properties: core_properties,
|
|
38
39
|
app_properties: app_properties, custom_properties: custom_properties,
|
|
39
|
-
workbook_protection: workbook_protection, workbook_properties: workbook_properties
|
|
40
|
+
workbook_protection: workbook_protection, workbook_properties: workbook_properties,
|
|
41
|
+
compression_level: compression_level)
|
|
40
42
|
writer.write_to(target)
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
def initialize(sheets:, shared_strings: [], shared_strings_index: nil, styles: nil,
|
|
44
46
|
defined_names: nil, core_properties: nil, app_properties: nil,
|
|
45
|
-
custom_properties: nil, workbook_protection: nil, workbook_properties: nil
|
|
47
|
+
custom_properties: nil, workbook_protection: nil, workbook_properties: nil,
|
|
48
|
+
compression_level: Zlib::DEFAULT_COMPRESSION)
|
|
46
49
|
@sheets = sheets
|
|
47
|
-
@shared_strings = shared_strings
|
|
50
|
+
@shared_strings = shared_strings || []
|
|
48
51
|
@shared_strings_index = shared_strings_index
|
|
49
52
|
@styles = styles || {}
|
|
50
53
|
@defined_names = defined_names || []
|
|
@@ -53,27 +56,42 @@ module Xlsxrb
|
|
|
53
56
|
@custom_properties = custom_properties || []
|
|
54
57
|
@workbook_protection = workbook_protection
|
|
55
58
|
@workbook_properties = workbook_properties || { update_links: "never" }
|
|
59
|
+
@compression_level = compression_level || Zlib::DEFAULT_COMPRESSION
|
|
56
60
|
@drawing_count = 0
|
|
57
61
|
@chart_count = 0
|
|
58
62
|
@comment_count = 0
|
|
59
63
|
@table_count = 0
|
|
60
64
|
@pivot_cache_count = 0
|
|
61
65
|
@pivot_table_count = 0
|
|
66
|
+
@threaded_comment_count = 0
|
|
67
|
+
@persons = []
|
|
62
68
|
end
|
|
63
69
|
|
|
64
70
|
def write_to(target)
|
|
65
71
|
preprocess_conditional_formats!
|
|
66
72
|
|
|
67
|
-
|
|
73
|
+
active_tab_idx = resolve_active_tab_index
|
|
74
|
+
effective_active_idx = active_tab_idx || 0
|
|
75
|
+
|
|
76
|
+
ZipWriter.open(target, compression_level: @compression_level) do |zip|
|
|
68
77
|
@sheets.each_with_index do |sheet, idx|
|
|
69
78
|
info = prepare_sheet_auxiliary(sheet, idx)
|
|
70
79
|
sheet_tables = sheet[:tables] || []
|
|
71
80
|
|
|
81
|
+
sv = sheet[:sheet_view]
|
|
82
|
+
if idx == effective_active_idx
|
|
83
|
+
sv = (sv || {}).merge(tab_selected: true)
|
|
84
|
+
elsif sv&.key?(:tab_selected)
|
|
85
|
+
sv = sv.dup
|
|
86
|
+
sv.delete(:tab_selected)
|
|
87
|
+
end
|
|
88
|
+
|
|
72
89
|
zip.start_entry("xl/worksheets/sheet#{idx + 1}.xml")
|
|
73
90
|
write_worksheet_xml(
|
|
74
91
|
ZipEntryIO.new(zip),
|
|
75
92
|
sheet,
|
|
76
93
|
drawing_rid: info[:drawing_rid],
|
|
94
|
+
picture_rid: info[:picture_rid],
|
|
77
95
|
sheet_protection: sheet[:sheet_protection],
|
|
78
96
|
auto_filter: sheet[:auto_filter],
|
|
79
97
|
filter_columns: sheet[:filter_columns],
|
|
@@ -91,11 +109,14 @@ module Xlsxrb
|
|
|
91
109
|
freeze_pane: sheet[:freeze_pane],
|
|
92
110
|
split_pane: sheet[:split_pane],
|
|
93
111
|
selection: sheet[:selection],
|
|
94
|
-
sheet_view:
|
|
112
|
+
sheet_view: sv,
|
|
95
113
|
sheet_properties: sheet[:sheet_properties],
|
|
96
114
|
sheet_format: sheet[:sheet_format],
|
|
97
115
|
protected_ranges: sheet[:protected_ranges],
|
|
98
116
|
cell_watches: sheet[:cell_watches],
|
|
117
|
+
ignored_errors: sheet[:ignored_errors],
|
|
118
|
+
phonetic_pr: sheet[:phonetic_properties] || sheet[:phonetic_pr],
|
|
119
|
+
sheet_calc_pr: sheet[:sheet_calc_pr],
|
|
99
120
|
tables: sheet_tables,
|
|
100
121
|
table_start_rid: info[:table_start_rid],
|
|
101
122
|
legacy_drawing_rid: info[:vml_rid],
|
|
@@ -108,20 +129,23 @@ module Xlsxrb
|
|
|
108
129
|
end
|
|
109
130
|
end
|
|
110
131
|
|
|
111
|
-
def prepare_sheet_auxiliary(sheet,
|
|
132
|
+
def prepare_sheet_auxiliary(sheet, idx)
|
|
112
133
|
sheet_images = sheet[:images] || []
|
|
113
134
|
sheet_charts = sheet[:charts] || []
|
|
114
135
|
sheet_shapes = sheet[:shapes] || []
|
|
115
136
|
sheet_comments = sheet[:comments] || []
|
|
137
|
+
sheet_threaded_comments = sheet[:threaded_comments] || []
|
|
116
138
|
sheet_tables = sheet[:tables] || []
|
|
117
139
|
sheet_hyperlinks = sheet[:hyperlinks] || []
|
|
118
140
|
has_drawing = sheet_images.any? || sheet_charts.any? || sheet_shapes.any?
|
|
119
141
|
has_comments = sheet_comments.any?
|
|
142
|
+
has_threaded_comments = sheet_threaded_comments.any?
|
|
120
143
|
|
|
121
144
|
sheet_rels = []
|
|
122
145
|
drawing_rid = nil
|
|
123
146
|
vml_rid = nil
|
|
124
147
|
table_start_rid = nil
|
|
148
|
+
picture_rid = nil
|
|
125
149
|
hyperlink_rels = []
|
|
126
150
|
|
|
127
151
|
if has_drawing
|
|
@@ -140,6 +164,12 @@ module Xlsxrb
|
|
|
140
164
|
vml_rid = "rId#{vml_rid_num}"
|
|
141
165
|
end
|
|
142
166
|
|
|
167
|
+
if has_threaded_comments
|
|
168
|
+
@threaded_comment_count += 1
|
|
169
|
+
tc_rid_num = sheet_rels.size + 1
|
|
170
|
+
sheet_rels << { id: "rId#{tc_rid_num}", type: "http://schemas.microsoft.com/office/2017/10/relationships/threadedComment", target: "../threadedComments/threadedComment#{@threaded_comment_count}.xml" }
|
|
171
|
+
end
|
|
172
|
+
|
|
143
173
|
sheet_hyperlinks.each do |link|
|
|
144
174
|
next unless link[:url]
|
|
145
175
|
|
|
@@ -178,6 +208,33 @@ module Xlsxrb
|
|
|
178
208
|
end
|
|
179
209
|
end
|
|
180
210
|
|
|
211
|
+
bg_img = sheet[:background_image] || sheet[:background_picture] || sheet[:picture]
|
|
212
|
+
if bg_img
|
|
213
|
+
bg_data = if bg_img.is_a?(Hash)
|
|
214
|
+
bg_img[:file_data] || bg_img[:data]
|
|
215
|
+
else
|
|
216
|
+
(bg_img.respond_to?(:data) ? bg_img.data : bg_img)
|
|
217
|
+
end
|
|
218
|
+
if bg_data.is_a?(String) && !bg_data.include?("\x00") && File.file?(bg_data)
|
|
219
|
+
bg_ext = File.extname(bg_data).delete_prefix(".").downcase
|
|
220
|
+
bg_data = File.binread(bg_data)
|
|
221
|
+
else
|
|
222
|
+
bg_ext = if bg_img.is_a?(Hash)
|
|
223
|
+
(bg_img[:ext] || (bg_img[:filename] && File.extname(bg_img[:filename]).delete_prefix(".")) || "png").to_s.downcase
|
|
224
|
+
elsif bg_img.respond_to?(:filename)
|
|
225
|
+
File.extname(bg_img.filename).delete_prefix(".").downcase
|
|
226
|
+
else
|
|
227
|
+
"png"
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
if bg_data && !bg_data.empty?
|
|
231
|
+
p_rid_num = sheet_rels.size + 1
|
|
232
|
+
picture_rid = "rId#{p_rid_num}"
|
|
233
|
+
sheet_rels << { id: picture_rid, type: "#{DOC_REL}/image", target: "../media/sheet#{idx + 1}_bg.#{bg_ext}" }
|
|
234
|
+
sheet[:_bg_media] = { path: "xl/media/sheet#{idx + 1}_bg.#{bg_ext}", data: bg_data.b, ext: bg_ext }
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
181
238
|
sheet[:_prepared_rels] = sheet_rels
|
|
182
239
|
sheet[:_enriched_hyperlinks] = enriched_hyperlinks
|
|
183
240
|
|
|
@@ -185,6 +242,7 @@ module Xlsxrb
|
|
|
185
242
|
drawing_rid: drawing_rid,
|
|
186
243
|
vml_rid: vml_rid,
|
|
187
244
|
table_start_rid: table_start_rid,
|
|
245
|
+
picture_rid: picture_rid,
|
|
188
246
|
enriched_hyperlinks: enriched_hyperlinks,
|
|
189
247
|
sheet_rels: sheet_rels
|
|
190
248
|
}
|
|
@@ -193,6 +251,7 @@ module Xlsxrb
|
|
|
193
251
|
def write_auxiliary_parts(zip)
|
|
194
252
|
drawing_idx = 0
|
|
195
253
|
comment_idx = 0
|
|
254
|
+
tc_idx = 0
|
|
196
255
|
table_idx = 0
|
|
197
256
|
pivot_cache_idx = 0
|
|
198
257
|
pivot_table_idx = 0
|
|
@@ -268,6 +327,29 @@ module Xlsxrb
|
|
|
268
327
|
zip.add_entry("xl/drawings/vmlDrawing#{comment_idx}.vml", comment_writer.send(:generate_vml_drawing_xml, comment_writer.comments))
|
|
269
328
|
end
|
|
270
329
|
|
|
330
|
+
sheet_threaded_comments = sheet[:threaded_comments] || []
|
|
331
|
+
if sheet_threaded_comments.any?
|
|
332
|
+
tc_idx += 1
|
|
333
|
+
tc_writer = Xlsxrb::Ooxml::Writer.new
|
|
334
|
+
sheet_threaded_comments.each do |c|
|
|
335
|
+
tc_writer.add_threaded_comment(
|
|
336
|
+
c[:cell] || c[:ref],
|
|
337
|
+
c[:text],
|
|
338
|
+
author: c[:author] || "Author",
|
|
339
|
+
timestamp: c[:timestamp] || c[:dt],
|
|
340
|
+
id: c[:id],
|
|
341
|
+
parent_id: c[:parent_id],
|
|
342
|
+
done: c[:done],
|
|
343
|
+
user_id: c[:user_id],
|
|
344
|
+
provider_id: c[:provider_id]
|
|
345
|
+
)
|
|
346
|
+
end
|
|
347
|
+
tc_writer.persons.each do |p|
|
|
348
|
+
@persons << p unless @persons.any? { |ep| ep[:id] == p[:id] || ep[:display_name] == p[:display_name] }
|
|
349
|
+
end
|
|
350
|
+
zip.add_entry("xl/threadedComments/threadedComment#{tc_idx}.xml", tc_writer.send(:generate_threaded_comments_xml, tc_writer.threaded_comments))
|
|
351
|
+
end
|
|
352
|
+
|
|
271
353
|
sheet_tables.each do |tbl|
|
|
272
354
|
table_idx += 1
|
|
273
355
|
zip.add_entry("xl/tables/table#{table_idx}.xml", build_table_xml(tbl, table_idx))
|
|
@@ -308,8 +390,15 @@ module Xlsxrb
|
|
|
308
390
|
end
|
|
309
391
|
end
|
|
310
392
|
|
|
393
|
+
zip.add_binary_entry(sheet[:_bg_media][:path], sheet[:_bg_media][:data]) if sheet[:_bg_media]
|
|
394
|
+
|
|
311
395
|
zip.add_entry("xl/worksheets/_rels/sheet#{idx + 1}.xml.rels", build_sheet_rels_from_list(sheet_rels)) unless sheet_rels.empty?
|
|
312
396
|
end
|
|
397
|
+
|
|
398
|
+
return unless @persons.any?
|
|
399
|
+
|
|
400
|
+
p_writer = Xlsxrb::Ooxml::Writer.new
|
|
401
|
+
zip.add_entry("xl/persons/person.xml", p_writer.send(:generate_persons_xml, @persons))
|
|
313
402
|
end
|
|
314
403
|
|
|
315
404
|
def write_package_metadata(zip)
|
|
@@ -407,6 +496,23 @@ module Xlsxrb
|
|
|
407
496
|
end
|
|
408
497
|
end
|
|
409
498
|
|
|
499
|
+
if sheet[:_bg_media]
|
|
500
|
+
bg_ext = sheet[:_bg_media][:ext]
|
|
501
|
+
unless image_exts[bg_ext]
|
|
502
|
+
mime = case bg_ext
|
|
503
|
+
when "png" then "image/png"
|
|
504
|
+
when "jpg", "jpeg" then "image/jpeg"
|
|
505
|
+
when "gif" then "image/gif"
|
|
506
|
+
when "bmp" then "image/bmp"
|
|
507
|
+
when "emf" then "image/x-emf"
|
|
508
|
+
when "wmf" then "image/x-wmf"
|
|
509
|
+
else "application/octet-stream"
|
|
510
|
+
end
|
|
511
|
+
b.empty_tag("Default", { Extension: bg_ext, ContentType: mime })
|
|
512
|
+
image_exts[bg_ext] = true
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
|
|
410
516
|
if sheet_comments.any?
|
|
411
517
|
comment_count += 1
|
|
412
518
|
b.empty_tag("Override", { PartName: "/xl/comments#{comment_count}.xml", ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml" })
|
|
@@ -427,6 +533,11 @@ module Xlsxrb
|
|
|
427
533
|
end
|
|
428
534
|
end
|
|
429
535
|
|
|
536
|
+
b.empty_tag("Override", { PartName: "/xl/persons/person.xml", ContentType: "application/vnd.ms-excel.person+xml" }) if @persons.any?
|
|
537
|
+
(1..@threaded_comment_count).each do |tc|
|
|
538
|
+
b.empty_tag("Override", { PartName: "/xl/threadedComments/threadedComment#{tc}.xml", ContentType: "application/vnd.ms-excel.threadedcomments+xml" })
|
|
539
|
+
end
|
|
540
|
+
|
|
430
541
|
b.close_tag("Types")
|
|
431
542
|
io.string
|
|
432
543
|
end
|
|
@@ -484,6 +595,7 @@ module Xlsxrb
|
|
|
484
595
|
attrs = {}
|
|
485
596
|
attrs[:date1904] = "1" if @workbook_properties[:date1904]
|
|
486
597
|
attrs[:updateLinks] = @workbook_properties[:update_links] if @workbook_properties[:update_links]
|
|
598
|
+
attrs[:codeName] = @workbook_properties[:code_name] if @workbook_properties[:code_name]
|
|
487
599
|
b.empty_tag("workbookPr", attrs) unless attrs.empty?
|
|
488
600
|
end
|
|
489
601
|
|
|
@@ -496,11 +608,23 @@ module Xlsxrb
|
|
|
496
608
|
b.empty_tag("workbookProtection", wp_attrs) unless wp_attrs.empty?
|
|
497
609
|
end
|
|
498
610
|
|
|
499
|
-
# Workbook view:
|
|
500
|
-
|
|
501
|
-
|
|
611
|
+
# Workbook view: activeTab, firstSheet, showSheetTabs, tabRatio
|
|
612
|
+
active_tab_idx = resolve_active_tab_index
|
|
613
|
+
first_sheet_idx = resolve_first_sheet_index
|
|
614
|
+
show_sheet_tabs = @workbook_properties&.[](:show_sheet_tabs)
|
|
615
|
+
tab_ratio = @workbook_properties&.[](:tab_ratio)
|
|
616
|
+
|
|
617
|
+
emit_active_tab = !@workbook_properties&.[](:active_tab).nil? || active_tab_idx&.positive?
|
|
618
|
+
|
|
619
|
+
has_book_views = emit_active_tab || first_sheet_idx || !show_sheet_tabs.nil? || tab_ratio
|
|
620
|
+
if has_book_views
|
|
621
|
+
bv_attrs = {}
|
|
622
|
+
bv_attrs[:activeTab] = active_tab_idx.to_s if emit_active_tab
|
|
623
|
+
bv_attrs[:firstSheet] = first_sheet_idx.to_s if first_sheet_idx
|
|
624
|
+
bv_attrs[:showSheetTabs] = show_sheet_tabs ? "1" : "0" unless show_sheet_tabs.nil?
|
|
625
|
+
bv_attrs[:tabRatio] = tab_ratio.to_s if tab_ratio
|
|
502
626
|
b.open_tag("bookViews")
|
|
503
|
-
b.empty_tag("workbookView",
|
|
627
|
+
b.empty_tag("workbookView", bv_attrs)
|
|
504
628
|
b.close_tag("bookViews")
|
|
505
629
|
end
|
|
506
630
|
|
|
@@ -597,6 +721,15 @@ module Xlsxrb
|
|
|
597
721
|
})
|
|
598
722
|
end
|
|
599
723
|
|
|
724
|
+
if @persons.any?
|
|
725
|
+
rid += 1
|
|
726
|
+
b.empty_tag("Relationship", {
|
|
727
|
+
Id: "rId#{rid}",
|
|
728
|
+
Type: "http://schemas.microsoft.com/office/2017/10/relationships/person",
|
|
729
|
+
Target: "persons/person.xml"
|
|
730
|
+
})
|
|
731
|
+
end
|
|
732
|
+
|
|
600
733
|
b.close_tag("Relationships")
|
|
601
734
|
io.string
|
|
602
735
|
end
|
|
@@ -650,12 +783,28 @@ module Xlsxrb
|
|
|
650
783
|
b.empty_tag("i") if font_props[:italic]
|
|
651
784
|
b.empty_tag("strike") if font_props[:strike]
|
|
652
785
|
if font_props[:underline]
|
|
653
|
-
|
|
786
|
+
u_val = font_props[:underline]
|
|
787
|
+
if u_val == true || u_val.to_s == "single"
|
|
788
|
+
b.empty_tag("u")
|
|
789
|
+
else
|
|
790
|
+
mapped_u = StyleBuilder::UNDERLINE_MAP[u_val.to_sym] || u_val.to_s
|
|
791
|
+
b.empty_tag("u", { val: mapped_u })
|
|
792
|
+
end
|
|
793
|
+
end
|
|
794
|
+
va_val = font_props[:vert_align] || font_props[:vertical_align]
|
|
795
|
+
va_val ||= "superscript" if font_props[:superscript]
|
|
796
|
+
va_val ||= "subscript" if font_props[:subscript]
|
|
797
|
+
if va_val
|
|
798
|
+
mapped_va = StyleBuilder::VERT_ALIGN_MAP[va_val.to_sym] || va_val.to_s
|
|
799
|
+
b.empty_tag("vertAlign", { val: mapped_va })
|
|
654
800
|
end
|
|
655
801
|
b.empty_tag("sz", { val: (font_props[:sz] || 11).to_s }) if font_props[:sz] || fonts == [{}]
|
|
656
802
|
color_attrs = color_tag_attrs(font_props[:color] || (font_props.key?(:rgb) || font_props.key?(:theme) || font_props.key?(:indexed) ? font_props : nil))
|
|
657
803
|
b.empty_tag("color", color_attrs) if color_attrs
|
|
658
804
|
b.empty_tag("name", { val: font_props[:name] || "Calibri" })
|
|
805
|
+
b.empty_tag("family", { val: font_props[:family].to_s }) if font_props[:family]
|
|
806
|
+
b.empty_tag("charset", { val: font_props[:charset].to_s }) if font_props[:charset]
|
|
807
|
+
b.empty_tag("scheme", { val: font_props[:scheme].to_s }) if font_props[:scheme]
|
|
659
808
|
end
|
|
660
809
|
end
|
|
661
810
|
end
|
|
@@ -834,22 +983,7 @@ module Xlsxrb
|
|
|
834
983
|
end
|
|
835
984
|
|
|
836
985
|
def extract_dxf_from_rule!(rule)
|
|
837
|
-
|
|
838
|
-
font[:color] = rule.delete(:font_color) if rule.key?(:font_color)
|
|
839
|
-
font[:bold] = rule.delete(:bold) if rule.key?(:bold)
|
|
840
|
-
font[:italic] = rule.delete(:italic) if rule.key?(:italic)
|
|
841
|
-
font[:underline] = rule.delete(:underline) if rule.key?(:underline)
|
|
842
|
-
|
|
843
|
-
fill = {}
|
|
844
|
-
if rule.key?(:fill_color)
|
|
845
|
-
fill[:pattern] = "solid"
|
|
846
|
-
fill[:fg_color] = rule.delete(:fill_color)
|
|
847
|
-
end
|
|
848
|
-
|
|
849
|
-
dxf = {}
|
|
850
|
-
dxf[:font] = font unless font.empty?
|
|
851
|
-
dxf[:fill] = fill unless fill.empty?
|
|
852
|
-
dxf.empty? ? nil : dxf
|
|
986
|
+
DslHelpers.extract_dxf_from_rule!(rule)
|
|
853
987
|
end
|
|
854
988
|
|
|
855
989
|
def write_dxf_font(builder, font)
|
|
@@ -867,22 +1001,34 @@ module Xlsxrb
|
|
|
867
1001
|
if [true, "single"].include?(font[:underline])
|
|
868
1002
|
builder.empty_tag("u")
|
|
869
1003
|
else
|
|
870
|
-
|
|
1004
|
+
mapped_u = StyleBuilder::UNDERLINE_MAP[font[:underline].to_sym] || font[:underline]
|
|
1005
|
+
builder.empty_tag("u", { val: mapped_u })
|
|
871
1006
|
end
|
|
872
1007
|
end
|
|
1008
|
+
va_val = font[:vert_align] || font[:vertical_align]
|
|
1009
|
+
va_val ||= "superscript" if font[:superscript]
|
|
1010
|
+
va_val ||= "subscript" if font[:subscript]
|
|
1011
|
+
if va_val
|
|
1012
|
+
mapped_va = StyleBuilder::VERT_ALIGN_MAP[va_val.to_sym] || va_val.to_s
|
|
1013
|
+
builder.empty_tag("vertAlign", { val: mapped_va })
|
|
1014
|
+
end
|
|
873
1015
|
color_attrs = color_tag_attrs(font[:color] || font)
|
|
874
1016
|
builder.empty_tag("color", color_attrs) if color_attrs
|
|
875
1017
|
builder.empty_tag("name", { val: font[:name] }) if font[:name]
|
|
876
|
-
|
|
1018
|
+
sz_val = font[:sz] || font[:size]
|
|
1019
|
+
builder.empty_tag("sz", { val: sz_val.to_s }) if sz_val
|
|
877
1020
|
end
|
|
878
1021
|
end
|
|
879
1022
|
|
|
880
1023
|
def write_dxf_fill(builder, fill)
|
|
881
1024
|
builder.tag("fill") do |_|
|
|
882
1025
|
pattern_attrs = {}
|
|
883
|
-
|
|
1026
|
+
pattern = fill[:pattern] || fill[:pattern_type]
|
|
1027
|
+
pattern ||= "solid" if (fill[:color] || fill[:fill_color] || fill[:fg_color]) && !fill[:bg_color]
|
|
1028
|
+
pattern_attrs[:patternType] = pattern if pattern
|
|
884
1029
|
builder.tag("patternFill", pattern_attrs) do |_|
|
|
885
|
-
|
|
1030
|
+
fg = fill[:fg_color] || fill[:color] || fill[:fill_color]
|
|
1031
|
+
fg_attrs = color_tag_attrs(fg)
|
|
886
1032
|
builder.empty_tag("fgColor", fg_attrs) if fg_attrs
|
|
887
1033
|
bg_attrs = color_tag_attrs(fill[:bg_color])
|
|
888
1034
|
builder.empty_tag("bgColor", bg_attrs) if bg_attrs
|
|
@@ -1028,14 +1174,22 @@ module Xlsxrb
|
|
|
1028
1174
|
buf << "<i/>" if font[:italic]
|
|
1029
1175
|
buf << "<strike/>" if font[:strike]
|
|
1030
1176
|
if font[:underline]
|
|
1031
|
-
if font[:underline] == true
|
|
1177
|
+
if font[:underline] == true || font[:underline].to_s == "single"
|
|
1032
1178
|
buf << "<u/>"
|
|
1033
1179
|
else
|
|
1034
|
-
|
|
1180
|
+
mapped_u = StyleBuilder::UNDERLINE_MAP[font[:underline].to_sym] || font[:underline]
|
|
1181
|
+
buf << '<u val="' << mapped_u.to_s << '"/>'
|
|
1035
1182
|
end
|
|
1036
1183
|
end
|
|
1037
|
-
|
|
1038
|
-
|
|
1184
|
+
va_val = font[:vert_align] || font[:vertical_align]
|
|
1185
|
+
va_val ||= "superscript" if font[:superscript]
|
|
1186
|
+
va_val ||= "subscript" if font[:subscript]
|
|
1187
|
+
if va_val
|
|
1188
|
+
mapped_va = StyleBuilder::VERT_ALIGN_MAP[va_val.to_sym] || va_val.to_s
|
|
1189
|
+
buf << '<vertAlign val="' << mapped_va << '"/>'
|
|
1190
|
+
end
|
|
1191
|
+
font_sz = font[:sz] || font[:size]
|
|
1192
|
+
buf << '<sz val="' << font_sz.to_s << '"/>' if font_sz
|
|
1039
1193
|
color_attrs = color_tag_attrs(font[:color] || font)
|
|
1040
1194
|
if color_attrs
|
|
1041
1195
|
attrs_str = color_attrs.map { |k, v| %(#{k}="#{v}") }.join(" ")
|
|
@@ -1067,16 +1221,17 @@ module Xlsxrb
|
|
|
1067
1221
|
zip.finish_entry
|
|
1068
1222
|
end
|
|
1069
1223
|
|
|
1070
|
-
def write_worksheet_xml(io, sheet, drawing_rid: nil, sheet_protection: nil,
|
|
1224
|
+
def write_worksheet_xml(io, sheet, drawing_rid: nil, picture_rid: nil, sheet_protection: nil,
|
|
1071
1225
|
protected_ranges: nil, auto_filter: nil, filter_columns: nil,
|
|
1072
1226
|
sort_state: nil, merge_cells: nil, conditional_formats: nil,
|
|
1073
1227
|
data_validations: nil, hyperlinks: nil,
|
|
1074
1228
|
print_options: nil, page_margins: nil, page_setup: nil,
|
|
1075
1229
|
header_footer: nil, row_breaks: nil, col_breaks: nil,
|
|
1076
|
-
cell_watches: nil, freeze_pane: nil, split_pane: nil,
|
|
1230
|
+
cell_watches: nil, ignored_errors: nil, freeze_pane: nil, split_pane: nil,
|
|
1077
1231
|
selection: nil, sheet_view: nil, sheet_properties: nil,
|
|
1078
1232
|
sheet_format: nil, tables: nil, table_start_rid: nil,
|
|
1079
|
-
legacy_drawing_rid: nil, sparkline_groups: nil
|
|
1233
|
+
legacy_drawing_rid: nil, sparkline_groups: nil, phonetic_pr: nil,
|
|
1234
|
+
sheet_calc_pr: nil)
|
|
1080
1235
|
date1904 = @workbook_properties&.[](:date1904) ? true : false
|
|
1081
1236
|
ws = WorksheetWriter.new(io, date1904: date1904)
|
|
1082
1237
|
ws.start(
|
|
@@ -1112,6 +1267,7 @@ module Xlsxrb
|
|
|
1112
1267
|
end
|
|
1113
1268
|
ws.finish(
|
|
1114
1269
|
drawing_rid: drawing_rid,
|
|
1270
|
+
picture_rid: picture_rid,
|
|
1115
1271
|
sheet_protection: sheet_protection,
|
|
1116
1272
|
protected_ranges: protected_ranges,
|
|
1117
1273
|
auto_filter: auto_filter,
|
|
@@ -1128,10 +1284,13 @@ module Xlsxrb
|
|
|
1128
1284
|
row_breaks: row_breaks,
|
|
1129
1285
|
col_breaks: col_breaks,
|
|
1130
1286
|
cell_watches: cell_watches,
|
|
1287
|
+
ignored_errors: ignored_errors,
|
|
1131
1288
|
tables: tables,
|
|
1132
1289
|
table_start_rid: table_start_rid,
|
|
1133
1290
|
legacy_drawing_rid: legacy_drawing_rid,
|
|
1134
|
-
sparkline_groups: sparkline_groups
|
|
1291
|
+
sparkline_groups: sparkline_groups,
|
|
1292
|
+
phonetic_pr: phonetic_pr,
|
|
1293
|
+
sheet_calc_pr: sheet_calc_pr
|
|
1135
1294
|
)
|
|
1136
1295
|
end
|
|
1137
1296
|
|
|
@@ -1150,8 +1309,8 @@ module Xlsxrb
|
|
|
1150
1309
|
t_attrs[:totalsRowCount] = tbl[:totals_row_count].to_s if tbl[:totals_row_count]&.positive?
|
|
1151
1310
|
b.open_tag("table", t_attrs)
|
|
1152
1311
|
|
|
1153
|
-
# Auto filter for the table range
|
|
1154
|
-
b.empty_tag("autoFilter", { ref: tbl[:ref] })
|
|
1312
|
+
# Auto filter for the table range unless auto_filter: false
|
|
1313
|
+
b.empty_tag("autoFilter", { ref: tbl[:ref] }) unless tbl[:auto_filter] == false
|
|
1155
1314
|
|
|
1156
1315
|
# Table columns
|
|
1157
1316
|
columns = tbl[:columns] || []
|
|
@@ -1159,18 +1318,23 @@ module Xlsxrb
|
|
|
1159
1318
|
columns.each_with_index do |col, ci|
|
|
1160
1319
|
col_name = col.is_a?(Hash) ? col[:name] : col
|
|
1161
1320
|
col_attrs = { id: (ci + 1).to_s, name: col_name.to_s }
|
|
1321
|
+
calc_formula = nil
|
|
1322
|
+
tot_formula = nil
|
|
1162
1323
|
if col.is_a?(Hash)
|
|
1163
|
-
|
|
1324
|
+
calc_formula = col[:calculated_column_formula] || col[:data_formula] || col[:formula]
|
|
1325
|
+
tot_formula = col[:totals_row_formula] || col[:total_row_formula] || col[:total_formula]
|
|
1326
|
+
trf = col[:totals_row_function] || ("custom" if tot_formula)
|
|
1327
|
+
col_attrs[:totalsRowFunction] = trf.to_s if trf
|
|
1164
1328
|
col_attrs[:totalsRowLabel] = col[:totals_row_label].to_s if col[:totals_row_label]
|
|
1165
1329
|
col_attrs[:dataDxfId] = col[:data_dxf_id].to_s if col[:data_dxf_id]
|
|
1166
1330
|
col_attrs[:totalsRowDxfId] = col[:totals_row_dxf_id].to_s if col[:totals_row_dxf_id]
|
|
1167
1331
|
col_attrs[:headerRowDxfId] = col[:header_row_dxf_id].to_s if col[:header_row_dxf_id]
|
|
1168
1332
|
col_attrs[:dataCellStyle] = col[:data_cell_style].to_s if col[:data_cell_style]
|
|
1169
1333
|
end
|
|
1170
|
-
if
|
|
1334
|
+
if calc_formula || tot_formula
|
|
1171
1335
|
b.open_tag("tableColumn", col_attrs)
|
|
1172
|
-
b.tag("calculatedColumnFormula") { |cb| cb.text(
|
|
1173
|
-
b.tag("totalsRowFormula") { |tb| tb.text(
|
|
1336
|
+
b.tag("calculatedColumnFormula") { |cb| cb.text(calc_formula.to_s) } if calc_formula
|
|
1337
|
+
b.tag("totalsRowFormula") { |tb| tb.text(tot_formula.to_s) } if tot_formula
|
|
1174
1338
|
b.close_tag("tableColumn")
|
|
1175
1339
|
else
|
|
1176
1340
|
b.empty_tag("tableColumn", col_attrs)
|
|
@@ -1289,6 +1453,62 @@ module Xlsxrb
|
|
|
1289
1453
|
b.close_tag("Properties")
|
|
1290
1454
|
io.string
|
|
1291
1455
|
end
|
|
1456
|
+
|
|
1457
|
+
def resolve_active_tab_index
|
|
1458
|
+
active_tab = @workbook_properties&.[](:active_tab)
|
|
1459
|
+
if active_tab.nil?
|
|
1460
|
+
first_vis_idx = @sheets.find_index do |s|
|
|
1461
|
+
state = s.respond_to?(:state) ? s.state : s[:state]
|
|
1462
|
+
state.nil? || state.to_sym == :visible
|
|
1463
|
+
end
|
|
1464
|
+
active_tab = first_vis_idx if first_vis_idx&.positive?
|
|
1465
|
+
end
|
|
1466
|
+
|
|
1467
|
+
if active_tab.is_a?(String) || active_tab.is_a?(Symbol)
|
|
1468
|
+
target_name = active_tab.to_s
|
|
1469
|
+
idx = @sheets.find_index do |s|
|
|
1470
|
+
(s.respond_to?(:name) ? s.name : s[:name]) == target_name
|
|
1471
|
+
end
|
|
1472
|
+
raise ArgumentError, "Active sheet '#{target_name}' not found" if idx.nil?
|
|
1473
|
+
|
|
1474
|
+
active_tab = idx
|
|
1475
|
+
end
|
|
1476
|
+
|
|
1477
|
+
if active_tab
|
|
1478
|
+
target_sheet = @sheets[active_tab]
|
|
1479
|
+
raise ArgumentError, "Active sheet index #{active_tab} out of bounds" if target_sheet.nil?
|
|
1480
|
+
|
|
1481
|
+
state = target_sheet.respond_to?(:state) ? target_sheet.state : target_sheet[:state]
|
|
1482
|
+
if state && state.to_sym != :visible
|
|
1483
|
+
name = target_sheet.respond_to?(:name) ? target_sheet.name : target_sheet[:name]
|
|
1484
|
+
raise ArgumentError, "Cannot set hidden sheet '#{name}' as active sheet"
|
|
1485
|
+
end
|
|
1486
|
+
end
|
|
1487
|
+
|
|
1488
|
+
active_tab
|
|
1489
|
+
end
|
|
1490
|
+
|
|
1491
|
+
def resolve_first_sheet_index
|
|
1492
|
+
first_sheet = @workbook_properties&.[](:first_sheet)
|
|
1493
|
+
return nil if first_sheet.nil?
|
|
1494
|
+
|
|
1495
|
+
if first_sheet.is_a?(String) || first_sheet.is_a?(Symbol)
|
|
1496
|
+
target_name = first_sheet.to_s
|
|
1497
|
+
idx = @sheets.find_index do |s|
|
|
1498
|
+
(s.respond_to?(:name) ? s.name : s[:name]) == target_name
|
|
1499
|
+
end
|
|
1500
|
+
raise ArgumentError, "First sheet '#{target_name}' not found" if idx.nil?
|
|
1501
|
+
|
|
1502
|
+
first_sheet = idx
|
|
1503
|
+
end
|
|
1504
|
+
|
|
1505
|
+
if first_sheet
|
|
1506
|
+
target_sheet = @sheets[first_sheet]
|
|
1507
|
+
raise ArgumentError, "First sheet index #{first_sheet} out of bounds" if target_sheet.nil?
|
|
1508
|
+
end
|
|
1509
|
+
|
|
1510
|
+
first_sheet
|
|
1511
|
+
end
|
|
1292
1512
|
end
|
|
1293
1513
|
end
|
|
1294
1514
|
end
|
|
@@ -201,6 +201,7 @@ module Xlsxrb
|
|
|
201
201
|
|
|
202
202
|
pos = sd_open_end + 1
|
|
203
203
|
last_row_index = -1
|
|
204
|
+
shared_masters = {}
|
|
204
205
|
|
|
205
206
|
while pos < sd_end
|
|
206
207
|
row_start = xml.index(row_start_pattern, pos)
|
|
@@ -245,7 +246,7 @@ module Xlsxrb
|
|
|
245
246
|
row_end = xml.index(row_end_tag, tag_end + 1)
|
|
246
247
|
break unless row_end
|
|
247
248
|
|
|
248
|
-
fast_scan_cells_events(xml, tag_end + 1, row_end, shared_strings, row_source, prefix, &block)
|
|
249
|
+
fast_scan_cells_events(xml, tag_end + 1, row_end, shared_strings, row_source, prefix, shared_masters, &block)
|
|
249
250
|
|
|
250
251
|
block.call(Event.new(
|
|
251
252
|
type: :row_end,
|
|
@@ -314,7 +315,7 @@ module Xlsxrb
|
|
|
314
315
|
|
|
315
316
|
private_class_method :tag_attr
|
|
316
317
|
|
|
317
|
-
def self.fast_scan_cells_events(xml, from, to, shared_strings, row_source, prefix = "", &block)
|
|
318
|
+
def self.fast_scan_cells_events(xml, from, to, shared_strings, row_source, prefix = "", shared_masters = {}, &block)
|
|
318
319
|
pos = from
|
|
319
320
|
c_start_pattern = "<#{prefix}c"
|
|
320
321
|
c_start_len = c_start_pattern.bytesize
|
|
@@ -393,13 +394,26 @@ module Xlsxrb
|
|
|
393
394
|
elsif xml.byteslice(tag_pos, f_tag_start_str.bytesize) == f_tag_start_str
|
|
394
395
|
f_tag_end = xml.index(">", tag_pos + f_tag_start_str.bytesize)
|
|
395
396
|
if f_tag_end && f_tag_end < c_end
|
|
397
|
+
f_tag_slice = xml.byteslice(tag_pos, f_tag_end - tag_pos)
|
|
398
|
+
is_shared = f_tag_slice.include?('t="shared"') || f_tag_slice.include?("t='shared'")
|
|
399
|
+
si = is_shared ? tag_attr(f_tag_slice, ' si="') : nil
|
|
396
400
|
if xml.getbyte(f_tag_end - 1) == 47 # self-closing <f ... />
|
|
401
|
+
if si && (master = shared_masters[si])
|
|
402
|
+
formula = Xlsxrb::Utils.expand_formula(master[:formula], from_ref: master[:ref], to_ref: ref)
|
|
403
|
+
end
|
|
397
404
|
cpos = f_tag_end + 1
|
|
398
405
|
else
|
|
399
406
|
f_end = xml.index(f_tag_end_str, f_tag_end + 1)
|
|
400
407
|
if f_end && f_end <= c_end
|
|
401
408
|
formula = xml.byteslice(f_tag_end + 1, f_end - f_tag_end - 1).force_encoding("UTF-8")
|
|
402
409
|
formula = decode_xml_entities(formula) if formula.include?("&")
|
|
410
|
+
if is_shared && si
|
|
411
|
+
if formula && !formula.empty?
|
|
412
|
+
shared_masters[si] = { ref: ref, formula: formula }
|
|
413
|
+
elsif (master = shared_masters[si])
|
|
414
|
+
formula = Xlsxrb::Utils.expand_formula(master[:formula], from_ref: master[:ref], to_ref: ref)
|
|
415
|
+
end
|
|
416
|
+
end
|
|
403
417
|
cpos = f_end + f_tag_end_len
|
|
404
418
|
else
|
|
405
419
|
cpos = f_tag_end + 1
|
|
@@ -598,8 +612,8 @@ module Xlsxrb
|
|
|
598
612
|
min: attrs["min"]&.to_i,
|
|
599
613
|
max: attrs["max"]&.to_i,
|
|
600
614
|
width: attrs["width"]&.to_f,
|
|
601
|
-
hidden: attrs["hidden"] == "1",
|
|
602
|
-
custom_width: attrs["customWidth"] == "1",
|
|
615
|
+
hidden: attrs["hidden"] == "1" || (attrs["hidden"]&.casecmp?("true") || false),
|
|
616
|
+
custom_width: attrs["customWidth"] == "1" || (attrs["customWidth"]&.casecmp?("true") || false),
|
|
603
617
|
outline_level: attrs["outlineLevel"]&.to_i,
|
|
604
618
|
style_index: attrs["style"]&.to_i
|
|
605
619
|
}
|