writeexcel 0.6.9 → 0.6.10

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