write_xlsx 1.02.0 → 1.08.1

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 (189) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -1
  3. data/Changes +72 -0
  4. data/README.md +1 -1
  5. data/examples/chart_data_labels.rb +320 -0
  6. data/examples/chart_line.rb +85 -10
  7. data/examples/tables.rb +77 -42
  8. data/lib/write_xlsx/chart/line.rb +15 -1
  9. data/lib/write_xlsx/chart/series.rb +100 -0
  10. data/lib/write_xlsx/chart.rb +155 -33
  11. data/lib/write_xlsx/drawing.rb +80 -17
  12. data/lib/write_xlsx/format.rb +5 -5
  13. data/lib/write_xlsx/package/app.rb +3 -3
  14. data/lib/write_xlsx/package/comments.rb +4 -4
  15. data/lib/write_xlsx/package/conditional_format.rb +2 -8
  16. data/lib/write_xlsx/package/packager.rb +1 -0
  17. data/lib/write_xlsx/package/relationships.rb +2 -2
  18. data/lib/write_xlsx/package/styles.rb +42 -11
  19. data/lib/write_xlsx/package/table.rb +16 -7
  20. data/lib/write_xlsx/package/vml.rb +20 -19
  21. data/lib/write_xlsx/sheets.rb +12 -20
  22. data/lib/write_xlsx/utility.rb +9 -3
  23. data/lib/write_xlsx/version.rb +1 -1
  24. data/lib/write_xlsx/workbook.rb +76 -35
  25. data/lib/write_xlsx/worksheet/data_validation.rb +1 -6
  26. data/lib/write_xlsx/worksheet.rb +197 -57
  27. data/test/drawing/{test_write_ext.rb → test_write_xdr_ext.rb} +2 -2
  28. data/test/perl_output/chart_data_labels.xlsx +0 -0
  29. data/test/perl_output/chart_line.xlsx +0 -0
  30. data/test/perl_output/comments2.xlsx +0 -0
  31. data/test/perl_output/tables.xlsx +0 -0
  32. data/test/regression/images/red2.png +0 -0
  33. data/test/regression/test_array_formula04.rb +31 -0
  34. data/test/regression/test_chart_crossing01.rb +1 -1
  35. data/test/regression/test_chart_crossing05.rb +46 -0
  36. data/test/regression/test_chart_crossing06.rb +46 -0
  37. data/test/regression/test_chart_data_labels26.rb +44 -0
  38. data/test/regression/test_chart_data_labels27.rb +44 -0
  39. data/test/regression/test_chart_data_labels28.rb +52 -0
  40. data/test/regression/test_chart_data_labels29.rb +43 -0
  41. data/test/regression/test_chart_data_labels30.rb +46 -0
  42. data/test/regression/test_chart_data_labels31.rb +49 -0
  43. data/test/regression/test_chart_data_labels32.rb +54 -0
  44. data/test/regression/test_chart_data_labels33.rb +52 -0
  45. data/test/regression/test_chart_data_labels34.rb +54 -0
  46. data/test/regression/test_chart_data_labels35.rb +46 -0
  47. data/test/regression/test_chart_data_labels36.rb +54 -0
  48. data/test/regression/test_chart_data_labels37.rb +51 -0
  49. data/test/regression/test_chart_data_labels38.rb +54 -0
  50. data/test/regression/test_chart_data_labels39.rb +53 -0
  51. data/test/regression/test_chart_data_labels40.rb +53 -0
  52. data/test/regression/test_chart_data_labels41.rb +54 -0
  53. data/test/regression/test_chart_data_labels42.rb +58 -0
  54. data/test/regression/test_chart_data_labels43.rb +58 -0
  55. data/test/regression/test_chart_data_labels44.rb +56 -0
  56. data/test/regression/test_chart_data_labels45.rb +57 -0
  57. data/test/regression/test_chart_data_labels46.rb +61 -0
  58. data/test/regression/test_chart_data_labels47.rb +61 -0
  59. data/test/regression/test_chart_data_labels48.rb +55 -0
  60. data/test/regression/test_chart_data_labels49.rb +55 -0
  61. data/test/regression/test_chart_data_labels50.rb +57 -0
  62. data/test/regression/test_chart_line05.rb +43 -0
  63. data/test/regression/test_chart_line06.rb +43 -0
  64. data/test/regression/test_comment15.rb +28 -0
  65. data/test/regression/test_comment16.rb +34 -0
  66. data/test/regression/test_format16.rb +24 -0
  67. data/test/regression/test_format17.rb +24 -0
  68. data/test/regression/test_header04.rb +30 -0
  69. data/test/regression/test_header_image15.rb +36 -0
  70. data/test/regression/test_header_image16.rb +42 -0
  71. data/test/regression/test_header_image17.rb +46 -0
  72. data/test/regression/test_header_image18.rb +48 -0
  73. data/test/regression/test_header_image19.rb +36 -0
  74. data/test/regression/test_hyperlink48.rb +31 -0
  75. data/test/regression/test_hyperlink49.rb +29 -0
  76. data/test/regression/test_hyperlink50.rb +27 -0
  77. data/test/regression/test_hyperlink51.rb +27 -0
  78. data/test/regression/test_ignore_error01.rb +23 -0
  79. data/test/regression/test_ignore_error02.rb +24 -0
  80. data/test/regression/test_ignore_error03.rb +26 -0
  81. data/test/regression/test_ignore_error04.rb +26 -0
  82. data/test/regression/test_ignore_error05.rb +32 -0
  83. data/test/regression/test_ignore_error06.rb +32 -0
  84. data/test/regression/test_image45.rb +2 -1
  85. data/test/regression/test_image46.rb +1 -1
  86. data/test/regression/test_image48.rb +32 -0
  87. data/test/regression/test_image49.rb +38 -0
  88. data/test/regression/test_image50.rb +24 -0
  89. data/test/regression/test_image51.rb +30 -0
  90. data/test/regression/test_image52.rb +26 -0
  91. data/test/regression/test_image53.rb +26 -0
  92. data/test/regression/test_image54.rb +26 -0
  93. data/test/regression/test_image55.rb +27 -0
  94. data/test/regression/test_object_position12.rb +25 -0
  95. data/test/regression/test_object_position13.rb +25 -0
  96. data/test/regression/test_object_position14.rb +25 -0
  97. data/test/regression/test_object_position15.rb +29 -0
  98. data/test/regression/test_object_position16.rb +29 -0
  99. data/test/regression/test_object_position17.rb +29 -0
  100. data/test/regression/test_object_position18.rb +29 -0
  101. data/test/regression/test_object_position19.rb +29 -0
  102. data/test/regression/test_object_position20.rb +29 -0
  103. data/test/regression/test_protect04.rb +32 -0
  104. data/test/regression/test_protect05.rb +35 -0
  105. data/test/regression/test_protect06.rb +35 -0
  106. data/test/regression/test_protect07.rb +23 -0
  107. data/test/regression/test_table24.rb +27 -0
  108. data/test/regression/test_table25.rb +27 -0
  109. data/test/regression/test_table26.rb +38 -0
  110. data/test/regression/xlsx_files/array_formula04.xlsx +0 -0
  111. data/test/regression/xlsx_files/chart_crossing05.xlsx +0 -0
  112. data/test/regression/xlsx_files/chart_crossing06.xlsx +0 -0
  113. data/test/regression/xlsx_files/chart_data_labels26.xlsx +0 -0
  114. data/test/regression/xlsx_files/chart_data_labels27.xlsx +0 -0
  115. data/test/regression/xlsx_files/chart_data_labels28.xlsx +0 -0
  116. data/test/regression/xlsx_files/chart_data_labels29.xlsx +0 -0
  117. data/test/regression/xlsx_files/chart_data_labels30.xlsx +0 -0
  118. data/test/regression/xlsx_files/chart_data_labels31.xlsx +0 -0
  119. data/test/regression/xlsx_files/chart_data_labels32.xlsx +0 -0
  120. data/test/regression/xlsx_files/chart_data_labels33.xlsx +0 -0
  121. data/test/regression/xlsx_files/chart_data_labels34.xlsx +0 -0
  122. data/test/regression/xlsx_files/chart_data_labels35.xlsx +0 -0
  123. data/test/regression/xlsx_files/chart_data_labels36.xlsx +0 -0
  124. data/test/regression/xlsx_files/chart_data_labels37.xlsx +0 -0
  125. data/test/regression/xlsx_files/chart_data_labels38.xlsx +0 -0
  126. data/test/regression/xlsx_files/chart_data_labels39.xlsx +0 -0
  127. data/test/regression/xlsx_files/chart_data_labels40.xlsx +0 -0
  128. data/test/regression/xlsx_files/chart_data_labels41.xlsx +0 -0
  129. data/test/regression/xlsx_files/chart_data_labels42.xlsx +0 -0
  130. data/test/regression/xlsx_files/chart_data_labels43.xlsx +0 -0
  131. data/test/regression/xlsx_files/chart_data_labels44.xlsx +0 -0
  132. data/test/regression/xlsx_files/chart_data_labels45.xlsx +0 -0
  133. data/test/regression/xlsx_files/chart_data_labels46.xlsx +0 -0
  134. data/test/regression/xlsx_files/chart_data_labels47.xlsx +0 -0
  135. data/test/regression/xlsx_files/chart_data_labels48.xlsx +0 -0
  136. data/test/regression/xlsx_files/chart_data_labels49.xlsx +0 -0
  137. data/test/regression/xlsx_files/chart_data_labels50.xlsx +0 -0
  138. data/test/regression/xlsx_files/chart_line05.xlsx +0 -0
  139. data/test/regression/xlsx_files/chart_line06.xlsx +0 -0
  140. data/test/regression/xlsx_files/comment15.xlsx +0 -0
  141. data/test/regression/xlsx_files/comment16.xlsx +0 -0
  142. data/test/regression/xlsx_files/format16.xlsx +0 -0
  143. data/test/regression/xlsx_files/format17.xlsx +0 -0
  144. data/test/regression/xlsx_files/header04.xlsx +0 -0
  145. data/test/regression/xlsx_files/header_image15.xlsx +0 -0
  146. data/test/regression/xlsx_files/header_image16.xlsx +0 -0
  147. data/test/regression/xlsx_files/header_image17.xlsx +0 -0
  148. data/test/regression/xlsx_files/header_image18.xlsx +0 -0
  149. data/test/regression/xlsx_files/header_image19.xlsx +0 -0
  150. data/test/regression/xlsx_files/hyperlink46.xlsx +0 -0
  151. data/test/regression/xlsx_files/hyperlink50.xlsx +0 -0
  152. data/test/regression/xlsx_files/hyperlink51.xlsx +0 -0
  153. data/test/regression/xlsx_files/ignore_error01.xlsx +0 -0
  154. data/test/regression/xlsx_files/ignore_error02.xlsx +0 -0
  155. data/test/regression/xlsx_files/ignore_error03.xlsx +0 -0
  156. data/test/regression/xlsx_files/ignore_error04.xlsx +0 -0
  157. data/test/regression/xlsx_files/ignore_error05.xlsx +0 -0
  158. data/test/regression/xlsx_files/ignore_error06.xlsx +0 -0
  159. data/test/regression/xlsx_files/image45.xlsx +0 -0
  160. data/test/regression/xlsx_files/image46.xlsx +0 -0
  161. data/test/regression/xlsx_files/image48.xlsx +0 -0
  162. data/test/regression/xlsx_files/image49.xlsx +0 -0
  163. data/test/regression/xlsx_files/image50.xlsx +0 -0
  164. data/test/regression/xlsx_files/image51.xlsx +0 -0
  165. data/test/regression/xlsx_files/image52.xlsx +0 -0
  166. data/test/regression/xlsx_files/image53.xlsx +0 -0
  167. data/test/regression/xlsx_files/image54.xlsx +0 -0
  168. data/test/regression/xlsx_files/image55.xlsx +0 -0
  169. data/test/regression/xlsx_files/object_position12.xlsx +0 -0
  170. data/test/regression/xlsx_files/object_position13.xlsx +0 -0
  171. data/test/regression/xlsx_files/object_position14.xlsx +0 -0
  172. data/test/regression/xlsx_files/object_position15.xlsx +0 -0
  173. data/test/regression/xlsx_files/object_position16.xlsx +0 -0
  174. data/test/regression/xlsx_files/object_position17.xlsx +0 -0
  175. data/test/regression/xlsx_files/object_position18.xlsx +0 -0
  176. data/test/regression/xlsx_files/object_position19.xlsx +0 -0
  177. data/test/regression/xlsx_files/object_position20.xlsx +0 -0
  178. data/test/regression/xlsx_files/protect04.xlsx +0 -0
  179. data/test/regression/xlsx_files/protect05.xlsx +0 -0
  180. data/test/regression/xlsx_files/protect06.xlsx +0 -0
  181. data/test/regression/xlsx_files/protect07.xlsx +0 -0
  182. data/test/regression/xlsx_files/table24.xlsx +0 -0
  183. data/test/regression/xlsx_files/table25.xlsx +0 -0
  184. data/test/regression/xlsx_files/table26.xlsx +0 -0
  185. data/test/test_example_match.rb +433 -10
  186. data/test/utility/test_range.rb +20 -0
  187. data/test/workbook/test_check_sheetname.rb +0 -10
  188. data/write_xlsx.gemspec +1 -0
  189. metadata +323 -8
