write_xlsx 1.12.3 → 1.15.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +22 -0
- data/Changes +23 -0
- data/README.md +1 -1
- data/lib/write_xlsx/chart/area.rb +2 -2
- data/lib/write_xlsx/chart/axis.rb +55 -32
- data/lib/write_xlsx/chart/axis_writer.rb +528 -0
- data/lib/write_xlsx/chart/bar.rb +2 -2
- data/lib/write_xlsx/chart/caption.rb +16 -9
- data/lib/write_xlsx/chart/chart_area.rb +121 -0
- data/lib/write_xlsx/chart/column.rb +2 -2
- data/lib/write_xlsx/chart/d_pt_point_writer.rb +14 -0
- data/lib/write_xlsx/chart/doughnut.rb +0 -3
- data/lib/write_xlsx/chart/formatting_writer.rb +652 -0
- data/lib/write_xlsx/chart/initialization.rb +100 -0
- data/lib/write_xlsx/chart/line.rb +4 -3
- data/lib/write_xlsx/chart/pie.rb +6 -2
- data/lib/write_xlsx/chart/radar.rb +2 -2
- data/lib/write_xlsx/chart/scatter.rb +4 -3
- data/lib/write_xlsx/chart/series.rb +35 -15
- data/lib/write_xlsx/chart/series_data.rb +132 -0
- data/lib/write_xlsx/chart/series_writer.rb +318 -0
- data/lib/write_xlsx/chart/settings.rb +226 -0
- data/lib/write_xlsx/chart/stock.rb +2 -2
- data/lib/write_xlsx/chart/table.rb +50 -0
- data/lib/write_xlsx/chart/xml_writer.rb +305 -0
- data/lib/write_xlsx/chart.rb +286 -2477
- data/lib/write_xlsx/chartsheet.rb +35 -83
- data/lib/write_xlsx/constants.rb +11 -0
- data/lib/write_xlsx/drawing.rb +5 -3
- data/lib/write_xlsx/format/alignment_state.rb +39 -0
- data/lib/write_xlsx/format/alignment_style.rb +92 -0
- data/lib/write_xlsx/format/border_state.rb +47 -0
- data/lib/write_xlsx/format/border_style.rb +116 -0
- data/lib/write_xlsx/format/fill_state.rb +26 -0
- data/lib/write_xlsx/format/fill_style.rb +52 -0
- data/lib/write_xlsx/format/font_state.rb +74 -0
- data/lib/write_xlsx/format/font_style.rb +172 -0
- data/lib/write_xlsx/format/format_state.rb +65 -0
- data/lib/write_xlsx/format/number_format_state.rb +20 -0
- data/lib/write_xlsx/format/number_format_style.rb +28 -0
- data/lib/write_xlsx/format/protection_state.rb +20 -0
- data/lib/write_xlsx/format/protection_style.rb +28 -0
- data/lib/write_xlsx/format.rb +1093 -426
- data/lib/write_xlsx/formats.rb +0 -2
- data/lib/write_xlsx/image_property.rb +4 -1
- data/lib/write_xlsx/inserted_chart.rb +1 -1
- data/lib/write_xlsx/object_positioning.rb +203 -0
- data/lib/write_xlsx/package/app.rb +3 -3
- data/lib/write_xlsx/package/button.rb +6 -2
- data/lib/write_xlsx/package/comments.rb +11 -3
- data/lib/write_xlsx/package/conditional_format.rb +7 -3
- data/lib/write_xlsx/package/content_types.rb +2 -2
- data/lib/write_xlsx/package/core.rb +2 -2
- data/lib/write_xlsx/package/custom.rb +3 -2
- data/lib/write_xlsx/package/metadata.rb +2 -2
- data/lib/write_xlsx/package/packager.rb +0 -3
- data/lib/write_xlsx/package/relationships.rb +2 -2
- data/lib/write_xlsx/package/rich_value.rb +4 -2
- data/lib/write_xlsx/package/rich_value_rel.rb +2 -2
- data/lib/write_xlsx/package/rich_value_structure.rb +2 -2
- data/lib/write_xlsx/package/rich_value_types.rb +3 -3
- data/lib/write_xlsx/package/shared_strings.rb +2 -2
- data/lib/write_xlsx/package/styles.rb +13 -9
- data/lib/write_xlsx/package/table.rb +8 -2
- data/lib/write_xlsx/package/theme.rb +0 -3
- data/lib/write_xlsx/package/vml.rb +2 -2
- data/lib/write_xlsx/page_setup.rb +192 -0
- data/lib/write_xlsx/shape.rb +97 -100
- data/lib/write_xlsx/sheets.rb +9 -4
- data/lib/write_xlsx/sparkline.rb +2 -2
- data/lib/write_xlsx/utility/cell_reference.rb +124 -0
- data/lib/write_xlsx/utility/chart_formatting.rb +262 -0
- data/lib/write_xlsx/utility/common.rb +44 -0
- data/lib/write_xlsx/utility/date_time.rb +113 -0
- data/lib/write_xlsx/utility/dimensions.rb +40 -0
- data/lib/write_xlsx/utility/drawing.rb +136 -0
- data/lib/write_xlsx/utility/rich_text.rb +184 -0
- data/lib/write_xlsx/utility/sheetname_quoting.rb +73 -0
- data/lib/write_xlsx/utility/string_width.rb +45 -0
- data/lib/write_xlsx/utility/url.rb +27 -0
- data/lib/write_xlsx/utility/xml_primitives.rb +32 -0
- data/lib/write_xlsx/version.rb +1 -1
- data/lib/write_xlsx/workbook/chart_data.rb +188 -0
- data/lib/write_xlsx/workbook/format_preparation.rb +199 -0
- data/lib/write_xlsx/workbook/initialization.rb +223 -0
- data/lib/write_xlsx/workbook/package_preparation.rb +231 -0
- data/lib/write_xlsx/workbook/workbook_writer.rb +164 -0
- data/lib/write_xlsx/workbook.rb +143 -981
- data/lib/write_xlsx/worksheet/asset_manager.rb +60 -0
- data/lib/write_xlsx/worksheet/autofilter.rb +390 -0
- data/lib/write_xlsx/worksheet/cell_data.rb +13 -6
- data/lib/write_xlsx/worksheet/cell_data_manager.rb +47 -0
- data/lib/write_xlsx/worksheet/cell_data_store.rb +61 -0
- data/lib/write_xlsx/worksheet/columns.rb +204 -0
- data/lib/write_xlsx/worksheet/comments_support.rb +61 -0
- data/lib/write_xlsx/worksheet/conditional_formats.rb +30 -0
- data/lib/write_xlsx/worksheet/data_validation.rb +9 -1
- data/lib/write_xlsx/worksheet/data_writing.rb +1017 -0
- data/lib/write_xlsx/worksheet/drawing_methods.rb +308 -0
- data/lib/write_xlsx/worksheet/drawing_preparation.rb +290 -0
- data/lib/write_xlsx/worksheet/drawing_relations.rb +76 -0
- data/lib/write_xlsx/worksheet/drawing_xml_writer.rb +50 -0
- data/lib/write_xlsx/worksheet/formatting.rb +418 -0
- data/lib/write_xlsx/worksheet/hyperlink.rb +9 -1
- data/lib/write_xlsx/worksheet/initialization.rb +146 -0
- data/lib/write_xlsx/worksheet/panes.rb +64 -0
- data/lib/write_xlsx/worksheet/print_options.rb +72 -0
- data/lib/write_xlsx/worksheet/protection.rb +65 -0
- data/lib/write_xlsx/worksheet/rich_text_helpers.rb +78 -0
- data/lib/write_xlsx/worksheet/row_col_sizing.rb +69 -0
- data/lib/write_xlsx/worksheet/rows.rb +84 -0
- data/lib/write_xlsx/worksheet/selection.rb +41 -0
- data/lib/write_xlsx/worksheet/xml_writer.rb +1246 -0
- data/lib/write_xlsx/worksheet.rb +376 -4530
- metadata +66 -4
- data/lib/write_xlsx/utility.rb +0 -986
- data/lib/write_xlsx/worksheet/page_setup.rb +0 -192
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
class Workbook
|
|
6
|
+
module FormatPreparation
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
#
|
|
10
|
+
# Prepare all of the format properties prior to passing them to Styles.rb.
|
|
11
|
+
#
|
|
12
|
+
def prepare_format_properties # :nodoc:
|
|
13
|
+
# Separate format objects into XF and DXF formats.
|
|
14
|
+
prepare_formats
|
|
15
|
+
|
|
16
|
+
# Set the font index for the format objects.
|
|
17
|
+
prepare_fonts
|
|
18
|
+
|
|
19
|
+
# Set the number format index for the format objects.
|
|
20
|
+
prepare_num_formats
|
|
21
|
+
|
|
22
|
+
# Set the border index for the format objects.
|
|
23
|
+
prepare_borders
|
|
24
|
+
|
|
25
|
+
# Set the fill index for the format objects.
|
|
26
|
+
prepare_fills
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# Iterate through the XF Format objects and separate them into XF and DXF
|
|
31
|
+
# formats.
|
|
32
|
+
#
|
|
33
|
+
def prepare_formats # :nodoc:
|
|
34
|
+
@formats.formats.each do |format|
|
|
35
|
+
xf_index = format.xf_index
|
|
36
|
+
dxf_index = format.dxf_index
|
|
37
|
+
|
|
38
|
+
@xf_formats[xf_index] = format if xf_index
|
|
39
|
+
@dxf_formats[dxf_index] = format if dxf_index
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# Iterate through the XF Format objects and give them an index to non-default
|
|
45
|
+
# font elements.
|
|
46
|
+
#
|
|
47
|
+
def prepare_fonts # :nodoc:
|
|
48
|
+
fonts = {}
|
|
49
|
+
|
|
50
|
+
@xf_formats.each { |format| format.set_font_info(fonts) }
|
|
51
|
+
|
|
52
|
+
@font_count = fonts.size
|
|
53
|
+
|
|
54
|
+
# For the DXF formats we only need to check if the properties have changed.
|
|
55
|
+
@dxf_formats.each do |format|
|
|
56
|
+
# The only font properties that can change for a DXF format are: color,
|
|
57
|
+
# bold, italic, underline and strikethrough.
|
|
58
|
+
format.has_dxf_font(true) if format.color? || format.bold? || format.italic? || format.underline? || format.strikeout?
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#
|
|
63
|
+
# Iterate through the XF Format objects and give them an index to non-default
|
|
64
|
+
# number format elements.
|
|
65
|
+
#
|
|
66
|
+
# User defined records start from index 0xA4.
|
|
67
|
+
#
|
|
68
|
+
def prepare_num_formats # :nodoc:
|
|
69
|
+
num_formats = []
|
|
70
|
+
unique_num_formats = {}
|
|
71
|
+
index = 164
|
|
72
|
+
|
|
73
|
+
(@xf_formats + @dxf_formats).each do |format|
|
|
74
|
+
num_format = format.num_format
|
|
75
|
+
|
|
76
|
+
# Check if num_format is an index to a built-in number format.
|
|
77
|
+
# Also check for a string of zeros, which is a valid number format
|
|
78
|
+
# string but would evaluate to zero.
|
|
79
|
+
#
|
|
80
|
+
if num_format.to_s =~ /^\d+$/ && num_format.to_s !~ /^0+\d/
|
|
81
|
+
# Number format '0' is indexed as 1 in Excel.
|
|
82
|
+
num_format = 1 if num_format == 0
|
|
83
|
+
# Index to a built-in number format.
|
|
84
|
+
format.num_format_index = num_format
|
|
85
|
+
next
|
|
86
|
+
elsif num_format.to_s == 'General'
|
|
87
|
+
# The 'General' format has an number format index of 0.
|
|
88
|
+
format.num_format_index = 0
|
|
89
|
+
next
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if unique_num_formats[num_format]
|
|
93
|
+
# Number format has already been used.
|
|
94
|
+
format.num_format_index = unique_num_formats[num_format]
|
|
95
|
+
else
|
|
96
|
+
# Add a new number format.
|
|
97
|
+
unique_num_formats[num_format] = index
|
|
98
|
+
format.num_format_index = index
|
|
99
|
+
index += 1
|
|
100
|
+
|
|
101
|
+
# Only store/increase number format count for XF formats
|
|
102
|
+
# (not for DXF formats).
|
|
103
|
+
num_formats << num_format if ptrue?(format.xf_index)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
@num_formats = num_formats
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
#
|
|
111
|
+
# Iterate through the XF Format objects and give them an index to non-default
|
|
112
|
+
# border elements.
|
|
113
|
+
#
|
|
114
|
+
def prepare_borders # :nodoc:
|
|
115
|
+
borders = {}
|
|
116
|
+
|
|
117
|
+
@xf_formats.each { |format| format.set_border_info(borders) }
|
|
118
|
+
|
|
119
|
+
@border_count = borders.size
|
|
120
|
+
|
|
121
|
+
# For the DXF formats we only need to check if the properties have changed.
|
|
122
|
+
@dxf_formats.each do |format|
|
|
123
|
+
key = format.get_border_key
|
|
124
|
+
format.has_dxf_border(true) if key =~ /[^0:]/
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
#
|
|
129
|
+
# Iterate through the XF Format objects and give them an index to non-default
|
|
130
|
+
# fill elements.
|
|
131
|
+
#
|
|
132
|
+
# The user defined fill properties start from 2 since there are 2 default
|
|
133
|
+
# fills: patternType="none" and patternType="gray125".
|
|
134
|
+
#
|
|
135
|
+
def prepare_fills # :nodoc:
|
|
136
|
+
fills = {}
|
|
137
|
+
index = 2 # Start from 2. See above.
|
|
138
|
+
|
|
139
|
+
# Add the default fills.
|
|
140
|
+
fills['0:0:0'] = 0
|
|
141
|
+
fills['17:0:0'] = 1
|
|
142
|
+
|
|
143
|
+
# Store the DXF colors separately since them may be reversed below.
|
|
144
|
+
@dxf_formats.each do |format|
|
|
145
|
+
next unless format.fill_style.pattern != 0 || format.fill_style.bg_color != 0 || format.fill_style.fg_color != 0
|
|
146
|
+
|
|
147
|
+
format.has_dxf_fill(true)
|
|
148
|
+
format.dxf_bg_color = format.fill_style.bg_color
|
|
149
|
+
format.dxf_fg_color = format.fill_style.fg_color
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
@xf_formats.each do |format|
|
|
153
|
+
# The following logical statements jointly take care of special cases
|
|
154
|
+
# in relation to cell colours and patterns:
|
|
155
|
+
# 1. For a solid fill (_pattern == 1) Excel reverses the role of
|
|
156
|
+
# foreground and background colours, and
|
|
157
|
+
# 2. If the user specifies a foreground or background colour without
|
|
158
|
+
# a pattern they probably wanted a solid fill, so we fill in the
|
|
159
|
+
# defaults.
|
|
160
|
+
#
|
|
161
|
+
if format.fill_style.pattern == 1 && ne_0?(format.fill_style.bg_color) && ne_0?(format.fill_style.fg_color)
|
|
162
|
+
format.fill_style.fg_color, format.fill_style.bg_color = format.fill_style.bg_color, format.fill_style.fg_color
|
|
163
|
+
elsif format.fill_style.pattern <= 1 && ne_0?(format.fill_style.bg_color) && eq_0?(format.fill_style.fg_color)
|
|
164
|
+
format.fill_style.fg_color = format.fill_style.bg_color
|
|
165
|
+
format.fill_style.bg_color = 0
|
|
166
|
+
format.fill_style.pattern = 1
|
|
167
|
+
elsif format.fill_style.pattern <= 1 && eq_0?(format.fill_style.bg_color) && ne_0?(format.fill_style.fg_color)
|
|
168
|
+
format.fill_style.bg_color = 0
|
|
169
|
+
format.fill_style.pattern = 1
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
key = format.get_fill_key
|
|
173
|
+
|
|
174
|
+
if fills[key]
|
|
175
|
+
# Fill has already been used.
|
|
176
|
+
format.fill_index = fills[key]
|
|
177
|
+
format.has_fill(false)
|
|
178
|
+
else
|
|
179
|
+
# This is a new fill.
|
|
180
|
+
fills[key] = index
|
|
181
|
+
format.fill_index = index
|
|
182
|
+
format.has_fill(true)
|
|
183
|
+
index += 1
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
@fill_count = index
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def eq_0?(val)
|
|
191
|
+
!ptrue?(val)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def ne_0?(val)
|
|
195
|
+
!eq_0?(val)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
class Workbook
|
|
6
|
+
#
|
|
7
|
+
# Workbook initialization helpers extracted from Workbook to keep the main
|
|
8
|
+
# class focused on public API and orchestration.
|
|
9
|
+
#
|
|
10
|
+
module Initialization
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def setup_core_state(file, options, default_formats)
|
|
14
|
+
@options = options.dup # for test
|
|
15
|
+
@default_formats = default_formats.dup # for test
|
|
16
|
+
@writer = Package::XMLWriterSimple.new
|
|
17
|
+
|
|
18
|
+
@file = file
|
|
19
|
+
@tempdir = options[:tempdir] ||
|
|
20
|
+
File.join(
|
|
21
|
+
Dir.tmpdir,
|
|
22
|
+
Digest::MD5.hexdigest("#{Time.now.to_f}-#{Process.pid}")
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
@date_1904 = options[:date_1904] || false
|
|
26
|
+
@activesheet = 0
|
|
27
|
+
@firstsheet = 0
|
|
28
|
+
@selected = 0
|
|
29
|
+
@fileclosed = false
|
|
30
|
+
@optimization = options[:optimization] || 0
|
|
31
|
+
@excel2003_style = options[:excel2003_style] || false
|
|
32
|
+
@read_only = 0
|
|
33
|
+
|
|
34
|
+
@strings_to_urls =
|
|
35
|
+
options[:strings_to_urls].nil? || options[:strings_to_urls] ? true : false
|
|
36
|
+
|
|
37
|
+
@max_url_length = if options[:max_url_length]
|
|
38
|
+
[options[:max_url_length].to_i, 255].max
|
|
39
|
+
else
|
|
40
|
+
MAX_URL_LENGTH
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def setup_workbook_state(_options)
|
|
45
|
+
@worksheets = Sheets.new
|
|
46
|
+
@charts = []
|
|
47
|
+
@drawings = []
|
|
48
|
+
@defined_names = []
|
|
49
|
+
@named_ranges = []
|
|
50
|
+
@custom_colors = []
|
|
51
|
+
@doc_properties = {}
|
|
52
|
+
@custom_properties = []
|
|
53
|
+
@image_types = {}
|
|
54
|
+
@images = []
|
|
55
|
+
@has_comments = false
|
|
56
|
+
@has_metadata = false
|
|
57
|
+
@has_embedded_images = false
|
|
58
|
+
@has_embedded_descriptions = false
|
|
59
|
+
|
|
60
|
+
@x_window = 240
|
|
61
|
+
@y_window = 15
|
|
62
|
+
@window_width = 16_095
|
|
63
|
+
@window_height = 9_660
|
|
64
|
+
@tab_ratio = 600
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def setup_format_state(_default_formats)
|
|
68
|
+
@formats = Formats.new
|
|
69
|
+
@xf_formats = []
|
|
70
|
+
@dxf_formats = []
|
|
71
|
+
@num_formats = []
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def setup_shared_strings
|
|
75
|
+
@shared_strings = Package::SharedStrings.new
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def setup_embedded_assets
|
|
79
|
+
@embedded_image_indexes = {}
|
|
80
|
+
@embedded_images = []
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def setup_calculation_state
|
|
84
|
+
@calc_id = 124519
|
|
85
|
+
@calc_mode = 'auto'
|
|
86
|
+
@calc_on_load = true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def setup_default_formats
|
|
90
|
+
if @excel2003_style
|
|
91
|
+
add_format(
|
|
92
|
+
@default_formats.merge(
|
|
93
|
+
xf_index: 0,
|
|
94
|
+
font_family: 0,
|
|
95
|
+
font: 'Arial',
|
|
96
|
+
size: 10,
|
|
97
|
+
theme: -1
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
else
|
|
101
|
+
add_format(@default_formats.merge(xf_index: 0))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Add a default URL format.
|
|
105
|
+
@default_url_format = add_format(hyperlink: 1)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
#
|
|
109
|
+
# Workbook の生成時のオプションハッシュを解析する
|
|
110
|
+
#
|
|
111
|
+
def process_workbook_options(*params)
|
|
112
|
+
case params.size
|
|
113
|
+
when 0
|
|
114
|
+
[{}, {}]
|
|
115
|
+
when 1 # one hash
|
|
116
|
+
options_keys = %i[tempdir date_1904 optimization excel2003_style strings_to_urls max_url_length]
|
|
117
|
+
|
|
118
|
+
hash = params.first
|
|
119
|
+
options = hash.select { |k, _v| options_keys.include?(k) }
|
|
120
|
+
|
|
121
|
+
default_format_properties =
|
|
122
|
+
hash[:default_format_properties] ||
|
|
123
|
+
hash.reject { |k, _v| options_keys.include?(k) }
|
|
124
|
+
|
|
125
|
+
[options, default_format_properties.dup]
|
|
126
|
+
when 2 # array which includes options and default_format_properties
|
|
127
|
+
options, default_format_properties = params
|
|
128
|
+
default_format_properties ||= {}
|
|
129
|
+
|
|
130
|
+
[options.dup, default_format_properties.dup]
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def filename
|
|
135
|
+
setup_filename unless @filename
|
|
136
|
+
@filename
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def fileobj
|
|
140
|
+
setup_filename unless @fileobj
|
|
141
|
+
@fileobj
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def setup_filename # :nodoc:
|
|
145
|
+
if @file.respond_to?(:to_str) && @file != ''
|
|
146
|
+
@filename = @file
|
|
147
|
+
@fileobj = nil
|
|
148
|
+
elsif @file.respond_to?(:write)
|
|
149
|
+
@filename = File.join(tempdir, Digest::MD5.hexdigest(Time.now.to_s) + '.xlsx.tmp')
|
|
150
|
+
@fileobj = @file
|
|
151
|
+
else
|
|
152
|
+
raise "'#{@file}' must be valid filename String of IO object."
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
attr_reader :tempdir
|
|
157
|
+
|
|
158
|
+
#
|
|
159
|
+
# Sets the colour palette to the Excel defaults.
|
|
160
|
+
#
|
|
161
|
+
def set_color_palette # :nodoc:
|
|
162
|
+
@palette = [
|
|
163
|
+
[0x00, 0x00, 0x00, 0x00], # 8
|
|
164
|
+
[0xff, 0xff, 0xff, 0x00], # 9
|
|
165
|
+
[0xff, 0x00, 0x00, 0x00], # 10
|
|
166
|
+
[0x00, 0xff, 0x00, 0x00], # 11
|
|
167
|
+
[0x00, 0x00, 0xff, 0x00], # 12
|
|
168
|
+
[0xff, 0xff, 0x00, 0x00], # 13
|
|
169
|
+
[0xff, 0x00, 0xff, 0x00], # 14
|
|
170
|
+
[0x00, 0xff, 0xff, 0x00], # 15
|
|
171
|
+
[0x80, 0x00, 0x00, 0x00], # 16
|
|
172
|
+
[0x00, 0x80, 0x00, 0x00], # 17
|
|
173
|
+
[0x00, 0x00, 0x80, 0x00], # 18
|
|
174
|
+
[0x80, 0x80, 0x00, 0x00], # 19
|
|
175
|
+
[0x80, 0x00, 0x80, 0x00], # 20
|
|
176
|
+
[0x00, 0x80, 0x80, 0x00], # 21
|
|
177
|
+
[0xc0, 0xc0, 0xc0, 0x00], # 22
|
|
178
|
+
[0x80, 0x80, 0x80, 0x00], # 23
|
|
179
|
+
[0x99, 0x99, 0xff, 0x00], # 24
|
|
180
|
+
[0x99, 0x33, 0x66, 0x00], # 25
|
|
181
|
+
[0xff, 0xff, 0xcc, 0x00], # 26
|
|
182
|
+
[0xcc, 0xff, 0xff, 0x00], # 27
|
|
183
|
+
[0x66, 0x00, 0x66, 0x00], # 28
|
|
184
|
+
[0xff, 0x80, 0x80, 0x00], # 29
|
|
185
|
+
[0x00, 0x66, 0xcc, 0x00], # 30
|
|
186
|
+
[0xcc, 0xcc, 0xff, 0x00], # 31
|
|
187
|
+
[0x00, 0x00, 0x80, 0x00], # 32
|
|
188
|
+
[0xff, 0x00, 0xff, 0x00], # 33
|
|
189
|
+
[0xff, 0xff, 0x00, 0x00], # 34
|
|
190
|
+
[0x00, 0xff, 0xff, 0x00], # 35
|
|
191
|
+
[0x80, 0x00, 0x80, 0x00], # 36
|
|
192
|
+
[0x80, 0x00, 0x00, 0x00], # 37
|
|
193
|
+
[0x00, 0x80, 0x80, 0x00], # 38
|
|
194
|
+
[0x00, 0x00, 0xff, 0x00], # 39
|
|
195
|
+
[0x00, 0xcc, 0xff, 0x00], # 40
|
|
196
|
+
[0xcc, 0xff, 0xff, 0x00], # 41
|
|
197
|
+
[0xcc, 0xff, 0xcc, 0x00], # 42
|
|
198
|
+
[0xff, 0xff, 0x99, 0x00], # 43
|
|
199
|
+
[0x99, 0xcc, 0xff, 0x00], # 44
|
|
200
|
+
[0xff, 0x99, 0xcc, 0x00], # 45
|
|
201
|
+
[0xcc, 0x99, 0xff, 0x00], # 46
|
|
202
|
+
[0xff, 0xcc, 0x99, 0x00], # 47
|
|
203
|
+
[0x33, 0x66, 0xff, 0x00], # 48
|
|
204
|
+
[0x33, 0xcc, 0xcc, 0x00], # 49
|
|
205
|
+
[0x99, 0xcc, 0x00, 0x00], # 50
|
|
206
|
+
[0xff, 0xcc, 0x00, 0x00], # 51
|
|
207
|
+
[0xff, 0x99, 0x00, 0x00], # 52
|
|
208
|
+
[0xff, 0x66, 0x00, 0x00], # 53
|
|
209
|
+
[0x66, 0x66, 0x99, 0x00], # 54
|
|
210
|
+
[0x96, 0x96, 0x96, 0x00], # 55
|
|
211
|
+
[0x00, 0x33, 0x66, 0x00], # 56
|
|
212
|
+
[0x33, 0x99, 0x66, 0x00], # 57
|
|
213
|
+
[0x00, 0x33, 0x00, 0x00], # 58
|
|
214
|
+
[0x33, 0x33, 0x00, 0x00], # 59
|
|
215
|
+
[0x99, 0x33, 0x00, 0x00], # 60
|
|
216
|
+
[0x99, 0x33, 0x66, 0x00], # 61
|
|
217
|
+
[0x33, 0x33, 0x99, 0x00], # 62
|
|
218
|
+
[0x33, 0x33, 0x33, 0x00] # 63
|
|
219
|
+
]
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
class Workbook
|
|
6
|
+
module PackagePreparation
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
#
|
|
10
|
+
# Assemble worksheets into a workbook.
|
|
11
|
+
#
|
|
12
|
+
def store_workbook # :nodoc:
|
|
13
|
+
# Add a default worksheet if non have been added.
|
|
14
|
+
add_worksheet if @worksheets.empty?
|
|
15
|
+
|
|
16
|
+
# Ensure that at least one worksheet has been selected.
|
|
17
|
+
@worksheets.visible_first.select if @activesheet == 0
|
|
18
|
+
|
|
19
|
+
# Set the active sheet.
|
|
20
|
+
@activesheet = @worksheets.visible_first.index if @activesheet == 0
|
|
21
|
+
@worksheets[@activesheet].activate
|
|
22
|
+
|
|
23
|
+
# Convert the SST strings data structure.
|
|
24
|
+
prepare_sst_string_data
|
|
25
|
+
|
|
26
|
+
# Prepare the worksheet VML elements such as comments and buttons.
|
|
27
|
+
prepare_vml_objects
|
|
28
|
+
# Set the defined names for the worksheets such as Print Titles.
|
|
29
|
+
prepare_defined_names
|
|
30
|
+
# Prepare the drawings, charts and images.
|
|
31
|
+
prepare_drawings
|
|
32
|
+
# Add cached data to charts.
|
|
33
|
+
add_chart_data
|
|
34
|
+
|
|
35
|
+
# Prepare the worksheet tables.
|
|
36
|
+
prepare_tables
|
|
37
|
+
|
|
38
|
+
# Prepare the metadata file links.
|
|
39
|
+
prepare_metadata
|
|
40
|
+
|
|
41
|
+
# Package the workbook.
|
|
42
|
+
packager = Package::Packager.new(self)
|
|
43
|
+
packager.set_package_dir(tempdir)
|
|
44
|
+
packager.create_package
|
|
45
|
+
|
|
46
|
+
# Free up the Packager object.
|
|
47
|
+
packager = nil
|
|
48
|
+
|
|
49
|
+
# Store the xlsx component files with the temp dir name removed.
|
|
50
|
+
ZipFileUtils.zip(tempdir.to_s, filename)
|
|
51
|
+
|
|
52
|
+
IO.copy_stream(filename, fileobj) if fileobj
|
|
53
|
+
delete_tempdir(tempdir)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
#
|
|
57
|
+
# Iterate through the worksheets and store any defined names in addition to
|
|
58
|
+
# any user defined names. Stores the defined names for the Workbook.xml and
|
|
59
|
+
# the named ranges for App.xml.
|
|
60
|
+
#
|
|
61
|
+
def prepare_defined_names # :nodoc:
|
|
62
|
+
@worksheets.each do |sheet|
|
|
63
|
+
# Check for Print Area settings.
|
|
64
|
+
if sheet.autofilter_area
|
|
65
|
+
@defined_names << [
|
|
66
|
+
'_xlnm._FilterDatabase',
|
|
67
|
+
sheet.index,
|
|
68
|
+
sheet.autofilter_area,
|
|
69
|
+
1
|
|
70
|
+
]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Check for Print Area settings.
|
|
74
|
+
unless sheet.print_area.empty?
|
|
75
|
+
@defined_names << [
|
|
76
|
+
'_xlnm.Print_Area',
|
|
77
|
+
sheet.index,
|
|
78
|
+
sheet.print_area
|
|
79
|
+
]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Check for repeat rows/cols. aka, Print Titles.
|
|
83
|
+
next unless !sheet.print_repeat_cols.empty? || !sheet.print_repeat_rows.empty?
|
|
84
|
+
|
|
85
|
+
range = if !sheet.print_repeat_cols.empty? && !sheet.print_repeat_rows.empty?
|
|
86
|
+
sheet.print_repeat_cols + ',' + sheet.print_repeat_rows
|
|
87
|
+
else
|
|
88
|
+
sheet.print_repeat_cols + sheet.print_repeat_rows
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Store the defined names.
|
|
92
|
+
@defined_names << ['_xlnm.Print_Titles', sheet.index, range]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@defined_names = sort_defined_names(@defined_names)
|
|
96
|
+
@named_ranges = extract_named_ranges(@defined_names)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
#
|
|
100
|
+
# Iterate through the worksheets and set up the VML objects.
|
|
101
|
+
#
|
|
102
|
+
def prepare_vml_objects # :nodoc:
|
|
103
|
+
comment_id = 0
|
|
104
|
+
vml_drawing_id = 0
|
|
105
|
+
vml_data_id = 1
|
|
106
|
+
vml_header_id = 0
|
|
107
|
+
vml_shape_id = 1024
|
|
108
|
+
has_button = false
|
|
109
|
+
|
|
110
|
+
@worksheets.each do |sheet|
|
|
111
|
+
next if !sheet.has_vml? && !sheet.has_header_vml?
|
|
112
|
+
|
|
113
|
+
if sheet.has_vml?
|
|
114
|
+
if sheet.has_comments?
|
|
115
|
+
comment_id += 1
|
|
116
|
+
@has_comments = true
|
|
117
|
+
end
|
|
118
|
+
vml_drawing_id += 1
|
|
119
|
+
|
|
120
|
+
sheet.prepare_vml_objects(
|
|
121
|
+
vml_data_id, vml_shape_id,
|
|
122
|
+
vml_drawing_id, comment_id
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Each VML file should start with a shape id incremented by 1024.
|
|
126
|
+
vml_data_id += 1 * (1 + sheet.num_comments_block)
|
|
127
|
+
vml_shape_id += 1024 * (1 + sheet.num_comments_block)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
if sheet.has_header_vml?
|
|
131
|
+
vml_header_id += 1
|
|
132
|
+
vml_drawing_id += 1
|
|
133
|
+
sheet.prepare_header_vml_objects(vml_header_id, vml_drawing_id)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Set the sheet vba_codename if it has a button and the workbook
|
|
137
|
+
# has a vbaProject binary.
|
|
138
|
+
unless sheet.buttons_data.empty?
|
|
139
|
+
has_button = true
|
|
140
|
+
sheet.set_vba_name if @vba_project && !sheet.vba_codename
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Set the workbook vba_codename if one of the sheets has a button and
|
|
145
|
+
# the workbook has a vbaProject binary.
|
|
146
|
+
set_vba_name if has_button && @vba_project && !@vba_codename
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
#
|
|
150
|
+
# Set the table ids for the worksheet tables.
|
|
151
|
+
#
|
|
152
|
+
def prepare_tables
|
|
153
|
+
table_id = 0
|
|
154
|
+
seen = {}
|
|
155
|
+
|
|
156
|
+
sheets.each do |sheet|
|
|
157
|
+
table_id += sheet.prepare_tables(table_id + 1, seen)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
#
|
|
162
|
+
# Set the metadata rel link.
|
|
163
|
+
#
|
|
164
|
+
def prepare_metadata
|
|
165
|
+
@worksheets.each do |sheet|
|
|
166
|
+
next unless sheet.has_dynamic_functions? || sheet.has_embedded_images?
|
|
167
|
+
|
|
168
|
+
@has_metadata = true
|
|
169
|
+
@has_dynamic_functions ||= sheet.has_dynamic_functions?
|
|
170
|
+
@has_embedded_images ||= sheet.has_embedded_images?
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
#
|
|
175
|
+
# Iterate through the worksheets and set up any chart or image drawings.
|
|
176
|
+
#
|
|
177
|
+
def prepare_drawings # :nodoc:
|
|
178
|
+
# Store the image types for any embedded images.
|
|
179
|
+
@embedded_images.each do |image|
|
|
180
|
+
store_image_types(image.type)
|
|
181
|
+
|
|
182
|
+
@has_embedded_descriptions = true if ptrue?(image.description)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
prepare_drawings_of_all_sheets
|
|
186
|
+
|
|
187
|
+
# Sort the workbook charts references into the order that the were
|
|
188
|
+
# written from the worksheets above.
|
|
189
|
+
@charts = @charts.reject { |chart| chart.id == -1 }
|
|
190
|
+
.sort_by(&:id)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def prepare_drawings_of_all_sheets
|
|
194
|
+
drawing_id = 0
|
|
195
|
+
chart_ref_id = 0
|
|
196
|
+
image_ids = {}
|
|
197
|
+
header_image_ids = {}
|
|
198
|
+
background_ids = {}
|
|
199
|
+
|
|
200
|
+
# The image IDs start from after the embedded images.
|
|
201
|
+
image_ref_id = @embedded_images.size
|
|
202
|
+
|
|
203
|
+
@worksheets.each do |sheet|
|
|
204
|
+
drawing_id, chart_ref_id, image_ref_id =
|
|
205
|
+
sheet.prepare_drawings(
|
|
206
|
+
drawing_id, chart_ref_id, image_ref_id, image_ids,
|
|
207
|
+
header_image_ids, background_ids
|
|
208
|
+
)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
#
|
|
213
|
+
# prepare_sst_string_data
|
|
214
|
+
#
|
|
215
|
+
def prepare_sst_string_data; end
|
|
216
|
+
|
|
217
|
+
def delete_tempdir(path)
|
|
218
|
+
if FileTest.file?(path)
|
|
219
|
+
File.delete(path)
|
|
220
|
+
elsif FileTest.directory?(path)
|
|
221
|
+
Dir.foreach(path) do |file|
|
|
222
|
+
next if file =~ /^\.\.?$/ # '.' or '..'
|
|
223
|
+
|
|
224
|
+
delete_tempdir(path.sub(%r{/+$}, "") + '/' + file)
|
|
225
|
+
end
|
|
226
|
+
Dir.rmdir(path)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|