writeexcel 0.6.9 → 0.6.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/README.rdoc +2 -0
  2. data/VERSION +1 -1
  3. data/lib/writeexcel/biffwriter.rb +29 -43
  4. data/lib/writeexcel/cell_range.rb +332 -0
  5. data/lib/writeexcel/chart.rb +50 -51
  6. data/lib/writeexcel/col_info.rb +87 -0
  7. data/lib/writeexcel/comments.rb +456 -0
  8. data/lib/writeexcel/convert_date_time.rb +117 -0
  9. data/lib/writeexcel/data_validations.rb +370 -0
  10. data/lib/writeexcel/debug_info.rb +5 -1
  11. data/lib/writeexcel/embedded_chart.rb +35 -0
  12. data/lib/writeexcel/format.rb +1 -1
  13. data/lib/writeexcel/formula.rb +3 -3
  14. data/lib/writeexcel/helper.rb +3 -0
  15. data/lib/writeexcel/image.rb +61 -1
  16. data/lib/writeexcel/olewriter.rb +2 -8
  17. data/lib/writeexcel/outline.rb +24 -0
  18. data/lib/writeexcel/shared_string_table.rb +153 -0
  19. data/lib/writeexcel/workbook.rb +86 -444
  20. data/lib/writeexcel/worksheet.rb +693 -2029
  21. data/lib/writeexcel/worksheets.rb +25 -0
  22. data/lib/writeexcel/write_file.rb +34 -15
  23. data/test/test_02_merge_formats.rb +0 -4
  24. data/test/test_04_dimensions.rb +0 -4
  25. data/test/test_05_rows.rb +0 -4
  26. data/test/test_06_extsst.rb +3 -6
  27. data/test/test_11_date_time.rb +0 -4
  28. data/test/test_12_date_only.rb +262 -231
  29. data/test/test_13_date_seconds.rb +0 -4
  30. data/test/test_21_escher.rb +71 -84
  31. data/test/test_22_mso_drawing_group.rb +0 -4
  32. data/test/test_23_note.rb +5 -21
  33. data/test/test_24_txo.rb +6 -7
  34. data/test/test_25_position_object.rb +0 -4
  35. data/test/test_26_autofilter.rb +0 -5
  36. data/test/test_27_autofilter.rb +0 -5
  37. data/test/test_28_autofilter.rb +0 -5
  38. data/test/test_29_process_jpg.rb +1 -1
  39. data/test/test_30_validation_dval.rb +4 -7
  40. data/test/test_31_validation_dv_strings.rb +9 -12
  41. data/test/test_32_validation_dv_formula.rb +11 -14
  42. data/test/test_42_set_properties.rb +0 -3
  43. data/test/test_50_name_stored.rb +0 -4
  44. data/test/test_51_name_print_area.rb +0 -4
  45. data/test/test_52_name_print_titles.rb +0 -4
  46. data/test/test_53_autofilter.rb +0 -4
  47. data/test/test_60_chart_generic.rb +42 -46
  48. data/test/test_61_chart_subclasses.rb +7 -11
  49. data/test/test_62_chart_formats.rb +12 -16
  50. data/test/test_63_chart_area_formats.rb +3 -7
  51. data/test/test_biff.rb +0 -4
  52. data/test/test_big_workbook.rb +17 -0
  53. data/test/test_format.rb +0 -3
  54. data/test/test_ole.rb +0 -4
  55. data/test/test_storage_lite.rb +0 -9
  56. data/test/test_workbook.rb +0 -4
  57. data/test/test_worksheet.rb +3 -7
  58. data/writeexcel.gemspec +12 -2
  59. metadata +12 -2
@@ -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, index, name_utf16be) # :nodoc:
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
- @xls_rowmax = RowMax
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
- @title_rowmin = nil
90
- @title_rowmax = nil
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
- @outline_row_level = 0
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
- @object_ids = []
138
- @images = {}
139
- @images_array = []
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
- unless @colinfo.empty?
189
- colinfo = @colinfo.dup
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
- sinfo[:activesheet] = @index
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
- sinfo[:activesheet] = 0
372
- sinfo[:firstsheet] = 0
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
- sinfo[:firstsheet] = @index
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
- @outline_row_level = level if level > @outline_row_level
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.push([firstcol, lastcol, *data])
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 0 will cause all outlines on the
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 1 for
758
+ # "Show Outline Symbols" command button. The default setting is true for
770
759
  # visible outlines.
771
760
  #
772
- # worksheet.outline_settings(0)
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
- @outline_on = args[0] || 1
796
- @outline_below = args[1] || 1
797
- @outline_right = args[2] || 1
798
- @outline_style = args[3] || 0
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 vale for Window2
801
- @outline_on = 1 if @outline_on == 0
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
- row1, col1, row2, col2 = args
1103
+ row_min, col_min, row_max, col_max = args
1115
1104
 