@@ -359,6 +359,8 @@ module Writexlsx
359
359
  @shape_hash = {}
360
360
  @drawing_rels = {}
361
361
  @drawing_rels_id = 0
362
+ @vml_drawing_rels = {}
363
+ @vml_drawing_rels_id = 0
362
364
  @header_images = []
363
365
  @footer_images = []
364
366
 
@@ -379,6 +381,7 @@ module Writexlsx
379
381
  @comments = Package::Comments.new(self)
380
382
  @buttons_array = []
381
383
  @header_images_array = []
384
+ @ignore_errors = nil
382
385
 
383
386
  @validations = []
384
387
 
@@ -386,6 +389,9 @@ module Writexlsx
386
389
  @data_bars_2010 = []
387
390
  @dxf_priority = 1
388
391
 
392
+ @protected_ranges = []
393
+ @num_protected_ranges = 0
394
+
389
395
  if excel2003_style?
390
396
  @original_row_height = 12.75
391
397
  @default_row_height = 12.75
@@ -412,6 +418,7 @@ module Writexlsx
412
418
  write_cols
413
419
  write_sheet_data
414
420
  write_sheet_protection
421
+ write_protected_ranges
415
422
  # write_sheet_calc_pr
416
423
  write_phonetic_pr if excel2003_style?
