writeexcel 0.6.9 → 0.6.10
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -0
- data/VERSION +1 -1
- data/lib/writeexcel/biffwriter.rb +29 -43
- data/lib/writeexcel/cell_range.rb +332 -0
- data/lib/writeexcel/chart.rb +50 -51
- data/lib/writeexcel/col_info.rb +87 -0
- data/lib/writeexcel/comments.rb +456 -0
- data/lib/writeexcel/convert_date_time.rb +117 -0
- data/lib/writeexcel/data_validations.rb +370 -0
- data/lib/writeexcel/debug_info.rb +5 -1
- data/lib/writeexcel/embedded_chart.rb +35 -0
- data/lib/writeexcel/format.rb +1 -1
- data/lib/writeexcel/formula.rb +3 -3
- data/lib/writeexcel/helper.rb +3 -0
- data/lib/writeexcel/image.rb +61 -1
- data/lib/writeexcel/olewriter.rb +2 -8
- data/lib/writeexcel/outline.rb +24 -0
- data/lib/writeexcel/shared_string_table.rb +153 -0
- data/lib/writeexcel/workbook.rb +86 -444
- data/lib/writeexcel/worksheet.rb +693 -2029
- data/lib/writeexcel/worksheets.rb +25 -0
- data/lib/writeexcel/write_file.rb +34 -15
- data/test/test_02_merge_formats.rb +0 -4
- data/test/test_04_dimensions.rb +0 -4
- data/test/test_05_rows.rb +0 -4
- data/test/test_06_extsst.rb +3 -6
- data/test/test_11_date_time.rb +0 -4
- data/test/test_12_date_only.rb +262 -231
- data/test/test_13_date_seconds.rb +0 -4
- data/test/test_21_escher.rb +71 -84
- data/test/test_22_mso_drawing_group.rb +0 -4
- data/test/test_23_note.rb +5 -21
- data/test/test_24_txo.rb +6 -7
- data/test/test_25_position_object.rb +0 -4
- data/test/test_26_autofilter.rb +0 -5
- data/test/test_27_autofilter.rb +0 -5
- data/test/test_28_autofilter.rb +0 -5
- data/test/test_29_process_jpg.rb +1 -1
- data/test/test_30_validation_dval.rb +4 -7
- data/test/test_31_validation_dv_strings.rb +9 -12
- data/test/test_32_validation_dv_formula.rb +11 -14
- data/test/test_42_set_properties.rb +0 -3
- data/test/test_50_name_stored.rb +0 -4
- data/test/test_51_name_print_area.rb +0 -4
- data/test/test_52_name_print_titles.rb +0 -4
- data/test/test_53_autofilter.rb +0 -4
- data/test/test_60_chart_generic.rb +42 -46
- data/test/test_61_chart_subclasses.rb +7 -11
- data/test/test_62_chart_formats.rb +12 -16
- data/test/test_63_chart_area_formats.rb +3 -7
- data/test/test_biff.rb +0 -4
- data/test/test_big_workbook.rb +17 -0
- data/test/test_format.rb +0 -3
- data/test/test_ole.rb +0 -4
- data/test/test_storage_lite.rb +0 -9
- data/test/test_workbook.rb +0 -4
- data/test/test_worksheet.rb +3 -7
- data/writeexcel.gemspec +12 -2
- metadata +12 -2
data/lib/writeexcel/worksheet.rb
CHANGED
@@ -16,6 +16,13 @@
|
|
16
16
|
require 'writeexcel/formula'
|
17
17
|
require 'writeexcel/compatibility'
|
18
18
|
require 'writeexcel/image'
|
19
|
+
require 'writeexcel/cell_range'
|
20
|
+
require 'writeexcel/embedded_chart'
|
21
|
+
require 'writeexcel/outline'
|
22
|
+
require 'writeexcel/col_info'
|
23
|
+
require 'writeexcel/comments'
|
24
|
+
require 'writeexcel/data_validations'
|
25
|
+
require 'writeexcel/convert_date_time'
|
19
26
|
|
20
27
|
class MaxSizeError < StandardError #:nodoc:
|
21
28
|
end
|
@@ -35,21 +42,34 @@ module Writeexcel
|
|
35
42
|
#
|
36
43
|
class Worksheet < BIFFWriter
|
37
44
|
require 'writeexcel/helper'
|
45
|
+
include ConvertDateTime
|
46
|
+
|
47
|
+
class ObjectIds
|
48
|
+
attr_accessor :spid
|
49
|
+
attr_reader :drawings_saved, :num_shapes, :max_spid
|
50
|
+
|
51
|
+
def initialize(spid, drawings_saved, num_shapes, max_spid)
|
52
|
+
@spid = spid
|
53
|
+
@drawings_saved = drawings_saved
|
54
|
+
@num_shapes = num_shapes
|
55
|
+
@max_spid = max_spid
|
56
|
+
end
|
57
|
+
end
|
38
58
|
|
39
59
|
RowMax = 65536 # :nodoc:
|
40
60
|
ColMax = 256 # :nodoc:
|
41
61
|
StrMax = 0 # :nodoc:
|
42
62
|
Buffer = 4096 # :nodoc:
|
43
63
|
|
64
|
+
attr_reader :title_range, :print_range, :filter_area, :object_ids
|
44
65
|
#
|
45
66
|
# Constructor. Creates a new Worksheet object from a BIFFwriter object
|
46
67
|
#
|
47
|
-
def initialize(workbook, name,
|
68
|
+
def initialize(workbook, name, name_utf16be) # :nodoc:
|
48
69
|
super()
|
49
70
|
|
50
71
|
@workbook = workbook
|
51
72
|
@name = name
|
52
|
-
@index = index
|
53
73
|
@name_utf16be = name_utf16be
|
54
74
|
|
55
75
|
@type = 0x0000
|
@@ -57,19 +77,12 @@ def initialize(workbook, name, index, name_utf16be) # :nodoc:
|
|
57
77
|
@using_tmpfile = true
|
58
78
|
@fileclosed = false
|
59
79
|
@offset = 0
|
60
|
-
@
|
61
|
-
@xls_colmax = ColMax
|
62
|
-
@xls_strmax = StrMax
|
63
|
-
@dim_rowmin = nil
|
64
|
-
@dim_rowmax = nil
|
65
|
-
@dim_colmin = nil
|
66
|
-
@dim_colmax = nil
|
80
|
+
@dimension = CellDimension.new(self)
|
67
81
|
@colinfo = []
|
68
82
|
@selection = [0, 0]
|
69
83
|
@panes = []
|
70
84
|
@active_pane = 3
|
71
85
|
@frozen_no_split = 1
|
72
|
-
@active = 0
|
73
86
|
@tab_color = 0
|
74
87
|
|
75
88
|
@first_row = 0
|
@@ -86,14 +99,8 @@ def initialize(workbook, name, index, name_utf16be) # :nodoc:
|
|
86
99
|
@margin_top = 1.00
|
87
100
|
@margin_bottom = 1.00
|
88
101
|
|
89
|
-
@
|
90
|
-
@
|
91
|
-
@title_colmin = nil
|
92
|
-
@title_colmax = nil
|
93
|
-
@print_rowmin = nil
|
94
|
-
@print_rowmax = nil
|
95
|
-
@print_colmin = nil
|
96
|
-
@print_colmax = nil
|
102
|
+
@title_range = TitleRange.new(self)
|
103
|
+
@print_range = PrintRange.new(self)
|
97
104
|
|
98
105
|
@print_gridlines = 1
|
99
106
|
@screen_gridlines = 1
|
@@ -126,38 +133,24 @@ def initialize(workbook, name, index, name_utf16be) # :nodoc:
|
|
126
133
|
|
127
134
|
@leading_zeros = false
|
128
135
|
|
129
|
-
@
|
130
|
-
@outline_style = 0
|
131
|
-
@outline_below = 1
|
132
|
-
@outline_right = 1
|
133
|
-
@outline_on = 1
|
136
|
+
@outline = Outline.new
|
134
137
|
|
135
138
|
@write_match = []
|
136
139
|
|
137
|
-
@
|
138
|
-
@
|
139
|
-
@
|
140
|
-
@charts = {}
|
141
|
-
@charts_array = []
|
142
|
-
@comments = {}
|
143
|
-
@comments_array = []
|
144
|
-
@comments_author = ''
|
145
|
-
@comments_author_enc = 0
|
146
|
-
@comments_visible = 0
|
140
|
+
@images = Collection.new
|
141
|
+
@charts = Collection.new
|
142
|
+
@comments = Comments.new
|
147
143
|
|
148
144
|
@num_images = 0
|
149
145
|
@image_mso_size = 0
|
150
146
|
|
151
|
-
@filter_area =
|
152
|
-
@filter_count = 0
|
147
|
+
@filter_area = FilterRange.new(self)
|
153
148
|
@filter_on = 0
|
154
149
|
@filter_cols = []
|
155
150
|
|
156
|
-
@writing_url = 0
|
157
|
-
|
158
151
|
@db_indices = []
|
159
152
|
|
160
|
-
@validations =
|
153
|
+
@validations = DataValidations.new
|
161
154
|
|
162
155
|
@table = []
|
163
156
|
@row_data = {}
|
@@ -185,12 +178,8 @@ def close #:nodoc:
|
|
185
178
|
store_filtermode
|
186
179
|
|
187
180
|
# Prepend the COLINFO records if they exist
|
188
|
-
|
189
|
-
colinfo
|
190
|
-
while (!colinfo.empty?)
|
191
|
-
arrayref = colinfo.pop
|
192
|
-
store_colinfo(*arrayref)
|
193
|
-
end
|
181
|
+
@colinfo.reverse.each do |colinfo|
|
182
|
+
store_colinfo(colinfo)
|
194
183
|
end
|
195
184
|
|
196
185
|
# Prepend the DEFCOLWIDTH record
|
@@ -341,7 +330,7 @@ def select
|
|
341
330
|
def activate
|
342
331
|
@hidden = false # Active worksheet can't be hidden.
|
343
332
|
@selected = true
|
344
|
-
|
333
|
+
@workbook.worksheets.activesheet = self
|
345
334
|
end
|
346
335
|
|
347
336
|
|
@@ -368,8 +357,8 @@ def hide
|
|
368
357
|
|
369
358
|
# A hidden worksheet shouldn't be active or selected.
|
370
359
|
@selected = false
|
371
|
-
|
372
|
-
|
360
|
+
@workbook.worksheets.activesheet = @workbook.worksheets.first
|
361
|
+
@workbook.worksheets.firstsheet = @workbook.worksheets.first
|
373
362
|
end
|
374
363
|
|
375
364
|
|
@@ -396,7 +385,7 @@ def hide
|
|
396
385
|
#
|
397
386
|
def set_first_sheet
|
398
387
|
@hidden = false # Active worksheet can't be hidden.
|
399
|
-
|
388
|
+
@workbook.worksheets.firstsheet = self
|
400
389
|
end
|
401
390
|
|
402
391
|
#
|
@@ -557,7 +546,7 @@ def set_row(row, height = nil, format = nil, hidden = false, level = 0, collapse
|
|
557
546
|
level = 0 if level < 0
|
558
547
|
level = 7 if level > 7
|
559
548
|
|
560
|
-
@
|
549
|
+
@outline.row_level = level if level > @outline.row_level
|
561
550
|
|
562
551
|
# Set the options flags.
|
563
552
|
# 0x10: The fCollapsed flag indicates that the row contains the "+"
|
@@ -702,7 +691,7 @@ def set_column(*args)
|
|
702
691
|
firstcol = ColMax - 1 if firstcol > ColMax - 1
|
703
692
|
lastcol = ColMax - 1 if lastcol > ColMax - 1
|
704
693
|
|
705
|
-
@colinfo.
|
694
|
+
@colinfo << ColInfo.new(firstcol, lastcol, *data)
|
706
695
|
|
707
696
|
# Store the col sizes for use when calculating image vertices taking
|
708
697
|
# hidden columns into account. Also store the column formats.
|
@@ -764,12 +753,12 @@ def set_selection(*args)
|
|
764
753
|
# "OUTLINES AND GROUPING IN EXCEL".
|
765
754
|
#
|
766
755
|
# The _visible_ parameter is used to control whether or not outlines are
|
767
|
-
# visible. Setting this parameter to
|
756
|
+
# visible. Setting this parameter to false will cause all outlines on the
|
768
757
|
# worksheet to be hidden. They can be unhidden in Excel by means of the
|
769
|
-
# "Show Outline Symbols" command button. The default setting is
|
758
|
+
# "Show Outline Symbols" command button. The default setting is true for
|
770
759
|
# visible outlines.
|
771
760
|
#
|
772
|
-
# worksheet.outline_settings(
|
761
|
+
# worksheet.outline_settings(false)
|
773
762
|
#
|
774
763
|
# The _symbols__below parameter is used to control whether the row outline
|
775
764
|
# symbol will appear above or below the outline level bar. The default
|
@@ -792,13 +781,13 @@ def set_selection(*args)
|
|
792
781
|
# The worksheet parameters controlled by outline_settings() are rarely used.
|
793
782
|
#
|
794
783
|
def outline_settings(*args)
|
795
|
-
@
|
796
|
-
@
|
797
|
-
@
|
798
|
-
@
|
784
|
+
@outline.visible = args[0] || 1
|
785
|
+
@outline.below = args[1] || 1
|
786
|
+
@outline.right = args[2] || 1
|
787
|
+
@outline.style = args[3] || 0
|
799
788
|
|
800
|
-
# Ensure this is a boolean
|
801
|
-
@
|
789
|
+
# Ensure this is a boolean value for Window2
|
790
|
+
@outline.visible = true unless @outline.visible?
|
802
791
|
end
|
803
792
|
|
804
793
|
#
|
@@ -1111,23 +1100,17 @@ def autofilter(*args)
|
|
1111
1100
|
|
1112
1101
|
return if args.size != 4 # Require 4 parameters
|
1113
1102
|
|
1114
|
-
|
1103
|
+
row_min, col_min, row_max, col_max = args
|
1115
1104
|
|
1116
1105
|
# Reverse max and min values if necessary.
|
1117
|
-
if
|
1118
|
-
|
1119
|
-
row1 = row2
|
1120
|
-
row2 = tmp
|
1121
|
-
end
|
1122
|
-
if col2 < col1
|
1123
|
-
tmp = col1
|
1124
|
-
col1 = col2
|
1125
|
-
col2 = col1
|
1126
|
-
end
|
1106
|
+
row_min, row_max = row_max, row_min if row_max < row_min
|
1107
|
+
col_min, col_max = col_max, col_min if col_max < col_min
|
1127
1108
|
|
1128
1109
|
# Store the Autofilter information
|
1129
|
-
@filter_area =
|
1130
|
-
@
|
1110
|
+
@filter_area.row_min = row_min
|
1111
|
+
@filter_area.row_max = row_max
|
1112
|
+
@filter_area.col_min = col_min
|
1113
|
+
@filter_area.col_max = col_max
|
1131
1114
|
end
|
1132
1115
|
|
1133
1116
|
#
|
@@ -1227,20 +1210,16 @@ def autofilter(*args)
|
|
1227
1210
|
# for a more detailed example.
|
1228
1211
|
#
|
1229
1212
|
def filter_column(col, expression)
|
1230
|
-
raise "Must call autofilter() before filter_column()" if @
|
1231
|
-
# raise "Incorrect number of arguments to filter_column()" unless @_ == 2
|
1213
|
+
raise "Must call autofilter() before filter_column()" if @filter_area.count == 0
|
1232
1214
|
|
1233
1215
|
# Check for a column reference in A1 notation and substitute.
|
1234
1216
|
# Convert col ref to a cell ref and then to a col number.
|
1235
|
-
|
1236
|
-
|
1237
|
-
col_first = @filter_area[2]
|
1238
|
-
col_last = @filter_area[3]
|
1217
|
+
dummy, col = substitute_cellref("#{col}1") if col =~ /^\D/
|
1239
1218
|
|
1240
1219
|
# Reject column if it is outside filter range.
|
1241
|
-
|
1220
|
+
unless @filter_area.inside?(col)
|
1242
1221
|
raise "Column '#{col}' outside autofilter() column range " +
|
1243
|
-
|
1222
|
+
"(#{@filter_area.col_min} .. #{@filter_area.col_max})"
|
1244
1223
|
end
|
1245
1224
|
|
1246
1225
|
tokens = extract_filter_tokens(expression)
|
@@ -1249,10 +1228,7 @@ def filter_column(col, expression)
|
|
1249
1228
|
raise "Incorrect number of tokens in expression '#{expression}'"
|
1250
1229
|
end
|
1251
1230
|
|
1252
|
-
|
1253
|
-
tokens = parse_filter_expression(expression, tokens)
|
1254
|
-
|
1255
|
-
@filter_cols[col] = Array.new(tokens)
|
1231
|
+
@filter_cols[col] = parse_filter_expression(expression, tokens)
|
1256
1232
|
@filter_on = 1
|
1257
1233
|
end
|
1258
1234
|
|
@@ -1613,8 +1589,8 @@ def set_footer(string = '', margin = 0.50, encoding = 0)
|
|
1613
1589
|
# worksheet2.repeat_rows(0, 1) # Repeat the first two rows
|
1614
1590
|
#
|
1615
1591
|
def repeat_rows(first_row, last_row = nil)
|
1616
|
-
@
|
1617
|
-
@
|
1592
|
+
@title_range.row_min = first_row
|
1593
|
+
@title_range.row_max = last_row || first_row # Second row is optional
|
1618
1594
|
end
|
1619
1595
|
|
1620
1596
|
#
|
@@ -1648,8 +1624,8 @@ def repeat_columns(*args)
|
|
1648
1624
|
firstcol, lastcol = args
|
1649
1625
|
end
|
1650
1626
|
|
1651
|
-
@
|
1652
|
-
@
|
1627
|
+
@title_range.col_min = firstcol
|
1628
|
+
@title_range.col_max = lastcol || firstcol # Second col is optional
|
1653
1629
|
end
|
1654
1630
|
|
1655
1631
|
#
|
@@ -1753,7 +1729,7 @@ def print_area(*args)
|
|
1753
1729
|
|
1754
1730
|
return if args.size != 4 # Require 4 parameters
|
1755
1731
|
|
1756
|
-
@
|
1732
|
+
@print_range.row_min, @print_range.col_min, @print_range.row_max, @print_range.col_max = args
|
1757
1733
|
end
|
1758
1734
|
|
1759
1735
|
#
|
@@ -1993,16 +1969,8 @@ def keep_leading_zeros(val = true)
|
|
1993
1969
|
# worksheet.write_comment('C3', 'Hello', :visible => 0)
|
1994
1970
|
#
|
1995
1971
|
#
|
1996
|
-
def show_comments(val =
|
1997
|
-
@
|
1998
|
-
end
|
1999
|
-
|
2000
|
-
#
|
2001
|
-
# Set the default author of the cell comments.
|
2002
|
-
#
|
2003
|
-
def set_comments_author(author = '', author_enc = 0)
|
2004
|
-
@comments_author = author
|
2005
|
-
@comments_author_enc = author_enc
|
1972
|
+
def show_comments(val = true)
|
1973
|
+
@comments.visible = val ? true : false
|
2006
1974
|
end
|
2007
1975
|
|
2008
1976
|
#
|
@@ -2266,13 +2234,13 @@ def write(*args)
|
|
2266
2234
|
elsif token.respond_to?(:coerce) # Numeric
|
2267
2235
|
write_number(*args)
|
2268
2236
|
# Match http, https or ftp URL
|
2269
|
-
elsif token =~ %r|^[fh]tt?ps?://|
|
2237
|
+
elsif token =~ %r|^[fh]tt?ps?://|
|
2270
2238
|
write_url(*args)
|
2271
2239
|
# Match mailto:
|
2272
|
-
elsif token =~ %r|^mailto:|
|
2240
|
+
elsif token =~ %r|^mailto:|
|
2273
2241
|
write_url(*args)
|
2274
2242
|
# Match internal or external sheet link
|
2275
|
-
elsif token =~ %r!^(?:in|ex)ternal:!
|
2243
|
+
elsif token =~ %r!^(?:in|ex)ternal:!
|
2276
2244
|
write_url(*args)
|
2277
2245
|
# Match formula
|
2278
2246
|
elsif token =~ /^=/
|
@@ -2329,7 +2297,7 @@ def write_number(*args)
|
|
2329
2297
|
data = [row, col, xf].pack('vvv')
|
2330
2298
|
xl_double = [num].pack("d")
|
2331
2299
|
|
2332
|
-
xl_double.reverse! if @byte_order
|
2300
|
+
xl_double.reverse! if @byte_order
|
2333
2301
|
|
2334
2302
|
# Store the data or write immediately depending on the compatibility mode.
|
2335
2303
|
if compatibility?
|
@@ -2390,14 +2358,9 @@ def write_string(*args)
|
|
2390
2358
|
|
2391
2359
|
return -1 if (args.size < 3) # Check the number of args
|
2392
2360
|
|
2393
|
-
|
2394
|
-
|
2395
|
-
|
2396
|
-
row = args[0] # Zero indexed row
|
2397
|
-
col = args[1] # Zero indexed column
|
2398
|
-
str = args[2].to_s
|
2399
|
-
strlen = str.bytesize
|
2400
|
-
xf = xf_record_index(row, col, args[3]) # The cell format
|
2361
|
+
row, col, str, format = args
|
2362
|
+
str = str.to_s
|
2363
|
+
xf = xf_record_index(row, col, format) # The cell format
|
2401
2364
|
encoding = 0x0
|
2402
2365
|
str_error = 0
|
2403
2366
|
|
@@ -2410,10 +2373,10 @@ def write_string(*args)
|
|
2410
2373
|
end
|
2411
2374
|
|
2412
2375
|
# Check that row and col are valid and store max and min values
|
2413
|
-
return -2
|
2376
|
+
return -2 unless check_dimensions(row, col) == 0
|
2414
2377
|
|
2415
2378
|
# Limit the string to the max number of chars.
|
2416
|
-
if
|
2379
|
+
if str.bytesize > 32767
|
2417
2380
|
str = str[0, 32767]
|
2418
2381
|
str_error = -3
|
2419
2382
|
end
|
@@ -2422,15 +2385,12 @@ def write_string(*args)
|
|
2422
2385
|
str_header = [str.length, encoding].pack('vC')
|
2423
2386
|
str = str_header + str
|
2424
2387
|
|
2425
|
-
|
2426
|
-
sinfo[:str_table][str] = sinfo[:str_unique]
|
2427
|
-
sinfo[:str_unique] += 1
|
2428
|
-
end
|
2429
|
-
|
2430
|
-
sinfo[:str_total] += 1
|
2388
|
+
str_unique = update_workbook_str_table(str)
|
2431
2389
|
|
2390
|
+
record = 0x00FD # Record identifier
|
2391
|
+
length = 0x000A # Bytes to follow
|
2432
2392
|
header = [record, length].pack('vv')
|
2433
|
-
data = [row, col, xf,
|
2393
|
+
data = [row, col, xf, str_unique].pack('vvvV')
|
2434
2394
|
|
2435
2395
|
# Store the data or write immediately depending on the compatibility mode.
|
2436
2396
|
store_with_compatibility(row, col, header + data)
|
@@ -2453,24 +2413,21 @@ def write_utf16be_string(*args)
|
|
2453
2413
|
# Check for a cell reference in A1 notation and substitute row and column
|
2454
2414
|
args = row_col_notation(args)
|
2455
2415
|
|
2456
|
-
return -1 if
|
2416
|
+
return -1 if args.size < 3 # Check the number of args
|
2457
2417
|
|
2458
2418
|
record = 0x00FD # Record identifier
|
2459
2419
|
length = 0x000A # Bytes to follow
|
2460
2420
|
|
2461
|
-
row
|
2462
|
-
col = args[1] # Zero indexed column
|
2463
|
-
strlen = args[2].bytesize
|
2464
|
-
str = args[2]
|
2421
|
+
row, col, str = args
|
2465
2422
|
xf = xf_record_index(row, col, args[3]) # The cell format
|
2466
2423
|
encoding = 0x1
|
2467
2424
|
str_error = 0
|
2468
2425
|
|
2469
2426
|
# Check that row and col are valid and store max and min values
|
2470
|
-
return -2
|
2427
|
+
return -2 unless check_dimensions(row, col) == 0
|
2471
2428
|
|
2472
2429
|
# Limit the utf16 string to the max number of chars (not bytes).
|
2473
|
-
if
|
2430
|
+
if str.bytesize > 32767* 2
|
2474
2431
|
str = str[0..32767*2]
|
2475
2432
|
str_error = -3
|
2476
2433
|
end
|
@@ -2479,24 +2436,19 @@ def write_utf16be_string(*args)
|
|
2479
2436
|
num_chars = (num_bytes / 2).to_i
|
2480
2437
|
|
2481
2438
|
# Check for a valid 2-byte char string.
|
2482
|
-
raise "Uneven number of bytes in Unicode string"
|
2439
|
+
raise "Uneven number of bytes in Unicode string" unless num_bytes % 2 == 0
|
2483
2440
|
|
2484
2441
|
# Change from UTF16 big-endian to little endian
|
2485
|
-
str = str
|
2442
|
+
str = utf16be_to_16le(str)
|
2486
2443
|
|
2487
2444
|
# Add the encoding and length header to the string.
|
2488
2445
|
str_header = [num_chars, encoding].pack("vC")
|
2489
2446
|
str = str_header + str
|
2490
2447
|
|
2491
|
-
|
2492
|
-
sinfo[:str_table][str] = sinfo[:str_unique]
|
2493
|
-
sinfo[:str_unique] += 1
|
2494
|
-
end
|
2495
|
-
|
2496
|
-
sinfo[:str_total] += 1
|
2448
|
+
str_unique = update_workbook_str_table(str)
|
2497
2449
|
|
2498
2450
|
header = [record, length].pack("vv")
|
2499
|
-
data = [row, col, xf,
|
2451
|
+
data = [row, col, xf, str_unique].pack("vvvV")
|
2500
2452
|
|
2501
2453
|
# Store the data or write immediately depending on the compatibility mode.
|
2502
2454
|
store_with_compatibility(row, col, header + data)
|
@@ -2519,18 +2471,12 @@ def write_utf16le_string(*args)
|
|
2519
2471
|
# Check for a cell reference in A1 notation and substitute row and column
|
2520
2472
|
args = row_col_notation(args)
|
2521
2473
|
|
2522
|
-
return -1 if (args.size < 3)
|
2523
|
-
|
2524
|
-
record = 0x00FD # Record identifier
|
2525
|
-
length = 0x000A # Bytes to follow
|
2474
|
+
return -1 if (args.size < 3) # Check the number of args
|
2526
2475
|
|
2527
|
-
row
|
2528
|
-
col = args[1] # Zero indexed column
|
2529
|
-
str = args[2]
|
2530
|
-
format = args[3] # The cell format
|
2476
|
+
row, col, str, format = args
|
2531
2477
|
|
2532
2478
|
# Change from UTF16 big-endian to little endian
|
2533
|
-
str = str
|
2479
|
+
str = utf16be_to_16le(str)
|
2534
2480
|
|
2535
2481
|
write_utf16be_string(row, col, str, format)
|
2536
2482
|
end
|
@@ -2580,16 +2526,15 @@ def write_blank(*args)
|
|
2580
2526
|
# Don't write a blank cell unless it has a format
|
2581
2527
|
return 0 unless args[2]
|
2582
2528
|
|
2583
|
-
|
2584
|
-
length = 0x0006 # Number of bytes to follow
|
2585
|
-
|
2586
|
-
row = args[0] # Zero indexed row
|
2587
|
-
col = args[1] # Zero indexed column
|
2588
|
-
xf = xf_record_index(row, col, args[2]) # The cell format
|
2529
|
+
row, col, format = args
|
2589
2530
|
|
2590
2531
|
# Check that row and col are valid and store max and min values
|
2591
|
-
return -2
|
2532
|
+
return -2 unless check_dimensions(row, col) == 0
|
2592
2533
|
|
2534
|
+
xf = xf_record_index(row, col, format) # The cell format
|
2535
|
+
|
2536
|
+
record = 0x0201 # Record identifier
|
2537
|
+
length = 0x0006 # Number of bytes to follow
|
2593
2538
|
header = [record, length].pack('vv')
|
2594
2539
|
data = [row, col, xf].pack('vvv')
|
2595
2540
|
|
@@ -2883,18 +2828,15 @@ def write_formula(*args)
|
|
2883
2828
|
|
2884
2829
|
return -1 if args.size < 3 # Check the number of args
|
2885
2830
|
|
2886
|
-
row
|
2887
|
-
col = args[1] # Zero indexed column
|
2888
|
-
formula = args[2].dup # The formula text string
|
2889
|
-
value = args[4] # The formula text string
|
2890
|
-
|
2891
|
-
xf = xf_record_index(row, col, args[3]) # The cell format
|
2831
|
+
row, col, formula, format, value = args
|
2892
2832
|
|
2893
2833
|
# Check that row and col are valid and store max and min values
|
2894
|
-
return -2
|
2834
|
+
return -2 unless check_dimensions(row, col) == 0
|
2835
|
+
|
2836
|
+
xf = xf_record_index(row, col, format) # The cell format
|
2895
2837
|
|
2896
2838
|
# Strip the = sign at the beginning of the formula string
|
2897
|
-
formula.sub
|
2839
|
+
formula = formula.sub(/^=/, '')
|
2898
2840
|
|
2899
2841
|
# Parse the formula using the parser in Formula.pm
|
2900
2842
|
# nakamura add: to get byte_stream, set second arg TRUE
|
@@ -2905,61 +2847,6 @@ def write_formula(*args)
|
|
2905
2847
|
0
|
2906
2848
|
end
|
2907
2849
|
|
2908
|
-
#
|
2909
|
-
# :call-seq:
|
2910
|
-
# store_formula(formula) # formula : text string of formula
|
2911
|
-
#
|
2912
|
-
# Pre-parse a formula. This is used in conjunction with repeat_formula()
|
2913
|
-
# to repetitively rewrite a formula without re-parsing it.
|
2914
|
-
#
|
2915
|
-
# The store_formula() method is used in conjunction with repeat_formula()
|
2916
|
-
# to speed up the generation of repeated formulas. See
|
2917
|
-
# "Improving performance when working with formulas" in
|
2918
|
-
# "FORMULAS AND FUNCTIONS IN EXCEL".
|
2919
|
-
#
|
2920
|
-
# The store_formula() method pre-parses a textual representation of a
|
2921
|
-
# formula and stores it for use at a later stage by the repeat_formula()
|
2922
|
-
# method.
|
2923
|
-
#
|
2924
|
-
# store_formula() carries the same speed penalty as write_formula(). However,
|
2925
|
-
# in practice it will be used less frequently.
|
2926
|
-
#
|
2927
|
-
# The return value of this method is a scalar that can be thought of as a
|
2928
|
-
# reference to a formula.
|
2929
|
-
#
|
2930
|
-
# sin = worksheet.store_formula('=SIN(A1)')
|
2931
|
-
# cos = worksheet.store_formula('=COS(A1)')
|
2932
|
-
#
|
2933
|
-
# worksheet.repeat_formula('B1', sin, format, 'A1', 'A2')
|
2934
|
-
# worksheet.repeat_formula('C1', cos, format, 'A1', 'A2')
|
2935
|
-
#
|
2936
|
-
# Although store_formula() is a worksheet method the return value can be used
|
2937
|
-
# in any worksheet:
|
2938
|
-
#
|
2939
|
-
# now = worksheet.store_formula('=NOW()')
|
2940
|
-
#
|
2941
|
-
# worksheet1.repeat_formula('B1', now)
|
2942
|
-
# worksheet2.repeat_formula('B1', now)
|
2943
|
-
# worksheet3.repeat_formula('B1', now)
|
2944
|
-
#
|
2945
|
-
def store_formula(formula) #:nodoc:
|
2946
|
-
# Strip the = sign at the beginning of the formula string
|
2947
|
-
formula.sub!(/^=/, '')
|
2948
|
-
|
2949
|
-
# In order to raise formula errors from the point of view of the calling
|
2950
|
-
# program we use an eval block and re-raise the error from here.
|
2951
|
-
#
|
2952
|
-
tokens = parser.parse_formula(formula)
|
2953
|
-
|
2954
|
-
# if ($@) {
|
2955
|
-
# $@ =~ s/\n$// # Strip the \n used in the Formula.pm die()
|
2956
|
-
# croak $@ # Re-raise the error
|
2957
|
-
# }
|
2958
|
-
|
2959
|
-
# Return the parsed tokens in an anonymous array
|
2960
|
-
[*tokens]
|
2961
|
-
end
|
2962
|
-
|
2963
2850
|
#
|
2964
2851
|
# :call-seq:
|
2965
2852
|
# repeat_formula(row, col, formula, format, pat, rep, (pat2, rep2,, ...) -> Fixnum
|
@@ -3057,26 +2944,24 @@ def repeat_formula(*args) #:nodoc:
|
|
3057
2944
|
# Check for a cell reference in A1 notation and substitute row and column
|
3058
2945
|
args = row_col_notation(args)
|
3059
2946
|
|
3060
|
-
return -1 if
|
2947
|
+
return -1 if args.size < 2 # Check the number of args
|
3061
2948
|
|
3062
|
-
row
|
3063
|
-
|
3064
|
-
|
3065
|
-
|
3066
|
-
pairs = args # Pattern/replacement pairs
|
2949
|
+
row, col, formula, format, *pairs = args
|
2950
|
+
|
2951
|
+
# Check that row and col are valid and store max and min values
|
2952
|
+
return -2 unless check_dimensions(row, col) == 0
|
3067
2953
|
|
3068
2954
|
# Enforce an even number of arguments in the pattern/replacement list
|
3069
|
-
raise "Odd number of elements in pattern/replacement list"
|
2955
|
+
raise "Odd number of elements in pattern/replacement list" unless pairs.size % 2 == 0
|
3070
2956
|
|
3071
2957
|
# Check that formula is an array ref
|
3072
|
-
raise "Not a valid formula" unless
|
2958
|
+
raise "Not a valid formula" unless formula.respond_to?(:to_ary)
|
3073
2959
|
|
3074
|
-
tokens =
|
2960
|
+
tokens = formula.join("\t").split("\t")
|
3075
2961
|
|
3076
2962
|
# Ensure that there are tokens to substitute
|
3077
2963
|
raise "No tokens in formula" if tokens.empty?
|
3078
2964
|
|
3079
|
-
|
3080
2965
|
# As a temporary and undocumented measure we allow the user to specify the
|
3081
2966
|
# result of the formula by appending a result => value pair to the end
|
3082
2967
|
# of the arguments.
|
@@ -3086,7 +2971,7 @@ def repeat_formula(*args) #:nodoc:
|
|
3086
2971
|
pairs.pop
|
3087
2972
|
end
|
3088
2973
|
|
3089
|
-
while
|
2974
|
+
while !pairs.empty?
|
3090
2975
|
pattern = pairs.shift
|
3091
2976
|
replace = pairs.shift
|
3092
2977
|
|
@@ -3096,14 +2981,11 @@ def repeat_formula(*args) #:nodoc:
|
|
3096
2981
|
end
|
3097
2982
|
|
3098
2983
|
# Change the parameters in the formula cached by the Formula.pm object
|
3099
|
-
formula
|
2984
|
+
formula = parser.parse_tokens(tokens)
|
3100
2985
|
|
3101
2986
|
raise "Unrecognised token in formula" unless formula
|
3102
2987
|
|
3103
|
-
xf
|
3104
|
-
|
3105
|
-
# Check that row and col are valid and store max and min values
|
3106
|
-
return -2 if check_dimensions(row, col) != 0
|
2988
|
+
xf = xf_record_index(row, col, format) # The cell format
|
3107
2989
|
|
3108
2990
|
store_formula_common(row, col, xf, value, formula)
|
3109
2991
|
0
|
@@ -3189,12 +3071,10 @@ def write_row(*args)
|
|
3189
3071
|
args = row_col_notation(args)
|
3190
3072
|
|
3191
3073
|
# Catch non array refs passed by user.
|
3192
|
-
unless args[2].respond_to?(:to_ary)
|
3193
|
-
raise "Not an array ref in call to write_row() #{$!}"
|
3194
|
-
end
|
3074
|
+
raise "Not an array ref in call to write_row() #{$!}" unless args[2].respond_to?(:to_ary)
|
3195
3075
|
|
3196
3076
|
row, col, tokens, options = args
|
3197
|
-
error
|
3077
|
+
error = false
|
3198
3078
|
if tokens
|
3199
3079
|
tokens.each do |token|
|
3200
3080
|
# Check for nested arrays
|
@@ -3209,10 +3089,9 @@ def write_row(*args)
|
|
3209
3089
|
col += 1
|
3210
3090
|
end
|
3211
3091
|
end
|
3212
|
-
error
|
3092
|
+
error || 0
|
3213
3093
|
end
|
3214
3094
|
|
3215
|
-
|
3216
3095
|
#
|
3217
3096
|
# :call-seq:
|
3218
3097
|
# write_column(row, col , array[, format])
|
@@ -3291,12 +3170,10 @@ def write_col(*args)
|
|
3291
3170
|
args = row_col_notation(args)
|
3292
3171
|
|
3293
3172
|
# Catch non array refs passed by user.
|
3294
|
-
unless args[2].respond_to?(:to_ary)
|
3295
|
-
raise "Not an array ref in call to write_row()"
|
3296
|
-
end
|
3173
|
+
raise "Not an array ref in call to write_row()" unless args[2].respond_to?(:to_ary)
|
3297
3174
|
|
3298
3175
|
row, col, tokens, options = args
|
3299
|
-
error =
|
3176
|
+
error = false
|
3300
3177
|
if tokens
|
3301
3178
|
tokens.each do |token|
|
3302
3179
|
# write() will deal with any nested arrays
|
@@ -3307,7 +3184,7 @@ def write_col(*args)
|
|
3307
3184
|
row += 1
|
3308
3185
|
end
|
3309
3186
|
end
|
3310
|
-
error
|
3187
|
+
error || 0
|
3311
3188
|
end
|
3312
3189
|
|
3313
3190
|
#
|
@@ -3363,23 +3240,20 @@ def write_date_time(*args)
|
|
3363
3240
|
# Check for a cell reference in A1 notation and substitute row and column
|
3364
3241
|
args = row_col_notation(args)
|
3365
3242
|
|
3366
|
-
return -1 if
|
3243
|
+
return -1 if args.size < 3 # Check the number of args
|
3367
3244
|
|
3368
|
-
row
|
3369
|
-
col = args[1] # Zero indexed column
|
3370
|
-
str = args[2]
|
3245
|
+
row, col, str, format = args
|
3371
3246
|
|
3372
3247
|
# Check that row and col are valid and store max and min values
|
3373
|
-
return -2
|
3248
|
+
return -2 unless check_dimensions(row, col) == 0
|
3374
3249
|
|
3375
|
-
|
3376
|
-
date_time = convert_date_time(str)
|
3250
|
+
date_time = convert_date_time(str, date_1904?)
|
3377
3251
|
|
3378
3252
|
if date_time
|
3379
3253
|
error = write_number(row, col, date_time, args[3])
|
3380
3254
|
else
|
3381
3255
|
# The date isn't valid so write it as a string.
|
3382
|
-
write_string(row, col, str,
|
3256
|
+
write_string(row, col, str, format)
|
3383
3257
|
error = -3
|
3384
3258
|
end
|
3385
3259
|
error
|
@@ -3578,17 +3452,20 @@ def write_comment(*args)
|
|
3578
3452
|
|
3579
3453
|
return -1 if args.size < 3 # Check the number of args
|
3580
3454
|
|
3581
|
-
row = args
|
3582
|
-
col = args[1]
|
3455
|
+
row, col, comment, params = args
|
3583
3456
|
|
3584
3457
|
# Check for pairs of optional arguments, i.e. an odd number of args.
|
3585
3458
|
# raise "Uneven number of additional arguments" if args.size % 2 == 0
|
3586
3459
|
|
3587
3460
|
# Check that row and col are valid and store max and min values
|
3588
|
-
return -2
|
3461
|
+
return -2 unless check_dimensions(row, col) == 0
|
3462
|
+
|
3463
|
+
if params && params[:start_cell]
|
3464
|
+
params[:start_row], params[:start_col] = substitute_cellref(params[:start_cell])
|
3465
|
+
end
|
3589
3466
|
|
3590
3467
|
# We have to avoid duplicate comments in cells or else Excel will complain.
|
3591
|
-
@comments
|
3468
|
+
@comments << Comment.new(self, *args)
|
3592
3469
|
end
|
3593
3470
|
|
3594
3471
|
#
|
@@ -3726,9 +3603,8 @@ def write_url_range(*args)
|
|
3726
3603
|
# Check the number of args
|
3727
3604
|
return -1 if args.size < 5
|
3728
3605
|
|
3729
|
-
# Reverse the order of _string_ and
|
3730
|
-
# in order to protect the callers args.
|
3731
|
-
# perl50005 threads.
|
3606
|
+
# Reverse the order of _string_ and _format_ if necessary. We work on a copy
|
3607
|
+
# in order to protect the callers args.
|
3732
3608
|
#
|
3733
3609
|
args[5], args[6] = [ args[6], args[5] ] if args[5].respond_to?(:xf_index)
|
3734
3610
|
|
@@ -3796,13 +3672,7 @@ def insert_chart(*args)
|
|
3796
3672
|
# Check for a cell reference in A1 notation and substitute row and column
|
3797
3673
|
args = row_col_notation(args)
|
3798
3674
|
|
3799
|
-
row = args[0]
|
3800
|
-
col = args[1]
|
3801
3675
|
chart = args[2]
|
3802
|
-
x_offset = args[3] || 0
|
3803
|
-
y_offset = args[4] || 0
|
3804
|
-
scale_x = args[5] || 1
|
3805
|
-
scale_y = args[6] || 1
|
3806
3676
|
|
3807
3677
|
if chart.respond_to?(:embedded)
|
3808
3678
|
print "Not a embedded style Chart object in insert_chart()" unless chart.embedded
|
@@ -3811,9 +3681,7 @@ def insert_chart(*args)
|
|
3811
3681
|
print "Couldn't locate #{chart} in insert_chart()" unless FileTest.exist?(chart)
|
3812
3682
|
end
|
3813
3683
|
|
3814
|
-
@charts
|
3815
|
-
col => [row, col, chart, x_offset, y_offset, scale_x, scale_y]
|
3816
|
-
}
|
3684
|
+
@charts << EmbeddedChart.new(self, *args)
|
3817
3685
|
end
|
3818
3686
|
|
3819
3687
|
#
|
@@ -3879,11 +3747,11 @@ def insert_image(*args)
|
|
3879
3747
|
# Check for a cell reference in A1 notation and substitute row and column
|
3880
3748
|
args = row_col_notation(args)
|
3881
3749
|
# args = [row, col, filename, x_offset, y_offset, scale_x, scale_y]
|
3882
|
-
image = Image.new(*args)
|
3750
|
+
image = Image.new(self, *args)
|
3883
3751
|
raise "Insufficient arguments in insert_image()" unless args.size >= 3
|
3884
3752
|
raise "Couldn't locate #{image.filename}: $!" unless test(?e, image.filename)
|
3885
3753
|
|
3886
|
-
@images
|
3754
|
+
@images << image
|
3887
3755
|
end
|
3888
3756
|
|
3889
3757
|
# Older method name for backwards compatibility.
|
@@ -4346,213 +4214,16 @@ def data_validation(*args)
|
|
4346
4214
|
# Check for a cell reference in A1 notation and substitute row and column
|
4347
4215
|
args = row_col_notation(args)
|
4348
4216
|
|
4349
|
-
# Check for a valid number of args.
|
4350
|
-
return -1 if args.size != 5 && args.size != 3
|
4351
|
-
|
4352
|
-
# The final hashref contains the validation parameters.
|
4353
|
-
param = args.pop
|
4354
|
-
|
4355
4217
|
# Make the last row/col the same as the first if not defined.
|
4356
4218
|
row1, col1, row2, col2 = args
|
4357
|
-
unless row2
|
4358
|
-
row2 = row1
|
4359
|
-
col2 = col1
|
4360
|
-
end
|
4361
|
-
|
4362
|
-
# Check that row and col are valid without storing the values.
|
4363
4219
|
return -2 if check_dimensions(row1, col1, 1, 1) != 0
|
4364
|
-
return -2 if check_dimensions(row2, col2, 1, 1) != 0
|
4365
|
-
|
4366
|
-
# Check that the last parameter is a hash list.
|
4367
|
-
unless param.respond_to?(:to_hash)
|
4368
|
-
# carp "Last parameter '$param' in data_validation() must be a hash ref";
|
4369
|
-
return -3
|
4370
|
-
end
|
4371
|
-
|
4372
|
-
# List of valid input parameters.
|
4373
|
-
valid_parameter = {
|
4374
|
-
:validate => 1,
|
4375
|
-
:criteria => 1,
|
4376
|
-
:value => 1,
|
4377
|
-
:source => 1,
|
4378
|
-
:minimum => 1,
|
4379
|
-
:maximum => 1,
|
4380
|
-
:ignore_blank => 1,
|
4381
|
-
:dropdown => 1,
|
4382
|
-
:show_input => 1,
|
4383
|
-
:input_title => 1,
|
4384
|
-
:input_message => 1,
|
4385
|
-
:show_error => 1,
|
4386
|
-
:error_title => 1,
|
4387
|
-
:error_message => 1,
|
4388
|
-
:error_type => 1,
|
4389
|
-
:other_cells => 1
|
4390
|
-
}
|
4391
|
-
|
4392
|
-
# Check for valid input parameters.
|
4393
|
-
param.each_key do |param_key|
|
4394
|
-
unless valid_parameter.has_key?(param_key)
|
4395
|
-
# carp "Unknown parameter '$param_key' in data_validation()";
|
4396
|
-
return -3
|
4397
|
-
end
|
4398
|
-
end
|
4399
|
-
|
4400
|
-
# Map alternative parameter names 'source' or 'minimum' to 'value'.
|
4401
|
-
param[:value] = param[:source] if param[:source]
|
4402
|
-
param[:value] = param[:minimum] if param[:minimum]
|
4403
|
-
|
4404
|
-
# 'validate' is a required parameter.
|
4405
|
-
unless param.has_key?(:validate)
|
4406
|
-
# carp "Parameter 'validate' is required in data_validation()";
|
4407
|
-
return -3
|
4408
|
-
end
|
4409
|
-
|
4410
|
-
# List of valid validation types.
|
4411
|
-
valid_type = {
|
4412
|
-
'any' => 0,
|
4413
|
-
'any value' => 0,
|
4414
|
-
'whole number' => 1,
|
4415
|
-
'whole' => 1,
|
4416
|
-
'integer' => 1,
|
4417
|
-
'decimal' => 2,
|
4418
|
-
'list' => 3,
|
4419
|
-
'date' => 4,
|
4420
|
-
'time' => 5,
|
4421
|
-
'text length' => 6,
|
4422
|
-
'length' => 6,
|
4423
|
-
'custom' => 7
|
4424
|
-
}
|
4425
|
-
|
4426
|
-
# Check for valid validation types.
|
4427
|
-
unless valid_type.has_key?(param[:validate].downcase)
|
4428
|
-
# carp "Unknown validation type '$param->{validate}' for parameter " .
|
4429
|
-
# "'validate' in data_validation()";
|
4430
|
-
return -3
|
4431
|
-
else
|
4432
|
-
param[:validate] = valid_type[param[:validate].downcase]
|
4433
|
-
end
|
4434
|
-
|
4435
|
-
# No action is required for validation type 'any'.
|
4436
|
-
# TODO: we should perhaps store 'any' for message only validations.
|
4437
|
-
return 0 if param[:validate] == 0
|
4438
|
-
|
4439
|
-
# The list and custom validations don't have a criteria so we use a default
|
4440
|
-
# of 'between'.
|
4441
|
-
if param[:validate] == 3 || param[:validate] == 7
|
4442
|
-
param[:criteria] = 'between'
|
4443
|
-
param[:maximum] = nil
|
4444
|
-
end
|
4445
|
-
|
4446
|
-
# 'criteria' is a required parameter.
|
4447
|
-
unless param.has_key?(:criteria)
|
4448
|
-
# carp "Parameter 'criteria' is required in data_validation()";
|
4449
|
-
return -3
|
4450
|
-
end
|
4451
|
-
|
4452
|
-
# List of valid criteria types.
|
4453
|
-
criteria_type = {
|
4454
|
-
'between' => 0,
|
4455
|
-
'not between' => 1,
|
4456
|
-
'equal to' => 2,
|
4457
|
-
'=' => 2,
|
4458
|
-
'==' => 2,
|
4459
|
-
'not equal to' => 3,
|
4460
|
-
'!=' => 3,
|
4461
|
-
'<>' => 3,
|
4462
|
-
'greater than' => 4,
|
4463
|
-
'>' => 4,
|
4464
|
-
'less than' => 5,
|
4465
|
-
'<' => 5,
|
4466
|
-
'greater than or equal to' => 6,
|
4467
|
-
'>=' => 6,
|
4468
|
-
'less than or equal to' => 7,
|
4469
|
-
'<=' => 7
|
4470
|
-
}
|
4471
|
-
|
4472
|
-
# Check for valid criteria types.
|
4473
|
-
unless criteria_type.has_key?(param[:criteria].downcase)
|
4474
|
-
# carp "Unknown criteria type '$param->{criteria}' for parameter " .
|
4475
|
-
# "'criteria' in data_validation()";
|
4476
|
-
return -3
|
4477
|
-
else
|
4478
|
-
param[:criteria] = criteria_type[param[:criteria].downcase]
|
4479
|
-
end
|
4480
|
-
|
4481
|
-
# 'Between' and 'Not between' criteria require 2 values.
|
4482
|
-
if param[:criteria] == 0 || param[:criteria] == 1
|
4483
|
-
unless param.has_key?(:maximum)
|
4484
|
-
# carp "Parameter 'maximum' is required in data_validation() " .
|
4485
|
-
# "when using 'between' or 'not between' criteria";
|
4486
|
-
return -3
|
4487
|
-
end
|
4488
|
-
else
|
4489
|
-
param[:maximum] = nil
|
4490
|
-
end
|
4491
|
-
|
4492
|
-
# List of valid error dialog types.
|
4493
|
-
error_type = {
|
4494
|
-
'stop' => 0,
|
4495
|
-
'warning' => 1,
|
4496
|
-
'information' => 2
|
4497
|
-
}
|
4498
|
-
|
4499
|
-
# Check for valid error dialog types.
|
4500
|
-
if not param.has_key?(:error_type)
|
4501
|
-
param[:error_type] = 0
|
4502
|
-
elsif not error_type.has_key?(param[:error_type].downcase)
|
4503
|
-
# carp "Unknown criteria type '$param->{error_type}' for parameter " .
|
4504
|
-
# "'error_type' in data_validation()";
|
4505
|
-
return -3
|
4506
|
-
else
|
4507
|
-
param[:error_type] = error_type[param[:error_type].downcase]
|
4508
|
-
end
|
4509
|
-
|
4510
|
-
# Convert date/times value if required.
|
4511
|
-
if param[:validate] == 4 || param[:validate] == 5
|
4512
|
-
if param[:value] =~ /T/
|
4513
|
-
date_time = convert_date_time(param[:value])
|
4514
|
-
unless date_time
|
4515
|
-
# carp "Invalid date/time value '$param->{value}' " .
|
4516
|
-
# "in data_validation()";
|
4517
|
-
return -3
|
4518
|
-
else
|
4519
|
-
param[:value] = date_time
|
4520
|
-
end
|
4521
|
-
end
|
4522
|
-
if param[:maximum] && param[:maximum] =~ /T/
|
4523
|
-
date_time = convert_date_time(param[:maximum])
|
4524
|
-
|
4525
|
-
unless date_time
|
4526
|
-
# carp "Invalid date/time value '$param->{maximum}' " .
|
4527
|
-
# "in data_validation()";
|
4528
|
-
return -3
|
4529
|
-
else
|
4530
|
-
param[:maximum] = date_time
|
4531
|
-
end
|
4532
|
-
end
|
4533
|
-
end
|
4534
|
-
|
4535
|
-
# Set some defaults if they haven't been defined by the user.
|
4536
|
-
param[:ignore_blank] = 1 unless param[:ignore_blank]
|
4537
|
-
param[:dropdown] = 1 unless param[:dropdown]
|
4538
|
-
param[:show_input] = 1 unless param[:show_input]
|
4539
|
-
param[:show_error] = 1 unless param[:show_error]
|
4540
|
-
|
4541
|
-
# These are the cells to which the validation is applied.
|
4542
|
-
param[:cells] = [[row1, col1, row2, col2]]
|
4220
|
+
return -2 if !row2.kind_of?(Hash) && check_dimensions(row2, col2, 1, 1) != 0
|
4543
4221
|
|
4544
|
-
|
4545
|
-
if
|
4546
|
-
|
4547
|
-
param[:cells].push(param[:other_cells])
|
4548
|
-
end
|
4222
|
+
validation = DataValidation.factory(parser, date_1904?, *args)
|
4223
|
+
return validation if (-3..-1).include?(validation)
|
4549
4224
|
|
4550
4225
|
# Store the validation information until we close the worksheet.
|
4551
|
-
@validations
|
4552
|
-
end
|
4553
|
-
|
4554
|
-
def active=(val) # :nodoc:
|
4555
|
-
@active = val
|
4226
|
+
@validations << validation
|
4556
4227
|
end
|
4557
4228
|
|
4558
4229
|
def is_name_utf16be? # :nodoc:
|
@@ -4566,11 +4237,7 @@ def is_name_utf16be? # :nodoc:
|
|
4566
4237
|
end
|
4567
4238
|
|
4568
4239
|
def index # :nodoc:
|
4569
|
-
@index
|
4570
|
-
end
|
4571
|
-
|
4572
|
-
def index=(val) # :nodoc:
|
4573
|
-
@index = val
|
4240
|
+
@workbook.worksheets.index(self)
|
4574
4241
|
end
|
4575
4242
|
|
4576
4243
|
def type # :nodoc:
|
@@ -4578,47 +4245,7 @@ def type # :nodoc:
|
|
4578
4245
|
end
|
4579
4246
|
|
4580
4247
|
def images_array # :nodoc:
|
4581
|
-
@
|
4582
|
-
end
|
4583
|
-
|
4584
|
-
def filter_area # :nodoc:
|
4585
|
-
@filter_area
|
4586
|
-
end
|
4587
|
-
|
4588
|
-
def filter_count # :nodoc:
|
4589
|
-
@filter_count
|
4590
|
-
end
|
4591
|
-
|
4592
|
-
def title_rowmin # :nodoc:
|
4593
|
-
@title_rowmin
|
4594
|
-
end
|
4595
|
-
|
4596
|
-
def title_rowmax # :nodoc:
|
4597
|
-
@title_rowmax
|
4598
|
-
end
|
4599
|
-
|
4600
|
-
def title_colmin # :nodoc:
|
4601
|
-
@title_colmin
|
4602
|
-
end
|
4603
|
-
|
4604
|
-
def title_colmax # :nodoc:
|
4605
|
-
@title_colmax
|
4606
|
-
end
|
4607
|
-
|
4608
|
-
def print_rowmin # :nodoc:
|
4609
|
-
@print_rowmin
|
4610
|
-
end
|
4611
|
-
|
4612
|
-
def print_rowmax # :nodoc:
|
4613
|
-
@print_rowmax
|
4614
|
-
end
|
4615
|
-
|
4616
|
-
def print_colmin # :nodoc:
|
4617
|
-
@print_colmin
|
4618
|
-
end
|
4619
|
-
|
4620
|
-
def print_colmax # :nodoc:
|
4621
|
-
@print_colmax
|
4248
|
+
@images.array
|
4622
4249
|
end
|
4623
4250
|
|
4624
4251
|
def offset # :nodoc:
|
@@ -4645,10 +4272,6 @@ def hidden=(val) # :nodoc:
|
|
4645
4272
|
@hidden = val
|
4646
4273
|
end
|
4647
4274
|
|
4648
|
-
def object_ids=(val) # :nodoc:
|
4649
|
-
@object_ids = val
|
4650
|
-
end
|
4651
|
-
|
4652
4275
|
def num_images # :nodoc:
|
4653
4276
|
@num_images
|
4654
4277
|
end
|
@@ -4665,90 +4288,438 @@ def image_mso_size=(val) # :nodoc:
|
|
4665
4288
|
@image_mso_size = val
|
4666
4289
|
end
|
4667
4290
|
|
4668
|
-
|
4669
|
-
|
4670
|
-
#
|
4671
|
-
def prepare_images #:nodoc:
|
4672
|
-
prepare_common(:images)
|
4291
|
+
def images_size #:nodoc:
|
4292
|
+
@images.array.size
|
4673
4293
|
end
|
4674
|
-
# private :prepare_images
|
4675
4294
|
|
4676
|
-
|
4677
|
-
|
4678
|
-
#
|
4679
|
-
def prepare_comments #:nodoc:
|
4680
|
-
prepare_common(:comments)
|
4295
|
+
def comments_size #:nodoc:
|
4296
|
+
@comments.array.size
|
4681
4297
|
end
|
4682
|
-
# private :prepare_comments
|
4683
4298
|
|
4684
|
-
|
4685
|
-
|
4686
|
-
#
|
4687
|
-
def prepare_charts #:nodoc:
|
4688
|
-
prepare_common(:charts)
|
4299
|
+
def charts_size #:nodoc:
|
4300
|
+
@charts.array.size
|
4689
4301
|
end
|
4690
|
-
# private :prepare_charts
|
4691
|
-
|
4692
|
-
###############################################################################
|
4693
|
-
#
|
4694
|
-
# Internal methods
|
4695
|
-
#
|
4696
|
-
|
4697
|
-
private
|
4698
4302
|
|
4699
|
-
def
|
4700
|
-
@
|
4303
|
+
def print_title_name_record_long #:nodoc:
|
4304
|
+
@title_range.name_record_long(@workbook.ext_refs["#{index}:#{index}"])
|
4701
4305
|
end
|
4702
4306
|
|
4703
|
-
def
|
4704
|
-
|
4307
|
+
def name_record_short(cell_range, hidden = nil)
|
4308
|
+
cell_range.name_record_short(@workbook.ext_refs["#{index}:#{index}"], hidden)
|
4705
4309
|
end
|
4706
4310
|
|
4707
|
-
def
|
4708
|
-
|
4709
|
-
|
4710
|
-
limit = encoding != 0 ? 255 *2 : 255
|
4711
|
-
|
4712
|
-
# Handle utf8 strings
|
4713
|
-
if is_utf8?(string)
|
4714
|
-
string = utf8_to_16be(string)
|
4715
|
-
encoding = 1
|
4716
|
-
end
|
4311
|
+
def print_title_name_record_short(hidden = nil) #:nodoc:
|
4312
|
+
name_record_short(@title_range, hidden)
|
4313
|
+
end
|
4717
4314
|
|
4718
|
-
|
4719
|
-
|
4720
|
-
|
4721
|
-
end
|
4315
|
+
def autofilter_name_record_short(hidden = nil) #:nodoc:
|
4316
|
+
name_record_short(@filter_area, hidden) if @filter_area.count != 0
|
4317
|
+
end
|
4722
4318
|
|
4723
|
-
|
4724
|
-
|
4725
|
-
@margin_header = margin
|
4726
|
-
@header_encoding = encoding
|
4727
|
-
else
|
4728
|
-
@footer = string
|
4729
|
-
@margin_footer = margin
|
4730
|
-
@footer_encoding = encoding
|
4731
|
-
end
|
4319
|
+
def print_area_name_record_short(hidden = nil) #:nodoc:
|
4320
|
+
name_record_short(@print_range, hidden) if @print_range.row_min
|
4732
4321
|
end
|
4733
4322
|
|
4734
4323
|
#
|
4735
|
-
#
|
4736
|
-
#
|
4737
|
-
# contain whitespace and/or quoted double quotes (Excel's escaped quotes).
|
4324
|
+
# Calculate the vertices that define the position of a graphical object within
|
4325
|
+
# the worksheet.
|
4738
4326
|
#
|
4739
|
-
#
|
4740
|
-
#
|
4741
|
-
#
|
4742
|
-
#
|
4743
|
-
#
|
4327
|
+
# +------------+------------+
|
4328
|
+
# | A | B |
|
4329
|
+
# +-----+------------+------------+
|
4330
|
+
# | |(x1,y1) | |
|
4331
|
+
# | 1 |(A1)._______|______ |
|
4332
|
+
# | | | | |
|
4333
|
+
# | | | | |
|
4334
|
+
# +-----+----| BITMAP |-----+
|
4335
|
+
# | | | | |
|
4336
|
+
# | 2 | |______________. |
|
4337
|
+
# | | | (B2)|
|
4338
|
+
# | | | (x2,y2)|
|
4339
|
+
# +---- +------------+------------+
|
4744
4340
|
#
|
4745
|
-
|
4746
|
-
|
4747
|
-
|
4748
|
-
|
4749
|
-
|
4750
|
-
|
4751
|
-
|
4341
|
+
# Example of a bitmap that covers some of the area from cell A1 to cell B2.
|
4342
|
+
#
|
4343
|
+
# Based on the width and height of the bitmap we need to calculate 8 vars:
|
4344
|
+
# $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
|
4345
|
+
# The width and height of the cells are also variable and have to be taken into
|
4346
|
+
# account.
|
4347
|
+
# The values of $col_start and $row_start are passed in from the calling
|
4348
|
+
# function. The values of $col_end and $row_end are calculated by subtracting
|
4349
|
+
# the width and height of the bitmap from the width and height of the
|
4350
|
+
# underlying cells.
|
4351
|
+
# The vertices are expressed as a percentage of the underlying cell width as
|
4352
|
+
# follows (rhs values are in pixels):
|
4353
|
+
#
|
4354
|
+
# x1 = X / W *1024
|
4355
|
+
# y1 = Y / H *256
|
4356
|
+
# x2 = (X-1) / W *1024
|
4357
|
+
# y2 = (Y-1) / H *256
|
4358
|
+
#
|
4359
|
+
# Where: X is distance from the left side of the underlying cell
|
4360
|
+
# Y is distance from the top of the underlying cell
|
4361
|
+
# W is the width of the cell
|
4362
|
+
# H is the height of the cell
|
4363
|
+
#
|
4364
|
+
# Note: the SDK incorrectly states that the height should be expressed as a
|
4365
|
+
# percentage of 1024.
|
4366
|
+
#
|
4367
|
+
def position_object(col_start, row_start, x1, y1, width, height) #:nodoc:
|
4368
|
+
# col_start; # Col containing upper left corner of object
|
4369
|
+
# x1; # Distance to left side of object
|
4370
|
+
|
4371
|
+
# row_start; # Row containing top left corner of object
|
4372
|
+
# y1; # Distance to top of object
|
4373
|
+
|
4374
|
+
# col_end; # Col containing lower right corner of object
|
4375
|
+
# x2; # Distance to right side of object
|
4376
|
+
|
4377
|
+
# row_end; # Row containing bottom right corner of object
|
4378
|
+
# y2; # Distance to bottom of object
|
4379
|
+
|
4380
|
+
# width; # Width of image frame
|
4381
|
+
# height; # Height of image frame
|
4382
|
+
|
4383
|
+
# Adjust start column for offsets that are greater than the col width
|
4384
|
+
x1, col_start = adjust_col_position(x1, col_start)
|
4385
|
+
|
4386
|
+
# Adjust start row for offsets that are greater than the row height
|
4387
|
+
y1, row_start = adjust_row_position(y1, row_start)
|
4388
|
+
|
4389
|
+
# Initialise end cell to the same as the start cell
|
4390
|
+
col_end = col_start
|
4391
|
+
row_end = row_start
|
4392
|
+
|
4393
|
+
width += x1
|
4394
|
+
height += y1
|
4395
|
+
|
4396
|
+
# Subtract the underlying cell widths to find the end cell of the image
|
4397
|
+
width, col_end = adjust_col_position(width, col_end)
|
4398
|
+
|
4399
|
+
# Subtract the underlying cell heights to find the end cell of the image
|
4400
|
+
height, row_end = adjust_row_position(height, row_end)
|
4401
|
+
|
4402
|
+
# Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
|
4403
|
+
# with zero eight or width.
|
4404
|
+
#
|
4405
|
+
return if size_col(col_start) == 0
|
4406
|
+
return if size_col(col_end) == 0
|
4407
|
+
return if size_row(row_start) == 0
|
4408
|
+
return if size_row(row_end) == 0
|
4409
|
+
|
4410
|
+
# Convert the pixel values to the percentage value expected by Excel
|
4411
|
+
x1 = 1024.0 * x1 / size_col(col_start)
|
4412
|
+
y1 = 256.0 * y1 / size_row(row_start)
|
4413
|
+
x2 = 1024.0 * width / size_col(col_end)
|
4414
|
+
y2 = 256.0 * height / size_row(row_end)
|
4415
|
+
|
4416
|
+
# Simulate ceil() without calling POSIX::ceil().
|
4417
|
+
x1 = (x1 +0.5).to_i
|
4418
|
+
y1 = (y1 +0.5).to_i
|
4419
|
+
x2 = (x2 +0.5).to_i
|
4420
|
+
y2 = (y2 +0.5).to_i
|
4421
|
+
|
4422
|
+
[
|
4423
|
+
col_start, x1,
|
4424
|
+
row_start, y1,
|
4425
|
+
col_end, x2,
|
4426
|
+
row_end, y2
|
4427
|
+
]
|
4428
|
+
end
|
4429
|
+
|
4430
|
+
def filter_count
|
4431
|
+
@filter_area.count
|
4432
|
+
end
|
4433
|
+
|
4434
|
+
def store_parent_mso_record(dg_length, spgr_length, spid) # :nodoc:
|
4435
|
+
store_mso_dg_container(dg_length) +
|
4436
|
+
store_mso_dg +
|
4437
|
+
store_mso_spgr_container(spgr_length) +
|
4438
|
+
store_mso_sp_container(40) +
|
4439
|
+
store_mso_spgr() +
|
4440
|
+
store_mso_sp(0x0, spid, 0x0005)
|
4441
|
+
end
|
4442
|
+
|
4443
|
+
#
|
4444
|
+
# Write the Escher SpContainer record that is part of MSODRAWING.
|
4445
|
+
#
|
4446
|
+
def store_mso_sp_container(length) #:nodoc:
|
4447
|
+
type = 0xF004
|
4448
|
+
version = 15
|
4449
|
+
instance = 0
|
4450
|
+
data = ''
|
4451
|
+
|
4452
|
+
add_mso_generic(type, version, instance, data, length)
|
4453
|
+
end
|
4454
|
+
|
4455
|
+
#
|
4456
|
+
# Write the Escher Sp record that is part of MSODRAWING.
|
4457
|
+
#
|
4458
|
+
def store_mso_sp(instance, spid, options) #:nodoc:
|
4459
|
+
type = 0xF00A
|
4460
|
+
version = 2
|
4461
|
+
data = ''
|
4462
|
+
length = 8
|
4463
|
+
data = [spid, options].pack('VV')
|
4464
|
+
|
4465
|
+
add_mso_generic(type, version, instance, data, length)
|
4466
|
+
end
|
4467
|
+
|
4468
|
+
#
|
4469
|
+
# Write the Escher Opt record that is part of MSODRAWING.
|
4470
|
+
#
|
4471
|
+
def store_mso_opt_image(spid) #:nodoc:
|
4472
|
+
type = 0xF00B
|
4473
|
+
version = 3
|
4474
|
+
instance = 3
|
4475
|
+
data = ''
|
4476
|
+
length = nil
|
4477
|
+
|
4478
|
+
data = [0x4104].pack('v') +
|
4479
|
+
[spid].pack('V') +
|
4480
|
+
[0x01BF].pack('v') +
|
4481
|
+
[0x00010000].pack('V') +
|
4482
|
+
[0x03BF].pack( 'v') +
|
4483
|
+
[0x00080000].pack( 'V')
|
4484
|
+
|
4485
|
+
add_mso_generic(type, version, instance, data, length)
|
4486
|
+
end
|
4487
|
+
|
4488
|
+
#
|
4489
|
+
# Write the Escher ClientAnchor record that is part of MSODRAWING.
|
4490
|
+
# flag
|
4491
|
+
# col_start # Col containing upper left corner of object
|
4492
|
+
# x1 # Distance to left side of object
|
4493
|
+
#
|
4494
|
+
# row_start # Row containing top left corner of object
|
4495
|
+
# y1 # Distance to top of object
|
4496
|
+
#
|
4497
|
+
# col_end # Col containing lower right corner of object
|
4498
|
+
# x2 # Distance to right side of object
|
4499
|
+
#
|
4500
|
+
# row_end # Row containing bottom right corner of object
|
4501
|
+
# y2 # Distance to bottom of object
|
4502
|
+
#
|
4503
|
+
def store_mso_client_anchor(flag, col_start, x1, row_start, y1, col_end, x2, row_end, y2) #:nodoc:
|
4504
|
+
type = 0xF010
|
4505
|
+
version = 0
|
4506
|
+
instance = 0
|
4507
|
+
data = ''
|
4508
|
+
length = 18
|
4509
|
+
|
4510
|
+
data = [flag, col_start, x1, row_start, y1, col_end, x2, row_end, y2].pack('v9')
|
4511
|
+
|
4512
|
+
add_mso_generic(type, version, instance, data, length)
|
4513
|
+
end
|
4514
|
+
|
4515
|
+
#
|
4516
|
+
# Write the Escher ClientData record that is part of MSODRAWING.
|
4517
|
+
#
|
4518
|
+
def store_mso_client_data #:nodoc:
|
4519
|
+
type = 0xF011
|
4520
|
+
version = 0
|
4521
|
+
instance = 0
|
4522
|
+
data = ''
|
4523
|
+
length = 0
|
4524
|
+
|
4525
|
+
add_mso_generic(type, version, instance, data, length)
|
4526
|
+
end
|
4527
|
+
|
4528
|
+
def comments_visible?
|
4529
|
+
@comments.visible?
|
4530
|
+
end
|
4531
|
+
|
4532
|
+
#
|
4533
|
+
# Excel BIFF BOUNDSHEET record.
|
4534
|
+
#
|
4535
|
+
# sheetname # Worksheet name
|
4536
|
+
# offset # Location of worksheet BOF
|
4537
|
+
# type # Worksheet type
|
4538
|
+
# hidden # Worksheet hidden flag
|
4539
|
+
# encoding # Sheet name encoding
|
4540
|
+
#
|
4541
|
+
def boundsheet #:nodoc:
|
4542
|
+
hidden = self.hidden? ? 1 : 0
|
4543
|
+
encoding = self.is_name_utf16be? ? 1 : 0
|
4544
|
+
|
4545
|
+
record = 0x0085 # Record identifier
|
4546
|
+
length = 0x08 + @name.bytesize # Number of bytes to follow
|
4547
|
+
|
4548
|
+
cch = @name.bytesize # Length of sheet name
|
4549
|
+
|
4550
|
+
# Character length is num of chars not num of bytes
|
4551
|
+
cch /= 2 if is_name_utf16be?
|
4552
|
+
|
4553
|
+
# Change the UTF-16 name from BE to LE
|
4554
|
+
sheetname = is_name_utf16be? ? @name.unpack('v*').pack('n*') : @name
|
4555
|
+
|
4556
|
+
grbit = @type | hidden
|
4557
|
+
|
4558
|
+
header = [record, length].pack("vv")
|
4559
|
+
data = [@offset, grbit, cch, encoding].pack("VvCC")
|
4560
|
+
|
4561
|
+
header + data + sheetname
|
4562
|
+
end
|
4563
|
+
|
4564
|
+
def num_shapes
|
4565
|
+
1 + num_images + comments_size + charts_size + filter_count
|
4566
|
+
end
|
4567
|
+
|
4568
|
+
def push_object_ids(mso_size, drawings_saved, max_spid, start_spid, clusters)
|
4569
|
+
mso_size += image_mso_size
|
4570
|
+
|
4571
|
+
# Add a drawing object for each sheet with comments.
|
4572
|
+
drawings_saved += 1
|
4573
|
+
|
4574
|
+
# For each sheet start the spids at the next 1024 interval.
|
4575
|
+
max_spid = 1024 * (1 + Integer((max_spid -1)/1024.0))
|
4576
|
+
start_spid = max_spid
|
4577
|
+
|
4578
|
+
# Max spid for each sheet and eventually for the workbook.
|
4579
|
+
max_spid += num_shapes
|
4580
|
+
|
4581
|
+
# Store the cluster ids
|
4582
|
+
mso_size += 8 * (num_shapes / 1024 + 1)
|
4583
|
+
push_cluster(num_shapes, drawings_saved, clusters)
|
4584
|
+
|
4585
|
+
# Pass calculated values back to the worksheet
|
4586
|
+
@object_ids = ObjectIds.new(start_spid, drawings_saved, num_shapes, max_spid -1)
|
4587
|
+
|
4588
|
+
[mso_size, drawings_saved, max_spid, start_spid]
|
4589
|
+
end
|
4590
|
+
|
4591
|
+
def push_cluster(num_shapes, drawings_saved, clusters)
|
4592
|
+
i = num_shapes
|
4593
|
+
while i > 0
|
4594
|
+
size = i > 1024 ? 1024 : i
|
4595
|
+
clusters << [drawings_saved, size]
|
4596
|
+
i -= 1024
|
4597
|
+
end
|
4598
|
+
end
|
4599
|
+
|
4600
|
+
###############################################################################
|
4601
|
+
#
|
4602
|
+
# Internal methods
|
4603
|
+
#
|
4604
|
+
|
4605
|
+
private
|
4606
|
+
|
4607
|
+
def update_workbook_str_table(str)
|
4608
|
+
@workbook.update_str_table(str)
|
4609
|
+
end
|
4610
|
+
|
4611
|
+
def active?
|
4612
|
+
self == @workbook.worksheets.activesheet
|
4613
|
+
end
|
4614
|
+
|
4615
|
+
def frozen?
|
4616
|
+
@frozen
|
4617
|
+
end
|
4618
|
+
|
4619
|
+
def display_zeros?
|
4620
|
+
!@hide_zeros
|
4621
|
+
end
|
4622
|
+
|
4623
|
+
#
|
4624
|
+
# :call-seq:
|
4625
|
+
# store_formula(formula) # formula : text string of formula
|
4626
|
+
#
|
4627
|
+
# Pre-parse a formula. This is used in conjunction with repeat_formula()
|
4628
|
+
# to repetitively rewrite a formula without re-parsing it.
|
4629
|
+
#
|
4630
|
+
# The store_formula() method is used in conjunction with repeat_formula()
|
4631
|
+
# to speed up the generation of repeated formulas. See
|
4632
|
+
# "Improving performance when working with formulas" in
|
4633
|
+
# "FORMULAS AND FUNCTIONS IN EXCEL".
|
4634
|
+
#
|
4635
|
+
# The store_formula() method pre-parses a textual representation of a
|
4636
|
+
# formula and stores it for use at a later stage by the repeat_formula()
|
4637
|
+
# method.
|
4638
|
+
#
|
4639
|
+
# store_formula() carries the same speed penalty as write_formula(). However,
|
4640
|
+
# in practice it will be used less frequently.
|
4641
|
+
#
|
4642
|
+
# The return value of this method is a scalar that can be thought of as a
|
4643
|
+
# reference to a formula.
|
4644
|
+
#
|
4645
|
+
# sin = worksheet.store_formula('=SIN(A1)')
|
4646
|
+
# cos = worksheet.store_formula('=COS(A1)')
|
4647
|
+
#
|
4648
|
+
# worksheet.repeat_formula('B1', sin, format, 'A1', 'A2')
|
4649
|
+
# worksheet.repeat_formula('C1', cos, format, 'A1', 'A2')
|
4650
|
+
#
|
4651
|
+
# Although store_formula() is a worksheet method the return value can be used
|
4652
|
+
# in any worksheet:
|
4653
|
+
#
|
4654
|
+
# now = worksheet.store_formula('=NOW()')
|
4655
|
+
#
|
4656
|
+
# worksheet1.repeat_formula('B1', now)
|
4657
|
+
# worksheet2.repeat_formula('B1', now)
|
4658
|
+
# worksheet3.repeat_formula('B1', now)
|
4659
|
+
#
|
4660
|
+
def store_formula(formula) #:nodoc:
|
4661
|
+
# Strip the = sign at the beginning of the formula string
|
4662
|
+
formula.sub!(/^=/, '')
|
4663
|
+
|
4664
|
+
# In order to raise formula errors from the point of view of the calling
|
4665
|
+
# program we use an eval block and re-raise the error from here.
|
4666
|
+
#
|
4667
|
+
tokens = parser.parse_formula(formula)
|
4668
|
+
|
4669
|
+
# if ($@) {
|
4670
|
+
# $@ =~ s/\n$// # Strip the \n used in the Formula.pm die()
|
4671
|
+
# croak $@ # Re-raise the error
|
4672
|
+
# }
|
4673
|
+
|
4674
|
+
# Return the parsed tokens in an anonymous array
|
4675
|
+
[*tokens]
|
4676
|
+
end
|
4677
|
+
|
4678
|
+
def set_header_footer_common(type, string, margin, encoding) # :nodoc:
|
4679
|
+
ruby_19 { string = convert_to_ascii_if_ascii(string) }
|
4680
|
+
|
4681
|
+
limit = encoding != 0 ? 255 *2 : 255
|
4682
|
+
|
4683
|
+
# Handle utf8 strings
|
4684
|
+
if is_utf8?(string)
|
4685
|
+
string = utf8_to_16be(string)
|
4686
|
+
encoding = 1
|
4687
|
+
end
|
4688
|
+
|
4689
|
+
if string.bytesize >= limit
|
4690
|
+
# carp 'Header string must be less than 255 characters';
|
4691
|
+
return
|
4692
|
+
end
|
4693
|
+
|
4694
|
+
if type == :header
|
4695
|
+
@header = string
|
4696
|
+
@margin_header = margin
|
4697
|
+
@header_encoding = encoding
|
4698
|
+
else
|
4699
|
+
@footer = string
|
4700
|
+
@margin_footer = margin
|
4701
|
+
@footer_encoding = encoding
|
4702
|
+
end
|
4703
|
+
end
|
4704
|
+
|
4705
|
+
#
|
4706
|
+
# Extract the tokens from the filter expression. The tokens are mainly non-
|
4707
|
+
# whitespace groups. The only tricky part is to extract string tokens that
|
4708
|
+
# contain whitespace and/or quoted double quotes (Excel's escaped quotes).
|
4709
|
+
#
|
4710
|
+
# Examples: 'x < 2000'
|
4711
|
+
# 'x > 2000 and x < 5000'
|
4712
|
+
# 'x = "foo"'
|
4713
|
+
# 'x = "foo bar"'
|
4714
|
+
# 'x = "foo "" bar"'
|
4715
|
+
#
|
4716
|
+
def extract_filter_tokens(expression = nil) #:nodoc:
|
4717
|
+
return [] unless expression
|
4718
|
+
|
4719
|
+
tokens = []
|
4720
|
+
str = expression
|
4721
|
+
while str =~ /"(?:[^"]|"")*"|\S+/
|
4722
|
+
tokens << $&
|
4752
4723
|
str = $~.post_match
|
4753
4724
|
end
|
4754
4725
|
|
@@ -4911,11 +4882,6 @@ def compatibility?
|
|
4911
4882
|
end
|
4912
4883
|
end
|
4913
4884
|
|
4914
|
-
# key: :activesheet, :firstsheet, :str_total, :str_unique, :str_table
|
4915
|
-
def sinfo
|
4916
|
-
@workbook.sinfo
|
4917
|
-
end
|
4918
|
-
|
4919
4885
|
#
|
4920
4886
|
# Returns an index to the XF record in the workbook.
|
4921
4887
|
#
|
@@ -5158,9 +5124,7 @@ def write_url_web(row1, col1, row2, col2, url, str = nil, format = nil) #:
|
|
5158
5124
|
|
5159
5125
|
# Write the visible label but protect against url recursion in write().
|
5160
5126
|
str = url unless str
|
5161
|
-
|
5162
|
-
error = write(row1, col1, str, xf)
|
5163
|
-
@writing_url = 0
|
5127
|
+
error = write_string(row1, col1, str, xf)
|
5164
5128
|
return error if error == -2
|
5165
5129
|
|
5166
5130
|
# Pack the undocumented parts of the hyperlink stream
|
@@ -5228,9 +5192,7 @@ def write_url_internal(row1, col1, row2, col2, url, str = nil, format = nil)
|
|
5228
5192
|
|
5229
5193
|
# Write the visible label but protect against url recursion in write().
|
5230
5194
|
str = url unless str
|
5231
|
-
|
5232
|
-
error = write(row1, col1, str, xf)
|
5233
|
-
@writing_url = 0
|
5195
|
+
error = write_string(row1, col1, str, xf)
|
5234
5196
|
return error if error == -2
|
5235
5197
|
|
5236
5198
|
# Pack the undocumented parts of the hyperlink stream
|
@@ -5303,9 +5265,7 @@ def write_url_external(row1, col1, row2, col2, url, str = nil, format = nil)
|
|
5303
5265
|
|
5304
5266
|
# Write the visible label but protect against url recursion in write().
|
5305
5267
|
str = url.sub!(/\#/, ' - ') unless str
|
5306
|
-
|
5307
|
-
error = write(row1, col1, str, xf)
|
5308
|
-
@writing_url = 0
|
5268
|
+
error = write_string(row1, col1, str, xf)
|
5309
5269
|
return error if error == -2
|
5310
5270
|
|
5311
5271
|
# Determine if the link is relative or absolute:
|
@@ -5389,9 +5349,7 @@ def write_url_external_net(row1, col1, row2, col2, url, str, format) #:nod
|
|
5389
5349
|
|
5390
5350
|
# Write the visible label but protect against url recursion in write().
|
5391
5351
|
str = url.sub!(/\#/, ' - ') unless str
|
5392
|
-
|
5393
|
-
error = write(row1, col1, str, xf)
|
5394
|
-
@writing_url = 0
|
5352
|
+
error = write_string(row1, col1, str, xf)
|
5395
5353
|
return error if error == -2
|
5396
5354
|
|
5397
5355
|
dir_long, link_type, sheet_len, sheet = analyze_link(url)
|
@@ -5454,125 +5412,6 @@ def analyze_link(url, absolute = nil) # :nodoc:
|
|
5454
5412
|
[dir_long, link_type, sheet_len, sheet]
|
5455
5413
|
end
|
5456
5414
|
|
5457
|
-
#
|
5458
|
-
# The function takes a date and time in ISO8601 "yyyy-mm-ddThh:mm:ss.ss" format
|
5459
|
-
# and converts it to a decimal number representing a valid Excel date.
|
5460
|
-
#
|
5461
|
-
# Dates and times in Excel are represented by real numbers. The integer part of
|
5462
|
-
# the number stores the number of days since the epoch and the fractional part
|
5463
|
-
# stores the percentage of the day in seconds. The epoch can be either 1900 or
|
5464
|
-
# 1904.
|
5465
|
-
#
|
5466
|
-
# Parameter: Date and time string in one of the following formats:
|
5467
|
-
# yyyy-mm-ddThh:mm:ss.ss # Standard
|
5468
|
-
# yyyy-mm-ddT # Date only
|
5469
|
-
# Thh:mm:ss.ss # Time only
|
5470
|
-
#
|
5471
|
-
# Returns:
|
5472
|
-
# A decimal number representing a valid Excel date, or
|
5473
|
-
# undef if the date is invalid.
|
5474
|
-
#
|
5475
|
-
def convert_date_time(date_time_string) #:nodoc:
|
5476
|
-
date_time = date_time_string
|
5477
|
-
|
5478
|
-
days = 0 # Number of days since epoch
|
5479
|
-
seconds = 0 # Time expressed as fraction of 24h hours in seconds
|
5480
|
-
|
5481
|
-
# Strip leading and trailing whitespace.
|
5482
|
-
date_time.sub!(/^\s+/, '')
|
5483
|
-
date_time.sub!(/\s+$/, '')
|
5484
|
-
|
5485
|
-
# Check for invalid date char.
|
5486
|
-
return nil if date_time =~ /[^0-9T:\-\.Z]/
|
5487
|
-
|
5488
|
-
# Check for "T" after date or before time.
|
5489
|
-
return nil unless date_time =~ /\dT|T\d/
|
5490
|
-
|
5491
|
-
# Strip trailing Z in ISO8601 date.
|
5492
|
-
date_time.sub!(/Z$/, '')
|
5493
|
-
|
5494
|
-
# Split into date and time.
|
5495
|
-
date, time = date_time.split(/T/)
|
5496
|
-
|
5497
|
-
# We allow the time portion of the input DateTime to be optional.
|
5498
|
-
if time
|
5499
|
-
# Match hh:mm:ss.sss+ where the seconds are optional
|
5500
|
-
if time =~ /^(\d\d):(\d\d)(:(\d\d(\.\d+)?))?/
|
5501
|
-
hour = $1.to_i
|
5502
|
-
min = $2.to_i
|
5503
|
-
sec = $4.to_f || 0
|
5504
|
-
else
|
5505
|
-
return nil # Not a valid time format.
|
5506
|
-
end
|
5507
|
-
|
5508
|
-
# Some boundary checks
|
5509
|
-
return nil if hour >= 24
|
5510
|
-
return nil if min >= 60
|
5511
|
-
return nil if sec >= 60
|
5512
|
-
|
5513
|
-
# Excel expresses seconds as a fraction of the number in 24 hours.
|
5514
|
-
seconds = (hour * 60* 60 + min * 60 + sec) / (24.0 * 60 * 60)
|
5515
|
-
end
|
5516
|
-
|
5517
|
-
# We allow the date portion of the input DateTime to be optional.
|
5518
|
-
return seconds if date == ''
|
5519
|
-
|
5520
|
-
# Match date as yyyy-mm-dd.
|
5521
|
-
if date =~ /^(\d\d\d\d)-(\d\d)-(\d\d)$/
|
5522
|
-
year = $1.to_i
|
5523
|
-
month = $2.to_i
|
5524
|
-
day = $3.to_i
|
5525
|
-
else
|
5526
|
-
return nil # Not a valid date format.
|
5527
|
-
end
|
5528
|
-
|
5529
|
-
# Set the epoch as 1900 or 1904. Defaults to 1900.
|
5530
|
-
# Special cases for Excel.
|
5531
|
-
unless date_1904?
|
5532
|
-
return seconds if date == '1899-12-31' # Excel 1900 epoch
|
5533
|
-
return seconds if date == '1900-01-00' # Excel 1900 epoch
|
5534
|
-
return 60 + seconds if date == '1900-02-29' # Excel false leapday
|
5535
|
-
end
|
5536
|
-
|
5537
|
-
|
5538
|
-
# We calculate the date by calculating the number of days since the epoch
|
5539
|
-
# and adjust for the number of leap days. We calculate the number of leap
|
5540
|
-
# days by normalising the year in relation to the epoch. Thus the year 2000
|
5541
|
-
# becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
|
5542
|
-
#
|
5543
|
-
epoch = date_1904? ? 1904 : 1900
|
5544
|
-
offset = date_1904? ? 4 : 0
|
5545
|
-
norm = 300
|
5546
|
-
range = year -epoch
|
5547
|
-
|
5548
|
-
# Set month days and check for leap year.
|
5549
|
-
mdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
5550
|
-
leap = 0
|
5551
|
-
leap = 1 if year % 4 == 0 && year % 100 != 0 || year % 400 == 0
|
5552
|
-
mdays[1] = 29 if leap != 0
|
5553
|
-
|
5554
|
-
# Some boundary checks
|
5555
|
-
return nil if year < epoch or year > 9999
|
5556
|
-
return nil if month < 1 or month > 12
|
5557
|
-
return nil if day < 1 or day > mdays[month -1]
|
5558
|
-
|
5559
|
-
# Accumulate the number of days since the epoch.
|
5560
|
-
days = day # Add days for current month
|
5561
|
-
(0 .. month-2).each do |m|
|
5562
|
-
days += mdays[m] # Add days for past months
|
5563
|
-
end
|
5564
|
-
days += range *365 # Add days for past years
|
5565
|
-
days += ((range) / 4) # Add leapdays
|
5566
|
-
days -= ((range + offset) /100) # Subtract 100 year leapdays
|
5567
|
-
days += ((range + offset + norm)/400) # Add 400 year leapdays
|
5568
|
-
days -= leap # Already counted above
|
5569
|
-
|
5570
|
-
# Adjust for Excel erroneously treating 1900 as a leap year.
|
5571
|
-
days += 1 if !date_1904? and days > 59
|
5572
|
-
|
5573
|
-
days + seconds
|
5574
|
-
end
|
5575
|
-
|
5576
5415
|
def date_1904? # :nodoc:
|
5577
5416
|
@workbook.date_1904
|
5578
5417
|
end
|
@@ -5610,20 +5449,13 @@ def write_row_default(row, colMic, colMac) #:nodoc:
|
|
5610
5449
|
#
|
5611
5450
|
def check_dimensions(row, col, ignore_row = 0, ignore_col = 0) #:nodoc:
|
5612
5451
|
return -2 unless row
|
5613
|
-
return -2 if row >=
|
5452
|
+
return -2 if row >= RowMax
|
5614
5453
|
|
5615
5454
|
return -2 unless col
|
5616
|
-
return -2 if col >=
|
5617
|
-
|
5618
|
-
if ignore_row == 0
|
5619
|
-
@dim_rowmin = row if !@dim_rowmin || (row < @dim_rowmin)
|
5620
|
-
@dim_rowmax = row if !@dim_rowmax || (row > @dim_rowmax)
|
5621
|
-
end
|
5455
|
+
return -2 if col >= ColMax
|
5622
5456
|
|
5623
|
-
if
|
5624
|
-
|
5625
|
-
@dim_colmax = col if !@dim_colmax || (col > @dim_colmax)
|
5626
|
-
end
|
5457
|
+
@dimension.row(row) if ignore_row == 0
|
5458
|
+
@dimension.col(col) if ignore_col == 0
|
5627
5459
|
|
5628
5460
|
0
|
5629
5461
|
end
|
@@ -5642,19 +5474,11 @@ def store_dimensions #:nodoc:
|
|
5642
5474
|
length = 0x000E # Number of bytes to follow
|
5643
5475
|
reserved = 0x0000 # Reserved by Excel
|
5644
5476
|
|
5645
|
-
|
5646
|
-
|
5647
|
-
col_min = @dim_colmin ? @dim_colmin : 0
|
5648
|
-
col_max = @dim_colmax ? @dim_colmax + 1 : 0
|
5649
|
-
|
5650
|
-
# Set member data to the new max/min value for use by store_table().
|
5651
|
-
@dim_rowmin = row_min
|
5652
|
-
@dim_rowmax = row_max
|
5653
|
-
@dim_colmin = col_min
|
5654
|
-
@dim_colmax = col_max
|
5477
|
+
@dimension.increment_row_max
|
5478
|
+
@dimension.increment_col_max
|
5655
5479
|
|
5656
5480
|
header = [record, length].pack("vv")
|
5657
|
-
fields = [row_min, row_max, col_min, col_max, reserved]
|
5481
|
+
fields = [@dimension.row_min, @dimension.row_max, @dimension.col_min, @dimension.col_max, reserved]
|
5658
5482
|
data = fields.pack("VVvvv")
|
5659
5483
|
|
5660
5484
|
prepend(header, data)
|
@@ -5685,10 +5509,10 @@ def store_window2 #:nodoc:
|
|
5685
5509
|
fDspZeros = display_zeros? ? 1 : 0 # 4
|
5686
5510
|
fDefaultHdr = 1 # 5
|
5687
5511
|
fArabic = @display_arabic || 0 # 6
|
5688
|
-
fDspGuts = @
|
5512
|
+
fDspGuts = @outline.visible? ? 1 : 0 # 7
|
5689
5513
|
fFrozenNoSplit = @frozen_no_split # 0 - bit
|
5690
5514
|
fSelected = selected? ? 1 : 0 # 1
|
5691
|
-
fPaged =
|
5515
|
+
fPaged = active? ? 1 : 0 # 2
|
5692
5516
|
fBreakPreview = 0 # 3
|
5693
5517
|
|
5694
5518
|
grbit = fDspFmla
|
@@ -5767,62 +5591,8 @@ def store_defcol #:nodoc:
|
|
5767
5591
|
prepend(header, data)
|
5768
5592
|
end
|
5769
5593
|
|
5770
|
-
#
|
5771
|
-
|
5772
|
-
# lastcol : Last formatted column
|
5773
|
-
# width : Col width in user units, 8.43 is default
|
5774
|
-
# format : format object
|
5775
|
-
# hidden : hidden flag
|
5776
|
-
# lebel : outline level
|
5777
|
-
# collapsed : ?
|
5778
|
-
#
|
5779
|
-
# Write BIFF record COLINFO to define column widths
|
5780
|
-
#
|
5781
|
-
# Note: The SDK says the record length is 0x0B but Excel writes a 0x0C
|
5782
|
-
# length record.
|
5783
|
-
#
|
5784
|
-
def store_colinfo(firstcol=0, lastcol=0, width=8.43, format=nil, hidden=false, level=0, collapsed=false) #:nodoc:
|
5785
|
-
record = 0x007D # Record identifier
|
5786
|
-
length = 0x000B # Number of bytes to follow
|
5787
|
-
|
5788
|
-
# Excel rounds the column width to the nearest pixel. Therefore we first
|
5789
|
-
# convert to pixels and then to the internal units. The pixel to users-units
|
5790
|
-
# relationship is different for values less than 1.
|
5791
|
-
#
|
5792
|
-
width ||= 8.43
|
5793
|
-
if width < 1
|
5794
|
-
pixels = width *12
|
5795
|
-
else
|
5796
|
-
pixels = width *7 +5
|
5797
|
-
end
|
5798
|
-
pixels = pixels.to_i
|
5799
|
-
|
5800
|
-
coldx = (pixels *256/7).to_i # Col width in internal units
|
5801
|
-
grbit = 0x0000 # Option flags
|
5802
|
-
reserved = 0x00 # Reserved
|
5803
|
-
|
5804
|
-
# Check for a format object
|
5805
|
-
if format && format.respond_to?(:xf_index)
|
5806
|
-
ixfe = format.xf_index
|
5807
|
-
else
|
5808
|
-
ixfe = 0x0F
|
5809
|
-
end
|
5810
|
-
|
5811
|
-
# Set the limits for the outline levels (0 <= x <= 7).
|
5812
|
-
level = 0 if level < 0
|
5813
|
-
level = 7 if level > 7
|
5814
|
-
|
5815
|
-
|
5816
|
-
# Set the options flags. (See set_row() for more details).
|
5817
|
-
grbit |= 0x0001 if hidden && hidden != 0
|
5818
|
-
grbit |= level << 8
|
5819
|
-
grbit |= 0x1000 if collapsed && collapsed != 0
|
5820
|
-
|
5821
|
-
header = [record, length].pack("vv")
|
5822
|
-
data = [firstcol, lastcol, coldx,
|
5823
|
-
ixfe, grbit, reserved].pack("vvvvvC")
|
5824
|
-
|
5825
|
-
prepend(header, data)
|
5594
|
+
def store_colinfo(colinfo) # :nodoc:
|
5595
|
+
prepend(*colinfo.biff_record)
|
5826
5596
|
end
|
5827
5597
|
|
5828
5598
|
#
|
@@ -5846,11 +5616,11 @@ def store_filtermode #:nodoc:
|
|
5846
5616
|
#
|
5847
5617
|
def store_autofilterinfo #:nodoc:
|
5848
5618
|
# Only write the record if the worksheet contains an autofilter.
|
5849
|
-
return '' if @
|
5619
|
+
return '' if @filter_area.count == 0
|
5850
5620
|
|
5851
5621
|
record = 0x009D # Record identifier
|
5852
5622
|
length = 0x0002 # Number of bytes to follow
|
5853
|
-
num_filters = @
|
5623
|
+
num_filters = @filter_area.count
|
5854
5624
|
|
5855
5625
|
header = [record, length].pack('vv')
|
5856
5626
|
data = [num_filters].pack('v')
|
@@ -5865,33 +5635,24 @@ def store_selection(first_row=0, first_col=0, last_row = nil, last_col =nil) #
|
|
5865
5635
|
record = 0x001D # Record identifier
|
5866
5636
|
length = 0x000F # Number of bytes to follow
|
5867
5637
|
|
5868
|
-
|
5869
|
-
|
5870
|
-
|
5638
|
+
pane_position = @active_pane # Pane position
|
5639
|
+
row_active = first_row # Active row
|
5640
|
+
col_active = first_col # Active column
|
5871
5641
|
irefAct = 0 # Active cell ref
|
5872
5642
|
cref = 1 # Number of refs
|
5873
5643
|
|
5874
|
-
|
5875
|
-
|
5876
|
-
|
5877
|
-
|
5644
|
+
row_first = first_row # First row in reference
|
5645
|
+
col_first = first_col # First col in reference
|
5646
|
+
row_last = last_row || row_first # Last row in reference
|
5647
|
+
col_last = last_col || col_first # Last col in reference
|
5878
5648
|
|
5879
5649
|
# Swap last row/col for first row/col as necessary
|
5880
|
-
if
|
5881
|
-
|
5882
|
-
rwFirst = rwLast
|
5883
|
-
rwLast = tmp
|
5884
|
-
end
|
5885
|
-
|
5886
|
-
if colFirst > colLast
|
5887
|
-
tmp = colFirst
|
5888
|
-
colFirst = colLast
|
5889
|
-
colLast = tmp
|
5890
|
-
end
|
5650
|
+
row_first, row_last = row_last, row_first if row_first > row_last
|
5651
|
+
col_first, col_last = col_last, col_first if col_first > col_last
|
5891
5652
|
|
5892
5653
|
header = [record, length].pack('vv')
|
5893
|
-
data = [
|
5894
|
-
|
5654
|
+
data = [pane_position, row_active, col_active, irefAct, cref,
|
5655
|
+
row_first, row_last, col_first, col_last].pack('CvvvvvvCC')
|
5895
5656
|
|
5896
5657
|
append(header, data)
|
5897
5658
|
end
|
@@ -6045,9 +5806,9 @@ def store_setup #:nodoc:
|
|
6045
5806
|
numHdr = [numHdr].pack('d')
|
6046
5807
|
numFtr = [numFtr].pack('d')
|
6047
5808
|
|
6048
|
-
if @byte_order
|
6049
|
-
numHdr
|
6050
|
-
numFtr
|
5809
|
+
if @byte_order
|
5810
|
+
numHdr.reverse!
|
5811
|
+
numFtr.reverse!
|
6051
5812
|
end
|
6052
5813
|
|
6053
5814
|
header = [record, length].pack('vv')
|
@@ -6155,7 +5916,7 @@ def store_margin_common(record, length, margin) # :nodoc:
|
|
6155
5916
|
header = [record, length].pack('vv')
|
6156
5917
|
data = [margin].pack('d')
|
6157
5918
|
|
6158
|
-
data
|
5919
|
+
data.reverse! if @byte_order
|
6159
5920
|
|
6160
5921
|
prepend(header, data)
|
6161
5922
|
end
|
@@ -6258,18 +6019,12 @@ def store_guts #:nodoc:
|
|
6258
6019
|
dxRwGut = 0x0000 # Size of row gutter
|
6259
6020
|
dxColGut = 0x0000 # Size of col gutter
|
6260
6021
|
|
6261
|
-
row_level = @
|
6262
|
-
col_level = 0
|
6263
|
-
|
6022
|
+
row_level = @outline.row_level
|
6264
6023
|
|
6265
6024
|
# Calculate the maximum column outline level. The equivalent calculation
|
6266
6025
|
# for the row outline level is carried out in set_row().
|
6267
6026
|
#
|
6268
|
-
@colinfo.
|
6269
|
-
# Skip cols without outline level info.
|
6270
|
-
next if colinfo.size < 6
|
6271
|
-
col_level = colinfo[5] if colinfo[5] > col_level
|
6272
|
-
end
|
6027
|
+
col_level = @colinfo.collect {|colinfo| colinfo.level}.max || 0
|
6273
6028
|
|
6274
6029
|
# Set the limits for the outline levels (0 <= x <= 7).
|
6275
6030
|
col_level = 0 if col_level < 0
|
@@ -6297,11 +6052,11 @@ def store_wsbool #:nodoc:
|
|
6297
6052
|
|
6298
6053
|
# Set the option flags
|
6299
6054
|
grbit |= 0x0001 # Auto page breaks visible
|
6300
|
-
grbit |= 0x0020 if @
|
6301
|
-
grbit |= 0x0040 if @
|
6302
|
-
grbit |= 0x0080 if @
|
6055
|
+
grbit |= 0x0020 if @outline.style != 0 # Auto outline styles
|
6056
|
+
grbit |= 0x0040 if @outline.below != 0 # Outline summary below
|
6057
|
+
grbit |= 0x0080 if @outline.right != 0 # Outline summary right
|
6303
6058
|
grbit |= 0x0100 if @fit_page != 0 # Page setup fit to page
|
6304
|
-
grbit |= 0x0400 if @
|
6059
|
+
grbit |= 0x0400 if @outline.visible? # Outline symbols displayed
|
6305
6060
|
|
6306
6061
|
header = [record, length].pack("vv")
|
6307
6062
|
data = [grbit].pack('v')
|
@@ -6437,7 +6192,7 @@ def store_table #:nodoc:
|
|
6437
6192
|
|
6438
6193
|
# Write the ROW records with updated max/min col fields.
|
6439
6194
|
#
|
6440
|
-
(0 .. @
|
6195
|
+
(0 .. @dimension.row_max-1).each do |row|
|
6441
6196
|
# Skip unless there is cell data in row or the row has been modified.
|
6442
6197
|
next unless @table[row] or @row_data[row]
|
6443
6198
|
|
@@ -6448,8 +6203,8 @@ def store_table #:nodoc:
|
|
6448
6203
|
row_offset += 20
|
6449
6204
|
|
6450
6205
|
# The max/min cols in the ROW records are the same as in DIMENSIONS.
|
6451
|
-
col_min = @
|
6452
|
-
col_max = @
|
6206
|
+
col_min = @dimension.col_min
|
6207
|
+
col_max = @dimension.col_max
|
6453
6208
|
|
6454
6209
|
# Write a user specified ROW record (modified by set_row()).
|
6455
6210
|
if @row_data[row]
|
@@ -6465,7 +6220,7 @@ def store_table #:nodoc:
|
|
6465
6220
|
# If 32 rows have been written or we are at the last row in the
|
6466
6221
|
# worksheet then write the cell data and the DBCELL record.
|
6467
6222
|
#
|
6468
|
-
if written_rows.size == 32
|
6223
|
+
if written_rows.size == 32 || row == @dimension.row_max - 1
|
6469
6224
|
# Offsets to the first cell of each row.
|
6470
6225
|
cell_offsets = []
|
6471
6226
|
cell_offsets.push(row_offset - 20)
|
@@ -6527,133 +6282,26 @@ def store_dbcell(row_offset, cell_offsets) #:nodoc:
|
|
6527
6282
|
# Store the INDEX record using the DBCELL offsets calculated in store_table().
|
6528
6283
|
#
|
6529
6284
|
# This is only used when compatibity_mode() is in operation.
|
6530
|
-
#
|
6531
|
-
def store_index #:nodoc:
|
6532
|
-
return unless compatibility?
|
6533
|
-
|
6534
|
-
indices = @db_indices
|
6535
|
-
reserved = 0x00000000
|
6536
|
-
row_min = @dim_rowmin
|
6537
|
-
row_max = @dim_rowmax
|
6538
|
-
|
6539
|
-
record = 0x020B # Record identifier
|
6540
|
-
length = 16 + 4 * indices.size # Bytes to follow
|
6541
|
-
|
6542
|
-
header = [record, length].pack('vv')
|
6543
|
-
data = [reserved, row_min, row_max, reserved].pack('VVVV')
|
6544
|
-
|
6545
|
-
indices.each do |index|
|
6546
|
-
data += [index + @offset + 20 + length + 4].pack('V')
|
6547
|
-
end
|
6548
|
-
|
6549
|
-
prepend(header, data)
|
6550
|
-
end
|
6551
|
-
|
6552
|
-
#
|
6553
|
-
# Calculate the vertices that define the position of a graphical object within
|
6554
|
-
# the worksheet.
|
6555
|
-
#
|
6556
|
-
# +------------+------------+
|
6557
|
-
# | A | B |
|
6558
|
-
# +-----+------------+------------+
|
6559
|
-
# | |(x1,y1) | |
|
6560
|
-
# | 1 |(A1)._______|______ |
|
6561
|
-
# | | | | |
|
6562
|
-
# | | | | |
|
6563
|
-
# +-----+----| BITMAP |-----+
|
6564
|
-
# | | | | |
|
6565
|
-
# | 2 | |______________. |
|
6566
|
-
# | | | (B2)|
|
6567
|
-
# | | | (x2,y2)|
|
6568
|
-
# +---- +------------+------------+
|
6569
|
-
#
|
6570
|
-
# Example of a bitmap that covers some of the area from cell A1 to cell B2.
|
6571
|
-
#
|
6572
|
-
# Based on the width and height of the bitmap we need to calculate 8 vars:
|
6573
|
-
# $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
|
6574
|
-
# The width and height of the cells are also variable and have to be taken into
|
6575
|
-
# account.
|
6576
|
-
# The values of $col_start and $row_start are passed in from the calling
|
6577
|
-
# function. The values of $col_end and $row_end are calculated by subtracting
|
6578
|
-
# the width and height of the bitmap from the width and height of the
|
6579
|
-
# underlying cells.
|
6580
|
-
# The vertices are expressed as a percentage of the underlying cell width as
|
6581
|
-
# follows (rhs values are in pixels):
|
6582
|
-
#
|
6583
|
-
# x1 = X / W *1024
|
6584
|
-
# y1 = Y / H *256
|
6585
|
-
# x2 = (X-1) / W *1024
|
6586
|
-
# y2 = (Y-1) / H *256
|
6587
|
-
#
|
6588
|
-
# Where: X is distance from the left side of the underlying cell
|
6589
|
-
# Y is distance from the top of the underlying cell
|
6590
|
-
# W is the width of the cell
|
6591
|
-
# H is the height of the cell
|
6592
|
-
#
|
6593
|
-
# Note: the SDK incorrectly states that the height should be expressed as a
|
6594
|
-
# percentage of 1024.
|
6595
|
-
#
|
6596
|
-
def position_object(col_start, row_start, x1, y1, width, height) #:nodoc:
|
6597
|
-
# col_start; # Col containing upper left corner of object
|
6598
|
-
# x1; # Distance to left side of object
|
6599
|
-
|
6600
|
-
# row_start; # Row containing top left corner of object
|
6601
|
-
# y1; # Distance to top of object
|
6602
|
-
|
6603
|
-
# col_end; # Col containing lower right corner of object
|
6604
|
-
# x2; # Distance to right side of object
|
6605
|
-
|
6606
|
-
# row_end; # Row containing bottom right corner of object
|
6607
|
-
# y2; # Distance to bottom of object
|
6608
|
-
|
6609
|
-
# width; # Width of image frame
|
6610
|
-
# height; # Height of image frame
|
6611
|
-
|
6612
|
-
# Adjust start column for offsets that are greater than the col width
|
6613
|
-
x1, col_start = adjust_col_position(x1, col_start)
|
6614
|
-
|
6615
|
-
# Adjust start row for offsets that are greater than the row height
|
6616
|
-
y1, row_start = adjust_row_position(y1, row_start)
|
6617
|
-
|
6618
|
-
# Initialise end cell to the same as the start cell
|
6619
|
-
col_end = col_start
|
6620
|
-
row_end = row_start
|
6621
|
-
|
6622
|
-
width += x1
|
6623
|
-
height += y1
|
6624
|
-
|
6625
|
-
# Subtract the underlying cell widths to find the end cell of the image
|
6626
|
-
width, col_end = adjust_col_position(width, col_end)
|
6285
|
+
#
|
6286
|
+
def store_index #:nodoc:
|
6287
|
+
return unless compatibility?
|
6627
6288
|
|
6628
|
-
|
6629
|
-
|
6289
|
+
indices = @db_indices
|
6290
|
+
reserved = 0x00000000
|
6291
|
+
row_min = @dimension.row_min
|
6292
|
+
row_max = @dimension.row_max
|
6630
6293
|
|
6631
|
-
#
|
6632
|
-
|
6633
|
-
#
|
6634
|
-
return if size_col(col_start) == 0
|
6635
|
-
return if size_col(col_end) == 0
|
6636
|
-
return if size_row(row_start) == 0
|
6637
|
-
return if size_row(row_end) == 0
|
6294
|
+
record = 0x020B # Record identifier
|
6295
|
+
length = 16 + 4 * indices.size # Bytes to follow
|
6638
6296
|
|
6639
|
-
|
6640
|
-
|
6641
|
-
y1 = 256.0 * y1 / size_row(row_start)
|
6642
|
-
x2 = 1024.0 * width / size_col(col_end)
|
6643
|
-
y2 = 256.0 * height / size_row(row_end)
|
6297
|
+
header = [record, length].pack('vv')
|
6298
|
+
data = [reserved, row_min, row_max, reserved].pack('VVVV')
|
6644
6299
|
|
6645
|
-
|
6646
|
-
|
6647
|
-
|
6648
|
-
x2 = (x2 +0.5).to_i
|
6649
|
-
y2 = (y2 +0.5).to_i
|
6300
|
+
indices.each do |index|
|
6301
|
+
data += [index + @offset + 20 + length + 4].pack('V')
|
6302
|
+
end
|
6650
6303
|
|
6651
|
-
|
6652
|
-
col_start, x1,
|
6653
|
-
row_start, y1,
|
6654
|
-
col_end, x2,
|
6655
|
-
row_end, y2
|
6656
|
-
]
|
6304
|
+
prepend(header, data)
|
6657
6305
|
end
|
6658
6306
|
|
6659
6307
|
def adjust_col_position(x, col) # :nodoc:
|
@@ -6738,8 +6386,8 @@ def store_autofilters #:nodoc:
|
|
6738
6386
|
# Skip all columns if no filter have been set.
|
6739
6387
|
return '' if @filter_on == 0
|
6740
6388
|
|
6741
|
-
col1 = @filter_area
|
6742
|
-
col2 = @filter_area
|
6389
|
+
col1 = @filter_area.col_min
|
6390
|
+
col2 = @filter_area.col_max
|
6743
6391
|
|
6744
6392
|
col1.upto(col2) do |i|
|
6745
6393
|
# Reverse order since records are being pre-pended.
|
@@ -6919,138 +6567,26 @@ def pack_string_doper(operator, length) #:nodoc:
|
|
6919
6567
|
#
|
6920
6568
|
def pack_number_doper(operator, number) #:nodoc:
|
6921
6569
|
number = [number].pack('d')
|
6922
|
-
number.reverse! if @byte_order
|
6570
|
+
number.reverse! if @byte_order
|
6923
6571
|
|
6924
6572
|
[0x04, operator].pack('CC') + number
|
6925
6573
|
end
|
6926
6574
|
|
6927
|
-
#
|
6928
|
-
# Methods related to comments and MSO objects.
|
6929
|
-
#
|
6930
|
-
|
6931
|
-
def prepare_common(param) # :nodoc:
|
6932
|
-
hash = {
|
6933
|
-
:images => @images, :comments => @comments, :charts => @charts
|
6934
|
-
}[param]
|
6935
|
-
|
6936
|
-
count = 0
|
6937
|
-
obj = []
|
6938
|
-
|
6939
|
-
# We sort the charts by row and column but that isn't strictly required.
|
6940
|
-
#
|
6941
|
-
rows = hash.keys.sort
|
6942
|
-
rows.each do |row|
|
6943
|
-
cols = hash[row].keys.sort
|
6944
|
-
cols.each do |col|
|
6945
|
-
obj.push(hash[row][col])
|
6946
|
-
count += 1
|
6947
|
-
end
|
6948
|
-
end
|
6949
|
-
|
6950
|
-
case param
|
6951
|
-
when :images
|
6952
|
-
@images = {}
|
6953
|
-
@images_array = obj
|
6954
|
-
when :comments
|
6955
|
-
@comments = {}
|
6956
|
-
@comments_array = obj
|
6957
|
-
when :charts
|
6958
|
-
@charts = {}
|
6959
|
-
@charts_array = obj
|
6960
|
-
end
|
6961
|
-
count
|
6962
|
-
end
|
6963
|
-
|
6964
6575
|
#
|
6965
6576
|
# Store the collections of records that make up images.
|
6966
6577
|
#
|
6967
6578
|
def store_images #:nodoc:
|
6968
|
-
record = 0x00EC # Record identifier
|
6969
|
-
length = 0x0000 # Bytes to follow
|
6970
|
-
|
6971
|
-
ids = @object_ids.dup
|
6972
|
-
spid = ids.shift
|
6973
|
-
|
6974
|
-
images = @images_array
|
6975
|
-
num_images = images.size
|
6976
|
-
|
6977
|
-
num_filters = @filter_count
|
6978
|
-
num_comments = @comments_array.size
|
6979
|
-
num_charts = @charts_array.size
|
6980
|
-
|
6981
6579
|
# Skip this if there aren't any images.
|
6982
|
-
return if
|
6983
|
-
|
6984
|
-
(0 .. num_images-1).each do |i|
|
6985
|
-
row = images[i].row
|
6986
|
-
col = images[i].col
|
6987
|
-
name = images[i].filename
|
6988
|
-
x_offset = images[i].x_offset
|
6989
|
-
y_offset = images[i].y_offset
|
6990
|
-
scale_x = images[i].scale_x
|
6991
|
-
scale_y = images[i].scale_y
|
6992
|
-
image_id = images[i].id
|
6993
|
-
type = images[i].type
|
6994
|
-
width = images[i].width
|
6995
|
-
height = images[i].height
|
6996
|
-
|
6997
|
-
width = width * scale_x unless scale_x == 0
|
6998
|
-
height = height * scale_y unless scale_y == 0
|
6999
|
-
|
7000
|
-
# Calculate the positions of image object.
|
7001
|
-
vertices = position_object(col,row,x_offset,y_offset,width,height)
|
7002
|
-
|
7003
|
-
if (i == 0)
|
7004
|
-
# Write the parent MSODRAWIING record.
|
7005
|
-
dg_length = 156 + 84*(num_images -1)
|
7006
|
-
spgr_length = 132 + 84*(num_images -1)
|
7007
|
-
|
7008
|
-
dg_length += 120 * num_charts
|
7009
|
-
spgr_length += 120 * num_charts
|
7010
|
-
|
7011
|
-
dg_length += 96 * num_filters
|
7012
|
-
spgr_length += 96 * num_filters
|
7013
|
-
|
7014
|
-
dg_length += 128 * num_comments
|
7015
|
-
spgr_length += 128 * num_comments
|
7016
|
-
|
7017
|
-
data = store_parent_mso_record(dg_length, ids, spgr_length, spid)
|
7018
|
-
spid += 1
|
7019
|
-
data +=
|
7020
|
-
store_mso_sp_container(76) +
|
7021
|
-
store_mso_sp(75, spid, 0x0A00)
|
7022
|
-
spid += 1
|
7023
|
-
data +=
|
7024
|
-
store_mso_opt_image(image_id) +
|
7025
|
-
store_mso_client_anchor(2, *vertices) +
|
7026
|
-
store_mso_client_data()
|
7027
|
-
else
|
7028
|
-
# Write the child MSODRAWIING record.
|
7029
|
-
data = store_mso_sp_container(76) +
|
7030
|
-
store_mso_sp(75, spid, 0x0A00)
|
7031
|
-
spid = spid + 1
|
7032
|
-
data = data +
|
7033
|
-
store_mso_opt_image(image_id) +
|
7034
|
-
store_mso_client_anchor(2, *vertices) +
|
7035
|
-
store_mso_client_data
|
7036
|
-
end
|
7037
|
-
length = data.bytesize
|
7038
|
-
header = [record, length].pack("vv")
|
7039
|
-
append(header, data)
|
6580
|
+
return if @images.array.empty?
|
7040
6581
|
|
7041
|
-
|
7042
|
-
end
|
6582
|
+
spid = @object_ids.spid
|
7043
6583
|
|
7044
|
-
@
|
7045
|
-
|
6584
|
+
@images.array.each_index do |i|
|
6585
|
+
@images.array[i].store_image_record(i, @images.array.size, charts_size, @filter_area.count, comments_size, spid)
|
6586
|
+
store_obj_image(i + 1)
|
6587
|
+
end
|
7046
6588
|
|
7047
|
-
|
7048
|
-
store_mso_dg_container(dg_length) +
|
7049
|
-
store_mso_dg(*ids) +
|
7050
|
-
store_mso_spgr_container(spgr_length) +
|
7051
|
-
store_mso_sp_container(40) +
|
7052
|
-
store_mso_spgr() +
|
7053
|
-
store_mso_sp(0x0, spid, 0x0005)
|
6589
|
+
@object_ids.spid = spid
|
7054
6590
|
end
|
7055
6591
|
|
7056
6592
|
def store_child_mso_record(spid, *vertices) # :nodoc:
|
@@ -7065,87 +6601,40 @@ def store_child_mso_record(spid, *vertices) # :nodoc:
|
|
7065
6601
|
# Store the collections of records that make up charts.
|
7066
6602
|
#
|
7067
6603
|
def store_charts #:nodoc:
|
7068
|
-
|
7069
|
-
|
7070
|
-
|
7071
|
-
|
7072
|
-
spid = ids.shift
|
7073
|
-
|
7074
|
-
charts = @charts_array
|
7075
|
-
num_charts = charts.size
|
7076
|
-
|
7077
|
-
num_filters = @filter_count
|
7078
|
-
num_comments = @comments_array.size
|
7079
|
-
|
7080
|
-
# Number of objects written so far.
|
7081
|
-
num_objects = @images_array.size
|
7082
|
-
|
7083
|
-
# Skip this if there aren't any charts.
|
7084
|
-
return if num_charts == 0
|
7085
|
-
|
7086
|
-
(0 .. num_charts-1 ).each do |i|
|
7087
|
-
row = charts[i][0]
|
7088
|
-
col = charts[i][1]
|
7089
|
-
chart = charts[i][2]
|
7090
|
-
x_offset = charts[i][3]
|
7091
|
-
y_offset = charts[i][4]
|
7092
|
-
scale_x = charts[i][5]
|
7093
|
-
scale_y = charts[i][6]
|
7094
|
-
width = 526
|
7095
|
-
height = 319
|
7096
|
-
|
7097
|
-
width *= scale_x if scale_x.respond_to?(:coerce) && scale_x != 0
|
7098
|
-
height *= scale_y if scale_y.respond_to?(:coerce) && scale_y != 0
|
7099
|
-
|
7100
|
-
# Calculate the positions of chart object.
|
7101
|
-
vertices = position_object( col,
|
7102
|
-
row,
|
7103
|
-
x_offset,
|
7104
|
-
y_offset,
|
7105
|
-
width,
|
7106
|
-
height
|
7107
|
-
)
|
7108
|
-
|
7109
|
-
if (i == 0 and num_objects == 0)
|
7110
|
-
# Write the parent MSODRAWIING record.
|
7111
|
-
dg_length = 192 + 120*(num_charts -1)
|
7112
|
-
spgr_length = 168 + 120*(num_charts -1)
|
7113
|
-
|
7114
|
-
dg_length += 96 *num_filters
|
7115
|
-
spgr_length += 96 *num_filters
|
7116
|
-
|
7117
|
-
dg_length += 128 *num_comments
|
7118
|
-
spgr_length += 128 *num_comments
|
7119
|
-
|
7120
|
-
|
7121
|
-
data = store_parent_mso_record(dg_length, ids, spgr_length, spid)
|
7122
|
-
spid += 1
|
7123
|
-
data += store_mso_sp_container_sp(spid)
|
7124
|
-
spid += 1
|
7125
|
-
data += store_mso_opt_chart_client_anchor_client_data(*vertices)
|
7126
|
-
else
|
7127
|
-
# Write the child MSODRAWIING record.
|
7128
|
-
data = store_mso_sp_container_sp(spid)
|
7129
|
-
spid += 1
|
7130
|
-
data += store_mso_opt_chart_client_anchor_client_data(*vertices)
|
7131
|
-
end
|
7132
|
-
length = data.bytesize
|
7133
|
-
header = [record, length].pack("vv")
|
7134
|
-
append(header, data)
|
6604
|
+
# Skip this if there aren't any charts.
|
6605
|
+
return if charts_size == 0
|
6606
|
+
|
6607
|
+
record = 0x00EC # Record identifier
|
7135
6608
|
|
7136
|
-
|
7137
|
-
|
6609
|
+
charts = @charts.array
|
6610
|
+
|
6611
|
+
charts.each_index do |i|
|
6612
|
+
data = ''
|
6613
|
+
if i == 0 && images_size == 0
|
6614
|
+
dg_length = 192 + 120 * (charts_size - 1) + 96 * filter_count + 128 * comments_size
|
6615
|
+
spgr_length = dg_length - 24
|
6616
|
+
|
6617
|
+
# Write the parent MSODRAWIING record.
|
6618
|
+
data += store_parent_mso_record(dg_length, spgr_length, @object_ids.spid)
|
6619
|
+
@object_ids.spid += 1
|
7138
6620
|
end
|
6621
|
+
data += store_mso_sp_container_sp(@object_ids.spid)
|
6622
|
+
data += store_mso_opt_chart_client_anchor_client_data(*charts[i].vertices)
|
6623
|
+
length = data.bytesize
|
6624
|
+
header = [record, length].pack("vv")
|
6625
|
+
append(header, data)
|
7139
6626
|
|
7140
|
-
|
7141
|
-
|
7142
|
-
|
7143
|
-
|
7144
|
-
#
|
7145
|
-
formula = "='#{@name}'!A1"
|
7146
|
-
store_formula(formula)
|
6627
|
+
store_obj_chart(images_size + i + 1)
|
6628
|
+
store_chart_binary(charts[i].chart)
|
6629
|
+
@object_ids.spid += 1
|
6630
|
+
end
|
7147
6631
|
|
7148
|
-
|
6632
|
+
# Simulate the EXTERNSHEET link between the chart and data using a formula
|
6633
|
+
# such as '=Sheet1!A1'.
|
6634
|
+
# TODO. Won't work for external data refs. Also should use a more direct
|
6635
|
+
# method.
|
6636
|
+
#
|
6637
|
+
store_formula("='#{@name}'!A1")
|
7149
6638
|
end
|
7150
6639
|
|
7151
6640
|
def store_mso_sp_container_sp(spid) # :nodoc:
|
@@ -7180,53 +6669,10 @@ def store_chart_binary(chart) #:nodoc:
|
|
7180
6669
|
# Store the collections of records that make up filters.
|
7181
6670
|
#
|
7182
6671
|
def store_filters #:nodoc:
|
7183
|
-
record = 0x00EC # Record identifier
|
7184
|
-
length = 0x0000 # Bytes to follow
|
7185
|
-
|
7186
|
-
ids = @object_ids.dup
|
7187
|
-
spid = ids.shift
|
7188
|
-
|
7189
|
-
filter_area = @filter_area
|
7190
|
-
num_filters = @filter_count
|
7191
|
-
|
7192
|
-
num_comments = @comments_array.size
|
7193
|
-
|
7194
|
-
# Number of objects written so far.
|
7195
|
-
num_objects = @images_array.size + @charts_array.size
|
7196
|
-
|
7197
6672
|
# Skip this if there aren't any filters.
|
7198
|
-
return if
|
7199
|
-
|
7200
|
-
row1, row2, col1, col2 = @filter_area
|
7201
|
-
|
7202
|
-
(0 .. num_filters-1).each do |i|
|
7203
|
-
vertices = [ col1 + i, 0, row1 , 0,
|
7204
|
-
col1 +i +1, 0, row1 + 1, 0]
|
7205
|
-
|
7206
|
-
if i == 0 && num_objects
|
7207
|
-
# Write the parent MSODRAWIING record.
|
7208
|
-
dg_length = 168 + 96 * (num_filters -1)
|
7209
|
-
spgr_length = 144 + 96 * (num_filters -1)
|
7210
|
-
|
7211
|
-
dg_length += 128 * num_comments
|
7212
|
-
spgr_length += 128 * num_comments
|
7213
|
-
|
7214
|
-
data = store_parent_mso_record(dg_length, ids, spgr_length, spid)
|
7215
|
-
spid += 1
|
7216
|
-
data += store_child_mso_record(spid, *vertices)
|
7217
|
-
spid += 1
|
7218
|
-
|
7219
|
-
else
|
7220
|
-
# Write the child MSODRAWIING record.
|
7221
|
-
data = store_child_mso_record(spid, *vertices)
|
7222
|
-
spid += 1
|
7223
|
-
end
|
7224
|
-
length = data.bytesize
|
7225
|
-
header = [record, length].pack("vv")
|
7226
|
-
append(header, data)
|
6673
|
+
return if @filter_area.count == 0
|
7227
6674
|
|
7228
|
-
|
7229
|
-
end
|
6675
|
+
@object_ids.spid = @filter_area.store
|
7230
6676
|
|
7231
6677
|
# Simulate the EXTERNSHEET link between the filter and data using a formula
|
7232
6678
|
# such as '=Sheet1!A1'.
|
@@ -7235,12 +6681,6 @@ def store_filters #:nodoc:
|
|
7235
6681
|
#
|
7236
6682
|
formula = "='#{@name}'!A1"
|
7237
6683
|
store_formula(formula)
|
7238
|
-
|
7239
|
-
@object_ids[0] = spid
|
7240
|
-
end
|
7241
|
-
|
7242
|
-
def unpack_record(data) # :nodoc:
|
7243
|
-
data.unpack('C*').map! {|c| sprintf("%02X", c) }.join(' ')
|
7244
6684
|
end
|
7245
6685
|
|
7246
6686
|
#
|
@@ -7250,73 +6690,18 @@ def unpack_record(data) # :nodoc:
|
|
7250
6690
|
# to write the NOTE records directly after the MSODRAWIING records.
|
7251
6691
|
#
|
7252
6692
|
def store_comments #:nodoc:
|
7253
|
-
|
7254
|
-
length = 0x0000 # Bytes to follow
|
7255
|
-
|
7256
|
-
ids = @object_ids.dup
|
7257
|
-
spid = ids.shift
|
6693
|
+
return if @comments.array.empty?
|
7258
6694
|
|
7259
|
-
|
7260
|
-
num_comments =
|
6695
|
+
spid = @object_ids.spid
|
6696
|
+
num_comments = comments_size
|
7261
6697
|
|
7262
6698
|
# Number of objects written so far.
|
7263
|
-
num_objects =
|
7264
|
-
|
7265
|
-
# Skip this if there aren't any comments.
|
7266
|
-
return if num_comments == 0
|
7267
|
-
|
7268
|
-
(0 .. num_comments-1).each do |i|
|
7269
|
-
row = comments[i][0]
|
7270
|
-
col = comments[i][1]
|
7271
|
-
str = comments[i][2]
|
7272
|
-
encoding = comments[i][3]
|
7273
|
-
visible = comments[i][6]
|
7274
|
-
color = comments[i][7]
|
7275
|
-
vertices = comments[i][8]
|
7276
|
-
str_len = str.bytesize
|
7277
|
-
str_len = str_len / 2 if encoding != 0 # Num of chars not bytes.
|
7278
|
-
formats = [[0, 9], [str_len, 0]]
|
7279
|
-
|
7280
|
-
if i == 0 and num_objects == 0
|
7281
|
-
# Write the parent MSODRAWIING record.
|
7282
|
-
dg_length = 200 + 128*(num_comments -1)
|
7283
|
-
spgr_length = 176 + 128*(num_comments -1)
|
7284
|
-
|
7285
|
-
data = store_parent_mso_record(dg_length, ids, spgr_length, spid)
|
7286
|
-
spid += 1
|
7287
|
-
else
|
7288
|
-
data = ''
|
7289
|
-
end
|
7290
|
-
data +=
|
7291
|
-
store_mso_sp_container(120) +
|
7292
|
-
store_mso_sp(202, spid, 0x0A00)
|
7293
|
-
spid += 1
|
7294
|
-
data +=
|
7295
|
-
store_mso_opt_comment(0x80, visible, color) +
|
7296
|
-
store_mso_client_anchor(3, *vertices) +
|
7297
|
-
store_mso_client_data
|
7298
|
-
length = data.bytesize
|
7299
|
-
header = [record, length].pack("vv")
|
7300
|
-
append(header, data)
|
6699
|
+
num_objects = images_size + @filter_area.count + charts_size
|
7301
6700
|
|
7302
|
-
|
7303
|
-
store_mso_drawing_text_box()
|
7304
|
-
store_txo(str_len)
|
7305
|
-
store_txo_continue_1(str, encoding)
|
7306
|
-
store_txo_continue_2(formats)
|
7307
|
-
end
|
6701
|
+
@comments.array.each_index { |i| spid = @comments.array[i].store_comment_record(i, num_objects, num_comments, spid) }
|
7308
6702
|
|
7309
6703
|
# Write the NOTE records after MSODRAWIING records.
|
7310
|
-
(
|
7311
|
-
row = comments[i][0]
|
7312
|
-
col = comments[i][1]
|
7313
|
-
author = comments[i][4]
|
7314
|
-
author_enc = comments[i][5]
|
7315
|
-
visible = comments[i][6]
|
7316
|
-
|
7317
|
-
store_note(row, col, num_objects + i + 1,
|
7318
|
-
author, author_enc, visible)
|
7319
|
-
end
|
6704
|
+
@comments.array.each_index { |i| @comments.array[i].store_note_record(num_objects + i + 1) }
|
7320
6705
|
end
|
7321
6706
|
|
7322
6707
|
#
|
@@ -7333,13 +6718,13 @@ def store_mso_dg_container(length) #:nodoc:
|
|
7333
6718
|
#
|
7334
6719
|
# Write the Escher Dg record that is part of MSODRAWING.
|
7335
6720
|
#
|
7336
|
-
def store_mso_dg
|
6721
|
+
def store_mso_dg #:nodoc:
|
7337
6722
|
type = 0xF008
|
7338
6723
|
version = 0
|
7339
6724
|
length = 8
|
7340
|
-
data = [num_shapes, max_spid].pack("VV")
|
6725
|
+
data = [@object_ids.num_shapes, @object_ids.max_spid].pack("VV")
|
7341
6726
|
|
7342
|
-
add_mso_generic(type, version,
|
6727
|
+
add_mso_generic(type, version, @object_ids.drawings_saved, data, length)
|
7343
6728
|
end
|
7344
6729
|
|
7345
6730
|
#
|
@@ -7354,18 +6739,6 @@ def store_mso_spgr_container(length) #:nodoc:
|
|
7354
6739
|
add_mso_generic(type, version, instance, data, length)
|
7355
6740
|
end
|
7356
6741
|
|
7357
|
-
#
|
7358
|
-
# Write the Escher SpContainer record that is part of MSODRAWING.
|
7359
|
-
#
|
7360
|
-
def store_mso_sp_container(length) #:nodoc:
|
7361
|
-
type = 0xF004
|
7362
|
-
version = 15
|
7363
|
-
instance = 0
|
7364
|
-
data = ''
|
7365
|
-
|
7366
|
-
add_mso_generic(type, version, instance, data, length)
|
7367
|
-
end
|
7368
|
-
|
7369
6742
|
#
|
7370
6743
|
# Write the Escher Spgr record that is part of MSODRAWING.
|
7371
6744
|
#
|
@@ -7379,68 +6752,6 @@ def store_mso_spgr #:nodoc:
|
|
7379
6752
|
add_mso_generic(type, version, instance, data, length)
|
7380
6753
|
end
|
7381
6754
|
|
7382
|
-
#
|
7383
|
-
# Write the Escher Sp record that is part of MSODRAWING.
|
7384
|
-
#
|
7385
|
-
def store_mso_sp(instance, spid, options) #:nodoc:
|
7386
|
-
type = 0xF00A
|
7387
|
-
version = 2
|
7388
|
-
data = ''
|
7389
|
-
length = 8
|
7390
|
-
data = [spid, options].pack('VV')
|
7391
|
-
|
7392
|
-
add_mso_generic(type, version, instance, data, length)
|
7393
|
-
end
|
7394
|
-
|
7395
|
-
#
|
7396
|
-
# Write the Escher Opt record that is part of MSODRAWING.
|
7397
|
-
#
|
7398
|
-
def store_mso_opt_comment(spid, visible = nil, colour = 0x50) #:nodoc:
|
7399
|
-
type = 0xF00B
|
7400
|
-
version = 3
|
7401
|
-
instance = 9
|
7402
|
-
data = ''
|
7403
|
-
length = 54
|
7404
|
-
|
7405
|
-
# Use the visible flag if set by the user or else use the worksheet value.
|
7406
|
-
# Note that the value used is the opposite of store_note().
|
7407
|
-
#
|
7408
|
-
if visible
|
7409
|
-
visible = visible != 0 ? 0x0000 : 0x0002
|
7410
|
-
else
|
7411
|
-
visible = @comments_visible != 0 ? 0x0000 : 0x0002
|
7412
|
-
end
|
7413
|
-
|
7414
|
-
data = [spid].pack('V') +
|
7415
|
-
['0000BF00080008005801000000008101'].pack("H*") +
|
7416
|
-
[colour].pack("C") +
|
7417
|
-
['000008830150000008BF011000110001'+'02000000003F0203000300BF03'].pack("H*") +
|
7418
|
-
[visible].pack('v') +
|
7419
|
-
['0A00'].pack('H*')
|
7420
|
-
|
7421
|
-
add_mso_generic(type, version, instance, data, length)
|
7422
|
-
end
|
7423
|
-
|
7424
|
-
#
|
7425
|
-
# Write the Escher Opt record that is part of MSODRAWING.
|
7426
|
-
#
|
7427
|
-
def store_mso_opt_image(spid) #:nodoc:
|
7428
|
-
type = 0xF00B
|
7429
|
-
version = 3
|
7430
|
-
instance = 3
|
7431
|
-
data = ''
|
7432
|
-
length = nil
|
7433
|
-
|
7434
|
-
data = [0x4104].pack('v') +
|
7435
|
-
[spid].pack('V') +
|
7436
|
-
[0x01BF].pack('v') +
|
7437
|
-
[0x00010000].pack('V') +
|
7438
|
-
[0x03BF].pack( 'v') +
|
7439
|
-
[0x00080000].pack( 'V')
|
7440
|
-
|
7441
|
-
add_mso_generic(type, version, instance, data, length)
|
7442
|
-
end
|
7443
|
-
|
7444
6755
|
#
|
7445
6756
|
# Write the Escher Opt record that is part of MSODRAWING.
|
7446
6757
|
#
|
@@ -7471,113 +6782,32 @@ def store_mso_opt_chart #:nodoc:
|
|
7471
6782
|
add_mso_generic(type, version, instance, data, length)
|
7472
6783
|
end
|
7473
6784
|
|
7474
|
-
#
|
7475
|
-
# Write the Escher Opt record that is part of MSODRAWING.
|
7476
|
-
#
|
7477
|
-
def store_mso_opt_filter #:nodoc:
|
7478
|
-
type = 0xF00B
|
7479
|
-
version = 3
|
7480
|
-
instance = 5
|
7481
|
-
data = ''
|
7482
|
-
length = nil
|
7483
|
-
|
7484
|
-
data = store_mso_protection_and_text
|
7485
|
-
data += [0x01BF].pack('v') + # Fill Style -> fNoFillHitTest
|
7486
|
-
[0x00010000].pack('V') +
|
7487
|
-
[0x01FF].pack('v') + # Line Style -> fNoLineDrawDash
|
7488
|
-
[0x00080000].pack('V') +
|
7489
|
-
[0x03BF].pack('v') + # Group Shape -> fPrint
|
7490
|
-
[0x000A0000].pack('V')
|
7491
|
-
|
7492
|
-
add_mso_generic(type, version, instance, data, length)
|
7493
|
-
end
|
7494
|
-
|
7495
|
-
def store_mso_protection_and_text # :nodoc:
|
7496
|
-
[0x007F].pack('v') + # Protection -> fLockAgainstGrouping
|
7497
|
-
[0x01040104].pack('V') +
|
7498
|
-
[0x00BF].pack('v') + # Text -> fFitTextToShape
|
7499
|
-
[0x00080008].pack('V')
|
7500
|
-
end
|
7501
|
-
|
7502
|
-
#
|
7503
|
-
# Write the Escher ClientAnchor record that is part of MSODRAWING.
|
7504
|
-
# flag
|
7505
|
-
# col_start # Col containing upper left corner of object
|
7506
|
-
# x1 # Distance to left side of object
|
7507
|
-
#
|
7508
|
-
# row_start # Row containing top left corner of object
|
7509
|
-
# y1 # Distance to top of object
|
7510
|
-
#
|
7511
|
-
# col_end # Col containing lower right corner of object
|
7512
|
-
# x2 # Distance to right side of object
|
7513
|
-
#
|
7514
|
-
# row_end # Row containing bottom right corner of object
|
7515
|
-
# y2 # Distance to bottom of object
|
7516
|
-
#
|
7517
|
-
def store_mso_client_anchor(flag, col_start, x1, row_start, y1, col_end, x2, row_end, y2) #:nodoc:
|
7518
|
-
type = 0xF010
|
7519
|
-
version = 0
|
7520
|
-
instance = 0
|
7521
|
-
data = ''
|
7522
|
-
length = 18
|
7523
|
-
|
7524
|
-
data = [flag, col_start, x1, row_start, y1, col_end, x2, row_end, y2].pack('v9')
|
7525
|
-
|
7526
|
-
add_mso_generic(type, version, instance, data, length)
|
7527
|
-
end
|
7528
|
-
|
7529
|
-
#
|
7530
|
-
# Write the Escher ClientData record that is part of MSODRAWING.
|
7531
|
-
#
|
7532
|
-
def store_mso_client_data #:nodoc:
|
7533
|
-
type = 0xF011
|
7534
|
-
version = 0
|
7535
|
-
instance = 0
|
7536
|
-
data = ''
|
7537
|
-
length = 0
|
7538
|
-
|
7539
|
-
add_mso_generic(type, version, instance, data, length)
|
7540
|
-
end
|
7541
|
-
|
7542
|
-
#
|
7543
|
-
# Write the OBJ record that is part of cell comments.
|
7544
|
-
# obj_id # Object ID number.
|
7545
|
-
#
|
7546
|
-
def store_obj_comment(obj_id) #:nodoc:
|
7547
|
-
record = 0x005D # Record identifier
|
7548
|
-
length = 0x0034 # Bytes to follow
|
7549
|
-
|
7550
|
-
obj_type = 0x0019 # Object type (comment).
|
7551
|
-
data = '' # Record data.
|
7552
|
-
|
7553
|
-
sub_record = 0x0000 # Sub-record identifier.
|
7554
|
-
sub_length = 0x0000 # Length of sub-record.
|
7555
|
-
sub_data = '' # Data of sub-record.
|
7556
|
-
options = 0x4011
|
7557
|
-
reserved = 0x0000
|
7558
|
-
|
7559
|
-
# Add ftCmo (common object data) subobject
|
7560
|
-
sub_record = 0x0015 # ftCmo
|
7561
|
-
sub_length = 0x0012
|
7562
|
-
sub_data = [obj_type, obj_id, options, reserved, reserved, reserved].pack( "vvvVVV")
|
7563
|
-
data = [sub_record, sub_length].pack("vv") + sub_data
|
7564
|
-
|
7565
|
-
# Add ftNts (note structure) subobject
|
7566
|
-
sub_record = 0x000D # ftNts
|
7567
|
-
sub_length = 0x0016
|
7568
|
-
sub_data = [reserved,reserved,reserved,reserved,reserved,reserved].pack( "VVVVVv")
|
7569
|
-
data += [sub_record, sub_length].pack("vv") + sub_data
|
7570
|
-
|
7571
|
-
# Add ftEnd (end of object) subobject
|
7572
|
-
sub_record = 0x0000 # ftNts
|
7573
|
-
sub_length = 0x0000
|
7574
|
-
data += [sub_record, sub_length].pack("vv")
|
7575
|
-
|
7576
|
-
# Pack the record.
|
7577
|
-
header = [record, length].pack("vv")
|
6785
|
+
#
|
6786
|
+
# Write the Escher Opt record that is part of MSODRAWING.
|
6787
|
+
#
|
6788
|
+
def store_mso_opt_filter #:nodoc:
|
6789
|
+
type = 0xF00B
|
6790
|
+
version = 3
|
6791
|
+
instance = 5
|
6792
|
+
data = ''
|
6793
|
+
length = nil
|
7578
6794
|
|
7579
|
-
|
6795
|
+
data = store_mso_protection_and_text
|
6796
|
+
data += [0x01BF].pack('v') + # Fill Style -> fNoFillHitTest
|
6797
|
+
[0x00010000].pack('V') +
|
6798
|
+
[0x01FF].pack('v') + # Line Style -> fNoLineDrawDash
|
6799
|
+
[0x00080000].pack('V') +
|
6800
|
+
[0x03BF].pack('v') + # Group Shape -> fPrint
|
6801
|
+
[0x000A0000].pack('V')
|
6802
|
+
|
6803
|
+
add_mso_generic(type, version, instance, data, length)
|
6804
|
+
end
|
7580
6805
|
|
6806
|
+
def store_mso_protection_and_text # :nodoc:
|
6807
|
+
[0x007F].pack('v') + # Protection -> fLockAgainstGrouping
|
6808
|
+
[0x01040104].pack('V') +
|
6809
|
+
[0x00BF].pack('v') + # Text -> fFitTextToShape
|
6810
|
+
[0x00080008].pack('V')
|
7581
6811
|
end
|
7582
6812
|
|
7583
6813
|
#
|
@@ -7632,15 +6862,8 @@ def store_obj_image(obj_id) #:nodoc:
|
|
7632
6862
|
# obj_id # Object ID number.
|
7633
6863
|
#
|
7634
6864
|
def store_obj_chart(obj_id) #:nodoc:
|
7635
|
-
record = 0x005D # Record identifier
|
7636
|
-
length = 0x001A # Bytes to follow
|
7637
|
-
|
7638
6865
|
obj_type = 0x0005 # Object type (chart).
|
7639
|
-
data = '' # Record data.
|
7640
6866
|
|
7641
|
-
sub_record = 0x0000 # Sub-record identifier.
|
7642
|
-
sub_length = 0x0000 # Length of sub-record.
|
7643
|
-
sub_data = '' # Data of sub-record.
|
7644
6867
|
options = 0x6011
|
7645
6868
|
reserved = 0x0000
|
7646
6869
|
|
@@ -7656,365 +6879,12 @@ def store_obj_chart(obj_id) #:nodoc:
|
|
7656
6879
|
data += [sub_record, sub_length].pack('vv')
|
7657
6880
|
|
7658
6881
|
# Pack the record.
|
7659
|
-
header = [record, length].pack('vv')
|
7660
|
-
|
7661
|
-
append(header, data)
|
7662
|
-
|
7663
|
-
end
|
7664
|
-
|
7665
|
-
#
|
7666
|
-
# Write the OBJ record that is part of filter records.
|
7667
|
-
# obj_id # Object ID number.
|
7668
|
-
# col
|
7669
|
-
#
|
7670
|
-
def store_obj_filter(obj_id, col) #:nodoc:
|
7671
6882
|
record = 0x005D # Record identifier
|
7672
|
-
length =
|
7673
|
-
|
7674
|
-
obj_type = 0x0014 # Object type (combo box).
|
7675
|
-
data = '' # Record data.
|
7676
|
-
|
7677
|
-
sub_record = 0x0000 # Sub-record identifier.
|
7678
|
-
sub_length = 0x0000 # Length of sub-record.
|
7679
|
-
sub_data = '' # Data of sub-record.
|
7680
|
-
options = 0x2101
|
7681
|
-
reserved = 0x0000
|
7682
|
-
|
7683
|
-
# Add ftCmo (common object data) subobject
|
7684
|
-
sub_record = 0x0015 # ftCmo
|
7685
|
-
sub_length = 0x0012
|
7686
|
-
sub_data = [obj_type, obj_id, options, reserved, reserved, reserved].pack('vvvVVV')
|
7687
|
-
data = [sub_record, sub_length].pack('vv') + sub_data
|
7688
|
-
|
7689
|
-
# Add ftSbs Scroll bar subobject
|
7690
|
-
sub_record = 0x000C # ftSbs
|
7691
|
-
sub_length = 0x0014
|
7692
|
-
sub_data = ['0000000000000000640001000A00000010000100'].pack('H*')
|
7693
|
-
data += [sub_record, sub_length].pack('vv') + sub_data
|
7694
|
-
|
7695
|
-
# Add ftLbsData (List box data) subobject
|
7696
|
-
sub_record = 0x0013 # ftLbsData
|
7697
|
-
sub_length = 0x1FEE # Special case (undocumented).
|
7698
|
-
|
7699
|
-
# If the filter is active we set one of the undocumented flags.
|
7700
|
-
|
7701
|
-
if @filter_cols[col]
|
7702
|
-
sub_data = ['000000000100010300000A0008005700'].pack('H*')
|
7703
|
-
else
|
7704
|
-
sub_data = ['00000000010001030000020008005700'].pack('H*')
|
7705
|
-
end
|
7706
|
-
|
7707
|
-
data += [sub_record, sub_length].pack('vv') + sub_data
|
7708
|
-
|
7709
|
-
# Add ftEnd (end of object) subobject
|
7710
|
-
sub_record = 0x0000 # ftNts
|
7711
|
-
sub_length = 0x0000
|
7712
|
-
data += [sub_record, sub_length].pack('vv')
|
7713
|
-
|
7714
|
-
# Pack the record.
|
7715
|
-
header = [record, length].pack('vv')
|
7716
|
-
|
7717
|
-
append(header, data)
|
7718
|
-
end
|
7719
|
-
|
7720
|
-
#
|
7721
|
-
# Write the MSODRAWING ClientTextbox record that is part of comments.
|
7722
|
-
#
|
7723
|
-
def store_mso_drawing_text_box #:nodoc:
|
7724
|
-
record = 0x00EC # Record identifier
|
7725
|
-
length = 0x0008 # Bytes to follow
|
7726
|
-
|
7727
|
-
data = store_mso_client_text_box()
|
7728
|
-
header = [record, length].pack('vv')
|
7729
|
-
|
7730
|
-
append(header, data)
|
7731
|
-
end
|
7732
|
-
|
7733
|
-
#
|
7734
|
-
# Write the Escher ClientTextbox record that is part of MSODRAWING.
|
7735
|
-
#
|
7736
|
-
def store_mso_client_text_box #:nodoc:
|
7737
|
-
type = 0xF00D
|
7738
|
-
version = 0
|
7739
|
-
instance = 0
|
7740
|
-
data = ''
|
7741
|
-
length = 0
|
7742
|
-
|
7743
|
-
add_mso_generic(type, version, instance, data, length)
|
7744
|
-
end
|
7745
|
-
|
7746
|
-
#
|
7747
|
-
# Write the worksheet TXO record that is part of cell comments.
|
7748
|
-
# string_len # Length of the note text.
|
7749
|
-
# format_len # Length of the format runs.
|
7750
|
-
# rotation # Options
|
7751
|
-
#
|
7752
|
-
def store_txo(string_len, format_len = 16, rotation = 0) #:nodoc:
|
7753
|
-
record = 0x01B6 # Record identifier
|
7754
|
-
length = 0x0012 # Bytes to follow
|
7755
|
-
|
7756
|
-
grbit = 0x0212 # Options
|
7757
|
-
reserved = 0x0000 # Options
|
7758
|
-
|
7759
|
-
# Pack the record.
|
7760
|
-
header = [record, length].pack('vv')
|
7761
|
-
data = [grbit, rotation, reserved, reserved,
|
7762
|
-
string_len, format_len, reserved].pack("vvVvvvV")
|
7763
|
-
|
7764
|
-
append(header, data)
|
7765
|
-
end
|
7766
|
-
|
7767
|
-
#
|
7768
|
-
# Write the first CONTINUE record to follow the TXO record. It contains the
|
7769
|
-
# text data.
|
7770
|
-
# string # Comment string.
|
7771
|
-
# encoding # Encoding of the string.
|
7772
|
-
#
|
7773
|
-
def store_txo_continue_1(string, encoding = 0) #:nodoc:
|
7774
|
-
record = 0x003C # Record identifier
|
7775
|
-
|
7776
|
-
# Split long comment strings into smaller continue blocks if necessary.
|
7777
|
-
# We can't let BIFFwriter::_add_continue() handled this since an extra
|
7778
|
-
# encoding byte has to be added similar to the SST block.
|
7779
|
-
#
|
7780
|
-
# We make the limit size smaller than the add_continue() size and even
|
7781
|
-
# so that UTF16 chars occur in the same block.
|
7782
|
-
#
|
7783
|
-
limit = 8218
|
7784
|
-
while string.bytesize > limit
|
7785
|
-
string[0 .. limit] = ""
|
7786
|
-
tmp_str = string
|
7787
|
-
data = [encoding].pack("C") +
|
7788
|
-
ruby_18 { tmp_str } ||
|
7789
|
-
ruby_19 { tmp_str.force_encoding('ASCII-8BIT') }
|
7790
|
-
length = data.bytesize
|
7791
|
-
header = [record, length].pack('vv')
|
7792
|
-
|
7793
|
-
append(header, data)
|
7794
|
-
end
|
7795
|
-
|
7796
|
-
# Pack the record.
|
7797
|
-
data =
|
7798
|
-
ruby_18 { [encoding].pack("C") + string } ||
|
7799
|
-
ruby_19 { [encoding].pack("C") + string.force_encoding('ASCII-8BIT') }
|
7800
|
-
length = data.bytesize
|
6883
|
+
length = 0x001A # Bytes to follow
|
7801
6884
|
header = [record, length].pack('vv')
|
7802
6885
|
|
7803
6886
|
append(header, data)
|
7804
|
-
end
|
7805
|
-
|
7806
|
-
#
|
7807
|
-
# Write the second CONTINUE record to follow the TXO record. It contains the
|
7808
|
-
# formatting information for the string.
|
7809
|
-
# formats # Formatting information
|
7810
|
-
#
|
7811
|
-
def store_txo_continue_2(formats) #:nodoc:
|
7812
|
-
record = 0x003C # Record identifier
|
7813
|
-
length = 0x0000 # Bytes to follow
|
7814
|
-
|
7815
|
-
# Pack the record.
|
7816
|
-
data = ''
|
7817
|
-
|
7818
|
-
formats.each do |a_ref|
|
7819
|
-
data += [a_ref[0], a_ref[1], 0x0].pack('vvV')
|
7820
|
-
end
|
7821
|
-
|
7822
|
-
length = data.bytesize
|
7823
|
-
header = [record, length].pack("vv")
|
7824
|
-
|
7825
|
-
append(header, data)
|
7826
|
-
end
|
7827
|
-
|
7828
|
-
#
|
7829
|
-
# Write the worksheet NOTE record that is part of cell comments.
|
7830
|
-
#
|
7831
|
-
def store_note(row, col, obj_id, author = nil, author_enc = nil, visible = nil) #:nodoc:
|
7832
|
-
ruby_19 { author = [author].pack('a*') if author.ascii_only? }
|
7833
|
-
record = 0x001C # Record identifier
|
7834
|
-
length = 0x000C # Bytes to follow
|
7835
|
-
|
7836
|
-
author = @comments_author unless author
|
7837
|
-
author_enc = @comments_author_enc unless author_enc
|
7838
|
-
|
7839
|
-
# Use the visible flag if set by the user or else use the worksheet value.
|
7840
|
-
# The flag is also set in store_mso_opt_comment() but with the opposite
|
7841
|
-
# value.
|
7842
|
-
if visible
|
7843
|
-
visible = visible != 0 ? 0x0002 : 0x0000
|
7844
|
-
else
|
7845
|
-
visible = @comments_visible != 0 ? 0x0002 : 0x0000
|
7846
|
-
end
|
7847
|
-
|
7848
|
-
# Get the number of chars in the author string (not bytes).
|
7849
|
-
num_chars = author.bytesize
|
7850
|
-
num_chars = num_chars / 2 if author_enc != 0 && author_enc
|
7851
|
-
|
7852
|
-
# Null terminate the author string.
|
7853
|
-
author =
|
7854
|
-
ruby_18 { author + "\0" } ||
|
7855
|
-
ruby_19 { author.force_encoding('BINARY') + "\0".force_encoding('BINARY') }
|
7856
|
-
|
7857
|
-
# Pack the record.
|
7858
|
-
data = [row, col, visible, obj_id, num_chars, author_enc].pack("vvvvvC")
|
7859
|
-
|
7860
|
-
length = data.bytesize + author.bytesize
|
7861
|
-
header = [record, length].pack("vv")
|
7862
|
-
|
7863
|
-
append(header, data, author)
|
7864
|
-
end
|
7865
|
-
|
7866
|
-
#
|
7867
|
-
# This method handles the additional optional parameters to write_comment() as
|
7868
|
-
# well as calculating the comment object position and vertices.
|
7869
|
-
#
|
7870
|
-
def comment_params(row, col, string, options = {}) #:nodoc:
|
7871
|
-
string = convert_to_ascii_if_ascii(string)
|
7872
|
-
|
7873
|
-
default_width = 128
|
7874
|
-
default_height = 74
|
7875
|
-
|
7876
|
-
params = {
|
7877
|
-
:author => '',
|
7878
|
-
:author_encoding => 0,
|
7879
|
-
:encoding => 0,
|
7880
|
-
:color => nil,
|
7881
|
-
:start_cell => nil,
|
7882
|
-
:start_col => nil,
|
7883
|
-
:start_row => nil,
|
7884
|
-
:visible => nil,
|
7885
|
-
:width => default_width,
|
7886
|
-
:height => default_height,
|
7887
|
-
:x_offset => nil,
|
7888
|
-
:x_scale => 1,
|
7889
|
-
:y_offset => nil,
|
7890
|
-
:y_scale => 1
|
7891
|
-
}
|
7892
|
-
|
7893
|
-
# Overwrite the defaults with any user supplied values. Incorrect or
|
7894
|
-
# misspelled parameters are silently ignored.
|
7895
|
-
params.update(options)
|
7896
|
-
|
7897
|
-
# Ensure that a width and height have been set.
|
7898
|
-
params[:width] = default_width unless params[:width] && params[:width] != 0
|
7899
|
-
params[:height] = default_height unless params[:height] && params[:height] != 0
|
7900
|
-
|
7901
|
-
# Check that utf16 strings have an even number of bytes.
|
7902
|
-
if params[:encoding] != 0
|
7903
|
-
raise "Uneven number of bytes in comment string" if string.bytesize % 2 != 0
|
7904
|
-
|
7905
|
-
# Change from UTF-16BE to UTF-16LE
|
7906
|
-
string = string.unpack('n*').pack('v*')
|
7907
|
-
# Handle utf8 strings
|
7908
|
-
else
|
7909
|
-
if is_utf8?(string)
|
7910
|
-
string = NKF.nkf('-w16L0 -m0 -W', string)
|
7911
|
-
ruby_19 { string.force_encoding('UTF-16LE') }
|
7912
|
-
params[:encoding] = 1
|
7913
|
-
end
|
7914
|
-
end
|
7915
|
-
|
7916
|
-
params[:author] = convert_to_ascii_if_ascii(params[:author])
|
7917
|
-
|
7918
|
-
if params[:author_encoding] != 0
|
7919
|
-
raise "Uneven number of bytes in author string" if params[:author].bytesize % 2 != 0
|
7920
6887
|
|
7921
|
-
# Change from UTF-16BE to UTF-16LE
|
7922
|
-
params[:author] = params[:author].unpack('n*').pack('v*')
|
7923
|
-
else
|
7924
|
-
if is_utf8?(params[:author])
|
7925
|
-
params[:author] = NKF.nkf('-w16L0 -m0 -W', params[:author])
|
7926
|
-
ruby_19 { params[:author].force_encoding('UTF-16LE') }
|
7927
|
-
params[:author_encoding] = 1
|
7928
|
-
end
|
7929
|
-
end
|
7930
|
-
|
7931
|
-
# Limit the string to the max number of chars (not bytes).
|
7932
|
-
max_len = 32767
|
7933
|
-
max_len = max_len * 2 if params[:encoding] != 0
|
7934
|
-
|
7935
|
-
if string.bytesize > max_len
|
7936
|
-
string = string[0 .. max_len]
|
7937
|
-
end
|
7938
|
-
|
7939
|
-
# Set the comment background colour.
|
7940
|
-
color = params[:color]
|
7941
|
-
color = Colors.new.get_color(color)
|
7942
|
-
color = 0x50 if color == 0x7FFF # Default color.
|
7943
|
-
params[:color] = color
|
7944
|
-
|
7945
|
-
# Convert a cell reference to a row and column.
|
7946
|
-
if params[:start_cell]
|
7947
|
-
params[:start_row], params[:start_col] = substitute_cellref(params[:start_cell])
|
7948
|
-
end
|
7949
|
-
|
7950
|
-
# Set the default start cell and offsets for the comment. These are
|
7951
|
-
# generally fixed in relation to the parent cell. However there are
|
7952
|
-
# some edge cases for cells at the, er, edges.
|
7953
|
-
#
|
7954
|
-
unless params[:start_row]
|
7955
|
-
case row
|
7956
|
-
when 0 then params[:start_row] = 0
|
7957
|
-
when 65533 then params[:start_row] = 65529
|
7958
|
-
when 65534 then params[:start_row] = 65530
|
7959
|
-
when 65535 then params[:start_row] = 65531
|
7960
|
-
else params[:start_row] = row -1
|
7961
|
-
end
|
7962
|
-
end
|
7963
|
-
|
7964
|
-
unless params[:y_offset]
|
7965
|
-
case row
|
7966
|
-
when 0 then params[:y_offset] = 2
|
7967
|
-
when 65533 then params[:y_offset] = 4
|
7968
|
-
when 65534 then params[:y_offset] = 4
|
7969
|
-
when 65535 then params[:y_offset] = 2
|
7970
|
-
else params[:y_offset] = 7
|
7971
|
-
end
|
7972
|
-
end
|
7973
|
-
|
7974
|
-
unless params[:start_col]
|
7975
|
-
case col
|
7976
|
-
when 253 then params[:start_col] = 250
|
7977
|
-
when 254 then params[:start_col] = 251
|
7978
|
-
when 255 then params[:start_col] = 252
|
7979
|
-
else params[:start_col] = col + 1
|
7980
|
-
end
|
7981
|
-
end
|
7982
|
-
|
7983
|
-
unless params[:x_offset]
|
7984
|
-
case col
|
7985
|
-
when 253 then params[:x_offset] = 49
|
7986
|
-
when 254 then params[:x_offset] = 49
|
7987
|
-
when 255 then params[:x_offset] = 49
|
7988
|
-
else params[:x_offset] = 15
|
7989
|
-
end
|
7990
|
-
end
|
7991
|
-
|
7992
|
-
# Scale the size of the comment box if required.
|
7993
|
-
if params[:x_scale] != 0
|
7994
|
-
params[:width] = params[:width] * params[:x_scale]
|
7995
|
-
end
|
7996
|
-
|
7997
|
-
if params[:y_scale] != 0
|
7998
|
-
params[:height] = params[:height] * params[:y_scale]
|
7999
|
-
end
|
8000
|
-
|
8001
|
-
# Calculate the positions of comment object.
|
8002
|
-
vertices = position_object( params[:start_col],
|
8003
|
-
params[:start_row],
|
8004
|
-
params[:x_offset],
|
8005
|
-
params[:y_offset],
|
8006
|
-
params[:width],
|
8007
|
-
params[:height]
|
8008
|
-
)
|
8009
|
-
|
8010
|
-
[row, col, string,
|
8011
|
-
params[:encoding],
|
8012
|
-
params[:author],
|
8013
|
-
params[:author_encoding],
|
8014
|
-
params[:visible],
|
8015
|
-
params[:color],
|
8016
|
-
vertices
|
8017
|
-
]
|
8018
6888
|
end
|
8019
6889
|
|
8020
6890
|
#
|
@@ -8024,12 +6894,7 @@ def comment_params(row, col, string, options = {}) #:nodoc:
|
|
8024
6894
|
# handling of the object id at a later stage.
|
8025
6895
|
#
|
8026
6896
|
def store_validation_count #:nodoc:
|
8027
|
-
|
8028
|
-
obj_id = -1
|
8029
|
-
|
8030
|
-
return if dv_count == 0
|
8031
|
-
|
8032
|
-
store_dval(obj_id , dv_count)
|
6897
|
+
append(@validations.count_dv_record)
|
8033
6898
|
end
|
8034
6899
|
|
8035
6900
|
#
|
@@ -8038,212 +6903,11 @@ def store_validation_count #:nodoc:
|
|
8038
6903
|
def store_validations #:nodoc:
|
8039
6904
|
return if @validations.size == 0
|
8040
6905
|
|
8041
|
-
@validations.each do |
|
8042
|
-
|
8043
|
-
param[:cells],
|
8044
|
-
param[:validate],
|
8045
|
-
param[:criteria],
|
8046
|
-
param[:value],
|
8047
|
-
param[:maximum],
|
8048
|
-
param[:input_title],
|
8049
|
-
param[:input_message],
|
8050
|
-
param[:error_title],
|
8051
|
-
param[:error_message],
|
8052
|
-
param[:error_type],
|
8053
|
-
param[:ignore_blank],
|
8054
|
-
param[:dropdown],
|
8055
|
-
param[:show_input],
|
8056
|
-
param[:show_error]
|
8057
|
-
)
|
6906
|
+
@validations.each do |data_validation|
|
6907
|
+
append(data_validation.dv_record)
|
8058
6908
|
end
|
8059
6909
|
end
|
8060
6910
|
|
8061
|
-
#
|
8062
|
-
# Store the DV record which contains the number of and information common to
|
8063
|
-
# all DV structures.
|
8064
|
-
# obj_id # Object ID number.
|
8065
|
-
# dv_count # Count of DV structs to follow.
|
8066
|
-
#
|
8067
|
-
def store_dval(obj_id, dv_count) #:nodoc:
|
8068
|
-
record = 0x01B2 # Record identifier
|
8069
|
-
length = 0x0012 # Bytes to follow
|
8070
|
-
|
8071
|
-
flags = 0x0004 # Option flags.
|
8072
|
-
x_coord = 0x00000000 # X coord of input box.
|
8073
|
-
y_coord = 0x00000000 # Y coord of input box.
|
8074
|
-
|
8075
|
-
# Pack the record.
|
8076
|
-
header = [record, length].pack('vv')
|
8077
|
-
data = [flags, x_coord, y_coord, obj_id, dv_count].pack('vVVVV')
|
8078
|
-
|
8079
|
-
append(header, data)
|
8080
|
-
end
|
8081
|
-
|
8082
|
-
#
|
8083
|
-
# Store the DV record that specifies the data validation criteria and options
|
8084
|
-
# for a range of cells..
|
8085
|
-
# cells # Aref of cells to which DV applies.
|
8086
|
-
# validation_type # Type of data validation.
|
8087
|
-
# criteria_type # Validation criteria.
|
8088
|
-
# formula_1 # Value/Source/Minimum formula.
|
8089
|
-
# formula_2 # Maximum formula.
|
8090
|
-
# input_title # Title of input message.
|
8091
|
-
# input_message # Text of input message.
|
8092
|
-
# error_title # Title of error message.
|
8093
|
-
# error_message # Text of input message.
|
8094
|
-
# error_type # Error dialog type.
|
8095
|
-
# ignore_blank # Ignore blank cells.
|
8096
|
-
# dropdown # Display dropdown with list.
|
8097
|
-
# input_box # Display input box.
|
8098
|
-
# error_box # Display error box.
|
8099
|
-
#
|
8100
|
-
def store_dv(cells, validation_type, criteria_type, #:nodoc:
|
8101
|
-
formula_1, formula_2, input_title, input_message,
|
8102
|
-
error_title, error_message, error_type,
|
8103
|
-
ignore_blank, dropdown, input_box, error_box)
|
8104
|
-
record = 0x01BE # Record identifier
|
8105
|
-
length = 0x0000 # Bytes to follow
|
8106
|
-
|
8107
|
-
flags = 0x00000000 # DV option flags.
|
8108
|
-
|
8109
|
-
ime_mode = 0 # IME input mode for far east fonts.
|
8110
|
-
str_lookup = 0 # See below.
|
8111
|
-
|
8112
|
-
# Set the string lookup flag for 'list' validations with a string array.
|
8113
|
-
if validation_type == 3 && formula_1.respond_to?(:to_ary)
|
8114
|
-
str_lookup = 1
|
8115
|
-
end
|
8116
|
-
|
8117
|
-
# The dropdown flag is stored as a negated value.
|
8118
|
-
no_dropdown = dropdown ? 0 : 1
|
8119
|
-
|
8120
|
-
# Set the required flags.
|
8121
|
-
flags |= validation_type
|
8122
|
-
flags |= error_type << 4
|
8123
|
-
flags |= str_lookup << 7
|
8124
|
-
flags |= ignore_blank << 8
|
8125
|
-
flags |= no_dropdown << 9
|
8126
|
-
flags |= ime_mode << 10
|
8127
|
-
flags |= input_box << 18
|
8128
|
-
flags |= error_box << 19
|
8129
|
-
flags |= criteria_type << 20
|
8130
|
-
|
8131
|
-
# Pack the validation formulas.
|
8132
|
-
formula_1 = pack_dv_formula(formula_1)
|
8133
|
-
formula_2 = pack_dv_formula(formula_2)
|
8134
|
-
|
8135
|
-
# Pack the input and error dialog strings.
|
8136
|
-
input_title = pack_dv_string(input_title, 32 )
|
8137
|
-
error_title = pack_dv_string(error_title, 32 )
|
8138
|
-
input_message = pack_dv_string(input_message, 255)
|
8139
|
-
error_message = pack_dv_string(error_message, 255)
|
8140
|
-
|
8141
|
-
# Pack the DV cell data.
|
8142
|
-
dv_count = cells.size
|
8143
|
-
dv_data = [dv_count].pack('v')
|
8144
|
-
cells.each do |range|
|
8145
|
-
dv_data += [range[0], range[2], range[1], range[3]].pack('vvvv')
|
8146
|
-
end
|
8147
|
-
|
8148
|
-
# Pack the record.
|
8149
|
-
data = [flags].pack('V') +
|
8150
|
-
input_title +
|
8151
|
-
error_title +
|
8152
|
-
input_message +
|
8153
|
-
error_message +
|
8154
|
-
formula_1 +
|
8155
|
-
formula_2 +
|
8156
|
-
dv_data
|
8157
|
-
|
8158
|
-
header = [record, data.bytesize].pack('vv')
|
8159
|
-
|
8160
|
-
append(header, data)
|
8161
|
-
end
|
8162
|
-
|
8163
|
-
#
|
8164
|
-
# Pack the strings used in the input and error dialog captions and messages.
|
8165
|
-
# Captions are limited to 32 characters. Messages are limited to 255 chars.
|
8166
|
-
#
|
8167
|
-
def pack_dv_string(string = nil, max_length = 0) #:nodoc:
|
8168
|
-
str_length = 0
|
8169
|
-
encoding = 0
|
8170
|
-
|
8171
|
-
# The default empty string is "\0".
|
8172
|
-
unless string && string != ''
|
8173
|
-
string =
|
8174
|
-
ruby_18 { "\0" } || ruby_19 { "\0".encode('BINARY') }
|
8175
|
-
end
|
8176
|
-
|
8177
|
-
# Excel limits DV captions to 32 chars and messages to 255.
|
8178
|
-
if string.bytesize > max_length
|
8179
|
-
string = string[0 .. max_length-1]
|
8180
|
-
end
|
8181
|
-
|
8182
|
-
str_length = string.bytesize
|
8183
|
-
|
8184
|
-
ruby_19 { string = convert_to_ascii_if_ascii(string) }
|
8185
|
-
|
8186
|
-
# Handle utf8 strings
|
8187
|
-
if is_utf8?(string)
|
8188
|
-
str_length = string.gsub(/[^\Wa-zA-Z_\d]/, ' ').bytesize # jlength
|
8189
|
-
string = utf8_to_16le(string)
|
8190
|
-
encoding = 1
|
8191
|
-
end
|
8192
|
-
|
8193
|
-
ruby_18 { [str_length, encoding].pack('vC') + string } ||
|
8194
|
-
ruby_19 { [str_length, encoding].pack('vC') + string.force_encoding('BINARY') }
|
8195
|
-
end
|
8196
|
-
|
8197
|
-
#
|
8198
|
-
# Pack the formula used in the DV record. This is the same as an cell formula
|
8199
|
-
# with some additional header information. Note, DV formulas in Excel use
|
8200
|
-
# relative addressing (R1C1 and ptgXxxN) however we use the Formula.pm's
|
8201
|
-
# default absolute addressing (A1 and ptgXxx).
|
8202
|
-
#
|
8203
|
-
def pack_dv_formula(formula = nil) #:nodoc:
|
8204
|
-
encoding = 0
|
8205
|
-
length = 0
|
8206
|
-
unused = 0x0000
|
8207
|
-
tokens = []
|
8208
|
-
|
8209
|
-
# Return a default structure for unused formulas.
|
8210
|
-
return [0, unused].pack('vv') unless formula && formula != ''
|
8211
|
-
|
8212
|
-
# Pack a list array ref as a null separated string.
|
8213
|
-
if formula.respond_to?(:to_ary)
|
8214
|
-
formula = formula.join("\0")
|
8215
|
-
formula = '"' + formula + '"'
|
8216
|
-
end
|
8217
|
-
|
8218
|
-
# Strip the = sign at the beginning of the formula string
|
8219
|
-
formula = formula.to_s unless formula.respond_to?(:to_str)
|
8220
|
-
formula.sub!(/^=/, '')
|
8221
|
-
|
8222
|
-
# In order to raise formula errors from the point of view of the calling
|
8223
|
-
# program we use an eval block and re-raise the error from here.
|
8224
|
-
#
|
8225
|
-
tokens = parser.parse_formula(formula) # ????
|
8226
|
-
|
8227
|
-
# if ($@) {
|
8228
|
-
# $@ =~ s/\n$//; # Strip the \n used in the Formula.pm die()
|
8229
|
-
# croak $@; # Re-raise the error
|
8230
|
-
# }
|
8231
|
-
# else {
|
8232
|
-
# # TODO test for non valid ptgs such as Sheet2!A1
|
8233
|
-
# }
|
8234
|
-
|
8235
|
-
# Force 2d ranges to be a reference class.
|
8236
|
-
tokens.each do |t|
|
8237
|
-
t.sub!(/_range2d/, "_range2dR")
|
8238
|
-
t.sub!(/_name/, "_nameR")
|
8239
|
-
end
|
8240
|
-
|
8241
|
-
# Parse the tokens into a formula string.
|
8242
|
-
formula = parser.parse_tokens(tokens)
|
8243
|
-
|
8244
|
-
[formula.length, unused].pack('vv') + formula
|
8245
|
-
end
|
8246
|
-
|
8247
6911
|
def parser # :nodoc:
|
8248
6912
|
@workbook.parser
|
8249
6913
|
end
|