1116
1105
  # Reverse max and min values if necessary.
1117
- if row2 < row1
1118
- tmp = row1
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 = [row1, row2, col1, col2]
1130
- @filter_count = 1 + col2 -col1
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 @filter_count == 0
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
- no_use, col = substitute_cellref(col + '1') if col =~ /^\D/
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
- if (col < col_first or col > col_last)
1220
+ unless @filter_area.inside?(col)
1242
1221
  raise "Column '#{col}' outside autofilter() column range " +
1243
- "(#{col_first} .. #{col_last})"
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
- @title_rowmin = first_row
1617
- @title_rowmax = last_row || first_row # Second row is optional
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
- @title_colmin = firstcol
1652
- @title_colmax = lastcol || firstcol # Second col is optional
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
- @print_rowmin, @print_colmin, @print_rowmax, @print_colmax = args
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 = nil)
1997
- @comments_visible = val ? val : 1
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?://| and @writing_url == 0
2237
+ elsif token =~ %r|^[fh]tt?ps?://|
2270
2238
  write_url(*args)
2271
2239
  # Match mailto:
2272
- elsif token =~ %r|^mailto:| and @writing_url == 0
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:! and @writing_url == 0
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 != 0 && @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
- record = 0x00FD # Record identifier
2394
- length = 0x000A # Bytes to follow
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 if check_dimensions(row, col) != 0
2376
+ return -2 unless check_dimensions(row, col) == 0
2414
2377
 
2415
2378
  # Limit the string to the max number of chars.
2416
- if (strlen > 32767)
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
- unless sinfo[:str_table][str]
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, sinfo[:str_table][str]].pack('vvvV')
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 (args.size < 3) # Check the number of args
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 = args[0] # Zero indexed 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 if check_dimensions(row, col) != 0
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 strlen > 32767* 2
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" if num_bytes % 2 != 0
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.unpack('n*').pack('v*')
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
- unless sinfo[:str_table][str]
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, sinfo[:str_table][str]].pack("vvvV")
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) # Check the number of args
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 = args[0] # Zero indexed 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.unpack('n*').pack("v*")
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
- record = 0x0201 # Record identifier
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 if check_dimensions(row, col) != 0
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 = args[0] # Zero indexed 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 if check_dimensions(row, col) != 0
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 (args.size < 2) # Check the number of args
2947
+ return -1 if args.size < 2 # Check the number of args
3061
2948
 
3062
- row = args.shift # Zero indexed row
3063
- col = args.shift # Zero indexed column
3064
- formula_ref = args.shift # Array ref with formula tokens
3065
- format = args.shift # XF format
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" if pairs.size % 2 != 0
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 formula_ref.respond_to?(:to_ary)
2958
+ raise "Not a valid formula" unless formula.respond_to?(:to_ary)
3073
2959
 
3074
- tokens = formula_ref.join("\t").split("\t")
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 (!pairs.empty?)
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 = parser.parse_tokens(tokens)
2984
+ formula = parser.parse_tokens(tokens)
3100
2985
 
3101
2986
  raise "Unrecognised token in formula" unless formula
3102
2987
 
3103
- xf = xf_record_index(row, col, format) # The cell format
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 = 0
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 = 0
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 (args.size < 3) # Check the number of args
3243
+ return -1 if args.size < 3 # Check the number of args
3367
3244
 
3368
- row = args[0] # Zero indexed 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 if check_dimensions(row, col) != 0
3248
+ return -2 unless check_dimensions(row, col) == 0
3374
3249
 
3375
- error = 0
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, args[3])
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[0]
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 if check_dimensions(row, col) != 0
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[row] = { col => comment_params(*args) }
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 $format if necessary. We work on a copy
3730
- # in order to protect the callers args. We don't use "local @_" in case of
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[row] = {
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[image.row] = { image.col => image }
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
- # A (for now) undocumented parameter to pass additional cell ranges.
4545
- if param.has_key?(:other_cells)
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.push(param)
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
- @images_array
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
- # Turn the HoH that stores the images into an array for easier handling.
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
- # Turn the HoH that stores the comments into an array for easier handling.
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
- # Turn the HoH that stores the charts into an array for easier handling.
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 frozen?
4700
- @frozen
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 display_zeros?
4704
- !@hide_zeros
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 set_header_footer_common(type, string, margin, encoding) # :nodoc:
4708
- ruby_19 { string = convert_to_ascii_if_ascii(string) }
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
- if string.bytesize >= limit
4719
- # carp 'Header string must be less than 255 characters';
4720
- return
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
- if type == :header
4724
- @header = string
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
- # Extract the tokens from the filter expression. The tokens are mainly non-
4736
- # whitespace groups. The only tricky part is to extract string tokens that
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
- # Examples: 'x < 2000'
4740
- # 'x > 2000 and x < 5000'
4741
- # 'x = "foo"'
4742
- # 'x = "foo bar"'
4743
- # 'x = "foo "" bar"'
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
- def extract_filter_tokens(expression = nil) #:nodoc:
4746
- return [] unless expression
4747
-
4748
- tokens = []
4749
- str = expression
4750
- while str =~ /"(?:[^"]|"")*"|\S+/
4751
- tokens << $&
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
- @writing_url = 1
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
- @writing_url = 1
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
- @writing_url = 1
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
- @writing_url = 1
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 >= @xls_rowmax
5452
+ return -2 if row >= RowMax
5614
5453
 