417
424
  write_auto_filter
@@ -425,6 +432,7 @@ module Writexlsx
425
432
  write_header_footer
426
433
  write_row_breaks
427
434
  write_col_breaks
435
+ write_ignored_errors
428
436
  write_drawings
429
437
  write_legacy_drawing
430
438
  write_legacy_drawing_hf
@@ -614,7 +622,25 @@ module Writexlsx
614
622
 
615
623
  # Set the password after the user defined values.
616
624
  @protect[:password] =
617
- sprintf("%X", encode_password(password)) if password && password != ''
625
+ encode_password(password) if password && password != ''
626
+ end
627
+
628
+ #
629
+ # Unprotect ranges within a protected worksheet.
630
+ #
631
+ def unprotect_range(range, range_name = nil, password = nil)
632
+ if range.nil?
633
+ raise "The range must be defined in unprotect_range())\n"
634
+ else
635
+ range.gsub!(/\$/, "")
636
+ range.sub!(/^=/, "")
637
+ @num_protected_ranges += 1
638
+ end
639
+
640
+ range_name ||= "Range#{@num_protected_ranges}"
641
+ password &&= encode_password(password)
642
+
643
+ @protected_ranges << [range, range_name, password]
618
644
  end
619
645
 
620
646
  def protect_default_settings # :nodoc:
