write_xlsx 1.13.0 → 1.15.1
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 +25 -4
- data/Changes +23 -0
- 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 +31 -82
- 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 +15 -1
- data/lib/write_xlsx/package/app.rb +2 -2
- 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 +3 -1
- data/lib/write_xlsx/shape.rb +97 -100
- data/lib/write_xlsx/sheets.rb +6 -1
- 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/autofilter.rb +3 -1
- data/lib/write_xlsx/worksheet/cell_data.rb +5 -1
- data/lib/write_xlsx/worksheet/columns.rb +8 -3
- data/lib/write_xlsx/worksheet/data_validation.rb +9 -1
- data/lib/write_xlsx/worksheet/data_writing.rb +42 -15
- data/lib/write_xlsx/worksheet/drawing_preparation.rb +1 -1
- data/lib/write_xlsx/worksheet/formatting.rb +3 -1
- data/lib/write_xlsx/worksheet/hyperlink.rb +9 -1
- data/lib/write_xlsx/worksheet/row_col_sizing.rb +3 -1
- data/lib/write_xlsx/worksheet/xml_writer.rb +9 -4
- data/lib/write_xlsx/worksheet.rb +19 -2
- metadata +42 -3
- data/lib/write_xlsx/utility.rb +0 -1034
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
class Workbook
|
|
6
|
+
module ChartData
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
#
|
|
10
|
+
# Convert a range formula such as Sheet1!$B$1:$B$5 into a sheet name and cell
|
|
11
|
+
# range such as ( 'Sheet1', 0, 1, 4, 1 ).
|
|
12
|
+
#
|
|
13
|
+
def get_chart_range(range) # :nodoc:
|
|
14
|
+
# Split the range formula into sheetname and cells at the last '!'.
|
|
15
|
+
pos = range.rindex('!')
|
|
16
|
+
return nil unless pos
|
|
17
|
+
|
|
18
|
+
if pos > 0
|
|
19
|
+
sheetname = range[0, pos]
|
|
20
|
+
cells = range[(pos + 1)..-1]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Split the cell range into 2 cells or else use single cell for both.
|
|
24
|
+
if cells =~ /:/
|
|
25
|
+
cell_1, cell_2 = cells.split(":")
|
|
26
|
+
else
|
|
27
|
+
cell_1 = cells
|
|
28
|
+
cell_2 = cells
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Remove leading/trailing apostrophes and convert escaped quotes to single.
|
|
32
|
+
sheetname.sub!(/^'/, '')
|
|
33
|
+
sheetname.sub!(/'$/, '')
|
|
34
|
+
sheetname.gsub!("''", "'")
|
|
35
|
+
|
|
36
|
+
row_start, col_start = xl_cell_to_rowcol(cell_1)
|
|
37
|
+
row_end, col_end = xl_cell_to_rowcol(cell_2)
|
|
38
|
+
|
|
39
|
+
# Check that we have a 1D range only.
|
|
40
|
+
return nil if row_start != row_end && col_start != col_end
|
|
41
|
+
|
|
42
|
+
[sheetname, row_start, col_start, row_end, col_end]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# Add "cached" data to charts to provide the numCache and strCache data for
|
|
47
|
+
# series and title/axis ranges.
|
|
48
|
+
#
|
|
49
|
+
def add_chart_data # :nodoc:
|
|
50
|
+
worksheets = {}
|
|
51
|
+
seen_ranges = {}
|
|
52
|
+
|
|
53
|
+
# Map worksheet names to worksheet objects.
|
|
54
|
+
@worksheets.each { |worksheet| worksheets[worksheet.name] = worksheet }
|
|
55
|
+
|
|
56
|
+
# Build an array of the worksheet charts including any combined charts.
|
|
57
|
+
@charts.collect { |chart| [chart, chart.combined] }.flatten.compact
|
|
58
|
+
.each do |chart|
|
|
59
|
+
chart.formula_ids.each do |range, id|
|
|
60
|
+
# Skip if the series has user defined data.
|
|
61
|
+
if chart.formula_data[id]
|
|
62
|
+
seen_ranges[range] = chart.formula_data[id] unless seen_ranges[range]
|
|
63
|
+
next
|
|
64
|
+
# Check to see if the data is already cached locally.
|
|
65
|
+
elsif seen_ranges[range]
|
|
66
|
+
chart.formula_data[id] = seen_ranges[range]
|
|
67
|
+
next
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Convert the range formula to a sheet name and cell range.
|
|
71
|
+
sheetname, *cells = get_chart_range(range)
|
|
72
|
+
|
|
73
|
+
# Skip if we couldn't parse the formula.
|
|
74
|
+
next unless sheetname
|
|
75
|
+
|
|
76
|
+
# Handle non-contiguous ranges: (Sheet1!$A$1:$A$2,Sheet1!$A$4:$A$5).
|
|
77
|
+
# We don't try to parse the ranges. We just return an empty list.
|
|
78
|
+
if sheetname =~ /^\([^,]+,/
|
|
79
|
+
chart.formula_data[id] = []
|
|
80
|
+
seen_ranges[range] = []
|
|
81
|
+
next
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Raise if the name is unknown since it indicates a user error in
|
|
85
|
+
# a chart series formula.
|
|
86
|
+
raise "Unknown worksheet reference '#{sheetname}' in range '#{range}' passed to add_series()\n" unless worksheets[sheetname]
|
|
87
|
+
|
|
88
|
+
# Add the data to the chart.
|
|
89
|
+
# And store range data locally to avoid lookup if seen agein.
|
|
90
|
+
chart.formula_data[id] =
|
|
91
|
+
seen_ranges[range] = chart_data(worksheets[sheetname], cells)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def chart_data(worksheet, cells)
|
|
97
|
+
# Get the data from the worksheet table.
|
|
98
|
+
data = worksheet.get_range_data(*cells)
|
|
99
|
+
|
|
100
|
+
# Convert shared string indexes to strings.
|
|
101
|
+
data.collect do |token|
|
|
102
|
+
if token.is_a?(Hash)
|
|
103
|
+
string = @shared_strings.string(token[:sst_id])
|
|
104
|
+
|
|
105
|
+
# Ignore rich strings for now. Deparse later if necessary.
|
|
106
|
+
if string =~ /^<r>/ && string =~ %r{</r>$}
|
|
107
|
+
''
|
|
108
|
+
else
|
|
109
|
+
string
|
|
110
|
+
end
|
|
111
|
+
else
|
|
112
|
+
token
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
#
|
|
118
|
+
# Sort internal and user defined names in the same order as used by Excel.
|
|
119
|
+
# This may not be strictly necessary but unsorted elements caused a lot of
|
|
120
|
+
# issues in the the Spreadsheet::WriteExcel binary version. Also makes
|
|
121
|
+
# comparison testing easier.
|
|
122
|
+
#
|
|
123
|
+
def sort_defined_names(names) # :nodoc:
|
|
124
|
+
names.sort do |a, b|
|
|
125
|
+
name_a = normalise_defined_name(a[0])
|
|
126
|
+
name_b = normalise_defined_name(b[0])
|
|
127
|
+
sheet_a = normalise_sheet_name(a[2])
|
|
128
|
+
sheet_b = normalise_sheet_name(b[2])
|
|
129
|
+
# Primary sort based on the defined name.
|
|
130
|
+
if name_a > name_b
|
|
131
|
+
1
|
|
132
|
+
elsif name_a < name_b
|
|
133
|
+
-1
|
|
134
|
+
elsif sheet_a >= sheet_b # name_a == name_b
|
|
135
|
+
# Secondary sort based on the sheet name.
|
|
136
|
+
1
|
|
137
|
+
else
|
|
138
|
+
-1
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Used in the above sort routine to normalise the defined names. Removes any
|
|
144
|
+
# leading '_xmln.' from internal names and lowercases the strings.
|
|
145
|
+
def normalise_defined_name(name) # :nodoc:
|
|
146
|
+
name.sub(/^_xlnm./, '').downcase
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Used in the above sort routine to normalise the worksheet names for the
|
|
150
|
+
# secondary sort. Removes leading quote and lowercases the strings.
|
|
151
|
+
def normalise_sheet_name(name) # :nodoc:
|
|
152
|
+
name.sub(/^'/, '').downcase
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
#
|
|
156
|
+
# Extract the named ranges from the sorted list of defined names. These are
|
|
157
|
+
# used in the App.xml file.
|
|
158
|
+
#
|
|
159
|
+
def extract_named_ranges(defined_names) # :nodoc:
|
|
160
|
+
named_ranges = []
|
|
161
|
+
|
|
162
|
+
defined_names.each do |defined_name|
|
|
163
|
+
name, index, range = defined_name
|
|
164
|
+
|
|
165
|
+
# Skip autoFilter ranges.
|
|
166
|
+
next if name == '_xlnm._FilterDatabase'
|
|
167
|
+
|
|
168
|
+
# We are only interested in defined names with ranges.
|
|
169
|
+
next unless range =~ /^([^!]+)!/
|
|
170
|
+
|
|
171
|
+
sheet_name = ::Regexp.last_match(1)
|
|
172
|
+
|
|
173
|
+
# Match Print_Area and Print_Titles xlnm types.
|
|
174
|
+
if name =~ /^_xlnm\.(.*)$/
|
|
175
|
+
xlnm_type = ::Regexp.last_match(1)
|
|
176
|
+
name = "#{sheet_name}!#{xlnm_type}"
|
|
177
|
+
elsif index != -1
|
|
178
|
+
name = "#{sheet_name}!#{name}"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
named_ranges << name
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
named_ranges
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -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
|