write_xlsx 0.0.2 → 0.0.3
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.
- 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
|