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
data/lib/write_xlsx/workbook.rb
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
###############################################################################
|
|
5
|
+
#
|
|
6
|
+
# Workbook
|
|
7
|
+
#
|
|
8
|
+
# The Workbook class acts as a facade coordinating workbook state,
|
|
9
|
+
# package preparation and XML generation.
|
|
10
|
+
#
|
|
11
|
+
# Responsibilities are delegated to specialized modules:
|
|
12
|
+
#
|
|
13
|
+
# Initialization - workbook setup and default state
|
|
14
|
+
# WorkbookWriter - workbook XML generation
|
|
15
|
+
# PackagePreparation - package assembly and workbook preparation
|
|
16
|
+
# FormatPreparation - format, font, border and fill preparation
|
|
17
|
+
# ChartData - chart cache data extraction and defined name helpers
|
|
18
|
+
#
|
|
19
|
+
###############################################################################
|
|
20
|
+
|
|
21
|
+
require 'write_xlsx/workbook/initialization'
|
|
22
|
+
require 'write_xlsx/workbook/workbook_writer'
|
|
23
|
+
require 'write_xlsx/workbook/package_preparation'
|
|
24
|
+
require 'write_xlsx/workbook/format_preparation'
|
|
25
|
+
require 'write_xlsx/workbook/chart_data'
|
|
4
26
|
require 'write_xlsx/chart'
|
|
5
27
|
require 'write_xlsx/chartsheet'
|
|
6
28
|
require 'write_xlsx/format'
|
|
@@ -8,7 +30,9 @@ require 'write_xlsx/formats'
|
|
|
8
30
|
require 'write_xlsx/image_property'
|
|
9
31
|
require 'write_xlsx/shape'
|
|
10
32
|
require 'write_xlsx/sheets'
|
|
11
|
-
require 'write_xlsx/utility'
|
|
33
|
+
require 'write_xlsx/utility/common'
|
|
34
|
+
require 'write_xlsx/utility/cell_reference'
|
|
35
|
+
require 'write_xlsx/utility/xml_primitives'
|
|
12
36
|
require 'write_xlsx/worksheet'
|
|
13
37
|
require 'write_xlsx/zip_file_utils'
|
|
14
38
|
require 'write_xlsx/package/xml_writer_simple'
|
|
@@ -22,7 +46,13 @@ module Writexlsx
|
|
|
22
46
|
MAX_URL_LENGTH = 2_079
|
|
23
47
|
|
|
24
48
|
class Workbook
|
|
25
|
-
include Writexlsx::Utility
|
|
49
|
+
include Writexlsx::Utility::Common
|
|
50
|
+
include Writexlsx::Utility::CellReference
|
|
51
|
+
include Writexlsx::Utility::XmlPrimitives
|
|
52
|
+
include Initialization
|
|
53
|
+
include PackagePreparation
|
|
54
|
+
include FormatPreparation
|
|
55
|
+
include ChartData
|
|
26
56
|
|
|
27
57
|
attr_writer :firstsheet # :nodoc:
|
|
28
58
|
attr_reader :palette # :nodoc:
|
|
@@ -44,92 +74,25 @@ module Writexlsx
|
|
|
44
74
|
attr_writer :has_embedded_descriptions # :nodoc:
|
|
45
75
|
attr_accessor :charts # :nodoc:
|
|
46
76
|
|
|
77
|
+
###############################################################################
|
|
78
|
+
#
|
|
79
|
+
# Lifecycle
|
|
80
|
+
#
|
|
81
|
+
###############################################################################
|
|
82
|
+
|
|
47
83
|
def initialize(file, *option_params)
|
|
48
84
|
options, default_formats = process_workbook_options(*option_params)
|
|
49
|
-
@options = options.dup # for test
|
|
50
|
-
@default_formats = default_formats.dup # for test
|
|
51
|
-
@writer = Package::XMLWriterSimple.new
|
|
52
|
-
|
|
53
|
-
@file = file
|
|
54
|
-
@tempdir = options[:tempdir] ||
|
|
55
|
-
File.join(
|
|
56
|
-
Dir.tmpdir,
|
|
57
|
-
Digest::MD5.hexdigest("#{Time.now.to_f}-#{Process.pid}")
|
|
58
|
-
)
|
|
59
|
-
@date_1904 = options[:date_1904] || false
|
|
60
|
-
@activesheet = 0
|
|
61
|
-
@firstsheet = 0
|
|
62
|
-
@selected = 0
|
|
63
|
-
@fileclosed = false
|
|
64
|
-
@worksheets = Sheets.new
|
|
65
|
-
@charts = []
|
|
66
|
-
@drawings = []
|
|
67
|
-
@formats = Formats.new
|
|
68
|
-
@xf_formats = []
|
|
69
|
-
@dxf_formats = []
|
|
70
|
-
@num_formats = []
|
|
71
|
-
@defined_names = []
|
|
72
|
-
@named_ranges = []
|
|
73
|
-
@custom_colors = []
|
|
74
|
-
@doc_properties = {}
|
|
75
|
-
@custom_properties = []
|
|
76
|
-
@optimization = options[:optimization] || 0
|
|
77
|
-
@x_window = 240
|
|
78
|
-
@y_window = 15
|
|
79
|
-
@window_width = 16_095
|
|
80
|
-
@window_height = 9_660
|
|
81
|
-
@tab_ratio = 600
|
|
82
|
-
@excel2003_style = options[:excel2003_style] || false
|
|
83
|
-
@image_types = {}
|
|
84
|
-
@images = []
|
|
85
|
-
@strings_to_urls = options[:strings_to_urls].nil? || options[:strings_to_urls] ? true : false
|
|
86
|
-
|
|
87
|
-
@max_url_length = MAX_URL_LENGTH
|
|
88
|
-
@has_comments = false
|
|
89
|
-
@read_only = 0
|
|
90
|
-
@has_metadata = false
|
|
91
|
-
@has_embedded_images = false
|
|
92
|
-
@has_embedded_descriptions = false
|
|
93
|
-
|
|
94
|
-
if options[:max_url_length]
|
|
95
|
-
@max_url_length = options[:max_url_length].to_i
|
|
96
|
-
|
|
97
|
-
@max_url_length = MAX_URL_LENGTH if @max_url_length < 255
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# Structures for the shared strings data.
|
|
101
|
-
@shared_strings = Package::SharedStrings.new
|
|
102
|
-
|
|
103
|
-
# Structures for embedded images.
|
|
104
|
-
@embedded_image_indexes = {}
|
|
105
|
-
@embedded_images = []
|
|
106
|
-
|
|
107
|
-
# Formula calculation default settings.
|
|
108
|
-
@calc_id = 124519
|
|
109
|
-
@calc_mode = 'auto'
|
|
110
|
-
@calc_on_load = true
|
|
111
|
-
|
|
112
|
-
if @excel2003_style
|
|
113
|
-
add_format(default_formats.merge(
|
|
114
|
-
xf_index: 0,
|
|
115
|
-
font_family: 0,
|
|
116
|
-
font: 'Arial',
|
|
117
|
-
size: 10,
|
|
118
|
-
theme: -1
|
|
119
|
-
))
|
|
120
|
-
else
|
|
121
|
-
add_format(default_formats.merge(xf_index: 0))
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Add a default URL format.
|
|
125
|
-
@default_url_format = add_format(hyperlink: 1)
|
|
126
85
|
|
|
86
|
+
setup_core_state(file, options, default_formats)
|
|
87
|
+
setup_workbook_state(options)
|
|
88
|
+
setup_format_state(default_formats)
|
|
89
|
+
setup_shared_strings
|
|
90
|
+
setup_embedded_assets
|
|
91
|
+
setup_calculation_state
|
|
92
|
+
setup_default_formats
|
|
127
93
|
set_color_palette
|
|
128
94
|
end
|
|
129
95
|
|
|
130
|
-
#
|
|
131
|
-
# The close method is used to close an Excel file.
|
|
132
|
-
#
|
|
133
96
|
def close
|
|
134
97
|
# In case close() is called twice.
|
|
135
98
|
return if @fileclosed
|
|
@@ -138,101 +101,11 @@ module Writexlsx
|
|
|
138
101
|
store_workbook
|
|
139
102
|
end
|
|
140
103
|
|
|
104
|
+
###############################################################################
|
|
141
105
|
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
# :call-seq:
|
|
145
|
-
# sheets -> array of all Wordsheet object
|
|
146
|
-
# sheets(1, 3, 4) -> array of spcified Worksheet object.
|
|
147
|
-
#
|
|
148
|
-
def sheets(*args)
|
|
149
|
-
if args.empty?
|
|
150
|
-
@worksheets
|
|
151
|
-
else
|
|
152
|
-
args.collect { |i| @worksheets[i] }
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
#
|
|
157
|
-
# Return a worksheet object in the workbook using the sheetname.
|
|
106
|
+
# Workbook object creation API
|
|
158
107
|
#
|
|
159
|
-
|
|
160
|
-
sheets.select { |s| s.name == sheetname }.first
|
|
161
|
-
end
|
|
162
|
-
alias get_worksheet_by_name worksheet_by_name
|
|
163
|
-
|
|
164
|
-
#
|
|
165
|
-
# Set the date system: false = 1900 (the default), true = 1904
|
|
166
|
-
#
|
|
167
|
-
def set_1904(mode = true)
|
|
168
|
-
raise "set_1904() must be called before add_worksheet()" unless sheets.empty?
|
|
169
|
-
|
|
170
|
-
@date_1904 = ptrue?(mode)
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
#
|
|
174
|
-
# return date system. false = 1900, true = 1904
|
|
175
|
-
#
|
|
176
|
-
def get_1904
|
|
177
|
-
@date_1904
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def set_tempdir(dir)
|
|
181
|
-
@tempdir = dir.dup
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
#
|
|
185
|
-
# user must not use. it is internal method.
|
|
186
|
-
#
|
|
187
|
-
def set_xml_writer(filename) # :nodoc:
|
|
188
|
-
@writer.set_xml_writer(filename)
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
#
|
|
192
|
-
# user must not use. it is internal method.
|
|
193
|
-
#
|
|
194
|
-
def xml_str # :nodoc:
|
|
195
|
-
@writer.string
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
#
|
|
199
|
-
# user must not use. it is internal method.
|
|
200
|
-
#
|
|
201
|
-
def assemble_xml_file # :nodoc:
|
|
202
|
-
return unless @writer
|
|
203
|
-
|
|
204
|
-
# Prepare format object for passing to Style.rb.
|
|
205
|
-
prepare_format_properties
|
|
206
|
-
|
|
207
|
-
write_xml_declaration do
|
|
208
|
-
# Write the root workbook element.
|
|
209
|
-
write_workbook do
|
|
210
|
-
# Write the XLSX file version.
|
|
211
|
-
write_file_version
|
|
212
|
-
|
|
213
|
-
# Write the fileSharing element.
|
|
214
|
-
write_file_sharing
|
|
215
|
-
|
|
216
|
-
# Write the workbook properties.
|
|
217
|
-
write_workbook_pr
|
|
218
|
-
|
|
219
|
-
# Write the workbook view properties.
|
|
220
|
-
write_book_views
|
|
221
|
-
|
|
222
|
-
# Write the worksheet names and ids.
|
|
223
|
-
@worksheets.write_sheets(@writer)
|
|
224
|
-
|
|
225
|
-
# Write the workbook defined names.
|
|
226
|
-
write_defined_names
|
|
227
|
-
|
|
228
|
-
# Write the workbook calculation properties.
|
|
229
|
-
write_calc_pr
|
|
230
|
-
|
|
231
|
-
# Write the workbook extension storage.
|
|
232
|
-
# write_ext_lst
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
end
|
|
108
|
+
###############################################################################
|
|
236
109
|
|
|
237
110
|
#
|
|
238
111
|
# At least one worksheet should be added to a new workbook. A worksheet is used to write data into cells:
|
|
@@ -312,6 +185,32 @@ module Writexlsx
|
|
|
312
185
|
shape
|
|
313
186
|
end
|
|
314
187
|
|
|
188
|
+
###############################################################################
|
|
189
|
+
#
|
|
190
|
+
# Workbook configuration API
|
|
191
|
+
#
|
|
192
|
+
###############################################################################
|
|
193
|
+
|
|
194
|
+
#
|
|
195
|
+
# Set the date system: false = 1900 (the default), true = 1904
|
|
196
|
+
#
|
|
197
|
+
def set_1904(mode = true)
|
|
198
|
+
raise "set_1904() must be called before add_worksheet()" unless sheets.empty?
|
|
199
|
+
|
|
200
|
+
@date_1904 = ptrue?(mode)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
#
|
|
204
|
+
# return date system. false = 1900, true = 1904
|
|
205
|
+
#
|
|
206
|
+
def get_1904
|
|
207
|
+
@date_1904
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def set_tempdir(dir)
|
|
211
|
+
@tempdir = dir.dup
|
|
212
|
+
end
|
|
213
|
+
|
|
315
214
|
#
|
|
316
215
|
# Create a defined name in Excel. We handle global/workbook level names and
|
|
317
216
|
# local/worksheet names.
|
|
@@ -497,14 +396,6 @@ module Writexlsx
|
|
|
497
396
|
@calc_id = calc_id if calc_id
|
|
498
397
|
end
|
|
499
398
|
|
|
500
|
-
#
|
|
501
|
-
# Get the default url format used when a user defined format isn't specified
|
|
502
|
-
# with write_url(). The format is the hyperlink style defined by Excel for the
|
|
503
|
-
# default theme.
|
|
504
|
-
#
|
|
505
|
-
attr_reader :default_url_format
|
|
506
|
-
alias get_default_url_format default_url_format
|
|
507
|
-
|
|
508
399
|
#
|
|
509
400
|
# Change the RGB components of the elements in the colour palette.
|
|
510
401
|
#
|
|
@@ -537,10 +428,67 @@ module Writexlsx
|
|
|
537
428
|
index + 8
|
|
538
429
|
end
|
|
539
430
|
|
|
431
|
+
###############################################################################
|
|
432
|
+
#
|
|
433
|
+
# Workbook accessors and lookup
|
|
434
|
+
#
|
|
435
|
+
###############################################################################
|
|
436
|
+
|
|
437
|
+
#
|
|
438
|
+
# get array of Worksheet objects
|
|
439
|
+
#
|
|
440
|
+
# :call-seq:
|
|
441
|
+
# sheets -> array of all Wordsheet object
|
|
442
|
+
# sheets(1, 3, 4) -> array of spcified Worksheet object.
|
|
443
|
+
#
|
|
444
|
+
def sheets(*args)
|
|
445
|
+
if args.empty?
|
|
446
|
+
@worksheets
|
|
447
|
+
else
|
|
448
|
+
args.collect { |i| @worksheets[i] }
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
#
|
|
453
|
+
# Return a worksheet object in the workbook using the sheetname.
|
|
454
|
+
#
|
|
455
|
+
def worksheet_by_name(sheetname = nil)
|
|
456
|
+
sheets.select { |s| s.name == sheetname }.first
|
|
457
|
+
end
|
|
458
|
+
alias get_worksheet_by_name worksheet_by_name
|
|
459
|
+
|
|
460
|
+
#
|
|
461
|
+
# user must not use. it is internal method.
|
|
462
|
+
#
|
|
463
|
+
def set_xml_writer(filename) # :nodoc:
|
|
464
|
+
@writer.set_xml_writer(filename)
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
#
|
|
468
|
+
# user must not use. it is internal method.
|
|
469
|
+
#
|
|
470
|
+
def xml_str # :nodoc:
|
|
471
|
+
@writer.string
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
#
|
|
475
|
+
# Get the default url format used when a user defined format isn't specified
|
|
476
|
+
# with write_url(). The format is the hyperlink style defined by Excel for the
|
|
477
|
+
# default theme.
|
|
478
|
+
#
|
|
479
|
+
attr_reader :default_url_format
|
|
480
|
+
alias get_default_url_format default_url_format
|
|
481
|
+
|
|
540
482
|
attr_writer :activesheet
|
|
541
483
|
|
|
542
484
|
attr_reader :writer
|
|
543
485
|
|
|
486
|
+
###############################################################################
|
|
487
|
+
#
|
|
488
|
+
# Workbook state queries
|
|
489
|
+
#
|
|
490
|
+
###############################################################################
|
|
491
|
+
|
|
544
492
|
def date_1904? # :nodoc:
|
|
545
493
|
@date_1904 ||= false
|
|
546
494
|
!!@date_1904
|
|
@@ -555,6 +503,7 @@ module Writexlsx
|
|
|
555
503
|
# return the string index.
|
|
556
504
|
#
|
|
557
505
|
EMPTY_HASH = {}.freeze
|
|
506
|
+
|
|
558
507
|
def shared_string_index(str) # :nodoc:
|
|
559
508
|
@shared_strings.index(str, EMPTY_HASH)
|
|
560
509
|
end
|
|
@@ -642,121 +591,18 @@ module Writexlsx
|
|
|
642
591
|
end
|
|
643
592
|
end
|
|
644
593
|
|
|
594
|
+
###############################################################################
|
|
595
|
+
#
|
|
596
|
+
# private helpers
|
|
597
|
+
#
|
|
598
|
+
###############################################################################
|
|
645
599
|
private
|
|
646
600
|
|
|
601
|
+
###############################################################################
|
|
647
602
|
#
|
|
648
|
-
#
|
|
603
|
+
# Worksheet and chart naming helpers
|
|
649
604
|
#
|
|
650
|
-
|
|
651
|
-
case params.size
|
|
652
|
-
when 0
|
|
653
|
-
[{}, {}]
|
|
654
|
-
when 1 # one hash
|
|
655
|
-
options_keys = %i[tempdir date_1904 optimization excel2003_style strings_to_urls max_url_length]
|
|
656
|
-
|
|
657
|
-
hash = params.first
|
|
658
|
-
options = hash.reject { |k, _v| !options_keys.include?(k) }
|
|
659
|
-
|
|
660
|
-
default_format_properties =
|
|
661
|
-
hash[:default_format_properties] ||
|
|
662
|
-
hash.reject { |k, _v| options_keys.include?(k) }
|
|
663
|
-
|
|
664
|
-
[options, default_format_properties.dup]
|
|
665
|
-
when 2 # array which includes options and default_format_properties
|
|
666
|
-
options, default_format_properties = params
|
|
667
|
-
default_format_properties ||= {}
|
|
668
|
-
|
|
669
|
-
[options.dup, default_format_properties.dup]
|
|
670
|
-
end
|
|
671
|
-
end
|
|
672
|
-
|
|
673
|
-
def filename
|
|
674
|
-
setup_filename unless @filename
|
|
675
|
-
@filename
|
|
676
|
-
end
|
|
677
|
-
|
|
678
|
-
def fileobj
|
|
679
|
-
setup_filename unless @fileobj
|
|
680
|
-
@fileobj
|
|
681
|
-
end
|
|
682
|
-
|
|
683
|
-
def setup_filename # :nodoc:
|
|
684
|
-
if @file.respond_to?(:to_str) && @file != ''
|
|
685
|
-
@filename = @file
|
|
686
|
-
@fileobj = nil
|
|
687
|
-
elsif @file.respond_to?(:write)
|
|
688
|
-
@filename = File.join(tempdir, Digest::MD5.hexdigest(Time.now.to_s) + '.xlsx.tmp')
|
|
689
|
-
@fileobj = @file
|
|
690
|
-
else
|
|
691
|
-
raise "'#{@file}' must be valid filename String of IO object."
|
|
692
|
-
end
|
|
693
|
-
end
|
|
694
|
-
|
|
695
|
-
attr_reader :tempdir
|
|
696
|
-
|
|
697
|
-
#
|
|
698
|
-
# Sets the colour palette to the Excel defaults.
|
|
699
|
-
#
|
|
700
|
-
def set_color_palette # :nodoc:
|
|
701
|
-
@palette = [
|
|
702
|
-
[0x00, 0x00, 0x00, 0x00], # 8
|
|
703
|
-
[0xff, 0xff, 0xff, 0x00], # 9
|
|
704
|
-
[0xff, 0x00, 0x00, 0x00], # 10
|
|
705
|
-
[0x00, 0xff, 0x00, 0x00], # 11
|
|
706
|
-
[0x00, 0x00, 0xff, 0x00], # 12
|
|
707
|
-
[0xff, 0xff, 0x00, 0x00], # 13
|
|
708
|
-
[0xff, 0x00, 0xff, 0x00], # 14
|
|
709
|
-
[0x00, 0xff, 0xff, 0x00], # 15
|
|
710
|
-
[0x80, 0x00, 0x00, 0x00], # 16
|
|
711
|
-
[0x00, 0x80, 0x00, 0x00], # 17
|
|
712
|
-
[0x00, 0x00, 0x80, 0x00], # 18
|
|
713
|
-
[0x80, 0x80, 0x00, 0x00], # 19
|
|
714
|
-
[0x80, 0x00, 0x80, 0x00], # 20
|
|
715
|
-
[0x00, 0x80, 0x80, 0x00], # 21
|
|
716
|
-
[0xc0, 0xc0, 0xc0, 0x00], # 22
|
|
717
|
-
[0x80, 0x80, 0x80, 0x00], # 23
|
|
718
|
-
[0x99, 0x99, 0xff, 0x00], # 24
|
|
719
|
-
[0x99, 0x33, 0x66, 0x00], # 25
|
|
720
|
-
[0xff, 0xff, 0xcc, 0x00], # 26
|
|
721
|
-
[0xcc, 0xff, 0xff, 0x00], # 27
|
|
722
|
-
[0x66, 0x00, 0x66, 0x00], # 28
|
|
723
|
-
[0xff, 0x80, 0x80, 0x00], # 29
|
|
724
|
-
[0x00, 0x66, 0xcc, 0x00], # 30
|
|
725
|
-
[0xcc, 0xcc, 0xff, 0x00], # 31
|
|
726
|
-
[0x00, 0x00, 0x80, 0x00], # 32
|
|
727
|
-
[0xff, 0x00, 0xff, 0x00], # 33
|
|
728
|
-
[0xff, 0xff, 0x00, 0x00], # 34
|
|
729
|
-
[0x00, 0xff, 0xff, 0x00], # 35
|
|
730
|
-
[0x80, 0x00, 0x80, 0x00], # 36
|
|
731
|
-
[0x80, 0x00, 0x00, 0x00], # 37
|
|
732
|
-
[0x00, 0x80, 0x80, 0x00], # 38
|
|
733
|
-
[0x00, 0x00, 0xff, 0x00], # 39
|
|
734
|
-
[0x00, 0xcc, 0xff, 0x00], # 40
|
|
735
|
-
[0xcc, 0xff, 0xff, 0x00], # 41
|
|
736
|
-
[0xcc, 0xff, 0xcc, 0x00], # 42
|
|
737
|
-
[0xff, 0xff, 0x99, 0x00], # 43
|
|
738
|
-
[0x99, 0xcc, 0xff, 0x00], # 44
|
|
739
|
-
[0xff, 0x99, 0xcc, 0x00], # 45
|
|
740
|
-
[0xcc, 0x99, 0xff, 0x00], # 46
|
|
741
|
-
[0xff, 0xcc, 0x99, 0x00], # 47
|
|
742
|
-
[0x33, 0x66, 0xff, 0x00], # 48
|
|
743
|
-
[0x33, 0xcc, 0xcc, 0x00], # 49
|
|
744
|
-
[0x99, 0xcc, 0x00, 0x00], # 50
|
|
745
|
-
[0xff, 0xcc, 0x00, 0x00], # 51
|
|
746
|
-
[0xff, 0x99, 0x00, 0x00], # 52
|
|
747
|
-
[0xff, 0x66, 0x00, 0x00], # 53
|
|
748
|
-
[0x66, 0x66, 0x99, 0x00], # 54
|
|
749
|
-
[0x96, 0x96, 0x96, 0x00], # 55
|
|
750
|
-
[0x00, 0x33, 0x66, 0x00], # 56
|
|
751
|
-
[0x33, 0x99, 0x66, 0x00], # 57
|
|
752
|
-
[0x00, 0x33, 0x00, 0x00], # 58
|
|
753
|
-
[0x33, 0x33, 0x00, 0x00], # 59
|
|
754
|
-
[0x99, 0x33, 0x00, 0x00], # 60
|
|
755
|
-
[0x99, 0x33, 0x66, 0x00], # 61
|
|
756
|
-
[0x33, 0x33, 0x99, 0x00], # 62
|
|
757
|
-
[0x33, 0x33, 0x33, 0x00] # 63
|
|
758
|
-
]
|
|
759
|
-
end
|
|
605
|
+
###############################################################################
|
|
760
606
|
|
|
761
607
|
#
|
|
762
608
|
# Check for valid worksheet names. We check the length, if it contains any
|
|
@@ -770,703 +616,19 @@ module Writexlsx
|
|
|
770
616
|
@worksheets.make_and_check_sheet_chart_name(:chart, name)
|
|
771
617
|
end
|
|
772
618
|
|
|
619
|
+
###############################################################################
|
|
773
620
|
#
|
|
774
|
-
#
|
|
775
|
-
# range such as ( 'Sheet1', 0, 1, 4, 1 ).
|
|
621
|
+
# Internal utility helpers
|
|
776
622
|
#
|
|
777
|
-
|
|
778
|
-
# Split the range formula into sheetname and cells at the last '!'.
|
|
779
|
-
pos = range.rindex('!')
|
|
780
|
-
return nil unless pos
|
|
781
|
-
|
|
782
|
-
if pos > 0
|
|
783
|
-
sheetname = range[0, pos]
|
|
784
|
-
cells = range[(pos + 1)..-1]
|
|
785
|
-
end
|
|
786
|
-
|
|
787
|
-
# Split the cell range into 2 cells or else use single cell for both.
|
|
788
|
-
if cells =~ /:/
|
|
789
|
-
cell_1, cell_2 = cells.split(":")
|
|
790
|
-
else
|
|
791
|
-
cell_1 = cells
|
|
792
|
-
cell_2 = cells
|
|
793
|
-
end
|
|
794
|
-
|
|
795
|
-
# Remove leading/trailing apostrophes and convert escaped quotes to single.
|
|
796
|
-
sheetname.sub!(/^'/, '')
|
|
797
|
-
sheetname.sub!(/'$/, '')
|
|
798
|
-
sheetname.gsub!("''", "'")
|
|
799
|
-
|
|
800
|
-
row_start, col_start = xl_cell_to_rowcol(cell_1)
|
|
801
|
-
row_end, col_end = xl_cell_to_rowcol(cell_2)
|
|
802
|
-
|
|
803
|
-
# Check that we have a 1D range only.
|
|
804
|
-
return nil if row_start != row_end && col_start != col_end
|
|
805
|
-
|
|
806
|
-
[sheetname, row_start, col_start, row_end, col_end]
|
|
807
|
-
end
|
|
808
|
-
|
|
809
|
-
def write_workbook(&block) # :nodoc:
|
|
810
|
-
schema = 'http://schemas.openxmlformats.org'
|
|
811
|
-
attributes = [
|
|
812
|
-
['xmlns',
|
|
813
|
-
schema + '/spreadsheetml/2006/main'],
|
|
814
|
-
['xmlns:r',
|
|
815
|
-
schema + '/officeDocument/2006/relationships']
|
|
816
|
-
]
|
|
817
|
-
@writer.tag_elements('workbook', attributes, &block)
|
|
818
|
-
end
|
|
819
|
-
|
|
820
|
-
def write_file_version # :nodoc:
|
|
821
|
-
attributes = [
|
|
822
|
-
%w[appName xl],
|
|
823
|
-
['lastEdited', 4],
|
|
824
|
-
['lowestEdited', 4],
|
|
825
|
-
['rupBuild', 4505]
|
|
826
|
-
]
|
|
827
|
-
|
|
828
|
-
attributes << [:codeName, '{37E998C4-C9E5-D4B9-71C8-EB1FF731991C}'] if @vba_project
|
|
829
|
-
|
|
830
|
-
@writer.empty_tag('fileVersion', attributes)
|
|
831
|
-
end
|
|
832
|
-
|
|
833
|
-
#
|
|
834
|
-
# Write the <fileSharing> element.
|
|
835
|
-
#
|
|
836
|
-
def write_file_sharing
|
|
837
|
-
return unless ptrue?(@read_only)
|
|
838
|
-
|
|
839
|
-
attributes = []
|
|
840
|
-
attributes << ['readOnlyRecommended', 1]
|
|
841
|
-
@writer.empty_tag('fileSharing', attributes)
|
|
842
|
-
end
|
|
843
|
-
|
|
844
|
-
def write_workbook_pr # :nodoc:
|
|
845
|
-
attributes = []
|
|
846
|
-
attributes << ['codeName', @vba_codename] if ptrue?(@vba_codename)
|
|
847
|
-
attributes << ['date1904', 1] if date_1904?
|
|
848
|
-
attributes << ['defaultThemeVersion', 124226]
|
|
849
|
-
@writer.empty_tag('workbookPr', attributes)
|
|
850
|
-
end
|
|
851
|
-
|
|
852
|
-
def write_book_views # :nodoc:
|
|
853
|
-
@writer.tag_elements('bookViews') { write_workbook_view }
|
|
854
|
-
end
|
|
855
|
-
|
|
856
|
-
def write_workbook_view # :nodoc:
|
|
857
|
-
attributes = [
|
|
858
|
-
['xWindow', @x_window],
|
|
859
|
-
['yWindow', @y_window],
|
|
860
|
-
['windowWidth', @window_width],
|
|
861
|
-
['windowHeight', @window_height]
|
|
862
|
-
]
|
|
863
|
-
attributes << ['tabRatio', @tab_ratio] if @tab_ratio != 600
|
|
864
|
-
attributes << ['firstSheet', @firstsheet + 1] if @firstsheet > 0
|
|
865
|
-
attributes << ['activeTab', @activesheet] if @activesheet > 0
|
|
866
|
-
@writer.empty_tag('workbookView', attributes)
|
|
867
|
-
end
|
|
868
|
-
|
|
869
|
-
def write_calc_pr # :nodoc:
|
|
870
|
-
attributes = [['calcId', @calc_id]]
|
|
871
|
-
|
|
872
|
-
case @calc_mode
|
|
873
|
-
when 'manual'
|
|
874
|
-
attributes << %w[calcMode manual]
|
|
875
|
-
attributes << ['calcOnSave', 0]
|
|
876
|
-
when 'autoNoTable'
|
|
877
|
-
attributes << %w[calcMode autoNoTable]
|
|
878
|
-
end
|
|
879
|
-
|
|
880
|
-
attributes << ['fullCalcOnLoad', 1] if @calc_on_load
|
|
881
|
-
|
|
882
|
-
@writer.empty_tag('calcPr', attributes)
|
|
883
|
-
end
|
|
884
|
-
|
|
885
|
-
def write_ext_lst # :nodoc:
|
|
886
|
-
@writer.tag_elements('extLst') { write_ext }
|
|
887
|
-
end
|
|
888
|
-
|
|
889
|
-
def write_ext # :nodoc:
|
|
890
|
-
attributes = [
|
|
891
|
-
['xmlns:mx', "#{OFFICE_URL}mac/excel/2008/main"],
|
|
892
|
-
['uri', uri]
|
|
893
|
-
]
|
|
894
|
-
@writer.tag_elements('ext', attributes) { write_mx_arch_id }
|
|
895
|
-
end
|
|
896
|
-
|
|
897
|
-
def write_mx_arch_id # :nodoc:
|
|
898
|
-
@writer.empty_tag('mx:ArchID', ['Flags', 2])
|
|
899
|
-
end
|
|
900
|
-
|
|
901
|
-
def write_defined_names # :nodoc:
|
|
902
|
-
return unless ptrue?(@defined_names)
|
|
903
|
-
|
|
904
|
-
@writer.tag_elements('definedNames') do
|
|
905
|
-
@defined_names.each { |defined_name| write_defined_name(defined_name) }
|
|
906
|
-
end
|
|
907
|
-
end
|
|
908
|
-
|
|
909
|
-
def write_defined_name(defined_name) # :nodoc:
|
|
910
|
-
name, id, range, hidden = defined_name
|
|
911
|
-
|
|
912
|
-
attributes = [['name', name]]
|
|
913
|
-
attributes << ['localSheetId', "#{id}"] unless id == -1
|
|
914
|
-
attributes << %w[hidden 1] if hidden
|
|
915
|
-
|
|
916
|
-
@writer.data_element('definedName', range, attributes)
|
|
917
|
-
end
|
|
918
|
-
|
|
919
|
-
def write_io(str) # :nodoc:
|
|
920
|
-
@writer << str
|
|
921
|
-
str
|
|
922
|
-
end
|
|
623
|
+
###############################################################################
|
|
923
624
|
|
|
924
625
|
# for test
|
|
925
626
|
def defined_names # :nodoc:
|
|
926
627
|
@defined_names ||= []
|
|
927
628
|
end
|
|
928
629
|
|
|
929
|
-
#
|
|
930
|
-
# Assemble worksheets into a workbook.
|
|
931
|
-
#
|
|
932
|
-
def store_workbook # :nodoc:
|
|
933
|
-
# Add a default worksheet if non have been added.
|
|
934
|
-
add_worksheet if @worksheets.empty?
|
|
935
|
-
|
|
936
|
-
# Ensure that at least one worksheet has been selected.
|
|
937
|
-
@worksheets.visible_first.select if @activesheet == 0
|
|
938
|
-
|
|
939
|
-
# Set the active sheet.
|
|
940
|
-
@activesheet = @worksheets.visible_first.index if @activesheet == 0
|
|
941
|
-
@worksheets[@activesheet].activate
|
|
942
|
-
|
|
943
|
-
# Convert the SST strings data structure.
|
|
944
|
-
prepare_sst_string_data
|
|
945
|
-
|
|
946
|
-
# Prepare the worksheet VML elements such as comments and buttons.
|
|
947
|
-
prepare_vml_objects
|
|
948
|
-
# Set the defined names for the worksheets such as Print Titles.
|
|
949
|
-
prepare_defined_names
|
|
950
|
-
# Prepare the drawings, charts and images.
|
|
951
|
-
prepare_drawings
|
|
952
|
-
# Add cached data to charts.
|
|
953
|
-
add_chart_data
|
|
954
|
-
|
|
955
|
-
# Prepare the worksheet tables.
|
|
956
|
-
prepare_tables
|
|
957
|
-
|
|
958
|
-
# Prepare the metadata file links.
|
|
959
|
-
prepare_metadata
|
|
960
|
-
|
|
961
|
-
# Package the workbook.
|
|
962
|
-
packager = Package::Packager.new(self)
|
|
963
|
-
packager.set_package_dir(tempdir)
|
|
964
|
-
packager.create_package
|
|
965
|
-
|
|
966
|
-
# Free up the Packager object.
|
|
967
|
-
packager = nil
|
|
968
|
-
|
|
969
|
-
# Store the xlsx component files with the temp dir name removed.
|
|
970
|
-
ZipFileUtils.zip("#{tempdir}", filename)
|
|
971
|
-
|
|
972
|
-
IO.copy_stream(filename, fileobj) if fileobj
|
|
973
|
-
Writexlsx::Utility.delete_files(tempdir)
|
|
974
|
-
end
|
|
975
|
-
|
|
976
630
|
def zip_entry_for_part(part)
|
|
977
631
|
Zip::Entry.new("", part)
|
|
978
632
|
end
|
|
979
|
-
|
|
980
|
-
#
|
|
981
|
-
# prepare_sst_string_data
|
|
982
|
-
#
|
|
983
|
-
def prepare_sst_string_data; end
|
|
984
|
-
|
|
985
|
-
#
|
|
986
|
-
# Prepare all of the format properties prior to passing them to Styles.rb.
|
|
987
|
-
#
|
|
988
|
-
def prepare_format_properties # :nodoc:
|
|
989
|
-
# Separate format objects into XF and DXF formats.
|
|
990
|
-
prepare_formats
|
|
991
|
-
|
|
992
|
-
# Set the font index for the format objects.
|
|
993
|
-
prepare_fonts
|
|
994
|
-
|
|
995
|
-
# Set the number format index for the format objects.
|
|
996
|
-
prepare_num_formats
|
|
997
|
-
|
|
998
|
-
# Set the border index for the format objects.
|
|
999
|
-
prepare_borders
|
|
1000
|
-
|
|
1001
|
-
# Set the fill index for the format objects.
|
|
1002
|
-
prepare_fills
|
|
1003
|
-
end
|
|
1004
|
-
|
|
1005
|
-
#
|
|
1006
|
-
# Iterate through the XF Format objects and separate them into XF and DXF
|
|
1007
|
-
# formats.
|
|
1008
|
-
#
|
|
1009
|
-
def prepare_formats # :nodoc:
|
|
1010
|
-
@formats.formats.each do |format|
|
|
1011
|
-
xf_index = format.xf_index
|
|
1012
|
-
dxf_index = format.dxf_index
|
|
1013
|
-
|
|
1014
|
-
@xf_formats[xf_index] = format if xf_index
|
|
1015
|
-
@dxf_formats[dxf_index] = format if dxf_index
|
|
1016
|
-
end
|
|
1017
|
-
end
|
|
1018
|
-
|
|
1019
|
-
#
|
|
1020
|
-
# Iterate through the XF Format objects and give them an index to non-default
|
|
1021
|
-
# font elements.
|
|
1022
|
-
#
|
|
1023
|
-
def prepare_fonts # :nodoc:
|
|
1024
|
-
fonts = {}
|
|
1025
|
-
|
|
1026
|
-
@xf_formats.each { |format| format.set_font_info(fonts) }
|
|
1027
|
-
|
|
1028
|
-
@font_count = fonts.size
|
|
1029
|
-
|
|
1030
|
-
# For the DXF formats we only need to check if the properties have changed.
|
|
1031
|
-
@dxf_formats.each do |format|
|
|
1032
|
-
# The only font properties that can change for a DXF format are: color,
|
|
1033
|
-
# bold, italic, underline and strikethrough.
|
|
1034
|
-
format.has_dxf_font(true) if format.color? || format.bold? || format.italic? || format.underline? || format.strikeout?
|
|
1035
|
-
end
|
|
1036
|
-
end
|
|
1037
|
-
|
|
1038
|
-
#
|
|
1039
|
-
# Iterate through the XF Format objects and give them an index to non-default
|
|
1040
|
-
# number format elements.
|
|
1041
|
-
#
|
|
1042
|
-
# User defined records start from index 0xA4.
|
|
1043
|
-
#
|
|
1044
|
-
def prepare_num_formats # :nodoc:
|
|
1045
|
-
num_formats = []
|
|
1046
|
-
unique_num_formats = {}
|
|
1047
|
-
index = 164
|
|
1048
|
-
|
|
1049
|
-
(@xf_formats + @dxf_formats).each do |format|
|
|
1050
|
-
num_format = format.num_format
|
|
1051
|
-
|
|
1052
|
-
# Check if num_format is an index to a built-in number format.
|
|
1053
|
-
# Also check for a string of zeros, which is a valid number format
|
|
1054
|
-
# string but would evaluate to zero.
|
|
1055
|
-
#
|
|
1056
|
-
if num_format.to_s =~ /^\d+$/ && num_format.to_s !~ /^0+\d/
|
|
1057
|
-
# Number format '0' is indexed as 1 in Excel.
|
|
1058
|
-
num_format = 1 if num_format == 0
|
|
1059
|
-
# Index to a built-in number format.
|
|
1060
|
-
format.num_format_index = num_format
|
|
1061
|
-
next
|
|
1062
|
-
elsif num_format.to_s == 'General'
|
|
1063
|
-
# The 'General' format has an number format index of 0.
|
|
1064
|
-
format.num_format_index = 0
|
|
1065
|
-
next
|
|
1066
|
-
end
|
|
1067
|
-
|
|
1068
|
-
if unique_num_formats[num_format]
|
|
1069
|
-
# Number format has already been used.
|
|
1070
|
-
format.num_format_index = unique_num_formats[num_format]
|
|
1071
|
-
else
|
|
1072
|
-
# Add a new number format.
|
|
1073
|
-
unique_num_formats[num_format] = index
|
|
1074
|
-
format.num_format_index = index
|
|
1075
|
-
index += 1
|
|
1076
|
-
|
|
1077
|
-
# Only store/increase number format count for XF formats
|
|
1078
|
-
# (not for DXF formats).
|
|
1079
|
-
num_formats << num_format if ptrue?(format.xf_index)
|
|
1080
|
-
end
|
|
1081
|
-
end
|
|
1082
|
-
|
|
1083
|
-
@num_formats = num_formats
|
|
1084
|
-
end
|
|
1085
|
-
|
|
1086
|
-
#
|
|
1087
|
-
# Iterate through the XF Format objects and give them an index to non-default
|
|
1088
|
-
# border elements.
|
|
1089
|
-
#
|
|
1090
|
-
def prepare_borders # :nodoc:
|
|
1091
|
-
borders = {}
|
|
1092
|
-
|
|
1093
|
-
@xf_formats.each { |format| format.set_border_info(borders) }
|
|
1094
|
-
|
|
1095
|
-
@border_count = borders.size
|
|
1096
|
-
|
|
1097
|
-
# For the DXF formats we only need to check if the properties have changed.
|
|
1098
|
-
@dxf_formats.each do |format|
|
|
1099
|
-
key = format.get_border_key
|
|
1100
|
-
format.has_dxf_border(true) if key =~ /[^0:]/
|
|
1101
|
-
end
|
|
1102
|
-
end
|
|
1103
|
-
|
|
1104
|
-
#
|
|
1105
|
-
# Iterate through the XF Format objects and give them an index to non-default
|
|
1106
|
-
# fill elements.
|
|
1107
|
-
#
|
|
1108
|
-
# The user defined fill properties start from 2 since there are 2 default
|
|
1109
|
-
# fills: patternType="none" and patternType="gray125".
|
|
1110
|
-
#
|
|
1111
|
-
def prepare_fills # :nodoc:
|
|
1112
|
-
fills = {}
|
|
1113
|
-
index = 2 # Start from 2. See above.
|
|
1114
|
-
|
|
1115
|
-
# Add the default fills.
|
|
1116
|
-
fills['0:0:0'] = 0
|
|
1117
|
-
fills['17:0:0'] = 1
|
|
1118
|
-
|
|
1119
|
-
# Store the DXF colors separately since them may be reversed below.
|
|
1120
|
-
@dxf_formats.each do |format|
|
|
1121
|
-
next unless format.pattern != 0 || format.bg_color != 0 || format.fg_color != 0
|
|
1122
|
-
|
|
1123
|
-
format.has_dxf_fill(true)
|
|
1124
|
-
format.dxf_bg_color = format.bg_color
|
|
1125
|
-
format.dxf_fg_color = format.fg_color
|
|
1126
|
-
end
|
|
1127
|
-
|
|
1128
|
-
@xf_formats.each do |format|
|
|
1129
|
-
# The following logical statements jointly take care of special cases
|
|
1130
|
-
# in relation to cell colours and patterns:
|
|
1131
|
-
# 1. For a solid fill (_pattern == 1) Excel reverses the role of
|
|
1132
|
-
# foreground and background colours, and
|
|
1133
|
-
# 2. If the user specifies a foreground or background colour without
|
|
1134
|
-
# a pattern they probably wanted a solid fill, so we fill in the
|
|
1135
|
-
# defaults.
|
|
1136
|
-
#
|
|
1137
|
-
if format.pattern == 1 && ne_0?(format.bg_color) && ne_0?(format.fg_color)
|
|
1138
|
-
format.fg_color, format.bg_color = format.bg_color, format.fg_color
|
|
1139
|
-
elsif format.pattern <= 1 && ne_0?(format.bg_color) && eq_0?(format.fg_color)
|
|
1140
|
-
format.fg_color = format.bg_color
|
|
1141
|
-
format.bg_color = 0
|
|
1142
|
-
format.pattern = 1
|
|
1143
|
-
elsif format.pattern <= 1 && eq_0?(format.bg_color) && ne_0?(format.fg_color)
|
|
1144
|
-
format.bg_color = 0
|
|
1145
|
-
format.pattern = 1
|
|
1146
|
-
end
|
|
1147
|
-
|
|
1148
|
-
key = format.get_fill_key
|
|
1149
|
-
|
|
1150
|
-
if fills[key]
|
|
1151
|
-
# Fill has already been used.
|
|
1152
|
-
format.fill_index = fills[key]
|
|
1153
|
-
format.has_fill(false)
|
|
1154
|
-
else
|
|
1155
|
-
# This is a new fill.
|
|
1156
|
-
fills[key] = index
|
|
1157
|
-
format.fill_index = index
|
|
1158
|
-
format.has_fill(true)
|
|
1159
|
-
index += 1
|
|
1160
|
-
end
|
|
1161
|
-
end
|
|
1162
|
-
|
|
1163
|
-
@fill_count = index
|
|
1164
|
-
end
|
|
1165
|
-
|
|
1166
|
-
def eq_0?(val)
|
|
1167
|
-
ptrue?(val) ? false : true
|
|
1168
|
-
end
|
|
1169
|
-
|
|
1170
|
-
def ne_0?(val)
|
|
1171
|
-
!eq_0?(val)
|
|
1172
|
-
end
|
|
1173
|
-
|
|
1174
|
-
#
|
|
1175
|
-
# Iterate through the worksheets and store any defined names in addition to
|
|
1176
|
-
# any user defined names. Stores the defined names for the Workbook.xml and
|
|
1177
|
-
# the named ranges for App.xml.
|
|
1178
|
-
#
|
|
1179
|
-
def prepare_defined_names # :nodoc:
|
|
1180
|
-
@worksheets.each do |sheet|
|
|
1181
|
-
# Check for Print Area settings.
|
|
1182
|
-
if sheet.autofilter_area
|
|
1183
|
-
@defined_names << [
|
|
1184
|
-
'_xlnm._FilterDatabase',
|
|
1185
|
-
sheet.index,
|
|
1186
|
-
sheet.autofilter_area,
|
|
1187
|
-
1
|
|
1188
|
-
]
|
|
1189
|
-
end
|
|
1190
|
-
|
|
1191
|
-
# Check for Print Area settings.
|
|
1192
|
-
unless sheet.print_area.empty?
|
|
1193
|
-
@defined_names << [
|
|
1194
|
-
'_xlnm.Print_Area',
|
|
1195
|
-
sheet.index,
|
|
1196
|
-
sheet.print_area
|
|
1197
|
-
]
|
|
1198
|
-
end
|
|
1199
|
-
|
|
1200
|
-
# Check for repeat rows/cols. aka, Print Titles.
|
|
1201
|
-
next unless !sheet.print_repeat_cols.empty? || !sheet.print_repeat_rows.empty?
|
|
1202
|
-
|
|
1203
|
-
range = if !sheet.print_repeat_cols.empty? && !sheet.print_repeat_rows.empty?
|
|
1204
|
-
sheet.print_repeat_cols + ',' + sheet.print_repeat_rows
|
|
1205
|
-
else
|
|
1206
|
-
sheet.print_repeat_cols + sheet.print_repeat_rows
|
|
1207
|
-
end
|
|
1208
|
-
|
|
1209
|
-
# Store the defined names.
|
|
1210
|
-
@defined_names << ['_xlnm.Print_Titles', sheet.index, range]
|
|
1211
|
-
end
|
|
1212
|
-
|
|
1213
|
-
@defined_names = sort_defined_names(@defined_names)
|
|
1214
|
-
@named_ranges = extract_named_ranges(@defined_names)
|
|
1215
|
-
end
|
|
1216
|
-
|
|
1217
|
-
#
|
|
1218
|
-
# Iterate through the worksheets and set up the VML objects.
|
|
1219
|
-
#
|
|
1220
|
-
def prepare_vml_objects # :nodoc:
|
|
1221
|
-
comment_id = 0
|
|
1222
|
-
vml_drawing_id = 0
|
|
1223
|
-
vml_data_id = 1
|
|
1224
|
-
vml_header_id = 0
|
|
1225
|
-
vml_shape_id = 1024
|
|
1226
|
-
has_button = false
|
|
1227
|
-
|
|
1228
|
-
@worksheets.each do |sheet|
|
|
1229
|
-
next if !sheet.has_vml? && !sheet.has_header_vml?
|
|
1230
|
-
|
|
1231
|
-
if sheet.has_vml?
|
|
1232
|
-
if sheet.has_comments?
|
|
1233
|
-
comment_id += 1
|
|
1234
|
-
@has_comments = true
|
|
1235
|
-
end
|
|
1236
|
-
vml_drawing_id += 1
|
|
1237
|
-
|
|
1238
|
-
sheet.prepare_vml_objects(
|
|
1239
|
-
vml_data_id, vml_shape_id,
|
|
1240
|
-
vml_drawing_id, comment_id
|
|
1241
|
-
)
|
|
1242
|
-
|
|
1243
|
-
# Each VML file should start with a shape id incremented by 1024.
|
|
1244
|
-
vml_data_id += 1 * (1 + sheet.num_comments_block)
|
|
1245
|
-
vml_shape_id += 1024 * (1 + sheet.num_comments_block)
|
|
1246
|
-
end
|
|
1247
|
-
|
|
1248
|
-
if sheet.has_header_vml?
|
|
1249
|
-
vml_header_id += 1
|
|
1250
|
-
vml_drawing_id += 1
|
|
1251
|
-
sheet.prepare_header_vml_objects(vml_header_id, vml_drawing_id)
|
|
1252
|
-
end
|
|
1253
|
-
|
|
1254
|
-
# Set the sheet vba_codename if it has a button and the workbook
|
|
1255
|
-
# has a vbaProject binary.
|
|
1256
|
-
unless sheet.buttons_data.empty?
|
|
1257
|
-
has_button = true
|
|
1258
|
-
sheet.set_vba_name if @vba_project && !sheet.vba_codename
|
|
1259
|
-
end
|
|
1260
|
-
end
|
|
1261
|
-
|
|
1262
|
-
# Set the workbook vba_codename if one of the sheets has a button and
|
|
1263
|
-
# the workbook has a vbaProject binary.
|
|
1264
|
-
set_vba_name if has_button && @vba_project && !@vba_codename
|
|
1265
|
-
end
|
|
1266
|
-
|
|
1267
|
-
#
|
|
1268
|
-
# Set the table ids for the worksheet tables.
|
|
1269
|
-
#
|
|
1270
|
-
def prepare_tables
|
|
1271
|
-
table_id = 0
|
|
1272
|
-
seen = {}
|
|
1273
|
-
|
|
1274
|
-
sheets.each do |sheet|
|
|
1275
|
-
table_id += sheet.prepare_tables(table_id + 1, seen)
|
|
1276
|
-
end
|
|
1277
|
-
end
|
|
1278
|
-
|
|
1279
|
-
#
|
|
1280
|
-
# Set the metadata rel link.
|
|
1281
|
-
#
|
|
1282
|
-
def prepare_metadata
|
|
1283
|
-
@worksheets.each do |sheet|
|
|
1284
|
-
next unless sheet.has_dynamic_functions? || sheet.has_embedded_images?
|
|
1285
|
-
|
|
1286
|
-
@has_metadata = true
|
|
1287
|
-
@has_dynamic_functions ||= sheet.has_dynamic_functions?
|
|
1288
|
-
@has_embedded_images ||= sheet.has_embedded_images?
|
|
1289
|
-
end
|
|
1290
|
-
end
|
|
1291
|
-
|
|
1292
|
-
#
|
|
1293
|
-
# Add "cached" data to charts to provide the numCache and strCache data for
|
|
1294
|
-
# series and title/axis ranges.
|
|
1295
|
-
#
|
|
1296
|
-
def add_chart_data # :nodoc:
|
|
1297
|
-
worksheets = {}
|
|
1298
|
-
seen_ranges = {}
|
|
1299
|
-
|
|
1300
|
-
# Map worksheet names to worksheet objects.
|
|
1301
|
-
@worksheets.each { |worksheet| worksheets[worksheet.name] = worksheet }
|
|
1302
|
-
|
|
1303
|
-
# Build an array of the worksheet charts including any combined charts.
|
|
1304
|
-
@charts.collect { |chart| [chart, chart.combined] }.flatten.compact
|
|
1305
|
-
.each do |chart|
|
|
1306
|
-
chart.formula_ids.each do |range, id|
|
|
1307
|
-
# Skip if the series has user defined data.
|
|
1308
|
-
if chart.formula_data[id]
|
|
1309
|
-
seen_ranges[range] = chart.formula_data[id] unless seen_ranges[range]
|
|
1310
|
-
next
|
|
1311
|
-
# Check to see if the data is already cached locally.
|
|
1312
|
-
elsif seen_ranges[range]
|
|
1313
|
-
chart.formula_data[id] = seen_ranges[range]
|
|
1314
|
-
next
|
|
1315
|
-
end
|
|
1316
|
-
|
|
1317
|
-
# Convert the range formula to a sheet name and cell range.
|
|
1318
|
-
sheetname, *cells = get_chart_range(range)
|
|
1319
|
-
|
|
1320
|
-
# Skip if we couldn't parse the formula.
|
|
1321
|
-
next unless sheetname
|
|
1322
|
-
|
|
1323
|
-
# Handle non-contiguous ranges: (Sheet1!$A$1:$A$2,Sheet1!$A$4:$A$5).
|
|
1324
|
-
# We don't try to parse the ranges. We just return an empty list.
|
|
1325
|
-
if sheetname =~ /^\([^,]+,/
|
|
1326
|
-
chart.formula_data[id] = []
|
|
1327
|
-
seen_ranges[range] = []
|
|
1328
|
-
next
|
|
1329
|
-
end
|
|
1330
|
-
|
|
1331
|
-
# Raise if the name is unknown since it indicates a user error in
|
|
1332
|
-
# a chart series formula.
|
|
1333
|
-
raise "Unknown worksheet reference '#{sheetname} in range '#{range}' passed to add_series()\n" unless worksheets[sheetname]
|
|
1334
|
-
|
|
1335
|
-
# Add the data to the chart.
|
|
1336
|
-
# And store range data locally to avoid lookup if seen agein.
|
|
1337
|
-
chart.formula_data[id] =
|
|
1338
|
-
seen_ranges[range] = chart_data(worksheets[sheetname], cells)
|
|
1339
|
-
end
|
|
1340
|
-
end
|
|
1341
|
-
end
|
|
1342
|
-
|
|
1343
|
-
def chart_data(worksheet, cells)
|
|
1344
|
-
# Get the data from the worksheet table.
|
|
1345
|
-
data = worksheet.get_range_data(*cells)
|
|
1346
|
-
|
|
1347
|
-
# Convert shared string indexes to strings.
|
|
1348
|
-
data.collect do |token|
|
|
1349
|
-
if token.is_a?(Hash)
|
|
1350
|
-
string = @shared_strings.string(token[:sst_id])
|
|
1351
|
-
|
|
1352
|
-
# Ignore rich strings for now. Deparse later if necessary.
|
|
1353
|
-
if string =~ /^<r>/ && string =~ %r{</r>$}
|
|
1354
|
-
''
|
|
1355
|
-
else
|
|
1356
|
-
string
|
|
1357
|
-
end
|
|
1358
|
-
else
|
|
1359
|
-
token
|
|
1360
|
-
end
|
|
1361
|
-
end
|
|
1362
|
-
end
|
|
1363
|
-
|
|
1364
|
-
#
|
|
1365
|
-
# Sort internal and user defined names in the same order as used by Excel.
|
|
1366
|
-
# This may not be strictly necessary but unsorted elements caused a lot of
|
|
1367
|
-
# issues in the the Spreadsheet::WriteExcel binary version. Also makes
|
|
1368
|
-
# comparison testing easier.
|
|
1369
|
-
#
|
|
1370
|
-
def sort_defined_names(names) # :nodoc:
|
|
1371
|
-
names.sort do |a, b|
|
|
1372
|
-
name_a = normalise_defined_name(a[0])
|
|
1373
|
-
name_b = normalise_defined_name(b[0])
|
|
1374
|
-
sheet_a = normalise_sheet_name(a[2])
|
|
1375
|
-
sheet_b = normalise_sheet_name(b[2])
|
|
1376
|
-
# Primary sort based on the defined name.
|
|
1377
|
-
if name_a > name_b
|
|
1378
|
-
1
|
|
1379
|
-
elsif name_a < name_b
|
|
1380
|
-
-1
|
|
1381
|
-
elsif sheet_a >= sheet_b # name_a == name_b
|
|
1382
|
-
# Secondary sort based on the sheet name.
|
|
1383
|
-
1
|
|
1384
|
-
else
|
|
1385
|
-
-1
|
|
1386
|
-
end
|
|
1387
|
-
end
|
|
1388
|
-
end
|
|
1389
|
-
|
|
1390
|
-
# Used in the above sort routine to normalise the defined names. Removes any
|
|
1391
|
-
# leading '_xmln.' from internal names and lowercases the strings.
|
|
1392
|
-
def normalise_defined_name(name) # :nodoc:
|
|
1393
|
-
name.sub(/^_xlnm./, '').downcase
|
|
1394
|
-
end
|
|
1395
|
-
|
|
1396
|
-
# Used in the above sort routine to normalise the worksheet names for the
|
|
1397
|
-
# secondary sort. Removes leading quote and lowercases the strings.
|
|
1398
|
-
def normalise_sheet_name(name) # :nodoc:
|
|
1399
|
-
name.sub(/^'/, '').downcase
|
|
1400
|
-
end
|
|
1401
|
-
|
|
1402
|
-
#
|
|
1403
|
-
# Extract the named ranges from the sorted list of defined names. These are
|
|
1404
|
-
# used in the App.xml file.
|
|
1405
|
-
#
|
|
1406
|
-
def extract_named_ranges(defined_names) # :nodoc:
|
|
1407
|
-
named_ranges = []
|
|
1408
|
-
|
|
1409
|
-
defined_names.each do |defined_name|
|
|
1410
|
-
name, index, range = defined_name
|
|
1411
|
-
|
|
1412
|
-
# Skip autoFilter ranges.
|
|
1413
|
-
next if name == '_xlnm._FilterDatabase'
|
|
1414
|
-
|
|
1415
|
-
# We are only interested in defined names with ranges.
|
|
1416
|
-
next unless range =~ /^([^!]+)!/
|
|
1417
|
-
|
|
1418
|
-
sheet_name = ::Regexp.last_match(1)
|
|
1419
|
-
|
|
1420
|
-
# Match Print_Area and Print_Titles xlnm types.
|
|
1421
|
-
if name =~ /^_xlnm\.(.*)$/
|
|
1422
|
-
xlnm_type = ::Regexp.last_match(1)
|
|
1423
|
-
name = "#{sheet_name}!#{xlnm_type}"
|
|
1424
|
-
elsif index != -1
|
|
1425
|
-
name = "#{sheet_name}!#{name}"
|
|
1426
|
-
end
|
|
1427
|
-
|
|
1428
|
-
named_ranges << name
|
|
1429
|
-
end
|
|
1430
|
-
|
|
1431
|
-
named_ranges
|
|
1432
|
-
end
|
|
1433
|
-
|
|
1434
|
-
#
|
|
1435
|
-
# Iterate through the worksheets and set up any chart or image drawings.
|
|
1436
|
-
#
|
|
1437
|
-
def prepare_drawings # :nodoc:
|
|
1438
|
-
# Store the image types for any embedded images.
|
|
1439
|
-
@embedded_images.each do |image|
|
|
1440
|
-
store_image_types(image.type)
|
|
1441
|
-
|
|
1442
|
-
@has_embedded_descriptions = true if ptrue?(image.description)
|
|
1443
|
-
end
|
|
1444
|
-
|
|
1445
|
-
prepare_drawings_of_all_sheets
|
|
1446
|
-
|
|
1447
|
-
# Sort the workbook charts references into the order that the were
|
|
1448
|
-
# written from the worksheets above.
|
|
1449
|
-
@charts = @charts.select { |chart| chart.id != -1 }
|
|
1450
|
-
.sort_by { |chart| chart.id }
|
|
1451
|
-
end
|
|
1452
|
-
|
|
1453
|
-
def prepare_drawings_of_all_sheets
|
|
1454
|
-
drawing_id = 0
|
|
1455
|
-
chart_ref_id = 0
|
|
1456
|
-
image_ids = {}
|
|
1457
|
-
header_image_ids = {}
|
|
1458
|
-
background_ids = {}
|
|
1459
|
-
|
|
1460
|
-
# The image IDs start from after the embedded images.
|
|
1461
|
-
image_ref_id = @embedded_images.size
|
|
1462
|
-
|
|
1463
|
-
@worksheets.each do |sheet|
|
|
1464
|
-
drawing_id, chart_ref_id, image_ref_id =
|
|
1465
|
-
sheet.prepare_drawings(
|
|
1466
|
-
drawing_id, chart_ref_id, image_ref_id, image_ids,
|
|
1467
|
-
header_image_ids, background_ids
|
|
1468
|
-
)
|
|
1469
|
-
end
|
|
1470
|
-
end
|
|
1471
633
|
end
|
|
1472
634
|
end
|