@@ -816,12 +842,7 @@ module Writexlsx
816
842
  row_first, row_last = row_last, row_first if row_first > row_last
817
843
  col_first, col_last = col_last, col_first if col_first > col_last
818
844
 
819
- # If the first and last cell are the same write a single cell.
820
- if row_first == row_last && col_first == col_last
821
- sqref = active_cell
822
- else
823
- sqref = xl_range(row_first, row_last, col_first, col_last)
824
- end
845
+ sqref = xl_range(row_first, row_last, col_first, col_last)
825
846
  else # Single cell selection.
826
847
  sqref = active_cell
827
848
  end
@@ -1205,14 +1226,10 @@ module Writexlsx
1205
1226
  # distribution.
1206
1227
  #
1207
1228
  def set_header(string = '', margin = 0.3, options = {})
1208
- raise 'Header string must be less than 255 characters' if string.length >= 255
1229
+ raise 'Header string must be less than 255 characters' if string.length > 255
1209
1230
  # Replace the Excel placeholder &[Picture] with the internal &G.
1210
1231
  @page_setup.header = string.gsub(/&\[Picture\]/, '&G')
1211
1232
 
1212
- if string.size >= 255
1213
- raise 'Header string must be less than 255 characters'
1214
- end
1215
-
1216
1233
  if options[:align_with_margins]
1217
1234
  @page_setup.header_footer_aligns = options[:align_with_margins]
1218
1235
  end
@@ -1253,17 +1270,13 @@ module Writexlsx
1253
1270
  # The syntax of the set_footer() method is the same as set_header()
1254
1271
  #
1255
1272
  def set_footer(string = '', margin = 0.3, options = {})
1256
- raise 'Footer string must be less than 255 characters' if string.length >= 255
1273
+ raise 'Footer string must be less than 255 characters' if string.length > 255
1257
1274
 
1258
1275
  @page_setup.footer = string.dup
1259
1276
 
1260
1277
  # Replace the Excel placeholder &[Picture] with the internal &G.
1261
1278
  @page_setup.footer = string.gsub(/&\[Picture\]/, '&G')
1262
1279
 
1263
- if string.size >= 255
1264
- raise 'Header string must be less than 255 characters'
1265
- end
1266
-
1267
1280
  if options[:align_with_margins]
1268
1281
  @page_setup.header_footer_aligns = options[:align_with_margins]
1269
1282
  end
@@ -2512,7 +2525,9 @@ module Writexlsx
2512
2525
  col1, col2 = col2, col1 if col1 > col2
2513
2526
 
2514
2527
  # Check that row and col are valid and store max and min values
2528
+ check_dimensions(row1, col1)
2515
2529
  check_dimensions(row2, col2)
2530
+ store_row_col_max_min_values(row1, col1)
2516
2531
  store_row_col_max_min_values(row2, col2)
2517
2532
 
2518
2533
  # Define array range
@@ -3044,14 +3059,16 @@ module Writexlsx
3044
3059
 
3045
3060
  if options.first.class == Hash
3046
3061
  # Newer hash bashed options
3047
- params = options.first
3048
- x_offset = params[:x_offset]
3049
- y_offset = params[:y_offset]
3050
- x_scale = params[:x_scale]
3051
- y_scale = params[:y_scale]
3052
- anchor = params[:object_position]
3053
- url = params[:url]
3054
- tip = params[:tip]
3062
+ params = options.first
3063
+ x_offset = params[:x_offset]
3064
+ y_offset = params[:y_offset]
3065
+ x_scale = params[:x_scale]
3066
+ y_scale = params[:y_scale]
3067
+ anchor = params[:object_position]
3068
+ url = params[:url]
3069
+ tip = params[:tip]
3070
+ description = params[:description]
3071
+ decorative = params[:decorative]
3055
3072
  else
3056
3073
  x_offset, y_offset, x_scale, y_scale, anchor = options
3057
3074
  end
@@ -3063,7 +3080,7 @@ module Writexlsx
3063
3080
 
3064
3081
  @images << [
3065
3082
  row, col, image, x_offset, y_offset,
3066
- x_scale, y_scale, url, tip, anchor
3083
+ x_scale, y_scale, url, tip, anchor, description, decorative
3067
3084
  ]
3068
3085
  end
3069
3086
 
@@ -3361,9 +3378,11 @@ module Writexlsx
3361
3378
  row_first, row_last = row_last, row_first if row_first > row_last
3362
3379
  col_first, col_last = col_last, col_first if col_first > col_last
3363
3380
 
3364
- # Check that column number is valid and store the max value
3365
- check_dimensions(row_last, col_last)
3366
- store_row_col_max_min_values(row_last, col_last)
3381
+ # Check that the data range is valid and store the max and min values.
3382
+ check_dimensions(row_first, col_first)
3383
+ check_dimensions(row_last, col_last)
3384
+ store_row_col_max_min_values(row_first, col_first)
3385
+ store_row_col_max_min_values(row_last, col_last)
3367
3386
 
