writeexcel 0.6.9 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +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
|