write_xlsx 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +3 -0
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/examples/formats.rb +498 -0
- data/lib/write_xlsx/chart.rb +15 -15
- data/lib/write_xlsx/chartsheet.rb +1 -1
- data/lib/write_xlsx/format.rb +10 -3
- data/lib/write_xlsx/package/comments.rb +171 -27
- data/lib/write_xlsx/package/packager.rb +8 -17
- data/lib/write_xlsx/package/shared_strings.rb +36 -15
- data/lib/write_xlsx/package/styles.rb +2 -12
- data/lib/write_xlsx/package/vml.rb +14 -22
- data/lib/write_xlsx/utility.rb +53 -4
- data/lib/write_xlsx/workbook.rb +21 -37
- data/lib/write_xlsx/worksheet.rb +533 -765
- data/test/helper.rb +10 -3
- data/test/package/comments/test_write_text_t.rb +1 -1
- data/test/package/shared_strings/test_shared_strings01.rb +3 -3
- data/test/package/shared_strings/test_shared_strings02.rb +3 -3
- data/test/package/shared_strings/test_write_sst.rb +3 -2
- data/test/package/vml/test_write_anchor.rb +1 -1
- data/test/package/vml/test_write_auto_fill.rb +1 -1
- data/test/package/vml/test_write_column.rb +1 -1
- data/test/package/vml/test_write_div.rb +1 -1
- data/test/package/vml/test_write_fill.rb +1 -1
- data/test/package/vml/test_write_idmap.rb +1 -1
- data/test/package/vml/test_write_move_with_cells.rb +1 -1
- data/test/package/vml/test_write_path.rb +2 -2
- data/test/package/vml/test_write_row.rb +1 -1
- data/test/package/vml/test_write_shadow.rb +1 -1
- data/test/package/vml/test_write_shapelayout.rb +1 -1
- data/test/package/vml/test_write_shapetype.rb +1 -1
- data/test/package/vml/test_write_size_with_cells.rb +1 -1
- data/test/package/vml/test_write_stroke.rb +1 -1
- data/test/package/vml/test_write_textbox.rb +1 -1
- data/test/perl_output/formats.xlsx +0 -0
- data/test/perl_output/indent.xlsx +0 -0
- data/test/perl_output/merge4.xlsx +0 -0
- data/test/perl_output/merge5.xlsx +0 -0
- data/test/test_example_match.rb +482 -0
- data/test/worksheet/test_repeat_formula.rb +5 -5
- data/test/worksheet/test_write_cell.rb +10 -5
- data/test/worksheet/test_write_legacy_drawing.rb +1 -1
- data/write_xlsx.gemspec +5 -5
- metadata +15 -15
- data/test/package/comments/test_comments01.rb +0 -36
- data/test/package/vml/test_vml_01.rb +0 -42
data/lib/write_xlsx/worksheet.rb
CHANGED
@@ -102,6 +102,156 @@ module Writexlsx
|
|
102
102
|
class Worksheet
|
103
103
|
include Writexlsx::Utility
|
104
104
|
|
105
|
+
class CellData # :nodoc:
|
106
|
+
include Writexlsx::Utility
|
107
|
+
|
108
|
+
attr_reader :row, :col, :token, :xf
|
109
|
+
attr_reader :result, :range, :link_type, :url, :tip
|
110
|
+
|
111
|
+
#
|
112
|
+
# Write the <cell> element. This is the innermost loop so efficiency is
|
113
|
+
# important where possible.
|
114
|
+
#
|
115
|
+
def write_cell(worksheet) #:nodoc:
|
116
|
+
xf_index = 0
|
117
|
+
xf_index = xf.get_xf_index if xf.respond_to?(:get_xf_index)
|
118
|
+
|
119
|
+
attributes = ['r', xl_rowcol_to_cell(row, col)]
|
120
|
+
|
121
|
+
# Add the cell format index.
|
122
|
+
if xf_index != 0
|
123
|
+
attributes << 's' << xf_index
|
124
|
+
elsif worksheet.set_rows[row] && worksheet.set_rows[row][1]
|
125
|
+
row_xf = worksheet.set_rows[row][1]
|
126
|
+
attributes << 's' << row_xf.get_xf_index
|
127
|
+
elsif worksheet.col_formats[col]
|
128
|
+
col_xf = worksheet.col_formats[col]
|
129
|
+
attributes << 's' << col_xf.get_xf_index
|
130
|
+
end
|
131
|
+
attributes
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class NumberCellData < CellData # :nodoc:
|
136
|
+
def initialize(row, col, num, xf)
|
137
|
+
@row, @col, @token, @xf = row, col, num, xf
|
138
|
+
end
|
139
|
+
|
140
|
+
def data
|
141
|
+
@token
|
142
|
+
end
|
143
|
+
|
144
|
+
def write_cell(worksheet)
|
145
|
+
attributes = super(worksheet)
|
146
|
+
worksheet.writer.start_tag('c', attributes)
|
147
|
+
worksheet.write_cell_value(token)
|
148
|
+
worksheet.writer.end_tag('c')
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class StringCellData < CellData # :nodoc:
|
153
|
+
def initialize(row, col, index, xf)
|
154
|
+
@row, @col, @token, @xf = row, col, index, xf
|
155
|
+
end
|
156
|
+
|
157
|
+
def data
|
158
|
+
{ :sst_id => token }
|
159
|
+
end
|
160
|
+
|
161
|
+
def write_cell(worksheet)
|
162
|
+
attributes = super(worksheet)
|
163
|
+
attributes << 't' << 's'
|
164
|
+
worksheet.writer.start_tag('c', attributes)
|
165
|
+
worksheet.write_cell_value(token)
|
166
|
+
worksheet.writer.end_tag('c')
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
class FormulaCellData < CellData # :nodoc:
|
171
|
+
def initialize(row, col, formula, xf, result)
|
172
|
+
@row, @col, @token, @xf, @result = row, col, formula, xf, result
|
173
|
+
end
|
174
|
+
|
175
|
+
def data
|
176
|
+
@result || 0
|
177
|
+
end
|
178
|
+
|
179
|
+
def write_cell(worksheet)
|
180
|
+
attributes = super(worksheet)
|
181
|
+
worksheet.writer.start_tag('c', attributes)
|
182
|
+
worksheet.write_cell_formula(token)
|
183
|
+
worksheet.write_cell_value(result || 0)
|
184
|
+
worksheet.writer.end_tag('c')
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class FormulaArrayCellData < CellData # :nodoc:
|
189
|
+
def initialize(row, col, formula, xf, range, result)
|
190
|
+
@row, @col, @token, @xf, @range, @result = row, col, formula, xf, range, result
|
191
|
+
end
|
192
|
+
|
193
|
+
def data
|
194
|
+
@result || 0
|
195
|
+
end
|
196
|
+
|
197
|
+
def write_cell(worksheet)
|
198
|
+
attributes = super(worksheet)
|
199
|
+
worksheet.writer.start_tag('c', attributes)
|
200
|
+
worksheet.write_cell_array_formula(token, range)
|
201
|
+
worksheet.write_cell_value(result)
|
202
|
+
worksheet.writer.end_tag('c')
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class HyperlinkCellData < CellData # :nodoc:
|
207
|
+
def initialize(row, col, index, xf, link_type, url, str, tip)
|
208
|
+
@row, @col, @token, @xf, @link_type, @url, @str, @tip =
|
209
|
+
row, col, index, xf, link_type, url, str, tip
|
210
|
+
end
|
211
|
+
|
212
|
+
def data
|
213
|
+
{ :sst_id => token }
|
214
|
+
end
|
215
|
+
|
216
|
+
def write_cell(worksheet)
|
217
|
+
attributes = super(worksheet)
|
218
|
+
attributes << 't' << 's'
|
219
|
+
worksheet.writer.start_tag('c', attributes)
|
220
|
+
worksheet.write_cell_value(token)
|
221
|
+
worksheet.writer.end_tag('c')
|
222
|
+
|
223
|
+
if link_type == 1
|
224
|
+
# External link with rel file relationship.
|
225
|
+
worksheet.hlink_count += 1
|
226
|
+
worksheet.hlink_refs <<
|
227
|
+
[
|
228
|
+
link_type, row, col,
|
229
|
+
worksheet.hlink_count, @str, @tip
|
230
|
+
]
|
231
|
+
|
232
|
+
worksheet.external_hyper_links << [ '/hyperlink', @url, 'External' ]
|
233
|
+
elsif link_type
|
234
|
+
# External link with rel file relationship.
|
235
|
+
worksheet.hlink_refs << [link_type, row, col, @url, @str, @tip ]
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
class BlankCellData < CellData # :nodoc:
|
241
|
+
def initialize(row, col, index, xf)
|
242
|
+
@row, @col, @xf = row, col, xf
|
243
|
+
end
|
244
|
+
|
245
|
+
def data
|
246
|
+
''
|
247
|
+
end
|
248
|
+
|
249
|
+
def write_cell(worksheet)
|
250
|
+
attributes = super(worksheet)
|
251
|
+
worksheet.writer.empty_tag('c', attributes)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
105
255
|
class PrintStyle # :nodoc:
|
106
256
|
attr_accessor :margin_left, :margin_right, :margin_top, :margin_bottom # :nodoc:
|
107
257
|
attr_accessor :margin_header, :margin_footer # :nodoc:
|
@@ -109,6 +259,7 @@ module Writexlsx
|
|
109
259
|
attr_accessor :hbreaks, :vbreaks, :scale # :nodoc:
|
110
260
|
attr_accessor :fit_page, :fit_width, :fit_height, :page_setup_changed # :nodoc:
|
111
261
|
attr_accessor :across # :nodoc:
|
262
|
+
attr_writer :orientation
|
112
263
|
|
113
264
|
def initialize # :nodoc:
|
114
265
|
@margin_left = 0.7
|
@@ -128,6 +279,7 @@ module Writexlsx
|
|
128
279
|
@fit_height = nil
|
129
280
|
@page_setup_changed = false
|
130
281
|
@across = false
|
282
|
+
@orientation = true
|
131
283
|
end
|
132
284
|
|
133
285
|
def attributes # :nodoc:
|
@@ -140,18 +292,20 @@ module Writexlsx
|
|
140
292
|
'footer', @margin_footer
|
141
293
|
]
|
142
294
|
end
|
143
|
-
end
|
144
295
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
296
|
+
def orientation?
|
297
|
+
!!@orientation
|
298
|
+
end
|
299
|
+
end
|
149
300
|
|
150
|
-
attr_reader :index
|
151
|
-
attr_reader :charts, :images, :drawing
|
152
|
-
attr_reader :external_hyper_links, :external_drawing_links
|
153
|
-
attr_reader :
|
154
|
-
attr_reader :
|
301
|
+
attr_reader :index # :nodoc:
|
302
|
+
attr_reader :charts, :images, :drawing # :nodoc:
|
303
|
+
attr_reader :external_hyper_links, :external_drawing_links # :nodoc:
|
304
|
+
attr_reader :external_comment_links, :drawing_links # :nodoc:
|
305
|
+
attr_reader :vml_data_id # :nodoc:
|
306
|
+
attr_reader :autofilter_area # :nodoc:
|
307
|
+
attr_reader :writer, :set_rows, :col_formats # :nodoc:
|
308
|
+
attr_accessor :vml_shape_id, :hlink_count, :hlink_refs # :nodoc:
|
155
309
|
|
156
310
|
def initialize(workbook, index, name) #:nodoc:
|
157
311
|
@writer = Package::XMLWriterSimple.new
|
@@ -160,18 +314,15 @@ module Writexlsx
|
|
160
314
|
@index = index
|
161
315
|
@name = name
|
162
316
|
@colinfo = []
|
163
|
-
@
|
317
|
+
@cell_data_table = {}
|
164
318
|
@filter_on = false
|
165
319
|
|
166
320
|
@print_style = PrintStyle.new
|
167
|
-
|
321
|
+
|
168
322
|
@print_area = ''
|
169
323
|
|
170
324
|
@screen_gridlines = true
|
171
325
|
@show_zeros = true
|
172
|
-
@xls_rowmax = RowMax
|
173
|
-
@xls_colmax = ColMax
|
174
|
-
@xls_strmax = StrMax
|
175
326
|
@dim_rowmin = nil
|
176
327
|
@dim_rowmax = nil
|
177
328
|
@dim_colmin = nil
|
@@ -181,8 +332,6 @@ module Writexlsx
|
|
181
332
|
|
182
333
|
@tab_color = 0
|
183
334
|
|
184
|
-
@orientation = true
|
185
|
-
|
186
335
|
@set_cols = {}
|
187
336
|
@set_rows = {}
|
188
337
|
@zoom = 100
|
@@ -214,8 +363,7 @@ module Writexlsx
|
|
214
363
|
|
215
364
|
@merge = []
|
216
365
|
|
217
|
-
@
|
218
|
-
@comments = {}
|
366
|
+
@comments = Package::Comments.new(self)
|
219
367
|
|
220
368
|
@validations = []
|
221
369
|
|
@@ -476,7 +624,7 @@ module Writexlsx
|
|
476
624
|
# and last_col are optional.
|
477
625
|
#
|
478
626
|
# If set_column() is applied to a single column the value of first_col
|
479
|
-
# and last_col should be the same. In the case where
|
627
|
+
# and last_col should be the same. In the case where last_col is zero
|
480
628
|
# it is set to the same value as first_col.
|
481
629
|
#
|
482
630
|
# It is also possible, and generally clearer, to specify a column range
|
@@ -614,7 +762,7 @@ module Writexlsx
|
|
614
762
|
#
|
615
763
|
# This method can be used to specify which cell or cells are selected
|
616
764
|
# in a worksheet. The most common requirement is to select a single cell,
|
617
|
-
# in which case
|
765
|
+
# in which case last_row and last_col can be omitted. The active cell
|
618
766
|
# within a selected range is determined by the order in which first and
|
619
767
|
# last are specified. It is also possible to specify a cell or a range
|
620
768
|
# using A1 notation. See the note about "Cell notation".
|
@@ -666,7 +814,7 @@ module Writexlsx
|
|
666
814
|
# that the splitter bars are not visible. This is the same as the
|
667
815
|
# Window->Freeze Panes menu command in Excel
|
668
816
|
#
|
669
|
-
# The parameters
|
817
|
+
# The parameters row and col are used to specify the location of
|
670
818
|
# the split. It should be noted that the split is specified at the
|
671
819
|
# top or left of a cell and that the method uses zero based indexing.
|
672
820
|
# Therefore to freeze the first row of a worksheet it is necessary
|
@@ -674,7 +822,7 @@ module Writexlsx
|
|
674
822
|
# This might lead you to think that you are using a 1 based index
|
675
823
|
# but this is not the case.
|
676
824
|
#
|
677
|
-
# You can set one of the
|
825
|
+
# You can set one of the row and col parameters as zero if you
|
678
826
|
# do not want either a vertical or horizontal split.
|
679
827
|
#
|
680
828
|
# Examples:
|
@@ -686,16 +834,16 @@ module Writexlsx
|
|
686
834
|
# worksheet.freeze_panes(1, 2) # Freeze first row and first 2 columns
|
687
835
|
# worksheet.freeze_panes('C2') # Same using A1 notation
|
688
836
|
#
|
689
|
-
# The parameters
|
837
|
+
# The parameters top_row and left_col are optional. They are used
|
690
838
|
# to specify the top-most or left-most visible row or column in the
|
691
839
|
# scrolling region of the panes. For example to freeze the first row
|
692
840
|
# and to have the scrolling region begin at row twenty:
|
693
841
|
#
|
694
842
|
# worksheet.freeze_panes(1, 0, 20, 0)
|
695
843
|
#
|
696
|
-
# You cannot use A1 notation for the
|
844
|
+
# You cannot use A1 notation for the top_row and left_col parameters.
|
697
845
|
#
|
698
|
-
# See also the panes.
|
846
|
+
# See also the panes.rb program in the examples directory of the
|
699
847
|
# distribution.
|
700
848
|
#
|
701
849
|
def freeze_panes(*args)
|
@@ -722,7 +870,7 @@ module Writexlsx
|
|
722
870
|
# file format and isn't sufficient to describe all cases of split panes.
|
723
871
|
# It should probably be something like:
|
724
872
|
#
|
725
|
-
# split_panes(
|
873
|
+
# split_panes(y, x, top_row, left_col, offset_row, offset_col)
|
726
874
|
#
|
727
875
|
# I'll look at changing this if it becomes an issue.
|
728
876
|
#++
|
@@ -731,15 +879,15 @@ module Writexlsx
|
|
731
879
|
# method in that the splits between the panes will be visible to the user
|
732
880
|
# and each pane will have its own scroll bars.
|
733
881
|
#
|
734
|
-
# The parameters
|
735
|
-
# position of the split. The units for
|
882
|
+
# The parameters y and x are used to specify the vertical and horizontal
|
883
|
+
# position of the split. The units for y and x are the same as those
|
736
884
|
# used by Excel to specify row height and column width. However, the
|
737
885
|
# vertical and horizontal units are different from each other. Therefore
|
738
|
-
# you must specify the
|
886
|
+
# you must specify the y and x parameters in terms of the row heights
|
739
887
|
# and column widths that you have set or the default values which are 15
|
740
888
|
# for a row and 8.43 for a column.
|
741
889
|
#
|
742
|
-
# You can set one of the
|
890
|
+
# You can set one of the y and x parameters as zero if you do not want
|
743
891
|
# either a vertical or horizontal split. The parameters top_row and left_col
|
744
892
|
# are optional. They are used to specify the top-most or left-most visible
|
745
893
|
# row or column in the bottom-right pane.
|
@@ -766,7 +914,7 @@ module Writexlsx
|
|
766
914
|
# need to call this method.
|
767
915
|
#
|
768
916
|
def set_portrait
|
769
|
-
@orientation = true
|
917
|
+
@print_style.orientation = true
|
770
918
|
@print_style.page_setup_changed = true
|
771
919
|
end
|
772
920
|
|
@@ -774,7 +922,7 @@ module Writexlsx
|
|
774
922
|
# Set the page orientation as landscape.
|
775
923
|
#
|
776
924
|
def set_landscape
|
777
|
-
@orientation = false
|
925
|
+
@print_style.orientation = false
|
778
926
|
@print_style.page_setup_changed = true
|
779
927
|
end
|
780
928
|
|
@@ -796,7 +944,7 @@ module Writexlsx
|
|
796
944
|
# worksheet1.set_tab_color('red')
|
797
945
|
# worksheet2.set_tab_color(0x0C)
|
798
946
|
#
|
799
|
-
# See the tab_colors.
|
947
|
+
# See the tab_colors.rb program in the examples directory of the distro.
|
800
948
|
#
|
801
949
|
def set_tab_color(color)
|
802
950
|
@tab_color = Colors.new.get_color(color)
|
@@ -1285,10 +1433,6 @@ module Writexlsx
|
|
1285
1433
|
@print_style.repeat_cols
|
1286
1434
|
end
|
1287
1435
|
|
1288
|
-
def print_area # :nodoc:
|
1289
|
-
@print_area.dup
|
1290
|
-
end
|
1291
|
-
|
1292
1436
|
#
|
1293
1437
|
# :call-seq:
|
1294
1438
|
# print_area(first_row, first_col, last_row, last_col)
|
@@ -1297,22 +1441,23 @@ module Writexlsx
|
|
1297
1441
|
# be printed. All four parameters must be specified. You can also use
|
1298
1442
|
# A1 notation, see the note about "Cell notation".
|
1299
1443
|
#
|
1300
|
-
#
|
1301
|
-
#
|
1302
|
-
#
|
1444
|
+
# worksheet1.print_area( 'A1:H20' ); # Cells A1 to H20
|
1445
|
+
# worksheet2.print_area( 0, 0, 19, 7 ); # The same
|
1446
|
+
# worksheet2.print_area( 'A:H' ); # Columns A to H if rows have data
|
1303
1447
|
#
|
1304
1448
|
def print_area(*args)
|
1305
|
-
return @print_area if args.empty?
|
1449
|
+
return @print_area.dup if args.empty?
|
1306
1450
|
row1, col1, row2, col2 = row_col_notation(args)
|
1307
1451
|
return if [row1, col1, row2, col2].include?(nil)
|
1308
1452
|
|
1309
1453
|
# Ignore max print area since this is the same as no print area for Excel.
|
1310
|
-
if row1 == 0 && col1 == 0 && row2 ==
|
1454
|
+
if row1 == 0 && col1 == 0 && row2 == ROW_MAX - 1 && col2 == COL_MAX - 1
|
1311
1455
|
return
|
1312
1456
|
end
|
1313
1457
|
|
1314
1458
|
# Build up the print area range "=Sheet2!R1C1:R2C1"
|
1315
1459
|
@print_area = convert_name_area(row1, col1, row2, col2)
|
1460
|
+
@print_area.dup
|
1316
1461
|
end
|
1317
1462
|
|
1318
1463
|
#
|
@@ -1321,7 +1466,7 @@ module Writexlsx
|
|
1321
1466
|
def set_zoom(scale = 100)
|
1322
1467
|
# Confine the scale to Excel's range
|
1323
1468
|
if scale < 10 or scale > 400
|
1324
|
-
# carp "Zoom factor
|
1469
|
+
# carp "Zoom factor scale outside range: 10 <= zoom <= 400"
|
1325
1470
|
scale = 100
|
1326
1471
|
end
|
1327
1472
|
|
@@ -1489,7 +1634,7 @@ module Writexlsx
|
|
1489
1634
|
# format.set_color('red')
|
1490
1635
|
# format.set_align('center')
|
1491
1636
|
#
|
1492
|
-
# worksheet.write(4, 0, 'Hello',
|
1637
|
+
# worksheet.write(4, 0, 'Hello', format) # Formatted string
|
1493
1638
|
#
|
1494
1639
|
# The write() method will ignore empty strings or nil tokens unless a format
|
1495
1640
|
# is also supplied. As such you needn't worry about special handling for
|
@@ -1864,14 +2009,14 @@ module Writexlsx
|
|
1864
2009
|
# This option is used to change the x offset, in pixels, of a comment
|
1865
2010
|
# within a cell:
|
1866
2011
|
#
|
1867
|
-
# worksheet.write_comment('C3',
|
2012
|
+
# worksheet.write_comment('C3', comment, :x_offset => 30)
|
1868
2013
|
#
|
1869
2014
|
# ===Option: y_offset
|
1870
2015
|
#
|
1871
2016
|
# This option is used to change the y offset, in pixels, of a comment
|
1872
2017
|
# within a cell:
|
1873
2018
|
#
|
1874
|
-
# worksheet.write_comment('C3',
|
2019
|
+
# worksheet.write_comment('C3', comment, :x_offset => 30)
|
1875
2020
|
#
|
1876
2021
|
# You can apply as many of these options as you require.
|
1877
2022
|
#
|
@@ -1882,7 +2027,7 @@ module Writexlsx
|
|
1882
2027
|
# mouse over them.
|
1883
2028
|
#
|
1884
2029
|
# Note about row height and comments. If you specify the height of a
|
1885
|
-
# row that contains a comment then
|
2030
|
+
# row that contains a comment then WriteXLSX will adjust the
|
1886
2031
|
# height of the comment to maintain the default or user specified
|
1887
2032
|
# dimensions. However, the height of a row can also be adjusted
|
1888
2033
|
# automatically by Excel if the text wrap property is set or large
|
@@ -1900,14 +2045,8 @@ module Writexlsx
|
|
1900
2045
|
check_dimensions(row, col)
|
1901
2046
|
store_row_col_max_min_values(row, col)
|
1902
2047
|
|
1903
|
-
@has_comments = true
|
1904
2048
|
# Process the properties of the cell comment.
|
1905
|
-
|
1906
|
-
@comments[row][col] = comment_params(row, col, string, options)
|
1907
|
-
else
|
1908
|
-
@comments[row] = {}
|
1909
|
-
@comments[row][col] = comment_params(row, col, string, options)
|
1910
|
-
end
|
2049
|
+
@comments.add(Package::Comment.new(@workbook, self, row, col, string, options))
|
1911
2050
|
end
|
1912
2051
|
|
1913
2052
|
#
|
@@ -1939,8 +2078,7 @@ module Writexlsx
|
|
1939
2078
|
check_dimensions(row, col)
|
1940
2079
|
store_row_col_max_min_values(row, col)
|
1941
2080
|
|
1942
|
-
store_data_to_table(row, col,
|
1943
|
-
0
|
2081
|
+
store_data_to_table(NumberCellData.new(row, col, num, xf))
|
1944
2082
|
end
|
1945
2083
|
|
1946
2084
|
#
|
@@ -1950,8 +2088,6 @@ module Writexlsx
|
|
1950
2088
|
# Write a string to the specified row and column (zero indexed).
|
1951
2089
|
# format is optional.
|
1952
2090
|
#
|
1953
|
-
# Returns 0 : normal termination
|
1954
|
-
#
|
1955
2091
|
# worksheet.write_string(0, 0, 'Your text here')
|
1956
2092
|
# worksheet.write_string('A2', 'or here')
|
1957
2093
|
#
|
@@ -1979,22 +2115,13 @@ module Writexlsx
|
|
1979
2115
|
row, col, str, xf = row_col_notation(args)
|
1980
2116
|
raise WriteXLSXInsufficientArgumentError if [row, col, str].include?(nil)
|
1981
2117
|
|
1982
|
-
type = 's' # The data type
|
1983
|
-
|
1984
2118
|
# Check that row and col are valid and store max and min values
|
1985
2119
|
check_dimensions(row, col)
|
1986
2120
|
store_row_col_max_min_values(row, col)
|
1987
2121
|
|
1988
|
-
|
1989
|
-
str_error = 0
|
1990
|
-
if str.length > @xls_strmax
|
1991
|
-
str = str[0, @xls_strmax]
|
1992
|
-
end
|
1993
|
-
|
1994
|
-
index = shared_string_index(str)
|
2122
|
+
index = shared_string_index(str[0, STR_MAX])
|
1995
2123
|
|
1996
|
-
store_data_to_table(row, col,
|
1997
|
-
str_error
|
2124
|
+
store_data_to_table(StringCellData.new(row, col, index, xf))
|
1998
2125
|
end
|
1999
2126
|
|
2000
2127
|
#
|
@@ -2115,7 +2242,7 @@ module Writexlsx
|
|
2115
2242
|
# If the first token is a string start the <r> element.
|
2116
2243
|
writer.start_tag('r') if !fragments[0].respond_to?(:get_xf_index)
|
2117
2244
|
|
2118
|
-
# Write the XML elements for the
|
2245
|
+
# Write the XML elements for the format string fragments.
|
2119
2246
|
fragments.each do |token|
|
2120
2247
|
if token.respond_to?(:get_xf_index)
|
2121
2248
|
# Write the font run.
|
@@ -2134,7 +2261,7 @@ module Writexlsx
|
|
2134
2261
|
# Add the XML string to the shared string table.
|
2135
2262
|
index = shared_string_index(writer.string)
|
2136
2263
|
|
2137
|
-
store_data_to_table(row, col,
|
2264
|
+
store_data_to_table(StringCellData.new(row, col, index, xf))
|
2138
2265
|
end
|
2139
2266
|
|
2140
2267
|
#
|
@@ -2172,13 +2299,11 @@ module Writexlsx
|
|
2172
2299
|
# Don't write a blank cell unless it has a format
|
2173
2300
|
return unless xf
|
2174
2301
|
|
2175
|
-
type = 'b' # The data type
|
2176
|
-
|
2177
2302
|
# Check that row and col are valid and store max and min values
|
2178
2303
|
check_dimensions(row, col)
|
2179
2304
|
store_row_col_max_min_values(row, col)
|
2180
2305
|
|
2181
|
-
store_data_to_table(row, col,
|
2306
|
+
store_data_to_table(BlankCellData.new(row, col, nil, xf))
|
2182
2307
|
end
|
2183
2308
|
|
2184
2309
|
#
|
@@ -2227,7 +2352,7 @@ module Writexlsx
|
|
2227
2352
|
|
2228
2353
|
formula.sub!(/^=/, '')
|
2229
2354
|
|
2230
|
-
store_data_to_table(row, col,
|
2355
|
+
store_data_to_table(FormulaCellData.new(row, col, formula, format, value))
|
2231
2356
|
end
|
2232
2357
|
|
2233
2358
|
#
|
@@ -2238,9 +2363,6 @@ module Writexlsx
|
|
2238
2363
|
#
|
2239
2364
|
# format is optional.
|
2240
2365
|
#
|
2241
|
-
# write_array_formula methods return:
|
2242
|
-
# Returns 0 : normal termination
|
2243
|
-
#
|
2244
2366
|
# In Excel an array formula is a formula that performs a calculation
|
2245
2367
|
# on a set of values. It can return a single value or a range of values.
|
2246
2368
|
#
|
@@ -2284,8 +2406,6 @@ module Writexlsx
|
|
2284
2406
|
row1, col1, row2, col2, formula, xf, value = row_col_notation(args)
|
2285
2407
|
raise WriteXLSXInsufficientArgumentError if [row1, col1, row2, col2, formula].include?(nil)
|
2286
2408
|
|
2287
|
-
type = 'a' # The data type
|
2288
|
-
|
2289
2409
|
# Swap last row/col with first row/col as necessary
|
2290
2410
|
row1, row2 = row2, row1 if row1 > row2
|
2291
2411
|
col1, col2 = col2, col1 if col1 > col2
|
@@ -2305,7 +2425,7 @@ module Writexlsx
|
|
2305
2425
|
formula.sub!(/^\{(.*)\}$/, '\1')
|
2306
2426
|
formula.sub!(/^=/, '')
|
2307
2427
|
|
2308
|
-
store_data_to_table(row1, col1,
|
2428
|
+
store_data_to_table(FormulaArrayCellData.new(row1, col1, formula, xf, range, value))
|
2309
2429
|
end
|
2310
2430
|
|
2311
2431
|
# The outline_settings() method is used to control the appearance of
|
@@ -2424,6 +2544,7 @@ module Writexlsx
|
|
2424
2544
|
def write_url(*args)
|
2425
2545
|
# Check for a cell reference in A1 notation and substitute row and column
|
2426
2546
|
row, col, url, xf, str, tip = row_col_notation(args)
|
2547
|
+
xf, str = str, xf if str.respond_to?(:xf_index)
|
2427
2548
|
raise WriteXLSXInsufficientArgumentError if [row, col, url].include?(nil)
|
2428
2549
|
|
2429
2550
|
type = 'l' # XML data type
|
@@ -2455,14 +2576,8 @@ module Writexlsx
|
|
2455
2576
|
check_dimensions(row, col)
|
2456
2577
|
store_row_col_max_min_values(row, col)
|
2457
2578
|
|
2458
|
-
# Check that the string is < 32767 chars
|
2459
|
-
str_error = 0
|
2460
|
-
if str.bytesize > @xls_strmax
|
2461
|
-
str = str[0, @xls_strmax]
|
2462
|
-
end
|
2463
|
-
|
2464
2579
|
# Store the URL displayed text in the shared string table.
|
2465
|
-
index = shared_string_index(str)
|
2580
|
+
index = shared_string_index(str[0, STR_MAX])
|
2466
2581
|
|
2467
2582
|
# External links to URLs and to other Excel workbooks have slightly
|
2468
2583
|
# different characteristics that we have to account for.
|
@@ -2484,9 +2599,7 @@ module Writexlsx
|
|
2484
2599
|
link_type = 1
|
2485
2600
|
end
|
2486
2601
|
|
2487
|
-
store_data_to_table(row, col,
|
2488
|
-
|
2489
|
-
return str_error
|
2602
|
+
store_data_to_table(HyperlinkCellData.new(row, col, index, xf, link_type, url, str, tip))
|
2490
2603
|
end
|
2491
2604
|
|
2492
2605
|
#
|
@@ -2494,7 +2607,7 @@ module Writexlsx
|
|
2494
2607
|
# write_date_time (row, col, date_string [ , format ] )
|
2495
2608
|
#
|
2496
2609
|
# Write a datetime string in ISO8601 "yyyy-mm-ddThh:mm:ss.ss" format as a
|
2497
|
-
# number representing an Excel date.
|
2610
|
+
# number representing an Excel date. format is optional.
|
2498
2611
|
#
|
2499
2612
|
# The write_date_time() method can be used to write a date or time
|
2500
2613
|
# to the cell specified by row and column:
|
@@ -2508,7 +2621,7 @@ module Writexlsx
|
|
2508
2621
|
# This conforms to an ISO8601 date but it should be noted that the
|
2509
2622
|
# full range of ISO8601 formats are not supported.
|
2510
2623
|
#
|
2511
|
-
# The following variations on the
|
2624
|
+
# The following variations on the date_string parameter are permitted:
|
2512
2625
|
#
|
2513
2626
|
# yyyy-mm-ddThh:mm:ss.sss # Standard format
|
2514
2627
|
# yyyy-mm-ddT # No time
|
@@ -2519,7 +2632,7 @@ module Writexlsx
|
|
2519
2632
|
#
|
2520
2633
|
# Note that the T is required in all cases.
|
2521
2634
|
#
|
2522
|
-
# A date should always have a
|
2635
|
+
# A date should always have a format, otherwise it will appear
|
2523
2636
|
# as a number, see "DATES AND TIME IN EXCEL" and "CELL FORMATTING".
|
2524
2637
|
# Here is a typical example:
|
2525
2638
|
#
|
@@ -2537,20 +2650,15 @@ module Writexlsx
|
|
2537
2650
|
row, col, str, xf = row_col_notation(args)
|
2538
2651
|
raise WriteXLSXInsufficientArgumentError if [row, col, str].include?(nil)
|
2539
2652
|
|
2540
|
-
type = 'n' # The data type
|
2541
|
-
|
2542
2653
|
# Check that row and col are valid and store max and min values
|
2543
2654
|
check_dimensions(row, col)
|
2544
2655
|
store_row_col_max_min_values(row, col)
|
2545
2656
|
|
2546
|
-
str_error = 0
|
2547
2657
|
date_time = convert_date_time(str)
|
2548
2658
|
|
2549
2659
|
# If the date isn't valid then write it as a string.
|
2550
2660
|
return write_string(args) unless date_time
|
2551
|
-
store_data_to_table(row, col,
|
2552
|
-
|
2553
|
-
str_error
|
2661
|
+
store_data_to_table(NumberCellData.new(row, col, date_time, xf))
|
2554
2662
|
end
|
2555
2663
|
|
2556
2664
|
#
|
@@ -2632,7 +2740,7 @@ module Writexlsx
|
|
2632
2740
|
# cell. This can be occasionally useful if you wish to align two or more
|
2633
2741
|
# images relative to the same cell.
|
2634
2742
|
#
|
2635
|
-
# The parameters
|
2743
|
+
# The parameters scale_x and scale_y can be used to scale the inserted
|
2636
2744
|
# image horizontally and vertically:
|
2637
2745
|
#
|
2638
2746
|
# # Scale the inserted image: width x 2.0, height x 0.8
|
@@ -2712,7 +2820,7 @@ module Writexlsx
|
|
2712
2820
|
end
|
2713
2821
|
|
2714
2822
|
#
|
2715
|
-
# convert_date_time(
|
2823
|
+
# convert_date_time(date_time_string)
|
2716
2824
|
#
|
2717
2825
|
# The function takes a date and time in ISO8601 "yyyy-mm-ddThh:mm:ss.ss" format
|
2718
2826
|
# and converts it to a decimal number representing a valid Excel date.
|
@@ -2852,14 +2960,14 @@ module Writexlsx
|
|
2852
2960
|
# don't have a format. For example
|
2853
2961
|
#
|
2854
2962
|
# worksheet.set_row(0, nil, format1) # Set the format for row 1
|
2855
|
-
# worksheet.write('A1', 'Hello') # Defaults to
|
2856
|
-
# worksheet.write('B1', 'Hello', format2) # Keeps
|
2963
|
+
# worksheet.write('A1', 'Hello') # Defaults to format1
|
2964
|
+
# worksheet.write('B1', 'Hello', format2) # Keeps format2
|
2857
2965
|
#
|
2858
2966
|
# If you wish to define a row format in this way you should call the
|
2859
2967
|
# method before any calls to write(). Calling it afterwards will overwrite
|
2860
2968
|
# any format that was previously specified.
|
2861
2969
|
#
|
2862
|
-
# The
|
2970
|
+
# The hidden parameter should be set to 1 if you wish to hide a row.
|
2863
2971
|
# This can be used, for example, to hide intermediary steps in a
|
2864
2972
|
# complicated calculation:
|
2865
2973
|
#
|
@@ -2892,7 +3000,7 @@ module Writexlsx
|
|
2892
3000
|
# programs in the examples directory of the distro.
|
2893
3001
|
#
|
2894
3002
|
# Excel allows up to 7 outline levels. Therefore the level parameter
|
2895
|
-
# should be in the range 0 <=
|
3003
|
+
# should be in the range 0 <= level <= 7.
|
2896
3004
|
#
|
2897
3005
|
def set_row(*args)
|
2898
3006
|
row = args[0]
|
@@ -2931,7 +3039,7 @@ module Writexlsx
|
|
2931
3039
|
end
|
2932
3040
|
|
2933
3041
|
#
|
2934
|
-
# merge_range(
|
3042
|
+
# merge_range(first_row, first_col, last_row, last_col, string, format)
|
2935
3043
|
#
|
2936
3044
|
# Merge a range of cells. The first cell should contain the data and the others
|
2937
3045
|
# should be blank. All cells should contain the same format.
|
@@ -3029,9 +3137,6 @@ module Writexlsx
|
|
3029
3137
|
# conditional_formatting(row, col, {...})
|
3030
3138
|
# conditional_formatting(first_row, first_col, last_row, last_col, {...})
|
3031
3139
|
#
|
3032
|
-
# conditional_formatting methods return:
|
3033
|
-
# Returns 0 : normal termination
|
3034
|
-
#
|
3035
3140
|
# The conditional_format() method is used to add formatting to a cell
|
3036
3141
|
# or range of cells based on user defined criteria.
|
3037
3142
|
#
|
@@ -3040,14 +3145,14 @@ module Writexlsx
|
|
3040
3145
|
# :type => 'cell',
|
3041
3146
|
# :criteria => '>=',
|
3042
3147
|
# :value => 50,
|
3043
|
-
# :format =>
|
3148
|
+
# :format => format1
|
3044
3149
|
# }
|
3045
3150
|
# )
|
3046
3151
|
#
|
3047
3152
|
# This method contains a lot of parameters and is described in detail in
|
3048
3153
|
# a separate section "CONDITIONAL FORMATTING IN EXCEL".
|
3049
3154
|
#
|
3050
|
-
# See also the conditional_format.
|
3155
|
+
# See also the conditional_format.rb program in the examples directory of the distro
|
3051
3156
|
#
|
3052
3157
|
def conditional_formatting(*args)
|
3053
3158
|
# Check for a cell reference in A1 notation and substitute row and column
|
@@ -3486,7 +3591,7 @@ module Writexlsx
|
|
3486
3591
|
# :input_title => 'Enter an integer:',
|
3487
3592
|
# :input_message => 'between 1 and 100',
|
3488
3593
|
# });
|
3489
|
-
# See also the data_validate.
|
3594
|
+
# See also the data_validate.rb program in the examples directory
|
3490
3595
|
# of the distro.
|
3491
3596
|
#
|
3492
3597
|
def data_validation(*args)
|
@@ -3502,111 +3607,38 @@ module Writexlsx
|
|
3502
3607
|
end
|
3503
3608
|
raise WriteXLSXInsufficientArgumentError if [row1, col1, row2, col2, param].include?(nil)
|
3504
3609
|
|
3505
|
-
# Check that row and col are valid without storing the values.
|
3506
3610
|
check_dimensions(row1, col1)
|
3507
3611
|
check_dimensions(row2, col2)
|
3508
3612
|
|
3509
|
-
|
3510
|
-
param.each_key do |param_key|
|
3511
|
-
unless valid_validation_parameter.include?(param_key)
|
3512
|
-
raise WriteXLSXOptionParameterError, "Unknown parameter '#{param_key}' in data_validation()"
|
3513
|
-
end
|
3514
|
-
end
|
3613
|
+
check_for_valid_input_params(param)
|
3515
3614
|
|
3516
|
-
# Map alternative parameter names 'source' or 'minimum' to 'value'.
|
3517
3615
|
param[:value] = param[:source] if param[:source]
|
3518
3616
|
param[:value] = param[:minimum] if param[:minimum]
|
3519
3617
|
|
3520
|
-
|
3521
|
-
unless param.has_key?(:validate)
|
3522
|
-
raise WriteXLSXOptionParameterError, "Parameter :validate is required in data_validation()"
|
3523
|
-
end
|
3524
|
-
|
3525
|
-
# Check for valid validation types.
|
3526
|
-
unless valid_validation_type.has_key?(param[:validate].downcase)
|
3527
|
-
raise WriteXLSXOptionParameterError,
|
3528
|
-
"Unknown validation type '#{param[:validate]}' for parameter :validate in data_validation()"
|
3529
|
-
else
|
3530
|
-
param[:validate] = valid_validation_type[param[:validate].downcase]
|
3531
|
-
end
|
3532
|
-
|
3533
|
-
# No action is required for validation type 'any'.
|
3534
|
-
# TODO: we should perhaps store 'any' for message only validations.
|
3618
|
+
param[:validate] = valid_validation_type[param[:validate].downcase]
|
3535
3619
|
return if param[:validate] == 'none'
|
3536
|
-
|
3537
|
-
# The list and custom validations don't have a criteria so we use a default
|
3538
|
-
# of 'between'.
|
3539
|
-
if param[:validate] == 'list' || param[:validate] == 'custom'
|
3620
|
+
if ['list', 'custom'].include?(param[:validate])
|
3540
3621
|
param[:criteria] = 'between'
|
3541
3622
|
param[:maximum] = nil
|
3542
3623
|
end
|
3543
3624
|
|
3544
|
-
|
3545
|
-
|
3546
|
-
|
3547
|
-
end
|
3548
|
-
|
3549
|
-
# List of valid criteria types.
|
3550
|
-
criteria_type = valid_criteria_type
|
3551
|
-
|
3552
|
-
# Check for valid criteria types.
|
3553
|
-
unless criteria_type.has_key?(param[:criteria].downcase)
|
3554
|
-
raise WriteXLSXOptionParameterError,
|
3555
|
-
"Unknown criteria type '#{param[:criteria]}' for parameter :criteria in data_validation()"
|
3556
|
-
else
|
3557
|
-
param[:criteria] = criteria_type[param[:criteria].downcase]
|
3558
|
-
end
|
3559
|
-
|
3560
|
-
# 'Between' and 'Not between' criteria require 2 values.
|
3561
|
-
if param[:criteria] == 'between' || param[:criteria] == 'notBetween'
|
3562
|
-
unless param.has_key?(:maximum)
|
3563
|
-
raise WriteXLSXOptionParameterError,
|
3564
|
-
"Parameter :maximum is required in data_validation() when using :between or :not between criteria"
|
3565
|
-
end
|
3566
|
-
else
|
3567
|
-
param[:maximum] = nil
|
3568
|
-
end
|
3569
|
-
|
3570
|
-
# List of valid error dialog types.
|
3571
|
-
error_type = {
|
3572
|
-
'stop' => 0,
|
3573
|
-
'warning' => 1,
|
3574
|
-
'information' => 2
|
3575
|
-
}
|
3576
|
-
|
3577
|
-
# Check for valid error dialog types.
|
3578
|
-
if not param.has_key?(:error_type)
|
3579
|
-
param[:error_type] = 0
|
3580
|
-
elsif not error_type.has_key?(param[:error_type].downcase)
|
3581
|
-
raise WriteXLSXOptionParameterError,
|
3582
|
-
"Unknown criteria type '#param[:error_type}' for parameter :error_type in data_validation()"
|
3583
|
-
else
|
3584
|
-
param[:error_type] = error_type[param[:error_type].downcase]
|
3585
|
-
end
|
3625
|
+
check_criteria_required(param)
|
3626
|
+
check_valid_citeria_types(param)
|
3627
|
+
param[:criteria] = valid_criteria_type[param[:criteria].downcase]
|
3586
3628
|
|
3587
|
-
|
3588
|
-
|
3589
|
-
unless convert_date_time_value(param, :value) && convert_date_time_value(param, :maximum)
|
3590
|
-
raise WriteXLSXOptionParameterError, "Invalid date/time value."
|
3591
|
-
end
|
3592
|
-
end
|
3629
|
+
check_maximum_value_when_criteria_is_between_or_notbetween(param)
|
3630
|
+
param[:error_type] = param.has_key?(:error_type) ? error_type[param[:error_type].downcase] : 0
|
3593
3631
|
|
3594
|
-
|
3595
|
-
param
|
3596
|
-
param[:dropdown] = 1 unless param[:dropdown]
|
3597
|
-
param[:show_input] = 1 unless param[:show_input]
|
3598
|
-
param[:show_error] = 1 unless param[:show_error]
|
3632
|
+
convert_date_time_value_if_required(param)
|
3633
|
+
set_some_defaults(param)
|
3599
3634
|
|
3600
|
-
# These are the cells to which the validation is applied.
|
3601
3635
|
param[:cells] = [[row1, col1, row2, col2]]
|
3602
3636
|
|
3603
3637
|
# A (for now) undocumented parameter to pass additional cell ranges.
|
3604
|
-
if param.has_key?(:other_cells)
|
3605
|
-
param[:other_cells].each { |cells| param[:cells] << cells }
|
3606
|
-
end
|
3638
|
+
param[:other_cells].each { |cells| param[:cells] << cells } if param.has_key?(:other_cells)
|
3607
3639
|
|
3608
3640
|
# Store the validation information until we close the worksheet.
|
3609
|
-
@validations
|
3641
|
+
@validations << param
|
3610
3642
|
end
|
3611
3643
|
|
3612
3644
|
#
|
@@ -4010,8 +4042,12 @@ module Writexlsx
|
|
4010
4042
|
@comments_author = author if author
|
4011
4043
|
end
|
4012
4044
|
|
4045
|
+
def comments_count # :nodoc:
|
4046
|
+
@comments.size
|
4047
|
+
end
|
4048
|
+
|
4013
4049
|
def has_comments? # :nodoc:
|
4014
|
-
|
4050
|
+
!@comments.empty?
|
4015
4051
|
end
|
4016
4052
|
|
4017
4053
|
def is_chartsheet? # :nodoc:
|
@@ -4022,42 +4058,26 @@ module Writexlsx
|
|
4022
4058
|
# Turn the HoH that stores the comments into an array for easier handling
|
4023
4059
|
# and set the external links.
|
4024
4060
|
#
|
4025
|
-
def
|
4026
|
-
|
4027
|
-
|
4028
|
-
# We sort the comments by row and column but that isn't strictly required.
|
4029
|
-
@comments.keys.sort.each do |row|
|
4030
|
-
@comments[row].keys.sort.each do |col|
|
4031
|
-
# Set comment visibility if required and not already user defined.
|
4032
|
-
@comments[row][col][4] ||= 1 if @comments_visible
|
4033
|
-
|
4034
|
-
# Set comment author if not already user defined.
|
4035
|
-
@comments[row][col][3] ||= @comments_author
|
4036
|
-
comments << @comments[row][col]
|
4037
|
-
end
|
4038
|
-
end
|
4039
|
-
|
4040
|
-
@comments_array = comments
|
4041
|
-
|
4042
|
-
@external_comment_links <<
|
4043
|
-
['/vmlDrawing', "../drawings/vmlDrawing#{comment_id}.vml"] <<
|
4044
|
-
['/comments', "../comments#{comment_id}.xml"]
|
4045
|
-
|
4046
|
-
count = comments.size
|
4061
|
+
def set_vml_data_id(vml_data_id) # :nodoc:
|
4062
|
+
count = @comments.sorted_comments.size
|
4047
4063
|
start_data_id = vml_data_id
|
4048
4064
|
|
4049
4065
|
# The VML o:idmap data id contains a comma separated range when there is
|
4050
4066
|
# more than one 1024 block of comments, like this: data="1,2".
|
4051
4067
|
(1 .. (count / 1024)).each do |i|
|
4052
|
-
vml_data_id = "vml_data_id,#{start_data_id + i}"
|
4068
|
+
vml_data_id = "#{vml_data_id},#{start_data_id + i}"
|
4053
4069
|
end
|
4054
|
-
|
4055
|
-
@vml_data_id = vml_data_id
|
4056
|
-
@vml_shape_id = vml_shape_id
|
4070
|
+
@vml_data_id = vml_data_id
|
4057
4071
|
|
4058
4072
|
count
|
4059
4073
|
end
|
4060
4074
|
|
4075
|
+
def set_external_comment_links(comment_id) # :nodoc:
|
4076
|
+
@external_comment_links <<
|
4077
|
+
['/vmlDrawing', "../drawings/vmlDrawing#{comment_id}.vml"] <<
|
4078
|
+
['/comments', "../comments#{comment_id}.xml"]
|
4079
|
+
end
|
4080
|
+
|
4061
4081
|
#
|
4062
4082
|
# Set up chart/drawings.
|
4063
4083
|
#
|
@@ -4094,43 +4114,21 @@ module Writexlsx
|
|
4094
4114
|
# Return nils for data that doesn't exist since Excel can chart series
|
4095
4115
|
# with data missing.
|
4096
4116
|
#
|
4097
|
-
def get_range_data(row_start, col_start, row_end, col_end)
|
4117
|
+
def get_range_data(row_start, col_start, row_end, col_end) # :nodoc:
|
4098
4118
|
# TODO. Check for worksheet limits.
|
4099
4119
|
|
4100
4120
|
# Iterate through the table data.
|
4101
4121
|
data = []
|
4102
4122
|
(row_start .. row_end).each do |row_num|
|
4103
4123
|
# Store nil if row doesn't exist.
|
4104
|
-
if !@
|
4124
|
+
if !@cell_data_table[row_num]
|
4105
4125
|
data << nil
|
4106
4126
|
next
|
4107
4127
|
end
|
4108
4128
|
|
4109
4129
|
(col_start .. col_end).each do |col_num|
|
4110
|
-
if cell = @
|
4111
|
-
|
4112
|
-
token = cell[1]
|
4113
|
-
|
4114
|
-
data << case type
|
4115
|
-
when 'n'
|
4116
|
-
# Store a number.
|
4117
|
-
token
|
4118
|
-
when 's'
|
4119
|
-
# Store a string.
|
4120
|
-
{:sst_id => token}
|
4121
|
-
when 'f'
|
4122
|
-
# Store a formula.
|
4123
|
-
cell[3] || 0
|
4124
|
-
when 'a'
|
4125
|
-
# Store an array formula.
|
4126
|
-
cell[4] || 0
|
4127
|
-
when 'l'
|
4128
|
-
# Store the string part a hyperlink.
|
4129
|
-
{:sst_id => token}
|
4130
|
-
when 'b'
|
4131
|
-
# Store a empty cell.
|
4132
|
-
''
|
4133
|
-
end
|
4130
|
+
if cell = @cell_data_table[row_num][col_num]
|
4131
|
+
data << cell.data
|
4134
4132
|
else
|
4135
4133
|
# Store nil if col doesn't exist.
|
4136
4134
|
data << nil
|
@@ -4141,49 +4139,262 @@ module Writexlsx
|
|
4141
4139
|
return data
|
4142
4140
|
end
|
4143
4141
|
|
4144
|
-
|
4142
|
+
#
|
4143
|
+
# Calculate the vertices that define the position of a graphical object within
|
4144
|
+
# the worksheet in pixels.
|
4145
|
+
#
|
4146
|
+
# +------------+------------+
|
4147
|
+
# | A | B |
|
4148
|
+
# +-----+------------+------------+
|
4149
|
+
# | |(x1,y1) | |
|
4150
|
+
# | 1 |(A1)._______|______ |
|
4151
|
+
# | | | | |
|
4152
|
+
# | | | | |
|
4153
|
+
# +-----+----| BITMAP |-----+
|
4154
|
+
# | | | | |
|
4155
|
+
# | 2 | |______________. |
|
4156
|
+
# | | | (B2)|
|
4157
|
+
# | | | (x2,y2)|
|
4158
|
+
# +---- +------------+------------+
|
4159
|
+
#
|
4160
|
+
# Example of an object that covers some of the area from cell A1 to cell B2.
|
4161
|
+
#
|
4162
|
+
# Based on the width and height of the object we need to calculate 8 vars:
|
4163
|
+
#
|
4164
|
+
# col_start, row_start, col_end, row_end, x1, y1, x2, y2.
|
4165
|
+
#
|
4166
|
+
# We also calculate the absolute x and y position of the top left vertex of
|
4167
|
+
# the object. This is required for images.
|
4168
|
+
#
|
4169
|
+
# x_abs, y_abs
|
4170
|
+
#
|
4171
|
+
# The width and height of the cells that the object occupies can be variable
|
4172
|
+
# and have to be taken into account.
|
4173
|
+
#
|
4174
|
+
# The values of col_start and row_start are passed in from the calling
|
4175
|
+
# function. The values of col_end and row_end are calculated by subtracting
|
4176
|
+
# the width and height of the object from the width and height of the
|
4177
|
+
# underlying cells.
|
4178
|
+
#
|
4179
|
+
# col_start # Col containing upper left corner of object.
|
4180
|
+
# x1 # Distance to left side of object.
|
4181
|
+
# row_start # Row containing top left corner of object.
|
4182
|
+
# y1 # Distance to top of object.
|
4183
|
+
# col_end # Col containing lower right corner of object.
|
4184
|
+
# x2 # Distance to right side of object.
|
4185
|
+
# row_end # Row containing bottom right corner of object.
|
4186
|
+
# y2 # Distance to bottom of object.
|
4187
|
+
# width # Width of object frame.
|
4188
|
+
# height # Height of object frame.
|
4189
|
+
def position_object_pixels(col_start, row_start, x1, y1, width, height, is_drawing = false) #:nodoc:
|
4190
|
+
# Calculate the absolute x offset of the top-left vertex.
|
4191
|
+
if @col_size_changed
|
4192
|
+
x_abs = (1 .. col_start).inject(0) {|sum, col| sum += size_col(col)}
|
4193
|
+
else
|
4194
|
+
# Optimisation for when the column widths haven't changed.
|
4195
|
+
x_abs = 64 * col_start
|
4196
|
+
end
|
4197
|
+
x_abs += x1
|
4145
4198
|
|
4146
|
-
|
4147
|
-
|
4148
|
-
|
4149
|
-
|
4150
|
-
|
4151
|
-
|
4152
|
-
|
4153
|
-
|
4154
|
-
|
4155
|
-
:ignore_blank,
|
4156
|
-
:dropdown,
|
4157
|
-
:show_input,
|
4158
|
-
:input_title,
|
4159
|
-
:input_message,
|
4160
|
-
:show_error,
|
4161
|
-
:error_title,
|
4162
|
-
:error_message,
|
4163
|
-
:error_type,
|
4164
|
-
:other_cells
|
4165
|
-
]
|
4166
|
-
end
|
4199
|
+
# Calculate the absolute y offset of the top-left vertex.
|
4200
|
+
# Store the column change to allow optimisations.
|
4201
|
+
if @row_size_changed
|
4202
|
+
y_abs = (1 .. row_start).inject(0) {|sum, row| sum += size_row(row)}
|
4203
|
+
else
|
4204
|
+
# Optimisation for when the row heights haven't changed.
|
4205
|
+
y_abs = 20 * row_start
|
4206
|
+
end
|
4207
|
+
y_abs += y1
|
4167
4208
|
|
4168
|
-
|
4169
|
-
|
4170
|
-
|
4171
|
-
|
4172
|
-
|
4173
|
-
|
4174
|
-
|
4175
|
-
|
4176
|
-
|
4177
|
-
|
4178
|
-
|
4179
|
-
|
4209
|
+
# Adjust start column for offsets that are greater than the col width.
|
4210
|
+
x1, col_start = adjust_column_offset(x1, col_start)
|
4211
|
+
|
4212
|
+
# Adjust start row for offsets that are greater than the row height.
|
4213
|
+
y1, row_start = adjust_row_offset(y1, row_start)
|
4214
|
+
|
4215
|
+
# Initialise end cell to the same as the start cell.
|
4216
|
+
col_end = col_start
|
4217
|
+
row_end = row_start
|
4218
|
+
|
4219
|
+
width += x1
|
4220
|
+
height += y1
|
4221
|
+
|
4222
|
+
# Subtract the underlying cell widths to find the end cell of the object.
|
4223
|
+
width, col_end = adjust_column_offset(width, col_end)
|
4224
|
+
|
4225
|
+
# Subtract the underlying cell heights to find the end cell of the object.
|
4226
|
+
height, row_end = adjust_row_offset(height, row_end)
|
4227
|
+
|
4228
|
+
# The following is only required for positioning drawing/chart objects
|
4229
|
+
# and not comments. It is probably the result of a bug.
|
4230
|
+
if is_drawing
|
4231
|
+
col_end -= 1 if width == 0
|
4232
|
+
row_end -= 1 if height == 0
|
4233
|
+
end
|
4234
|
+
|
4235
|
+
# The end vertices are whatever is left from the width and height.
|
4236
|
+
x2 = width
|
4237
|
+
y2 = height
|
4238
|
+
|
4239
|
+
[col_start, row_start, x1, y1, col_end, row_end, x2, y2, x_abs, y_abs]
|
4240
|
+
end
|
4241
|
+
|
4242
|
+
def comments_visible? # :nodoc:
|
4243
|
+
!!@comments_visible
|
4244
|
+
end
|
4245
|
+
|
4246
|
+
def comments_xml_writer=(file) # :nodoc:
|
4247
|
+
@comments.set_xml_writer(file)
|
4248
|
+
end
|
4249
|
+
|
4250
|
+
def comments_assemble_xml_file # :nodoc:
|
4251
|
+
@comments.assemble_xml_file
|
4252
|
+
end
|
4253
|
+
|
4254
|
+
def comments_array # :nodoc:
|
4255
|
+
@comments.sorted_comments
|
4256
|
+
end
|
4257
|
+
|
4258
|
+
#
|
4259
|
+
# Write the cell value <v> element.
|
4260
|
+
#
|
4261
|
+
def write_cell_value(value = '') #:nodoc:
|
4262
|
+
value ||= ''
|
4263
|
+
value = value.to_i if value == value.to_i
|
4264
|
+
@writer.data_element('v', value)
|
4265
|
+
end
|
4266
|
+
|
4267
|
+
#
|
4268
|
+
# Write the cell formula <f> element.
|
4269
|
+
#
|
4270
|
+
def write_cell_formula(formula = '') #:nodoc:
|
4271
|
+
@writer.data_element('f', formula)
|
4272
|
+
end
|
4273
|
+
|
4274
|
+
#
|
4275
|
+
# Write the cell array formula <f> element.
|
4276
|
+
#
|
4277
|
+
def write_cell_array_formula(formula, range) #:nodoc:
|
4278
|
+
attributes = ['t', 'array', 'ref', range]
|
4279
|
+
|
4280
|
+
@writer.data_element('f', formula, attributes)
|
4281
|
+
end
|
4282
|
+
|
4283
|
+
private
|
4284
|
+
|
4285
|
+
def check_for_valid_input_params(param)
|
4286
|
+
param.each_key do |param_key|
|
4287
|
+
unless valid_validation_parameter.include?(param_key)
|
4288
|
+
raise WriteXLSXOptionParameterError, "Unknown parameter '#{param_key}' in data_validation()"
|
4289
|
+
end
|
4290
|
+
end
|
4291
|
+
unless param.has_key?(:validate)
|
4292
|
+
raise WriteXLSXOptionParameterError, "Parameter :validate is required in data_validation()"
|
4293
|
+
end
|
4294
|
+
unless valid_validation_type.has_key?(param[:validate].downcase)
|
4295
|
+
raise WriteXLSXOptionParameterError,
|
4296
|
+
"Unknown validation type '#{param[:validate]}' for parameter :validate in data_validation()"
|
4297
|
+
end
|
4298
|
+
if param[:error_type] && !error_type.has_key?(param[:error_type].downcase)
|
4299
|
+
raise WriteXLSXOptionParameterError,
|
4300
|
+
"Unknown criteria type '#param[:error_type}' for parameter :error_type in data_validation()"
|
4301
|
+
end
|
4302
|
+
end
|
4303
|
+
|
4304
|
+
def check_criteria_required(param)
|
4305
|
+
unless param.has_key?(:criteria)
|
4306
|
+
raise WriteXLSXOptionParameterError, "Parameter :criteria is required in data_validation()"
|
4307
|
+
end
|
4308
|
+
end
|
4309
|
+
|
4310
|
+
def check_valid_citeria_types(param)
|
4311
|
+
unless valid_criteria_type.has_key?(param[:criteria].downcase)
|
4312
|
+
raise WriteXLSXOptionParameterError,
|
4313
|
+
"Unknown criteria type '#{param[:criteria]}' for parameter :criteria in data_validation()"
|
4314
|
+
end
|
4315
|
+
end
|
4316
|
+
|
4317
|
+
def check_maximum_value_when_criteria_is_between_or_notbetween(param)
|
4318
|
+
if param[:criteria] == 'between' || param[:criteria] == 'notBetween'
|
4319
|
+
unless param.has_key?(:maximum)
|
4320
|
+
raise WriteXLSXOptionParameterError,
|
4321
|
+
"Parameter :maximum is required in data_validation() when using :between or :not between criteria"
|
4322
|
+
end
|
4323
|
+
else
|
4324
|
+
param[:maximum] = nil
|
4325
|
+
end
|
4326
|
+
end
|
4327
|
+
|
4328
|
+
def error_type
|
4329
|
+
{'stop' => 0, 'warning' => 1, 'information' => 2}
|
4330
|
+
end
|
4331
|
+
|
4332
|
+
def convert_date_time_value_if_required(param)
|
4333
|
+
if param[:validate] == 'date' || param[:validate] == 'time'
|
4334
|
+
unless convert_date_time_value(param, :value) && convert_date_time_value(param, :maximum)
|
4335
|
+
raise WriteXLSXOptionParameterError, "Invalid date/time value."
|
4336
|
+
end
|
4337
|
+
end
|
4338
|
+
end
|
4339
|
+
|
4340
|
+
def set_some_defaults(param)
|
4341
|
+
param[:ignore_blank] ||= 1
|
4342
|
+
param[:dropdown] ||= 1
|
4343
|
+
param[:show_input] ||= 1
|
4344
|
+
param[:show_error] ||= 1
|
4345
|
+
end
|
4346
|
+
|
4347
|
+
# Minor modification to allow comparison testing. Change RGB colors
|
4348
|
+
# from long format, ffcc00 to short format fc0 used by VML.
|
4349
|
+
def rgb_color(rgb)
|
4350
|
+
result = sprintf("%02x%02x%02x", *rgb)
|
4351
|
+
if result =~ /^([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3$/
|
4352
|
+
result = "#{$1}#{$2}#{$3}"
|
4353
|
+
end
|
4354
|
+
result
|
4355
|
+
end
|
4356
|
+
|
4357
|
+
# List of valid input parameters.
|
4358
|
+
def valid_validation_parameter
|
4359
|
+
[
|
4360
|
+
:validate,
|
4361
|
+
:criteria,
|
4362
|
+
:value,
|
4363
|
+
:source,
|
4364
|
+
:minimum,
|
4365
|
+
:maximum,
|
4366
|
+
:ignore_blank,
|
4367
|
+
:dropdown,
|
4368
|
+
:show_input,
|
4369
|
+
:input_title,
|
4370
|
+
:input_message,
|
4371
|
+
:show_error,
|
4372
|
+
:error_title,
|
4373
|
+
:error_message,
|
4374
|
+
:error_type,
|
4375
|
+
:other_cells
|
4376
|
+
]
|
4377
|
+
end
|
4378
|
+
|
4379
|
+
def valid_validation_type # :nodoc:
|
4380
|
+
{
|
4381
|
+
'any' => 'none',
|
4382
|
+
'any value' => 'none',
|
4383
|
+
'whole number' => 'whole',
|
4384
|
+
'whole' => 'whole',
|
4385
|
+
'integer' => 'whole',
|
4386
|
+
'decimal' => 'decimal',
|
4387
|
+
'list' => 'list',
|
4388
|
+
'date' => 'date',
|
4389
|
+
'time' => 'time',
|
4390
|
+
'text length' => 'textLength',
|
4180
4391
|
'length' => 'textLength',
|
4181
4392
|
'custom' => 'custom'
|
4182
4393
|
}
|
4183
4394
|
end
|
4184
4395
|
|
4185
|
-
# Convert the list of
|
4186
|
-
# except for the first
|
4396
|
+
# Convert the list of format, string tokens to pairs of (format, string)
|
4397
|
+
# except for the first string fragment which doesn't require a default
|
4187
4398
|
# formatting run. Use the default for strings without a leading format.
|
4188
4399
|
def rich_strings_fragments(rich_strings) # :nodoc:
|
4189
4400
|
# Create a temp format with the default font for unformatted fragments.
|
@@ -4437,68 +4648,6 @@ module Writexlsx
|
|
4437
4648
|
sprintf("FF%02X%02X%02X", *rgb[0, 3])
|
4438
4649
|
end
|
4439
4650
|
|
4440
|
-
#
|
4441
|
-
# Substitute an Excel cell reference in A1 notation for zero based row and
|
4442
|
-
# column values in an argument list.
|
4443
|
-
#
|
4444
|
-
# Ex: ("A4", "Hello") is converted to (3, 0, "Hello").
|
4445
|
-
#
|
4446
|
-
def substitute_cellref(cell, *args) #:nodoc:
|
4447
|
-
return [*args] if cell.respond_to?(:coerce) # Numeric
|
4448
|
-
|
4449
|
-
cell.upcase!
|
4450
|
-
|
4451
|
-
case cell
|
4452
|
-
# Convert a column range: 'A:A' or 'B:G'.
|
4453
|
-
# A range such as A:A is equivalent to A1:65536, so add rows as required
|
4454
|
-
when /\$?([A-Z]{1,3}):\$?([A-Z]{1,3})/
|
4455
|
-
row1, col1 = cell_to_rowcol($1 + '1')
|
4456
|
-
row2, col2 = cell_to_rowcol($2 + @xls_rowmax.to_s)
|
4457
|
-
return [row1, col1, row2, col2, *args]
|
4458
|
-
# Convert a cell range: 'A1:B7'
|
4459
|
-
when /\$?([A-Z]{1,3}\$?\d+):\$?([A-Z]{1,3}\$?\d+)/
|
4460
|
-
row1, col1 = cell_to_rowcol($1)
|
4461
|
-
row2, col2 = cell_to_rowcol($2)
|
4462
|
-
return [row1, col1, row2, col2, *args]
|
4463
|
-
# Convert a cell reference: 'A1' or 'AD2000'
|
4464
|
-
when /\$?([A-Z]{1,3}\$?\d+)/
|
4465
|
-
row1, col1 = cell_to_rowcol($1)
|
4466
|
-
return [row1, col1, *args]
|
4467
|
-
else
|
4468
|
-
raise("Unknown cell reference #{cell}")
|
4469
|
-
end
|
4470
|
-
end
|
4471
|
-
|
4472
|
-
#
|
4473
|
-
# Convert an Excel cell reference in A1 notation to a zero based row and column
|
4474
|
-
# reference converts C1 to (0, 2).
|
4475
|
-
#
|
4476
|
-
# Returns: row, column
|
4477
|
-
#
|
4478
|
-
def cell_to_rowcol(cell) #:nodoc:
|
4479
|
-
cell =~ /(\$?)([A-Z]{1,3})(\$?)(\d+)/
|
4480
|
-
col_abs = $1 == '' ? 0 : 1
|
4481
|
-
col = $2
|
4482
|
-
row_abs = $3 == '' ? 0 : 1
|
4483
|
-
row = $4.to_i
|
4484
|
-
|
4485
|
-
# Convert base26 column string to number
|
4486
|
-
# All your Base are belong to us.
|
4487
|
-
chars = col.split(//)
|
4488
|
-
expn = 0
|
4489
|
-
col = 0
|
4490
|
-
chars.reverse.each do |char|
|
4491
|
-
col += (char.ord - 'A'.ord + 1) * (26 ** expn)
|
4492
|
-
expn += 1
|
4493
|
-
end
|
4494
|
-
|
4495
|
-
# Convert 1-index to zero-index
|
4496
|
-
row -= 1
|
4497
|
-
col -= 1
|
4498
|
-
|
4499
|
-
[row, col, row_abs, col_abs]
|
4500
|
-
end
|
4501
|
-
|
4502
4651
|
#
|
4503
4652
|
# This is an internal method that is used to filter elements of the array of
|
4504
4653
|
# pagebreaks used in the _store_hbreak() and _store_vbreak() methods. It:
|
@@ -4522,106 +4671,6 @@ module Writexlsx
|
|
4522
4671
|
end
|
4523
4672
|
end
|
4524
4673
|
|
4525
|
-
#
|
4526
|
-
# Calculate the vertices that define the position of a graphical object within
|
4527
|
-
# the worksheet in pixels.
|
4528
|
-
#
|
4529
|
-
# +------------+------------+
|
4530
|
-
# | A | B |
|
4531
|
-
# +-----+------------+------------+
|
4532
|
-
# | |(x1,y1) | |
|
4533
|
-
# | 1 |(A1)._______|______ |
|
4534
|
-
# | | | | |
|
4535
|
-
# | | | | |
|
4536
|
-
# +-----+----| BITMAP |-----+
|
4537
|
-
# | | | | |
|
4538
|
-
# | 2 | |______________. |
|
4539
|
-
# | | | (B2)|
|
4540
|
-
# | | | (x2,y2)|
|
4541
|
-
# +---- +------------+------------+
|
4542
|
-
#
|
4543
|
-
# Example of an object that covers some of the area from cell A1 to cell B2.
|
4544
|
-
#
|
4545
|
-
# Based on the width and height of the object we need to calculate 8 vars:
|
4546
|
-
#
|
4547
|
-
# $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
|
4548
|
-
#
|
4549
|
-
# We also calculate the absolute x and y position of the top left vertex of
|
4550
|
-
# the object. This is required for images.
|
4551
|
-
#
|
4552
|
-
# $x_abs, $y_abs
|
4553
|
-
#
|
4554
|
-
# The width and height of the cells that the object occupies can be variable
|
4555
|
-
# and have to be taken into account.
|
4556
|
-
#
|
4557
|
-
# The values of $col_start and $row_start are passed in from the calling
|
4558
|
-
# function. The values of $col_end and $row_end are calculated by subtracting
|
4559
|
-
# the width and height of the object from the width and height of the
|
4560
|
-
# underlying cells.
|
4561
|
-
#
|
4562
|
-
# col_start # Col containing upper left corner of object.
|
4563
|
-
# x1 # Distance to left side of object.
|
4564
|
-
# row_start # Row containing top left corner of object.
|
4565
|
-
# y1 # Distance to top of object.
|
4566
|
-
# col_end # Col containing lower right corner of object.
|
4567
|
-
# x2 # Distance to right side of object.
|
4568
|
-
# row_end # Row containing bottom right corner of object.
|
4569
|
-
# y2 # Distance to bottom of object.
|
4570
|
-
# width # Width of object frame.
|
4571
|
-
# height # Height of object frame.
|
4572
|
-
def position_object_pixels(col_start, row_start, x1, y1, width, height, is_drawing = false) #:nodoc:
|
4573
|
-
# Calculate the absolute x offset of the top-left vertex.
|
4574
|
-
if @col_size_changed
|
4575
|
-
x_abs = (1 .. col_start).inject(0) {|sum, col| sum += size_col(col)}
|
4576
|
-
else
|
4577
|
-
# Optimisation for when the column widths haven't changed.
|
4578
|
-
x_abs = 64 * col_start
|
4579
|
-
end
|
4580
|
-
x_abs += x1
|
4581
|
-
|
4582
|
-
# Calculate the absolute y offset of the top-left vertex.
|
4583
|
-
# Store the column change to allow optimisations.
|
4584
|
-
if @row_size_changed
|
4585
|
-
y_abs = (1 .. row_start).inject(0) {|sum, row| sum += size_row(row)}
|
4586
|
-
else
|
4587
|
-
# Optimisation for when the row heights haven't changed.
|
4588
|
-
y_abs = 20 * row_start
|
4589
|
-
end
|
4590
|
-
y_abs += y1
|
4591
|
-
|
4592
|
-
# Adjust start column for offsets that are greater than the col width.
|
4593
|
-
x1, col_start = adjust_column_offset(x1, col_start)
|
4594
|
-
|
4595
|
-
# Adjust start row for offsets that are greater than the row height.
|
4596
|
-
y1, row_start = adjust_row_offset(y1, row_start)
|
4597
|
-
|
4598
|
-
# Initialise end cell to the same as the start cell.
|
4599
|
-
col_end = col_start
|
4600
|
-
row_end = row_start
|
4601
|
-
|
4602
|
-
width += x1
|
4603
|
-
height += y1
|
4604
|
-
|
4605
|
-
# Subtract the underlying cell widths to find the end cell of the object.
|
4606
|
-
width, col_end = adjust_column_offset(width, col_end)
|
4607
|
-
|
4608
|
-
# Subtract the underlying cell heights to find the end cell of the object.
|
4609
|
-
height, row_end = adjust_row_offset(height, row_end)
|
4610
|
-
|
4611
|
-
# The following is only required for positioning drawing/chart objects
|
4612
|
-
# and not comments. It is probably the result of a bug.
|
4613
|
-
if is_drawing
|
4614
|
-
col_end -= 1 if width == 0
|
4615
|
-
row_end -= 1 if height == 0
|
4616
|
-
end
|
4617
|
-
|
4618
|
-
# The end vertices are whatever is left from the width and height.
|
4619
|
-
x2 = width
|
4620
|
-
y2 = height
|
4621
|
-
|
4622
|
-
[col_start, row_start, x1, y1, col_end, row_end, x2, y2, x_abs, y_abs]
|
4623
|
-
end
|
4624
|
-
|
4625
4674
|
def adjust_column_offset(x, column)
|
4626
4675
|
while x >= size_col(column)
|
4627
4676
|
x -= size_col(column)
|
@@ -4744,149 +4793,6 @@ module Writexlsx
|
|
4744
4793
|
@drawing_links << ['/image', "../media/image#{image_id}.#{image_type}"]
|
4745
4794
|
end
|
4746
4795
|
|
4747
|
-
#
|
4748
|
-
# This method handles the additional optional parameters to write_comment() as
|
4749
|
-
# well as calculating the comment object position and vertices.
|
4750
|
-
#
|
4751
|
-
def comment_params(row, col, string, options) #:nodoc:
|
4752
|
-
options ||= {}
|
4753
|
-
default_width = 128
|
4754
|
-
default_height = 74
|
4755
|
-
|
4756
|
-
params = {
|
4757
|
-
:author => nil,
|
4758
|
-
:color => 81,
|
4759
|
-
:start_cell => nil,
|
4760
|
-
:start_col => nil,
|
4761
|
-
:start_row => nil,
|
4762
|
-
:visible => nil,
|
4763
|
-
:width => default_width,
|
4764
|
-
:height => default_height,
|
4765
|
-
:x_offset => nil,
|
4766
|
-
:x_scale => 1,
|
4767
|
-
:y_offset => nil,
|
4768
|
-
:y_scale => 1
|
4769
|
-
}
|
4770
|
-
|
4771
|
-
# Overwrite the defaults with any user supplied values. Incorrect or
|
4772
|
-
# misspelled parameters are silently ignored.
|
4773
|
-
params.update(options)
|
4774
|
-
|
4775
|
-
# Ensure that a width and height have been set.
|
4776
|
-
params[:width] ||= default_width
|
4777
|
-
params[:height] ||= default_height
|
4778
|
-
|
4779
|
-
# Limit the string to the max number of chars.
|
4780
|
-
max_len = 32767
|
4781
|
-
|
4782
|
-
string = string[0, max_len] if string.length > max_len
|
4783
|
-
|
4784
|
-
# Set the comment background colour.
|
4785
|
-
color = params[:color]
|
4786
|
-
color_id = Format.get_color(color)
|
4787
|
-
|
4788
|
-
if color_id == 0
|
4789
|
-
params[:color] = '#ffffe1'
|
4790
|
-
else
|
4791
|
-
# Get the RGB color from the palette.
|
4792
|
-
rgb = @workbook.palette[color_id - 8]
|
4793
|
-
|
4794
|
-
# Minor modification to allow comparison testing. Change RGB colors
|
4795
|
-
# from long format, ffcc00 to short format fc0 used by VML.
|
4796
|
-
rgb_color = sprintf("%02x%02x%02x", *rgb)
|
4797
|
-
|
4798
|
-
if rgb_color =~ /^([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3$/
|
4799
|
-
rgb_color = "#{$1}#{$2}#{$3}"
|
4800
|
-
end
|
4801
|
-
|
4802
|
-
params[:color] = sprintf("#%s [%d]\n", rgb_color, color_id)
|
4803
|
-
end
|
4804
|
-
|
4805
|
-
# Convert a cell reference to a row and column.
|
4806
|
-
if params[:start_cell]
|
4807
|
-
params[:start_row], params[:start_col] = substitute_cellref(params[:start_cell])
|
4808
|
-
end
|
4809
|
-
|
4810
|
-
# Set the default start cell and offsets for the comment. These are
|
4811
|
-
# generally fixed in relation to the parent cell. However there are
|
4812
|
-
# some edge cases for cells at the, er, edges.
|
4813
|
-
#
|
4814
|
-
row_max = @xls_rowmax
|
4815
|
-
col_max = @xls_colmax
|
4816
|
-
|
4817
|
-
params[:start_row] ||= case row
|
4818
|
-
when 0
|
4819
|
-
0
|
4820
|
-
when row_max - 3
|
4821
|
-
row_max - 7
|
4822
|
-
when row_max - 2
|
4823
|
-
row_max - 6
|
4824
|
-
when row_max - 1
|
4825
|
-
row_max - 5
|
4826
|
-
else
|
4827
|
-
row - 1
|
4828
|
-
end
|
4829
|
-
|
4830
|
-
params[:y_offset] ||= case row
|
4831
|
-
when 0
|
4832
|
-
2
|
4833
|
-
when row_max - 3, row_max - 2
|
4834
|
-
16
|
4835
|
-
when row_max - 1
|
4836
|
-
14
|
4837
|
-
else
|
4838
|
-
10
|
4839
|
-
end
|
4840
|
-
|
4841
|
-
params[:start_col] ||= case col
|
4842
|
-
when col_max - 3
|
4843
|
-
col_max - 6
|
4844
|
-
when col_max - 2
|
4845
|
-
col_max - 5
|
4846
|
-
when col_max - 1
|
4847
|
-
col_max - 4
|
4848
|
-
else
|
4849
|
-
col + 1
|
4850
|
-
end
|
4851
|
-
|
4852
|
-
params[:x_offset] ||= case col
|
4853
|
-
when col_max - 3, col_max - 2, col_max - 1
|
4854
|
-
49
|
4855
|
-
else
|
4856
|
-
15
|
4857
|
-
end
|
4858
|
-
|
4859
|
-
# Scale the size of the comment box if required.
|
4860
|
-
params[:width] = params[:width] * params[:x_scale] if params[:x_scale]
|
4861
|
-
|
4862
|
-
params[:height] = params[:height] * params[:y_scale] if params[:y_scale]
|
4863
|
-
|
4864
|
-
# Round the dimensions to the nearest pixel.
|
4865
|
-
params[:width] = (0.5 + params[:width]).to_i
|
4866
|
-
params[:height] = (0.5 + params[:height]).to_i
|
4867
|
-
|
4868
|
-
# Calculate the positions of comment object.
|
4869
|
-
vertices = position_object_pixels(
|
4870
|
-
params[:start_col], params[:start_row], params[:x_offset],
|
4871
|
-
params[:y_offset], params[:width], params[:height]
|
4872
|
-
)
|
4873
|
-
|
4874
|
-
# Add the width and height for VML.
|
4875
|
-
vertices << [params[:width], params[:height]]
|
4876
|
-
|
4877
|
-
return [
|
4878
|
-
row,
|
4879
|
-
col,
|
4880
|
-
string,
|
4881
|
-
|
4882
|
-
params[:author],
|
4883
|
-
params[:visible],
|
4884
|
-
params[:color],
|
4885
|
-
|
4886
|
-
vertices
|
4887
|
-
]
|
4888
|
-
end
|
4889
|
-
|
4890
4796
|
#
|
4891
4797
|
# Based on the algorithm provided by Daniel Rentz of OpenOffice.
|
4892
4798
|
#
|
@@ -5076,21 +4982,15 @@ module Writexlsx
|
|
5076
4982
|
# Write the <col> element.
|
5077
4983
|
#
|
5078
4984
|
def write_col_info(*args) #:nodoc:
|
5079
|
-
min
|
5080
|
-
max
|
5081
|
-
width
|
5082
|
-
format = args[3]
|
5083
|
-
hidden = args[4] || 0
|
5084
|
-
level
|
5085
|
-
collapsed = args[6] || 0
|
5086
|
-
|
5087
|
-
|
5088
|
-
# width = $_[2] # Col width in user units.
|
5089
|
-
# format = $_[3] # Format index.
|
5090
|
-
# hidden = $_[4] // 0 # Hidden flag.
|
5091
|
-
# level = $_[5] // 0 # Outline level.
|
5092
|
-
# collapsed = $_[6] // 0 # Outline level.
|
5093
|
-
custom_width = 1
|
4985
|
+
min = args[0] || 0 # First formatted column.
|
4986
|
+
max = args[1] || 0 # Last formatted column.
|
4987
|
+
width = args[2] # Col width in user units.
|
4988
|
+
format = args[3] # Format index.
|
4989
|
+
hidden = args[4] || 0 # Hidden flag.
|
4990
|
+
level = args[5] || 0 # Outline level.
|
4991
|
+
collapsed = args[6] || 0 # Outline level.
|
4992
|
+
|
4993
|
+
custom_width = true
|
5094
4994
|
xf_index = 0
|
5095
4995
|
xf_index = format.get_xf_index if format.respond_to?(:get_xf_index)
|
5096
4996
|
|
@@ -5098,13 +4998,13 @@ module Writexlsx
|
|
5098
4998
|
if width.nil?
|
5099
4999
|
if hidden == 0
|
5100
5000
|
width = 8.43
|
5101
|
-
custom_width =
|
5001
|
+
custom_width = false
|
5102
5002
|
else
|
5103
5003
|
width = 0
|
5104
5004
|
end
|
5105
5005
|
else
|
5106
5006
|
# Width is defined but same as default.
|
5107
|
-
custom_width =
|
5007
|
+
custom_width = false if width == 8.43
|
5108
5008
|
end
|
5109
5009
|
|
5110
5010
|
# Convert column width from user units to character width.
|
@@ -5121,7 +5021,7 @@ module Writexlsx
|
|
5121
5021
|
|
5122
5022
|
(attributes << 'style' << xf_index) if xf_index != 0
|
5123
5023
|
(attributes << 'hidden' << 1) if hidden != 0
|
5124
|
-
(attributes << 'customWidth' << 1) if custom_width
|
5024
|
+
(attributes << 'customWidth' << 1) if custom_width
|
5125
5025
|
(attributes << 'outlineLevel' << level) if level != 0
|
5126
5026
|
(attributes << 'collapsed' << 1) if collapsed != 0
|
5127
5027
|
|
@@ -5156,7 +5056,7 @@ module Writexlsx
|
|
5156
5056
|
span = @row_spans[span_index]
|
5157
5057
|
|
5158
5058
|
# Write the cells if the row contains data.
|
5159
|
-
if @
|
5059
|
+
if @cell_data_table[row_num]
|
5160
5060
|
if !@set_rows[row_num]
|
5161
5061
|
write_row_element(row_num, span)
|
5162
5062
|
else
|
@@ -5188,8 +5088,7 @@ module Writexlsx
|
|
5188
5088
|
return not_contain_formatting_or_data?(row_num)
|
5189
5089
|
|
5190
5090
|
# Write the cells if the row contains data.
|
5191
|
-
|
5192
|
-
if row_ref
|
5091
|
+
if @cell_data_table[row_num]
|
5193
5092
|
if !@set_rows[row_num]
|
5194
5093
|
write_row(row_num)
|
5195
5094
|
else
|
@@ -5204,17 +5103,16 @@ module Writexlsx
|
|
5204
5103
|
end
|
5205
5104
|
|
5206
5105
|
# Reset table.
|
5207
|
-
@
|
5106
|
+
@cell_data_table = {}
|
5208
5107
|
end
|
5209
5108
|
|
5210
5109
|
def not_contain_formatting_or_data?(row_num) # :nodoc:
|
5211
|
-
!@set_rows[row_num] && !@
|
5110
|
+
!@set_rows[row_num] && !@cell_data_table[row_num] && !@comments.has_comment_in_row?(row_num)
|
5212
5111
|
end
|
5213
5112
|
|
5214
5113
|
def write_cell_column_dimension(row_num) # :nodoc:
|
5215
5114
|
(@dim_colmin .. @dim_colmax).each do |col_num|
|
5216
|
-
|
5217
|
-
write_cell(row_num, col_num, col_ref) if col_ref
|
5115
|
+
@cell_data_table[row_num][col_num].write_cell(self) if @cell_data_table[row_num][col_num]
|
5218
5116
|
end
|
5219
5117
|
end
|
5220
5118
|
|
@@ -5258,121 +5156,6 @@ module Writexlsx
|
|
5258
5156
|
write_row_element(*new_args)
|
5259
5157
|
end
|
5260
5158
|
|
5261
|
-
#
|
5262
|
-
# Write the <cell> element. This is the innermost loop so efficiency is
|
5263
|
-
# important where possible. The basic methodology is that the data of every
|
5264
|
-
# cell type is passed in as follows:
|
5265
|
-
#
|
5266
|
-
# [ $row, $col, $aref]
|
5267
|
-
#
|
5268
|
-
# The aref, called $cell below, contains the following structure in all types:
|
5269
|
-
#
|
5270
|
-
# [ $type, $token, $xf, @args ]
|
5271
|
-
#
|
5272
|
-
# Where $type: represents the cell type, such as string, number, formula, etc.
|
5273
|
-
# $token: is the actual data for the string, number, formula, etc.
|
5274
|
-
# $xf: is the XF format object index.
|
5275
|
-
# @args: additional args relevant to the specific data type.
|
5276
|
-
#
|
5277
|
-
def write_cell(row, col, cell) #:nodoc:
|
5278
|
-
type, token, xf = cell
|
5279
|
-
|
5280
|
-
xf_index = 0
|
5281
|
-
xf_index = xf.get_xf_index if xf.respond_to?(:get_xf_index)
|
5282
|
-
|
5283
|
-
range = xl_rowcol_to_cell(row, col)
|
5284
|
-
attributes = ['r', range]
|
5285
|
-
|
5286
|
-
# Add the cell format index.
|
5287
|
-
if xf_index != 0
|
5288
|
-
attributes << 's' << xf_index
|
5289
|
-
elsif @set_rows[row] && @set_rows[row][1]
|
5290
|
-
row_xf = @set_rows[row][1]
|
5291
|
-
attributes << 's' << row_xf.get_xf_index
|
5292
|
-
elsif @col_formats[col]
|
5293
|
-
col_xf = @col_formats[col]
|
5294
|
-
attributes << 's' << col_xf.get_xf_index
|
5295
|
-
end
|
5296
|
-
|
5297
|
-
# Write the various cell types.
|
5298
|
-
case type
|
5299
|
-
when 'n'
|
5300
|
-
# Write a number.
|
5301
|
-
@writer.start_tag('c', attributes)
|
5302
|
-
write_cell_value(token)
|
5303
|
-
@writer.end_tag('c')
|
5304
|
-
when 's'
|
5305
|
-
# Write a string.
|
5306
|
-
attributes << 't' << 's'
|
5307
|
-
@writer.start_tag('c', attributes)
|
5308
|
-
write_cell_value(token)
|
5309
|
-
@writer.end_tag('c')
|
5310
|
-
when 'f'
|
5311
|
-
# Write a formula.
|
5312
|
-
@writer.start_tag('c', attributes)
|
5313
|
-
write_cell_formula(token)
|
5314
|
-
write_cell_value(cell[3] || 0)
|
5315
|
-
@writer.end_tag('c')
|
5316
|
-
when 'a'
|
5317
|
-
# Write an array formula.
|
5318
|
-
@writer.start_tag('c', attributes)
|
5319
|
-
write_cell_array_formula(token, cell[3])
|
5320
|
-
write_cell_value(cell[4])
|
5321
|
-
@writer.end_tag('c')
|
5322
|
-
when 'l'
|
5323
|
-
link_type = cell[3]
|
5324
|
-
|
5325
|
-
# Write the string part a hyperlink.
|
5326
|
-
attributes << 't' << 's'
|
5327
|
-
@writer.start_tag('c', attributes)
|
5328
|
-
write_cell_value(token)
|
5329
|
-
@writer.end_tag('c')
|
5330
|
-
|
5331
|
-
if link_type == 1
|
5332
|
-
# External link with rel file relationship.
|
5333
|
-
@hlink_count += 1
|
5334
|
-
@hlink_refs <<
|
5335
|
-
[
|
5336
|
-
link_type, row, col,
|
5337
|
-
@hlink_count, cell[5], cell[6]
|
5338
|
-
]
|
5339
|
-
|
5340
|
-
@external_hyper_links << [ '/hyperlink', cell[4], 'External' ]
|
5341
|
-
elsif link_type
|
5342
|
-
# External link with rel file relationship.
|
5343
|
-
@hlink_refs << [link_type, row, col, cell[4], cell[5], cell[6] ]
|
5344
|
-
end
|
5345
|
-
when 'b'
|
5346
|
-
# Write a empty cell.
|
5347
|
-
@writer.empty_tag('c', attributes)
|
5348
|
-
end
|
5349
|
-
end
|
5350
|
-
|
5351
|
-
#
|
5352
|
-
# Write the cell value <v> element.
|
5353
|
-
#
|
5354
|
-
def write_cell_value(value = '') #:nodoc:
|
5355
|
-
value ||= ''
|
5356
|
-
value = value.to_i if value == value.to_i
|
5357
|
-
@writer.data_element('v', value)
|
5358
|
-
end
|
5359
|
-
|
5360
|
-
#
|
5361
|
-
# Write the cell formula <f> element.
|
5362
|
-
#
|
5363
|
-
def write_cell_formula(formula = '') #:nodoc:
|
5364
|
-
@writer.data_element('f', formula)
|
5365
|
-
end
|
5366
|
-
|
5367
|
-
#
|
5368
|
-
# Write the cell array formula <f> element.
|
5369
|
-
#
|
5370
|
-
def write_cell_array_formula(formula, range) #:nodoc:
|
5371
|
-
attributes = ['t', 'array', 'ref', range]
|
5372
|
-
|
5373
|
-
@writer.data_element('f', formula, attributes)
|
5374
|
-
end
|
5375
|
-
|
5376
5159
|
#
|
5377
5160
|
# Write the frozen or split <pane> elements.
|
5378
5161
|
#
|
@@ -5566,7 +5349,7 @@ module Writexlsx
|
|
5566
5349
|
attributes << 'pageOrder' << "overThenDown" if print_across?
|
5567
5350
|
|
5568
5351
|
# Set page orientation.
|
5569
|
-
if orientation?
|
5352
|
+
if @print_style.orientation?
|
5570
5353
|
attributes << 'orientation' << 'portrait'
|
5571
5354
|
else
|
5572
5355
|
attributes << 'orientation' << 'landscape'
|
@@ -6000,7 +5783,7 @@ module Writexlsx
|
|
6000
5783
|
# Write the <legacyDrawing> element.
|
6001
5784
|
#
|
6002
5785
|
def write_legacy_drawing #:nodoc:
|
6003
|
-
return unless
|
5786
|
+
return unless has_comments?
|
6004
5787
|
|
6005
5788
|
# Increment the relationship id for any drawings or comments.
|
6006
5789
|
id = @hlink_count + 1
|
@@ -6057,17 +5840,7 @@ module Writexlsx
|
|
6057
5840
|
# Write the underline font element.
|
6058
5841
|
#
|
6059
5842
|
def write_underline(writer, underline) #:nodoc:
|
6060
|
-
|
6061
|
-
if underline == 2
|
6062
|
-
attributes = [val, 'double']
|
6063
|
-
elsif underline == 33
|
6064
|
-
attributes = [val, 'singleAccounting']
|
6065
|
-
elsif underline == 34
|
6066
|
-
attributes = [val, 'doubleAccounting']
|
6067
|
-
else
|
6068
|
-
attributes = [] # Default to single underline.
|
6069
|
-
end
|
6070
|
-
|
5843
|
+
attributes = underline_attributes(underline)
|
6071
5844
|
writer.empty_tag('u', attributes)
|
6072
5845
|
end
|
6073
5846
|
|
@@ -6237,12 +6010,13 @@ module Writexlsx
|
|
6237
6010
|
@writer.end_tag('cfRule')
|
6238
6011
|
end
|
6239
6012
|
|
6240
|
-
def store_data_to_table(
|
6241
|
-
|
6242
|
-
|
6013
|
+
def store_data_to_table(cell_data) #:nodoc:
|
6014
|
+
row, col = cell_data.row, cell_data.col
|
6015
|
+
if @cell_data_table[row]
|
6016
|
+
@cell_data_table[row][col] = cell_data
|
6243
6017
|
else
|
6244
|
-
@
|
6245
|
-
@
|
6018
|
+
@cell_data_table[row] = {}
|
6019
|
+
@cell_data_table[row][col] = cell_data
|
6246
6020
|
end
|
6247
6021
|
end
|
6248
6022
|
|
@@ -6273,7 +6047,7 @@ module Writexlsx
|
|
6273
6047
|
end
|
6274
6048
|
|
6275
6049
|
def check_dimensions(row, col)
|
6276
|
-
if !row || row >=
|
6050
|
+
if !row || row >= ROW_MAX || !col || col >= COL_MAX
|
6277
6051
|
raise WriteXLSXDimensionError
|
6278
6052
|
end
|
6279
6053
|
0
|
@@ -6288,7 +6062,7 @@ module Writexlsx
|
|
6288
6062
|
@dim_rowmin = row if !@dim_rowmin || (row < @dim_rowmin)
|
6289
6063
|
@dim_rowmax = row if !@dim_rowmax || (row > @dim_rowmax)
|
6290
6064
|
end
|
6291
|
-
|
6065
|
+
|
6292
6066
|
def store_col_max_min_values(col)
|
6293
6067
|
@dim_colmin = col if !@dim_colmin || (col < @dim_colmin)
|
6294
6068
|
@dim_colmax = col if !@dim_colmax || (col > @dim_colmax)
|
@@ -6306,11 +6080,9 @@ module Writexlsx
|
|
6306
6080
|
span_max = 0
|
6307
6081
|
spans = []
|
6308
6082
|
(@dim_rowmin .. @dim_rowmax).each do |row_num|
|
6309
|
-
|
6310
|
-
if row_ref
|
6083
|
+
if @cell_data_table[row_num]
|
6311
6084
|
(@dim_colmin .. @dim_colmax).each do |col_num|
|
6312
|
-
|
6313
|
-
if col_ref
|
6085
|
+
if @cell_data_table[row_num][col_num]
|
6314
6086
|
if !span_min
|
6315
6087
|
span_min = col_num
|
6316
6088
|
span_max = col_num
|
@@ -6370,11 +6142,11 @@ module Writexlsx
|
|
6370
6142
|
row_char_2 = "$#{row_num_2 + 1}"
|
6371
6143
|
|
6372
6144
|
# We need to handle some special cases that refer to rows or columns only.
|
6373
|
-
if row_num_1 == 0 and row_num_2 ==
|
6145
|
+
if row_num_1 == 0 and row_num_2 == ROW_MAX - 1
|
6374
6146
|
range1 = col_char_1
|
6375
6147
|
range2 = col_char_2
|
6376
6148
|
row_col_only = true
|
6377
|
-
elsif col_num_1 == 0 and col_num_2 ==
|
6149
|
+
elsif col_num_1 == 0 and col_num_2 == COL_MAX - 1
|
6378
6150
|
range1 = row_char_1
|
6379
6151
|
range2 = row_char_2
|
6380
6152
|
row_col_only = true
|
@@ -6480,10 +6252,6 @@ module Writexlsx
|
|
6480
6252
|
@print_style.page_setup_changed
|
6481
6253
|
end
|
6482
6254
|
|
6483
|
-
def orientation? #:nodoc:
|
6484
|
-
!!@orientation
|
6485
|
-
end
|
6486
|
-
|
6487
6255
|
def header_footer_changed? #:nodoc:
|
6488
6256
|
!!@header_footer_changed
|
6489
6257
|
end
|
@@ -6503,7 +6271,7 @@ module Writexlsx
|
|
6503
6271
|
def print_across?
|
6504
6272
|
@print_style.across
|
6505
6273
|
end
|
6506
|
-
|
6274
|
+
|
6507
6275
|
# List of valid criteria types.
|
6508
6276
|
def valid_criteria_type # :nodoc:
|
6509
6277
|
{
|
@@ -6553,7 +6321,7 @@ module Writexlsx
|
|
6553
6321
|
|
6554
6322
|
# Convert col ref to a cell ref and then to a col number.
|
6555
6323
|
dummy, col = substitute_cellref("#{col}1")
|
6556
|
-
raise "Invalid column '#{col_letter}'" if col >=
|
6324
|
+
raise "Invalid column '#{col_letter}'" if col >= COL_MAX
|
6557
6325
|
end
|
6558
6326
|
|
6559
6327
|
col_first, col_last = @filter_range
|