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,60 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
class Worksheet
|
|
6
|
+
class AssetManager
|
|
7
|
+
attr_reader :charts, :images, :tables, :sparklines, :shapes
|
|
8
|
+
attr_accessor :background_image
|
|
9
|
+
attr_reader :header_images, :footer_images
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@charts = []
|
|
13
|
+
@images = []
|
|
14
|
+
@tables = []
|
|
15
|
+
@sparklines = []
|
|
16
|
+
@shapes = []
|
|
17
|
+
|
|
18
|
+
@header_images = []
|
|
19
|
+
@footer_images = []
|
|
20
|
+
@background_image = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_chart(chart)
|
|
24
|
+
@charts << chart
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def add_image(image)
|
|
28
|
+
@images << image
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def add_table(table)
|
|
32
|
+
@tables << table
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def add_sparkline(sparkline)
|
|
36
|
+
@sparklines << sparkline
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def add_shape(shape)
|
|
40
|
+
@shapes << shape
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def reset_header_images
|
|
44
|
+
@header_images
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def reset_footer_images
|
|
48
|
+
@footer_images
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def add_header_image(image)
|
|
52
|
+
@header_images << image
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def add_footer_image(image)
|
|
56
|
+
@footer_images << image
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'write_xlsx/constants'
|
|
4
|
+
|
|
5
|
+
module Writexlsx
|
|
6
|
+
class Worksheet
|
|
7
|
+
# Autofilter-related operations extracted from Worksheet to slim the main class.
|
|
8
|
+
module Autofilter
|
|
9
|
+
include Constants
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# :call-seq:
|
|
13
|
+
# autofilter(first_row, first_col, last_row, last_col)
|
|
14
|
+
#
|
|
15
|
+
# Set the autofilter area in the worksheet.
|
|
16
|
+
#
|
|
17
|
+
def autofilter(row1, col1 = nil, row2 = nil, col2 = nil)
|
|
18
|
+
if (row_col_array = row_col_notation(row1))
|
|
19
|
+
_row1, _col1, _row2, _col2 = row_col_array
|
|
20
|
+
else
|
|
21
|
+
_row1 = row1
|
|
22
|
+
_col1 = col1
|
|
23
|
+
_row2 = row2
|
|
24
|
+
_col2 = col2
|
|
25
|
+
end
|
|
26
|
+
return if [_row1, _col1, _row2, _col2].include?(nil)
|
|
27
|
+
|
|
28
|
+
# Reverse max and min values if necessary.
|
|
29
|
+
_row1, _row2 = _row2, _row1 if _row2 < _row1
|
|
30
|
+
_col1, _col2 = _col2, _col1 if _col2 < _col1
|
|
31
|
+
|
|
32
|
+
@autofilter_area = convert_name_area(_row1, _col1, _row2, _col2)
|
|
33
|
+
@autofilter_ref = xl_range(_row1, _row2, _col1, _col2)
|
|
34
|
+
@filter_range = [_col1, _col2]
|
|
35
|
+
|
|
36
|
+
# Store the filter cell positions for use in the autofit calculation.
|
|
37
|
+
(_col1.._col2).each do |col|
|
|
38
|
+
@filter_cells["#{_row1}:#{col}"] = 1
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
#
|
|
43
|
+
# Set the column filter criteria.
|
|
44
|
+
#
|
|
45
|
+
# The filter_column method can be used to filter columns in a autofilter
|
|
46
|
+
# range based on simple conditions.
|
|
47
|
+
#
|
|
48
|
+
def filter_column(col, expression)
|
|
49
|
+
raise "Must call autofilter before filter_column" unless @autofilter_area
|
|
50
|
+
|
|
51
|
+
col = prepare_filter_column(col)
|
|
52
|
+
|
|
53
|
+
tokens = extract_filter_tokens(expression)
|
|
54
|
+
|
|
55
|
+
raise "Incorrect number of tokens in expression '#{expression}'" unless [3, 7].include?(tokens.size)
|
|
56
|
+
|
|
57
|
+
tokens = parse_filter_expression(expression, tokens)
|
|
58
|
+
|
|
59
|
+
# Excel handles single or double custom filters as default filters. We need
|
|
60
|
+
# to check for them and handle them accordingly.
|
|
61
|
+
if tokens.size == 2 && tokens[0] == 2
|
|
62
|
+
# Single equality.
|
|
63
|
+
filter_column_list(col, tokens[1])
|
|
64
|
+
elsif tokens.size == 5 && tokens[0] == 2 && tokens[2] == 1 && tokens[3] == 2
|
|
65
|
+
# Double equality with "or" operator.
|
|
66
|
+
filter_column_list(col, tokens[1], tokens[4])
|
|
67
|
+
else
|
|
68
|
+
# Non default custom filter.
|
|
69
|
+
@filter_cols[col] = Array.new(tokens)
|
|
70
|
+
@filter_type[col] = 0
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
@filter_on = 1
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#
|
|
77
|
+
# Set the column filter criteria in Excel 2007 list style.
|
|
78
|
+
#
|
|
79
|
+
def filter_column_list(col, *tokens)
|
|
80
|
+
tokens.flatten!
|
|
81
|
+
raise "Incorrect number of arguments to filter_column_list" if tokens.empty?
|
|
82
|
+
raise "Must call autofilter before filter_column_list" unless @autofilter_area
|
|
83
|
+
|
|
84
|
+
col = prepare_filter_column(col)
|
|
85
|
+
|
|
86
|
+
@filter_cols[col] = tokens
|
|
87
|
+
@filter_type[col] = 1 # Default style.
|
|
88
|
+
@filter_on = 1
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def prepare_filter_column(col) # :nodoc:
|
|
92
|
+
# Check for a column reference in A1 notation and substitute.
|
|
93
|
+
if col.to_s =~ /^\D/
|
|
94
|
+
col_letter = col
|
|
95
|
+
|
|
96
|
+
# Convert col ref to a cell ref and then to a col number.
|
|
97
|
+
_dummy, col = substitute_cellref("#{col}1")
|
|
98
|
+
raise "Invalid column '#{col_letter}'" if col >= COL_MAX
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
col_first, col_last = @filter_range
|
|
102
|
+
|
|
103
|
+
# Reject column if it is outside filter range.
|
|
104
|
+
raise "Column '#{col}' outside autofilter column range (#{col_first} .. #{col_last})" if col < col_first || col > col_last
|
|
105
|
+
|
|
106
|
+
col
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
#
|
|
110
|
+
# Write the <autoFilter> element.
|
|
111
|
+
#
|
|
112
|
+
def write_auto_filter # :nodoc:
|
|
113
|
+
return unless autofilter_ref?
|
|
114
|
+
|
|
115
|
+
attributes = [
|
|
116
|
+
['ref', @autofilter_ref]
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
if filter_on?
|
|
120
|
+
# Autofilter defined active filters.
|
|
121
|
+
@writer.tag_elements('autoFilter', attributes) do
|
|
122
|
+
write_autofilters
|
|
123
|
+
end
|
|
124
|
+
else
|
|
125
|
+
# Autofilter defined without active filters.
|
|
126
|
+
@writer.empty_tag('autoFilter', attributes)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
#
|
|
131
|
+
# Function to iterate through the columns that form part of an autofilter
|
|
132
|
+
# range and write the appropriate filters.
|
|
133
|
+
#
|
|
134
|
+
def write_autofilters # :nodoc:
|
|
135
|
+
col1, col2 = @filter_range
|
|
136
|
+
|
|
137
|
+
(col1..col2).each do |col|
|
|
138
|
+
# Skip if column doesn't have an active filter.
|
|
139
|
+
next unless @filter_cols[col]
|
|
140
|
+
|
|
141
|
+
# Retrieve the filter tokens and write the autofilter records.
|
|
142
|
+
tokens = @filter_cols[col]
|
|
143
|
+
type = @filter_type[col]
|
|
144
|
+
|
|
145
|
+
# Filters are relative to first column in the autofilter.
|
|
146
|
+
write_filter_column(col - col1, type, *tokens)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
#
|
|
151
|
+
# Write the <filterColumn> element.
|
|
152
|
+
#
|
|
153
|
+
def write_filter_column(col_id, type, *filters) # :nodoc:
|
|
154
|
+
@writer.tag_elements('filterColumn', [['colId', col_id]]) do
|
|
155
|
+
if type == 1
|
|
156
|
+
# Type == 1 is the new XLSX style filter.
|
|
157
|
+
write_filters(*filters)
|
|
158
|
+
else
|
|
159
|
+
# Type == 0 is the classic "custom" filter.
|
|
160
|
+
write_custom_filters(*filters)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
#
|
|
166
|
+
# Write the <filters> element.
|
|
167
|
+
#
|
|
168
|
+
def write_filters(*filters) # :nodoc:
|
|
169
|
+
non_blanks = filters.reject { |filter| filter.to_s =~ /^blanks$/i }
|
|
170
|
+
attributes = []
|
|
171
|
+
|
|
172
|
+
attributes = [['blank', 1]] if filters != non_blanks
|
|
173
|
+
|
|
174
|
+
if filters.size == 1 && non_blanks.empty?
|
|
175
|
+
# Special case for blank cells only.
|
|
176
|
+
@writer.empty_tag('filters', attributes)
|
|
177
|
+
else
|
|
178
|
+
# General case.
|
|
179
|
+
@writer.tag_elements('filters', attributes) do
|
|
180
|
+
non_blanks.sort.each { |filter| write_filter(filter) }
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
#
|
|
186
|
+
# Write the <filter> element.
|
|
187
|
+
#
|
|
188
|
+
def write_filter(val) # :nodoc:
|
|
189
|
+
@writer.empty_tag('filter', [['val', val]])
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
#
|
|
193
|
+
# Write the <customFilters> element.
|
|
194
|
+
#
|
|
195
|
+
def write_custom_filters(*tokens) # :nodoc:
|
|
196
|
+
if tokens.size == 2
|
|
197
|
+
# One filter expression only.
|
|
198
|
+
@writer.tag_elements('customFilters') { write_custom_filter(*tokens) }
|
|
199
|
+
else
|
|
200
|
+
# Two filter expressions.
|
|
201
|
+
|
|
202
|
+
# Check if the "join" operand is "and" or "or".
|
|
203
|
+
attributes = if tokens[2] == 0
|
|
204
|
+
[['and', 1]]
|
|
205
|
+
else
|
|
206
|
+
[['and', 0]]
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Write the two custom filters.
|
|
210
|
+
@writer.tag_elements('customFilters', attributes) do
|
|
211
|
+
write_custom_filter(tokens[0], tokens[1])
|
|
212
|
+
write_custom_filter(tokens[3], tokens[4])
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
#
|
|
218
|
+
# Write the <customFilter> element.
|
|
219
|
+
#
|
|
220
|
+
def write_custom_filter(operator, val) # :nodoc:
|
|
221
|
+
operators = {
|
|
222
|
+
1 => 'lessThan',
|
|
223
|
+
2 => 'equal',
|
|
224
|
+
3 => 'lessThanOrEqual',
|
|
225
|
+
4 => 'greaterThan',
|
|
226
|
+
5 => 'notEqual',
|
|
227
|
+
6 => 'greaterThanOrEqual',
|
|
228
|
+
7 => 'startsWith',
|
|
229
|
+
8 => 'notStartsWith',
|
|
230
|
+
9 => 'endsWith',
|
|
231
|
+
10 => 'notEndsWith',
|
|
232
|
+
11 => 'contains',
|
|
233
|
+
12 => 'notContains',
|
|
234
|
+
13 => 'between',
|
|
235
|
+
14 => 'notBetween'
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
attributes = [
|
|
239
|
+
['operator', operators[operator]],
|
|
240
|
+
['val', val]
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
@writer.empty_tag('customFilter', attributes)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
private
|
|
247
|
+
|
|
248
|
+
#
|
|
249
|
+
# Extract the tokens from the filter expression. The tokens are mainly non-
|
|
250
|
+
# whitespace groups. The only tricky part is to extract string tokens that
|
|
251
|
+
# contain whitespace and/or quoted double quotes (Excel's escaped quotes).
|
|
252
|
+
#
|
|
253
|
+
def extract_filter_tokens(expression = nil) # :nodoc:
|
|
254
|
+
return [] unless expression
|
|
255
|
+
|
|
256
|
+
tokens = []
|
|
257
|
+
str = expression
|
|
258
|
+
while str =~ /"(?:[^"]|"")*"|\S+/
|
|
259
|
+
tokens << ::Regexp.last_match(0)
|
|
260
|
+
str = $LAST_MATCH_INFO.post_match
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Remove leading and trailing quotes and unescape other quotes
|
|
264
|
+
tokens.map! do |token|
|
|
265
|
+
token.sub!(/^"/, '')
|
|
266
|
+
token.sub!(/"$/, '')
|
|
267
|
+
token.gsub!('""', '"')
|
|
268
|
+
|
|
269
|
+
# if token is number, convert to numeric.
|
|
270
|
+
if token =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/
|
|
271
|
+
token.to_f == token.to_i ? token.to_i : token.to_f
|
|
272
|
+
else
|
|
273
|
+
token
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
tokens
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
#
|
|
281
|
+
# Converts the tokens of a possibly conditional expression into 1 or 2
|
|
282
|
+
# sub expressions for further parsing.
|
|
283
|
+
#
|
|
284
|
+
def parse_filter_expression(expression, tokens) # :nodoc:
|
|
285
|
+
# The number of tokens will be either 3 (for 1 expression)
|
|
286
|
+
# or 7 (for 2 expressions).
|
|
287
|
+
#
|
|
288
|
+
if tokens.size == 7
|
|
289
|
+
conditional = tokens[3]
|
|
290
|
+
if conditional =~ /^(and|&&)$/
|
|
291
|
+
conditional = 0
|
|
292
|
+
elsif conditional =~ /^(or|\|\|)$/
|
|
293
|
+
conditional = 1
|
|
294
|
+
else
|
|
295
|
+
raise "Token '#{conditional}' is not a valid conditional " \
|
|
296
|
+
"in filter expression '#{expression}'"
|
|
297
|
+
end
|
|
298
|
+
expression_1 = parse_filter_tokens(expression, tokens[0..2])
|
|
299
|
+
expression_2 = parse_filter_tokens(expression, tokens[4..6])
|
|
300
|
+
[expression_1, conditional, expression_2].flatten
|
|
301
|
+
else
|
|
302
|
+
parse_filter_tokens(expression, tokens)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
#
|
|
307
|
+
# Parse the 3 tokens of a filter expression and return the operator and token.
|
|
308
|
+
#
|
|
309
|
+
def parse_filter_tokens(expression, tokens) # :nodoc:
|
|
310
|
+
operators = {
|
|
311
|
+
'==' => 2,
|
|
312
|
+
'=' => 2,
|
|
313
|
+
'=~' => 2,
|
|
314
|
+
'eq' => 2,
|
|
315
|
+
|
|
316
|
+
'!=' => 5,
|
|
317
|
+
'!~' => 5,
|
|
318
|
+
'ne' => 5,
|
|
319
|
+
'<>' => 5,
|
|
320
|
+
|
|
321
|
+
'<' => 1,
|
|
322
|
+
'<=' => 3,
|
|
323
|
+
'>' => 4,
|
|
324
|
+
'>=' => 6
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
operator = operators[tokens[1]]
|
|
328
|
+
token = tokens[2]
|
|
329
|
+
|
|
330
|
+
# Special handling of "Top" filter expressions.
|
|
331
|
+
if tokens[0] =~ /^top|bottom$/i
|
|
332
|
+
value = tokens[1]
|
|
333
|
+
if value.to_s =~ /\D/ || value.to_i < 1 || value.to_i > 500
|
|
334
|
+
raise "The value '#{value}' in expression '#{expression}' " \
|
|
335
|
+
"must be in the range 1 to 500"
|
|
336
|
+
end
|
|
337
|
+
token.downcase!
|
|
338
|
+
if token != 'items' && token != '%'
|
|
339
|
+
raise "The type '#{token}' in expression '#{expression}' " \
|
|
340
|
+
"must be either 'items' or '%'"
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
operator = if tokens[0] =~ /^top$/i
|
|
344
|
+
30
|
|
345
|
+
else
|
|
346
|
+
32
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
operator += 1 if tokens[2] == '%'
|
|
350
|
+
|
|
351
|
+
token = value
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
if !operator && tokens[0]
|
|
355
|
+
raise "Token '#{tokens[1]}' is not a valid operator " \
|
|
356
|
+
"in filter expression '#{expression}'"
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# Special handling for Blanks/NonBlanks.
|
|
360
|
+
if token.to_s =~ /^blanks|nonblanks$/i
|
|
361
|
+
# Only allow Equals or NotEqual in this context.
|
|
362
|
+
if operator != 2 && operator != 5
|
|
363
|
+
raise "The operator '#{tokens[1]}' in expression '#{expression}' " \
|
|
364
|
+
"is not valid in relation to Blanks/NonBlanks'"
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
token.downcase!
|
|
368
|
+
|
|
369
|
+
# The operator should always be 2 (=) to flag a "simple" equality in
|
|
370
|
+
# the binary record. Therefore we convert <> to =.
|
|
371
|
+
if token == 'blanks'
|
|
372
|
+
token = ' ' if operator == 5
|
|
373
|
+
elsif operator == 5
|
|
374
|
+
operator = 2
|
|
375
|
+
token = 'blanks'
|
|
376
|
+
else
|
|
377
|
+
operator = 5
|
|
378
|
+
token = ' '
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# if the string token contains an Excel match character then change the
|
|
383
|
+
# operator type to indicate a non "simple" equality.
|
|
384
|
+
operator = 22 if operator == 2 && token.to_s =~ /[*?]/
|
|
385
|
+
|
|
386
|
+
[operator, token]
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require 'write_xlsx/utility/common'
|
|
5
|
+
require 'write_xlsx/utility/cell_reference'
|
|
6
|
+
|
|
4
7
|
module Writexlsx
|
|
5
8
|
class Worksheet
|
|
6
9
|
class CellData # :nodoc:
|
|
7
|
-
include Writexlsx::Utility
|
|
10
|
+
include Writexlsx::Utility::Common
|
|
11
|
+
include Writexlsx::Utility::CellReference
|
|
8
12
|
|
|
9
13
|
attr_reader :xf
|
|
10
14
|
|
|
@@ -105,20 +109,23 @@ module Writexlsx
|
|
|
105
109
|
truefalse = { 'TRUE' => 1, 'FALSE' => 0 }
|
|
106
110
|
error_code = ['#DIV/0!', '#N/A', '#NAME?', '#NULL!', '#NUM!', '#REF!', '#VALUE!']
|
|
107
111
|
|
|
112
|
+
value = @result
|
|
108
113
|
attributes = cell_attributes(worksheet, row, row_name, col)
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
|
|
115
|
+
if value && !(value.to_s =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/)
|
|
116
|
+
if truefalse[value]
|
|
111
117
|
attributes << %w[t b]
|
|
112
|
-
|
|
113
|
-
elsif error_code.include?(
|
|
118
|
+
value = truefalse[value]
|
|
119
|
+
elsif error_code.include?(value)
|
|
114
120
|
attributes << %w[t e]
|
|
115
121
|
else
|
|
116
122
|
attributes << %w[t str]
|
|
117
123
|
end
|
|
118
124
|
end
|
|
125
|
+
|
|
119
126
|
worksheet.writer.tag_elements('c', attributes) do
|
|
120
127
|
worksheet.write_cell_formula(token)
|
|
121
|
-
worksheet.write_cell_value(
|
|
128
|
+
worksheet.write_cell_value(value || 0)
|
|
122
129
|
end
|
|
123
130
|
end
|
|
124
131
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
class Worksheet
|
|
6
|
+
module CellDataManager
|
|
7
|
+
#
|
|
8
|
+
# Store a CellData object in the worksheet cell table.
|
|
9
|
+
#
|
|
10
|
+
def store_data_to_table(cell_data, row, col) # :nodoc:
|
|
11
|
+
@cell_data_store.store(cell_data, row, col)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
#
|
|
15
|
+
# Track row/col min/max used for range calculation.
|
|
16
|
+
#
|
|
17
|
+
def store_row_col_max_min_values(row, col)
|
|
18
|
+
store_row_max_min_values(row)
|
|
19
|
+
store_col_max_min_values(col)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#
|
|
23
|
+
# Add a string to the shared string table, if it isn't already there, and
|
|
24
|
+
# return the string index.
|
|
25
|
+
#
|
|
26
|
+
def shared_string_index(str) # :nodoc:
|
|
27
|
+
@workbook.shared_string_index(str)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# Returns a range of data from the worksheet cell table to be used in
|
|
32
|
+
# chart cached data. Strings are returned as SST ids and decoded in the
|
|
33
|
+
# workbook. Return nils for data that doesn't exist since Excel can chart
|
|
34
|
+
# series with data missing.
|
|
35
|
+
#
|
|
36
|
+
def get_range_data(row_start, col_start, row_end, col_end)
|
|
37
|
+
@cell_data_store.get_range_data(row_start, col_start, row_end, col_end)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def cell_data_store # :nodoc:
|
|
43
|
+
@cell_data_store
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
class Worksheet
|
|
6
|
+
class CellDataStore # :nodoc:
|
|
7
|
+
def initialize
|
|
8
|
+
@table = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def [](row)
|
|
12
|
+
@table[row]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def row(row)
|
|
16
|
+
@table[row]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def row?(row)
|
|
20
|
+
!@table[row].nil?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def store(cell_data, row, col)
|
|
24
|
+
@table[row] ||= []
|
|
25
|
+
@table[row][col] = cell_data
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def fetch(row, col)
|
|
29
|
+
return nil unless @table[row]
|
|
30
|
+
|
|
31
|
+
@table[row][col]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def get_range_data(row_start, col_start, row_end, col_end)
|
|
35
|
+
data = []
|
|
36
|
+
|
|
37
|
+
(row_start..row_end).each do |row_num|
|
|
38
|
+
unless row?(row_num)
|
|
39
|
+
data << nil
|
|
40
|
+
next
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
(col_start..col_end).each do |col_num|
|
|
44
|
+
cell = fetch(row_num, col_num)
|
|
45
|
+
data << (cell ? cell.data : nil)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
data
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def each_row
|
|
53
|
+
return enum_for(__method__) unless block_given?
|
|
54
|
+
|
|
55
|
+
@table.each_with_index do |row_data, row_num|
|
|
56
|
+
yield row_num, row_data if row_data
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|