5615
5454
  return -2 unless col
5616
- return -2 if col >= @xls_colmax
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 ignore_col == 0
5624
- @dim_colmin = col if !@dim_colmin || (col < @dim_colmin)
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
- row_min = @dim_rowmin ? @dim_rowmin : 0
5646
- row_max = @dim_rowmax ? @dim_rowmax + 1 : 0
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 = @outline_on # 7
5512
+ fDspGuts = @outline.visible? ? 1 : 0 # 7
5689
5513
  fFrozenNoSplit = @frozen_no_split # 0 - bit
5690
5514
  fSelected = selected? ? 1 : 0 # 1
5691
- fPaged = @active # 2
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
- # firstcol : First formatted column
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 @filter_count == 0
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 = @filter_count
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
- pnn = @active_pane # Pane position
5869
- rwAct = first_row # Active row
5870
- colAct = first_col # Active column
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
- rwFirst = first_row # First row in reference
5875
- colFirst = first_col # First col in reference
5876
- rwLast = last_row || rwFirst # Last row in reference
5877
- colLast = last_col || colFirst # Last col in reference
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 rwFirst > rwLast
5881
- tmp = rwFirst
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 = [pnn, rwAct, colAct, irefAct, cref,
5894
- rwFirst, rwLast, colFirst, colLast].pack('CvvvvvvCC')
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 != 0 && @byte_order != ''
6049
- numHdr = numHdr.reverse
6050
- numFtr = numFtr.reverse
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 = data.reverse if @byte_order != 0 && @byte_order != ''
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 = @outline_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.each do |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 @outline_style != 0 # Auto outline styles
6301
- grbit |= 0x0040 if @outline_below != 0 # Outline summary below
6302
- grbit |= 0x0080 if @outline_right != 0 # Outline summary right
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 @outline_on != 0 # Outline symbols displayed
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 .. @dim_rowmax-1).each do |row|
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 = @dim_colmin
6452
- col_max = @dim_colmax
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 or row == @dim_rowmax -1
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
- # Subtract the underlying cell heights to find the end cell of the image
6629
- height, row_end = adjust_row_position(height, row_end)
6289
+ indices = @db_indices
6290
+ reserved = 0x00000000
6291
+ row_min = @dimension.row_min
6292
+ row_max = @dimension.row_max
6630
6293
 
6631
- # Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
6632
- # with zero eight or width.
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
- # Convert the pixel values to the percentage value expected by Excel
6640
- x1 = 1024.0 * x1 / size_col(col_start)
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
- # Simulate ceil() without calling POSIX::ceil().
6646
- x1 = (x1 +0.5).to_i
6647
- y1 = (y1 +0.5).to_i
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[2]
6742
- col2 = @filter_area[3]
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 != '' && @byte_order != 0
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 num_images == 0
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
- store_obj_image(i+1)
7042
- end
6582
+ spid = @object_ids.spid
7043
6583
 
7044
- @object_ids[0] = spid
7045
- end
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
- def store_parent_mso_record(dg_length, ids, spgr_length, spid) # :nodoc:
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
- record = 0x00EC # Record identifier
7069
- length = 0x0000 # Bytes to follow
7070
-
7071
- ids = @object_ids.dup
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
- store_obj_chart(num_objects + i + 1)
7137
- store_chart_binary(chart)
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
- # Simulate the EXTERNSHEET link between the chart and data using a formula
7141
- # such as '=Sheet1!A1'.
7142
- # TODO. Won't work for external data refs. Also should use a more direct
7143
- # method.
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
- @object_ids[0] = spid
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 num_filters == 0
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
- store_obj_filter(num_objects+i+1, col1 +i)
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
- record = 0x00EC # Record identifier
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
- comments = @comments_array
7260
- num_comments = comments.size
6695
+ spid = @object_ids.spid
6696
+ num_comments = comments_size
7261
6697
 
7262
6698
  # Number of objects written so far.
7263
- num_objects = @images_array.size + @filter_count + @charts_array.size
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
- store_obj_comment(num_objects + i + 1)
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
- (0 .. num_comments-1).each do |i|
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(instance, num_shapes, max_spid) #:nodoc:
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, instance, data, length)
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
- append(header, data)
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 = 0x0046 # Bytes to follow
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
- dv_count = @validations.size
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 |param|
8042
- store_dv(
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