3368
3387
  # Store the merge range.
3369
3388
  @merge << [row_first, col_first, row_last, col_last]
@@ -3426,9 +3445,11 @@ module Writexlsx
3426
3445
  row_first, row_last = row_last, row_first if row_first > row_last
3427
3446
  col_first, col_last = col_last, col_first if col_first > col_last
3428
3447
 
3429
- # Check that column number is valid and store the max value
3430
- check_dimensions(row_last, col_last)
3431
- store_row_col_max_min_values(row_last, col_last)
3448
+ # Check that the data range is valid and store the max and min values.
3449
+ check_dimensions(row_first, col_first)
3450
+ check_dimensions(row_last, col_last)
3451
+ store_row_col_max_min_values(row_first, col_first)
3452
+ store_row_col_max_min_values(row_last, col_last)
3432
3453
 
3433
3454
  # Store the merge range.
3434
3455
  @merge << [row_first, col_first, row_last, col_last]
@@ -5736,7 +5757,7 @@ module Writexlsx
5736
5757
  name = chart.name
5737
5758
 
5738
5759
  # Create a Drawing object to use with worksheet unless one already exists.
5739
- drawing = Drawing.new(drawing_type, dimensions, 0, 0, name, nil, anchor, drawing_rel_index, 0, nil)
5760
+ drawing = Drawing.new(drawing_type, dimensions, 0, 0, name, nil, anchor, drawing_rel_index, 0, nil, 0)
5740
5761
  if !drawings?
5741
5762
  @drawings = Drawings.new
5742
5763
  @drawings.add_drawing_object(drawing)
@@ -5868,27 +5889,24 @@ module Writexlsx
5868
5889
  y_abs += y1
5869
5890
 
5870
5891
  # Adjust start column for offsets that are greater than the col width.
5871
- if size_col(col_start) > 0
5872
- while x1 >= size_col(col_start)
5873
- x1 -= size_col(col_start)
5874
- col_start += 1
5875
- end
5892
+ while x1 >= size_col(col_start, anchor)
5893
+ x1 -= size_col(col_start)
5894
+ col_start += 1
5876
5895
  end
5877
5896
 
5878
5897
  # Adjust start row for offsets that are greater than the row height.
5879
- if size_row(row_start) > 0
5880
- while y1 >= size_row(row_start)
5881
- y1 -= size_row(row_start)
5882
- row_start += 1
5883
- end
5898
+ while y1 >= size_row(row_start, anchor)
5899
+ y1 -= size_row(row_start)
5900
+ row_start += 1
5884
5901
  end
5885
5902
 
5886
5903
  # Initialise end cell to the same as the start cell.
5887
5904
  col_end = col_start
5888
5905
  row_end = row_start
5889
5906
 
5890
- width += x1 if size_col(col_start) > 0
5891
- height += y1 if size_row(row_start) > 0
5907
+ # Only offset the image in the cell if the row/col isn't hidden.
5908
+ width += x1 if size_col(col_start, anchor) > 0
5909
+ height += y1 if size_row(row_start, anchor) > 0
5892
5910
 
5893
5911
  # Subtract the underlying cell widths to find the end cell of the object.
5894
5912
  while width >= size_col(col_end, anchor)
@@ -6063,6 +6081,30 @@ module Writexlsx
6063
6081
  end
6064
6082
  end
6065
6083
 
6084
+ #
6085
+ # Ignore worksheet errors/warnings in user defined ranges.
6086
+ #
6087
+ def ignore_errors(ignores)
6088
+ # List of valid input parameters.
6089
+ valid_parameter_keys = [
6090
+ :number_stored_as_text,
6091
+ :eval_error,
6092
+ :formula_differs,
6093
+ :formula_range,
6094
+ :formula_unlocked,
6095
+ :empty_cell_reference,
6096
+ :list_data_validation,
6097
+ :calculated_column,
6098
+ :two_digit_text_year
6099
+ ]
6100
+
6101
+ unless (ignores.keys - valid_parameter_keys).empty?
6102
+ raise "Unknown parameter '#{ignores.key - valid_parameter_keys}' in ignore_errors()."
6103
+ end
6104
+
6105
+ @ignore_errors = ignores
6106
+ end
6107
+
6066
6108
  def write_ext(url)
6067
6109
  attributes = [
6068
6110
  ['xmlns:x14', "#{OFFICE_URL}spreadsheetml/2009/9/main"],
@@ -6100,6 +6142,18 @@ module Writexlsx
6100
6142
  end
6101
6143
  end
6102
6144
 
6145
+ #
6146
+ # Get the index used to address a vml_drawing rel link.
6147
+ #
6148
+ def get_vml_drawing_rel_index(target)
6149
+ if @vml_drawing_rels[target]
6150
+ @vml_drawing_rels[target]
6151
+ else
6152
+ @vml_drawing_rels_id += 1
6153
+ @vml_drawing_rels[target] = @vml_drawing_rels_id
6154
+ end
6155
+ end
6156
+
6103
6157
  def hyperlinks_count
6104
6158
  @hyperlinks.keys.inject(0) { |s, n| s += @hyperlinks[n].keys.size }
6105
6159
  end
@@ -6447,13 +6501,13 @@ module Writexlsx
6447
6501
  #
6448
6502
  # Set up image/drawings.
6449
6503
  #
6450
- def prepare_image(index, image_id, drawing_id, width, height, name, image_type, x_dpi = 96, y_dpi = 96) #:nodoc:
6504
+ def prepare_image(index, image_id, drawing_id, width, height, name, image_type, x_dpi = 96, y_dpi = 96, md5 = nil) #:nodoc:
6451
6505
  x_dpi ||= 96
6452
6506
  y_dpi ||= 96
6453
6507
  drawing_type = 2
6454
6508
 
6455
6509
  row, col, image, x_offset, y_offset,
6456
- x_scale, y_scale, url, tip, anchor = @images[index]
6510
+ x_scale, y_scale, url, tip, anchor, description, decorative = @images[index]
6457
6511
 
6458
6512
  width *= x_scale
6459
6513
  height *= y_scale
@@ -6468,7 +6522,7 @@ module Writexlsx
6468
6522
  height = (0.5 + (height * 9_525)).to_i
6469
6523
 
6470
6524
  # Create a Drawing object to use with worksheet unless one already exists.
6471
- drawing = Drawing.new(drawing_type, dimensions, width, height, name, nil, anchor, 0, 0, tip)
6525
+ drawing = Drawing.new(drawing_type, dimensions, width, height, name, nil, anchor, 0, 0, tip, decorative)
6472
6526
  if !drawings?
6473
6527
  drawings = Drawings.new
6474
6528
  drawings.embedded = 1
@@ -6481,6 +6535,10 @@ module Writexlsx
6481
6535
  end
6482
6536
  drawings.add_drawing_object(drawing)
6483
6537
 
6538
+ if description
6539
+ drawing.description = description
6540
+ end
6541
+
6484
6542
  if url
6485
6543
  rel_type = '/hyperlink'
6486
6544
  target_mode = 'External'
@@ -6488,10 +6546,19 @@ module Writexlsx
6488
6546
  target = escape_url(url)
6489
6547
  end
6490
6548
  if url =~ /^external:/
6491
- target = escape_url(url.sub(/^external:/, 'file:///'))
6549
+ target = escape_url(url.sub(/^external:/, ''))
6550
+
6492
6551
  # Additional escape not required in worksheet hyperlinks
6493
6552
  target = target.gsub(/#/, '%23')
6553
+
6554
+ # Prefix absolute paths (not relative) with file:///
6555
+ if target =~ /^\w:/ || target =~ /^\\\\/
6556
+ target = "file:///#{target}"
6557
+ else
6558
+ target = target.gsub(/\\/, '/')
6559
+ end
6494
6560
  end
6561
+
6495
6562
  if url =~ /^internal:/
6496
6563
  target = url.sub(/^internal:/, '#')
6497
6564
  target_mode = nil
@@ -6503,24 +6570,30 @@ Ignoring URL #{target} where link or anchor > 255 characters since it exceeds Ex
6503
6570
  EOS
6504
6571
  end
6505
6572
 
6506
- if target
6573
+ if target && !@drawing_rels[url]
6507
6574
  @drawing_links << [rel_type, target, target_mode]
6508
6575
  end
6509
- drawing.url_rel_index = drawing_rel_index
6576
+ drawing.url_rel_index = drawing_rel_index(url)
6510
6577
  end
6511
6578
 
6512
- drawing.rel_index = drawing_rel_index
6513
- @drawing_links << ['/image', "../media/image#{image_id}.#{image_type}"]
6579
+ if !@drawing_rels[md5]
6580
+ @drawing_links << ['/image', "../media/image#{image_id}.#{image_type}"]
6581
+ end
6582
+ drawing.rel_index = drawing_rel_index(md5)
6514
6583
  end
6515
6584
  public :prepare_image
6516
6585
 
6517
- def prepare_header_image(image_id, width, height, name, image_type, position, x_dpi, y_dpi)
6586
+ def prepare_header_image(image_id, width, height, name, image_type, position, x_dpi, y_dpi, md5)
6518
6587
  # Strip the extension from the filename.
6519
6588
  body = name.dup
6520
6589
  body[/\.[^\.]+$/, 0] = ''
6521
6590
 
6522
- @header_images_array << [width, height, body, position, x_dpi, y_dpi]
6523
- @vml_drawing_links << ['/image', "../media/image#{image_id}.#{image_type}" ]
6591
+ if !@vml_drawing_rels[md5]
6592
+ @vml_drawing_links << ['/image', "../media/image#{image_id}.#{image_type}" ]
6593
+ end
6594
+
6595
+ ref_id = get_vml_drawing_rel_index(md5)
6596
+ @header_images_array << [width, height, body, position, x_dpi, y_dpi, ref_id]
6524
6597
  end
6525
6598
  public :prepare_header_image
6526
6599
 
@@ -6628,7 +6701,7 @@ EOS
6628
6701
  shape.calc_position_emus(self)
6629
6702
 
6630
6703
  drawing_type = 3
6631
- drawing = Drawing.new(drawing_type, shape.dimensions, shape.width_emu, shape.height_emu, shape.name, shape, shape.anchor, drawing_rel_index, 0, nil)
6704
+ drawing = Drawing.new(drawing_type, shape.dimensions, shape.width_emu, shape.height_emu, shape.name, shape, shape.anchor, drawing_rel_index, 0, nil, 0)
6632
6705
  drawings.add_drawing_object(drawing)
6633
6706
  end
6634
6707
  public :prepare_shape
@@ -6718,6 +6791,8 @@ EOS
6718
6791
  chars.each { |c| encoded_password ^= c }
6719
6792
  encoded_password ^= count
6720
6793
  encoded_password ^= 0xCE4B
6794
+
6795
+ sprintf("%X", encoded_password)
6721
6796
  end
6722
6797
 
6723
6798
  #
@@ -7533,6 +7608,32 @@ EOS
7533
7608
  @writer.empty_tag('sheetProtection', attributes)
7534
7609
  end
7535
7610
 
7611
+ #
7612
+ # Write the <protectedRanges> element.
7613
+ #
7614
+ def write_protected_ranges
7615
+ return if @num_protected_ranges == 0
7616
+
7617
+ @writer.tag_elements('protectedRanges') do
7618
+ @protected_ranges.each do |protected_range|
7619
+ write_protected_range(*protected_range)
7620
+ end
7621
+ end
7622
+ end
7623
+
7624
+ #
7625
+ # Write the <protectedRange> element.
7626
+ #
7627
+ def write_protected_range(sqref, name, password)
7628
+ attributes = []
7629
+
7630
+ attributes << ['password', password] if password
7631
+ attributes << ['sqref', sqref]
7632
+ attributes << ['name', name]
7633
+
7634
+ @writer.empty_tag('protectedRange', attributes)
7635
+ end
7636
+
7536
7637
  #
7537
7638
  # Write the <drawing> elements.
7538
7639
  #
@@ -8065,5 +8166,44 @@ EOS
8065
8166
  end
8066
8167
  col
8067
8168
  end
8169
+
8170
+ #
8171
+ # Write the <ignoredErrors> element.
8172
+ #
8173
+ def write_ignored_errors
8174
+ return unless @ignore_errors
8175
+
8176
+ ignore = @ignore_errors
8177
+
8178
+ @writer.tag_elements('ignoredErrors' ) do
8179
+ {
8180
+ :number_stored_as_text => 'numberStoredAsText',
8181
+ :eval_error => 'evalError',
8182
+ :formula_differs => 'formula',
8183
+ :formula_range => 'formulaRange',
8184
+ :formula_unlocked => 'unlockedFormula',
8185
+ :empty_cell_reference => 'emptyCellReference',
8186
+ :list_data_validation => 'listDataValidation',
8187
+ :calculated_column => 'calculatedColumn',
8188
+ :two_digit_text_year => 'twoDigitTextYear'
8189
+ }.each do |key, value|
8190
+ if ignore[key]
8191
+ write_ignored_error(value, ignore[key])
8192
+ end
8193
+ end
8194
+ end
8195
+ end
8196
+
8197
+ #
8198
+ # Write the <ignoredError> element.
8199
+ #
8200
+ def write_ignored_error(type, sqref)
8201
+ attributes = {
8202
+ 'sqref' => sqref,
8203
+ type => 1
8204
+ }
8205
+
8206
+ @writer.empty_tag('ignoredError', attributes)
8207
+ end
8068
8208
  end
8069
8209
  end
@@ -2,7 +2,7 @@
2
2
  require 'helper'
3
3
  require 'write_xlsx/drawing'
4
4
 
5
- class TestWriteExt < Minitest::Test
5
+ class TestWriteXdrExt < Minitest::Test
6
6
  def setup
7
7
  @drawing = Writexlsx::Drawings.new
8
8
  end
@@ -10,7 +10,7 @@ class TestWriteExt < Minitest::Test
10
10
  def test_write_ext
11
11
  expected = '<xdr:ext cx="9308969" cy="6078325"/>'
12
12
 
13
- @drawing.__send__(:write_ext, 9308969, 6078325)
13
+ @drawing.__send__(:write_xdr_ext, 9308969, 6078325)
14
14
  result = @drawing.instance_variable_get(:@writer).string
15
15
 
16
16
  assert_equal(expected, result)
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,31 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'helper'
3
+
4
+ class TestRegressionArrayFormula04 < Minitest::Test
5
+ def setup
6
+ setup_dir_var
7
+ end
8
+
9
+ def teardown
10
+ @tempfile.close(true)
11
+ end
12
+
13
+ def test_array_formula04
14
+ @xlsx = 'array_formula04.xlsx'
15
+ workbook = WriteXLSX.new(@io)
16
+ worksheet = workbook.add_worksheet
17
+
18
+ worksheet.write_array_formula(
19
+ 'A1:A3',
20
+ '{=SUM(B1:C1*B2:C2)}',
21
+ nil,
22
+ 0
23
+ )
24
+
25
+ workbook.close
26
+ compare_for_regression(
27
+ [ 'xl/calcChain.xml', '[Content_Types].xml', 'xl/_rels/workbook.xml.rels' ],
28
+ {'xl/workbook.xml' => ['<workbookView']}
29
+ )
30
+ end
31
+ end
@@ -33,7 +33,7 @@ class TestRegressionChartCrossing01 < Minitest::Test
33
33
 
34
34
  chart.set_y_axis(:crossing => 'max')
35
35
 
36
- # Not stictly required. Just to match reference file.
36
+ # Not strictly required. Just to match reference file.
37
37
  chart.set_x_axis(:position => 't')
38
38
 
39
39
  worksheet.insert_chart('E9', chart)
@@ -0,0 +1,46 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'helper'
3
+
4
+ class TestRegressionChartCrossing05 < Minitest::Test
5
+ def setup
6
+ setup_dir_var
7
+ end
8
+
9
+ def teardown
10
+ @tempfile.close(true)
11
+ end
12
+
13
+ def test_chart_crossing05
14
+ @xlsx = 'chart_crossing05.xlsx'
15
+ workbook = WriteXLSX.new(@io)
16
+ worksheet = workbook.add_worksheet
17
+ chart = workbook.add_chart(:type => 'column', :embedded => 1)
18
+
19
+ # For testing, copy the randomly generated axis ids in the target xlsx file.
20
+ chart.instance_variable_set(:@axis_ids, [55948032, 55950336])
21
+
22
+ data = [
23
+ [1, 2, 3, 4, 5],
24
+ [2, 4, 6, 8, 10],
25
+ [3, 6, 9, 12, 15]
26
+ ]
27
+
28
+ worksheet.write('A1', data)
29
+
30
+ chart.add_series(:values => '=Sheet1!$A$1:$A$5')
31
+ chart.add_series(:values => '=Sheet1!$B$1:$B$5')
32
+ chart.add_series(:values => '=Sheet1!$C$1:$C$5')
33
+
34
+ chart.set_x_axis(:crossing => 'min')
35
+
36
+ worksheet.insert_chart('E9', chart)
37
+
38
+ workbook.close
39
+ compare_for_regression(
40
+ nil,
41
+ {
42
+ 'xl/charts/chart1.xml' => [ '<c:pageMargins' ]
43
+ }
44
+ )
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'helper'
3
+
4
+ class TestRegressionChartCrossing06 < Minitest::Test
5
+ def setup
6
+ setup_dir_var
7
+ end
8
+
9
+ def teardown
10
+ @tempfile.close(true)
11
+ end
12
+
13
+ def test_chart_crossing06
14
+ @xlsx = 'chart_crossing06.xlsx'
15
+ workbook = WriteXLSX.new(@io)
16
+ worksheet = workbook.add_worksheet
17
+ chart = workbook.add_chart(:type => 'column', :embedded => 1)
18
+
19
+ # For testing, copy the randomly generated axis ids in the target xlsx file.
20
+ chart.instance_variable_set(:@axis_ids, [72794880, 72796416])
21
+
22
+ data = [
23
+ [1, 2, 3, 4, 5],
24
+ [2, 4, 6, 8, 10],
25
+ [3, 6, 9, 12, 15]
26
+ ]
27
+
28
+ worksheet.write('A1', data)
29
+
30
+ chart.add_series(:values => '=Sheet1!$A$1:$A$5')
31
+ chart.add_series(:values => '=Sheet1!$B$1:$B$5')
32
+ chart.add_series(:values => '=Sheet1!$C$1:$C$5')
33
+
34
+ chart.set_y_axis(:crossing => 'min')
35
+
36
+ worksheet.insert_chart('E9', chart)
37
+
38
+ workbook.close
39
+ compare_for_regression(
40
+ nil,
41
+ {
42
+ 'xl/charts/chart1.xml' => [ '<c:pageMargins' ]
43
+ }
44
+ )
45
+ end
46
+ end
@@ -0,0 +1,44 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'helper'
3
+
4
+ class TestRegressionChartDataLabels26 < Minitest::Test
5
+ def setup
6
+ setup_dir_var
7
+ end
8
+
9
+ def teardown
10
+ @tempfile.close(true)
11
+ end
12
+
13
+ def test_chart_data_labels26
14
+ @xlsx = 'chart_data_labels26.xlsx'
15
+ workbook = WriteXLSX.new(@io)
16
+ worksheet = workbook.add_worksheet
17
+ chart = workbook.add_chart(:type => 'column', :embedded => 1)
18
+
19
+ # For testing, copy the randomly generated axis ids in the target xlsx file.
20
+ chart.instance_variable_set(:@axis_ids, [48514944, 48516480])
21
+
22
+ data = [
23
+ [ 1, 2, 3, 4, 5],
24
+ [ 2, 4, 6, 8, 10],
25
+ [ 3, 6, 9, 12, 15],
26
+ [10, 20, 30, 40, 50]
27
+ ]
28
+
29
+ worksheet.write('A1', data)
30
+
31
+ chart.add_series(
32
+ :values => '=Sheet1!$A$1:$A$5',
33
+ :data_labels => {:value => 1, :custom => [{:value => 33}]}
34
+ )
35
+
36
+ chart.add_series(:values => '=Sheet1!$B$1:$B$5')
37
+ chart.add_series(:values => '=Sheet1!$C$1:$C$5')
38
+
39
+ worksheet.insert_chart('E9', chart)
40
+
41
+ workbook.close
42
+ compare_for_regression
43
+ end
44
+ end