write_xlsx 1.07.0 → 1.08.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/Changes +31 -0
  3. data/README.md +1 -1
  4. data/lib/write_xlsx/chart.rb +3 -5
  5. data/lib/write_xlsx/drawing.rb +80 -17
  6. data/lib/write_xlsx/package/app.rb +3 -3
  7. data/lib/write_xlsx/package/conditional_format.rb +2 -8
  8. data/lib/write_xlsx/package/packager.rb +1 -0
  9. data/lib/write_xlsx/package/styles.rb +16 -3
  10. data/lib/write_xlsx/utility.rb +5 -1
  11. data/lib/write_xlsx/version.rb +1 -1
  12. data/lib/write_xlsx/workbook.rb +23 -0
  13. data/lib/write_xlsx/worksheet/data_validation.rb +1 -6
  14. data/lib/write_xlsx/worksheet.rb +71 -20
  15. data/test/drawing/{test_write_ext.rb → test_write_xdr_ext.rb} +2 -2
  16. data/test/regression/test_chart_crossing01.rb +1 -1
  17. data/test/regression/test_chart_crossing05.rb +46 -0
  18. data/test/regression/test_chart_crossing06.rb +46 -0
  19. data/test/regression/test_chart_data_labels48.rb +55 -0
  20. data/test/regression/test_chart_data_labels49.rb +55 -0
  21. data/test/regression/test_chart_data_labels50.rb +57 -0
  22. data/test/regression/test_format16.rb +24 -0
  23. data/test/regression/test_format17.rb +24 -0
  24. data/test/regression/test_image52.rb +26 -0
  25. data/test/regression/test_image53.rb +26 -0
  26. data/test/regression/test_image54.rb +26 -0
  27. data/test/regression/test_image55.rb +27 -0
  28. data/test/regression/test_protect04.rb +32 -0
  29. data/test/regression/test_protect05.rb +35 -0
  30. data/test/regression/test_protect06.rb +35 -0
  31. data/test/regression/test_protect07.rb +23 -0
  32. data/test/regression/xlsx_files/chart_crossing05.xlsx +0 -0
  33. data/test/regression/xlsx_files/chart_crossing06.xlsx +0 -0
  34. data/test/regression/xlsx_files/chart_data_labels48.xlsx +0 -0
  35. data/test/regression/xlsx_files/chart_data_labels49.xlsx +0 -0
  36. data/test/regression/xlsx_files/chart_data_labels50.xlsx +0 -0
  37. data/test/regression/xlsx_files/format16.xlsx +0 -0
  38. data/test/regression/xlsx_files/format17.xlsx +0 -0
  39. data/test/regression/xlsx_files/image52.xlsx +0 -0
  40. data/test/regression/xlsx_files/image53.xlsx +0 -0
  41. data/test/regression/xlsx_files/image54.xlsx +0 -0
  42. data/test/regression/xlsx_files/image55.xlsx +0 -0
  43. data/test/regression/xlsx_files/protect04.xlsx +0 -0
  44. data/test/regression/xlsx_files/protect05.xlsx +0 -0
  45. data/test/regression/xlsx_files/protect06.xlsx +0 -0
  46. data/test/regression/xlsx_files/protect07.xlsx +0 -0
  47. data/test/utility/test_range.rb +20 -0
  48. metadata +70 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 677847fdb975ab7253b3a5ed5f6aa779a9bbf2a459e941f5037b7d557e2abfe4
4
- data.tar.gz: 4c4b0b03ce3b510a0ad6423c30a76fc778463afe92f992da52b49bbe65c7a1ac
3
+ metadata.gz: 0bce1d8cc8f140d38ac7d9149e644d8b1d5133c80933fc2faa2a33d25dcbd168
4
+ data.tar.gz: 4edf0897d13ec8cee0b545177fe4117286067ba2e14b89c2866192885414633b
5
5
  SHA512:
6
- metadata.gz: b3f81d2d54e5f7a7170c34e7666f58c7f4f77b993ff3b95263327b0ff8edff5c9a0b1a6ea825ece5b43c5b1d2b91a6f01764237f22ee6442bd8c747de8e04150
7
- data.tar.gz: 3e49064459345dbcc5d97fef0f8154159776e8361cb0709e8a4564f32dd062fe95b6e4f08976df81b5ac478a40287be9a33086726444af3c75d1cbeb5c869dc2
6
+ metadata.gz: 267233a3a8222f817bc455b5d8723bf29808854a7b5410c090becd44baee604201b53792f03b29db84c2fa82f9a4085e11175893abb11bab50b1a74c9f0526e8
7
+ data.tar.gz: bc5138fcae79f92aaa9a5d6d708575e9615cec51e4c229ec46e0e75e45bc1eca37056f27466f321c8ad6376119bd37c61754b8809c991fd2352ca8a1108089f0
data/Changes CHANGED
@@ -1,5 +1,36 @@
1
1
  Change history of write_xlsx rubygem.
2
2
 
3
+ 2021-11-19 v1.08.0
4
+
5
+ Added ability to add accessibility options "description" and
6
+ "decorative" to images via insert_image().
7
+
8
+ Added the workbook read_only_recommended() method to set the Excel
9
+ "Read-only Recommended" option that is available when saving a file.
10
+
11
+ Added option to set a chart crossing to 'min' as well as the existing
12
+ 'max' option. The 'min' option isn't available in the Excel interface
13
+ but can be enabled via VBA.
14
+
15
+ Added option to unprotect ranges in protected worksheets.
16
+
17
+ Added check, and warning, for worksheet tables with no data row. Either
18
+ with or without a header row.
19
+
20
+ Added ignore_errors() worksheet method to ignore Excel worksheet
21
+ errors/warnings in user defined ranges.
22
+
23
+ Fixed issue where pattern formats without colours where given a default
24
+ black fill colour.
25
+
26
+ Fix issue where custom chart data labels didn't inherit the position for
27
+ the data labels in the series.
28
+
29
+ Fixed issue with relative url links in images.
30
+
31
+ Fixed issue where headers/footers were restricted to 254 characters
32
+ instead of 255.
33
+
3
34
  2021-02-17 v1.07.0
4
35
 
5
36
  Added support for Border, Fill, Pattern and Gradient formatting to chart
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  gem to create a new file in the Excel 2007+ XLSX format, and you can use the
7
7
  same interface as writeexcel gem. write_xlsx is converted from Perl's module
8
- [Excel::Writer::XLSX-1.07](https://github.com/jmcnamara/excel-writer-xlsx/tree/CPAN_1.07)
8
+ [Excel::Writer::XLSX-1.08](https://github.com/jmcnamara/excel-writer-xlsx/tree/CPAN_1.08)
9
9
 
10
10
  ## Description
11
11
 
@@ -1397,7 +1397,7 @@ module Writexlsx
1397
1397
 
1398
1398
  def write_crossing(crossing)
1399
1399
  # Note, the category crossing comes from the value axis.
1400
- if nil_or_max?(crossing)
1400
+ if [nil, 'max', 'min'].include?(crossing)
1401
1401
  # Write the c:crosses element.
1402
1402
  write_crosses(crossing)
1403
1403
  else
@@ -2418,12 +2418,14 @@ module Writexlsx
2418
2418
  elsif label[:formula]
2419
2419
  write_custom_label_formula(label)
2420
2420
 
2421
+ write_d_lbl_pos(parent[:position]) if parent[:position]
2421
2422
  write_show_val if parent[:value]
2422
2423
  write_show_cat_name if parent[:category]
2423
2424
  write_show_ser_name if parent[:series_name]
2424
2425
  elsif label[:value]
2425
2426
  write_custom_label_str(label)
2426
2427
 
2428
+ write_d_lbl_pos(parent[:position]) if parent[:position]
2427
2429
  write_show_val if parent[:value]
2428
2430
  write_show_cat_name if parent[:category]
2429
2431
  write_show_ser_name if parent[:series_name]
@@ -2926,9 +2928,5 @@ module Writexlsx
2926
2928
  @writer.empty_tag(tag)
2927
2929
  end
2928
2930
  end
2929
-
2930
- def nil_or_max?(val) # :nodoc:
2931
- val.nil? || val == 'max'
2932
- end
2933
2931
  end
2934
2932
  end
@@ -5,11 +5,11 @@ require 'write_xlsx/utility'
5
5
  module Writexlsx
6
6
  class Drawing
7
7
  attr_accessor :type, :dimensions, :width, :height, :description, :shape, :anchor, :rel_index, :url_rel_index
8
- attr_reader :tip
8
+ attr_reader :tip, :decorative
9
9
 
10
- def initialize(type, dimensions, width, height, description, shape, anchor, rel_index = nil, url_rel_index = nil, tip = nil)
11
- @type, @dimensions, @width, @height, @description, @shape, @anchor, @rel_index, @url_rel_index, @tip =
12
- type, dimensions, width, height, description, shape, anchor, rel_index, url_rel_index, tip
10
+ def initialize(type, dimensions, width, height, description, shape, anchor, rel_index = nil, url_rel_index = nil, tip = nil, decorative = nil)
11
+ @type, @dimensions, @width, @height, @description, @shape, @anchor, @rel_index, @url_rel_index, @tip, @decorative =
12
+ type, dimensions, width, height, description, shape, anchor, rel_index, url_rel_index, tip, decorative
13
13
  end
14
14
  end
15
15
 
@@ -92,6 +92,7 @@ module Writexlsx
92
92
  rel_index = drawing.rel_index
93
93
  url_rel_index = drawing.url_rel_index
94
94
  tip = drawing.tip
95
+ decorative = drawing.decorative
95
96
 
96
97
  col_from, row_from, col_from_offset, row_from_offset,
97
98
  col_to, row_to, col_to_offset, row_to_offset, col_absolute, row_absolute = drawing.dimensions
@@ -124,7 +125,7 @@ module Writexlsx
124
125
  write_pic(
125
126
  index, rel_index, col_absolute,
126
127
  row_absolute, width, height,
127
- description, url_rel_index , tip
128
+ description, url_rel_index , tip, decorative
128
129
  )
129
130
  else
130
131
  # Write the xdr:sp element for shapes.
@@ -148,13 +149,13 @@ module Writexlsx
148
149
  write_pos(0, 0)
149
150
 
150
151
  # Write the xdr:ext element.
151
- write_ext(9308969, 6078325)
152
+ write_xdr_ext(9308969, 6078325)
152
153
  else
153
154
  # Write the xdr:pos element.
154
155
  write_pos(0, -47625)
155
156
 
156
157
  # Write the xdr:ext element.
157
- write_ext(6162675, 6124575)
158
+ write_xdr_ext(6162675, 6124575)
158
159
  end
159
160
 
160
161
  # Write the xdr:graphicFrame element.
@@ -242,7 +243,7 @@ module Writexlsx
242
243
  #
243
244
  # Write the <xdr:ext> element.
244
245
  #
245
- def write_ext(cx, cy)
246
+ def write_xdr_ext(cx, cy)
246
247
  attributes = [
247
248
  ['cx', cx],
248
249
  ['cy', cy]
@@ -286,19 +287,25 @@ module Writexlsx
286
287
  #
287
288
  # Write the <xdr:cNvPr> element.
288
289
  #
289
- def write_c_nv_pr(index, name, description = nil, url_rel_index = nil, tip = nil)
290
+ def write_c_nv_pr(index, name, description = nil, url_rel_index = nil, tip = nil, decorative = nil)
290
291
  attributes = [
291
292
  ['id', index],
292
293
  ['name', name]
293
294
  ]
294
295
 
295
296
  # Add description attribute for images.
296
- attributes << ['descr', description] if description
297
+ attributes << ['descr', description] if ptrue?(description) && !ptrue?(decorative)
297
298
 
298
- if ptrue?(url_rel_index)
299
+ if ptrue?(url_rel_index) || ptrue?(decorative)
299
300
  @writer.tag_elements('xdr:cNvPr', attributes) do
300
- # Write the a:hlinkClick element.
301
- write_a_hlink_click(url_rel_index, tip)
301
+ if ptrue?(url_rel_index)
302
+ # Write the a:hlinkClick element.
303
+ write_a_hlink_click(url_rel_index, tip)
304
+ end
305
+ if ptrue?(decorative)
306
+ # Write the adec:decorative element.
307
+ write_decorative
308
+ end
302
309
  end
303
310
  else
304
311
  @writer.empty_tag('xdr:cNvPr', attributes)
@@ -323,6 +330,62 @@ module Writexlsx
323
330
  @writer.empty_tag('a:hlinkClick', attributes)
324
331
  end
325
332
 
333
+ #
334
+ # Write the <adec:decorative> element.
335
+ #
336
+ def write_decorative
337
+ @writer.tag_elements('a:extLst') do
338
+ write_a_uri_ext('{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}')
339
+ write_a16_creation_id
340
+ @writer.end_tag('a:ext')
341
+
342
+ write_a_uri_ext('{C183D7F6-B498-43B3-948B-1728B52AA6E4}')
343
+ write_adec_decorative
344
+ @writer.end_tag('a:ext')
345
+ end
346
+ end
347
+
348
+ #
349
+ # Write the <a:ext> element.
350
+ #
351
+ def write_a_uri_ext(uri)
352
+ attributes = [
353
+ ['uri', uri]
354
+ ]
355
+
356
+ @writer.start_tag('a:ext', attributes)
357
+ end
358
+
359
+ #
360
+ # Write the <adec:decorative> element.
361
+ #
362
+ def write_adec_decorative
363
+ xmlns_adec = 'http://schemas.microsoft.com/office/drawing/2017/decorative'
364
+ val = 1
365
+
366
+ attributes = [
367
+ ['xmlns:adec', xmlns_adec],
368
+ ['val', val]
369
+ ]
370
+
371
+ @writer.empty_tag('adec:decorative', attributes)
372
+ end
373
+
374
+ #
375
+ # Write the <a16:creationId> element.
376
+ #
377
+ def write_a16_creation_id
378
+ xmlns_a_16 = 'http://schemas.microsoft.com/office/drawing/2014/main'
379
+ id = '{00000000-0008-0000-0000-000002000000}'
380
+
381
+ attributes = [
382
+ ['xmlns:a16', xmlns_a_16],
383
+ ['id', id]
384
+ ]
385
+
386
+ @writer.empty_tag('a16:creationId', attributes)
387
+ end
388
+
326
389
  #
327
390
  # Write the <xdr:cNvGraphicFramePr> element.
328
391
  #
@@ -527,10 +590,10 @@ module Writexlsx
527
590
  #
528
591
  # Write the <xdr:pic> element.
529
592
  #
530
- def write_pic(index, rel_index, col_absolute, row_absolute, width, height, description, url_rel_index, tip)
593
+ def write_pic(index, rel_index, col_absolute, row_absolute, width, height, description, url_rel_index, tip, decorative)
531
594
  @writer.tag_elements('xdr:pic') do
532
595
  # Write the xdr:nvPicPr element.
533
- write_nv_pic_pr(index, rel_index, description, url_rel_index, tip)
596
+ write_nv_pic_pr(index, rel_index, description, url_rel_index, tip, decorative)
534
597
  # Write the xdr:blipFill element.
535
598
  write_blip_fill(rel_index)
536
599
 
@@ -546,12 +609,12 @@ module Writexlsx
546
609
  #
547
610
  # Write the <xdr:nvPicPr> element.
548
611
  #
549
- def write_nv_pic_pr(index, rel_index, description, url_rel_index, tip)
612
+ def write_nv_pic_pr(index, rel_index, description, url_rel_index, tip, decorative)
550
613
  @writer.tag_elements('xdr:nvPicPr') do
551
614
  # Write the xdr:cNvPr element.
552
615
  write_c_nv_pr(
553
616
  index + 1, "Picture #{index}", description,
554
- url_rel_index, tip
617
+ url_rel_index, tip, decorative
555
618
  )
556
619
  # Write the xdr:cNvPicPr element.
557
620
  write_c_nv_pic_pr
@@ -7,6 +7,7 @@ module Writexlsx
7
7
  class App
8
8
 
9
9
  include Writexlsx::Utility
10
+ attr_writer :doc_security
10
11
 
11
12
  def initialize(workbook)
12
13
  @writer = Package::XMLWriterSimple.new
@@ -14,6 +15,7 @@ module Writexlsx
14
15
  @part_names = []
15
16
  @heading_pairs = []
16
17
  @properties = {}
18
+ @doc_security = 0
17
19
  end
18
20
 
19
21
  def set_xml_writer(filename)
@@ -123,9 +125,7 @@ module Writexlsx
123
125
  # Write the <DocSecurity> element.
124
126
  #
125
127
  def write_doc_security
126
- data = 0
127
-
128
- @writer.data_element('DocSecurity', data)
128
+ @writer.data_element('DocSecurity', @doc_security)
129
129
  end
130
130
 
131
131
  #
@@ -397,14 +397,8 @@ module Writexlsx
397
397
  def range_start_cell_for_conditional_formatting(*args) # :nodoc:
398
398
  row1, row2, col1, col2, user_range, param =
399
399
  row_col_param_for_conditional_formatting(*args)
400
- # If the first and last cell are the same write a single cell.
401
- if row1 == row2 && col1 == col2
402
- range = xl_rowcol_to_cell(row1, col1)
403
- @start_cell = range
404
- else
405
- range = xl_range(row1, row2, col1, col2)
406
- @start_cell = xl_rowcol_to_cell(row1, col1)
407
- end
400
+ range = xl_range(row1, row2, col1, col2)
401
+ @start_cell = xl_rowcol_to_cell(row1, col1)
408
402
 
409
403
  # Override with user defined multiple range if provided.
410
404
  range = user_range if user_range
@@ -155,6 +155,7 @@ module Writexlsx
155
155
  app.add_named_ranges_parts
156
156
 
157
157
  app.set_properties(@workbook.doc_properties)
158
+ app.doc_security = @workbook.read_only
158
159
 
159
160
  FileUtils.mkdir_p("#{@package_dir}/docProps")
160
161
  app.set_xml_writer("#{@package_dir}/docProps/app.xml")
@@ -253,11 +253,22 @@ module Writexlsx
253
253
  # Write the <fill> element.
254
254
  #
255
255
  def write_fill(format, dxf_format = nil)
256
- @writer.tag_elements('fill' ) do
257
- write_fill_base(format, dxf_format)
256
+ # Special handling for pattern only case.
257
+ if pattern_only_case?(format, dxf_format)
258
+ write_default_fill(PATTERNS[format.pattern])
259
+ else
260
+ @writer.tag_elements('fill' ) do
261
+ write_fill_base(format, dxf_format)
262
+ end
258
263
  end
259
264
  end
260
265
 
266
+ def pattern_only_case?(format, dxf_format)
267
+ bg_color, fg_color = bg_and_fg_color(format, dxf_format)
268
+
269
+ !ptrue?(fg_color) && !ptrue?(bg_color) && ptrue?(format.pattern)
270
+ end
271
+
261
272
  def write_fill_base(format, dxf_format)
262
273
  # The "none" pattern is handled differently for dxf formats.
263
274
  if dxf_format && format.pattern <= 1
@@ -281,7 +292,9 @@ module Writexlsx
281
292
  if bg_color && bg_color != 0
282
293
  @writer.empty_tag('bgColor', [ ['rgb', palette_color(bg_color)] ])
283
294
  else
284
- @writer.empty_tag('bgColor', [ ['indexed', 64] ]) if !dxf_format
295
+ if !dxf_format && format.pattern <= 1
296
+ @writer.empty_tag('bgColor', [ ['indexed', 64] ])
297
+ end
285
298
  end
286
299
  end
287
300
 
@@ -60,7 +60,11 @@ module Writexlsx
60
60
  range1 = xl_rowcol_to_cell(row_1, col_1, row_abs_1, col_abs_1)
61
61
  range2 = xl_rowcol_to_cell(row_2, col_2, row_abs_2, col_abs_2)
62
62
 
63
- "#{range1}:#{range2}"
63
+ if range1 == range2
64
+ range1
65
+ else
66
+ "#{range1}:#{range2}"
67
+ end
64
68
  end
65
69
 
66
70
  def xl_range_formula(sheetname, row_1, row_2, col_1, col_2)
@@ -1 +1 @@
1
- WriteXLSX_VERSION = "1.07.0"
1
+ WriteXLSX_VERSION = "1.08.0"
@@ -52,6 +52,7 @@ module Writexlsx
52
52
  attr_reader :max_url_length # :nodoc:
53
53
  attr_reader :strings_to_urls # :nodoc:
54
54
  attr_reader :default_url_format # :nodoc:
55
+ attr_reader :read_only # :nodoc:
55
56
 
56
57
  #
57
58
  # A new Excel workbook is created using the +new+ constructor
@@ -133,6 +134,7 @@ module Writexlsx
133
134
 
134
135
  @max_url_length = 2079
135
136
  @has_comments = false
137
+ @read_only = 0
136
138
  if options[:max_url_length]
137
139
  @max_url_length = options[:max_url_length]
138
140
 
@@ -292,6 +294,9 @@ module Writexlsx
292
294
  # Write the XLSX file version.
293
295
  write_file_version
294
296
 
297
+ # Write the fileSharing element.
298
+ write_file_sharing
299
+
295
300
  # Write the workbook properties.
296
301
  write_workbook_pr
297
302
 
@@ -954,6 +959,13 @@ module Writexlsx
954
959
  end
955
960
  end
956
961
 
962
+ #
963
+ # Set the Excel "Read-only recommended" save option.
964
+ #
965
+ def read_only_recommended
966
+ @read_only = 2
967
+ end
968
+
957
969
  #
958
970
  # set_calc_mode()
959
971
  #
@@ -1311,6 +1323,17 @@ module Writexlsx
1311
1323
  @writer.empty_tag('fileVersion', attributes)
1312
1324
  end
1313
1325
 
1326
+ #
1327
+ # Write the <fileSharing> element.
1328
+ #
1329
+ def write_file_sharing
1330
+ return if !ptrue?(@read_only)
1331
+
1332
+ attributes = []
1333
+ attributes << ['readOnlyRecommended', 1]
1334
+ @writer.empty_tag('fileSharing', attributes)
1335
+ end
1336
+
1314
1337
  def write_workbook_pr #:nodoc:
1315
1338
  attributes = []
1316
1339
  attributes << ['codeName', @vba_codename] if ptrue?(@vba_codename)
@@ -138,12 +138,7 @@ module Writexlsx
138
138
  row_first, row_last = row_last, row_first if row_first > row_last
139
139
  col_first, col_last = col_last, col_first if col_first > col_last
140
140
 
141
- # If the first and last cell are the same write a single cell.
142
- if row_first == row_last && col_first == col_last
143
- sqref += xl_rowcol_to_cell(row_first, col_first)
144
- else
145
- sqref += xl_range(row_first, row_last, col_first, col_last)
146
- end
141
+ sqref += xl_range(row_first, row_last, col_first, col_last)
147
142
  end
148
143
 
149
144
  if @validate != 'none'
@@ -388,6 +388,9 @@ module Writexlsx
388
388
  @data_bars_2010 = []
389
389
  @dxf_priority = 1
390
390
 
391
+ @protected_ranges = []
392
+ @num_protected_ranges = 0
393
+
391
394
  if excel2003_style?
392
395
  @original_row_height = 12.75
393
396
  @default_row_height = 12.75
@@ -414,6 +417,7 @@ module Writexlsx
414
417
  write_cols
415
418
  write_sheet_data
416
419
  write_sheet_protection
420
+ write_protected_ranges
417
421
  # write_sheet_calc_pr
418
422
  write_phonetic_pr if excel2003_style?
419
423
  write_auto_filter
@@ -616,7 +620,25 @@ module Writexlsx
616
620
 
617
621
  # Set the password after the user defined values.
618
622
  @protect[:password] =
619
- sprintf("%X", encode_password(password)) if password && password != ''
623
+ encode_password(password) if password && password != ''
624
+ end
625
+
626
+ #
627
+ # Unprotect ranges within a protected worksheet.
628
+ #
629
+ def unprotect_range(range, range_name = nil, password = nil)
630
+ if range.nil?
631
+ raise "The range must be defined in unprotect_range())\n"
632
+ else
633
+ range.gsub!(/\$/, "")
634
+ range.sub!(/^=/, "")
635
+ @num_protected_ranges += 1
636
+ end
637
+
638
+ range_name ||= "Range#{@num_protected_ranges}"
639
+ password &&= encode_password(password)
640
+
641
+ @protected_ranges << [range, range_name, password]
620
642
  end
621
643
 
622
644
  def protect_default_settings # :nodoc:
@@ -818,12 +840,7 @@ module Writexlsx
818
840
  row_first, row_last = row_last, row_first if row_first > row_last
819
841
  col_first, col_last = col_last, col_first if col_first > col_last
820
842
 
821
- # If the first and last cell are the same write a single cell.
822
- if row_first == row_last && col_first == col_last
823
- sqref = active_cell
824
- else
825
- sqref = xl_range(row_first, row_last, col_first, col_last)
826
- end
843
+ sqref = xl_range(row_first, row_last, col_first, col_last)
827
844
  else # Single cell selection.
828
845
  sqref = active_cell
829
846
  end
@@ -3048,14 +3065,16 @@ module Writexlsx
3048
3065
 
3049
3066
  if options.first.class == Hash
3050
3067
  # Newer hash bashed options
3051
- params = options.first
3052
- x_offset = params[:x_offset]
3053
- y_offset = params[:y_offset]
3054
- x_scale = params[:x_scale]
3055
- y_scale = params[:y_scale]
3056
- anchor = params[:object_position]
3057
- url = params[:url]
3058
- tip = params[:tip]
3068
+ params = options.first
3069
+ x_offset = params[:x_offset]
3070
+ y_offset = params[:y_offset]
3071
+ x_scale = params[:x_scale]
3072
+ y_scale = params[:y_scale]
3073
+ anchor = params[:object_position]
3074
+ url = params[:url]
3075
+ tip = params[:tip]
3076
+ description = params[:description]
3077
+ decorative = params[:decorative]
3059
3078
  else
3060
3079
  x_offset, y_offset, x_scale, y_scale, anchor = options
3061
3080
  end
@@ -3067,7 +3086,7 @@ module Writexlsx
3067
3086
 
3068
3087
  @images << [
3069
3088
  row, col, image, x_offset, y_offset,
3070
- x_scale, y_scale, url, tip, anchor
3089
+ x_scale, y_scale, url, tip, anchor, description, decorative
3071
3090
  ]
3072
3091
  end
3073
3092
 
@@ -5744,7 +5763,7 @@ module Writexlsx
5744
5763
  name = chart.name
5745
5764
 
5746
5765
  # Create a Drawing object to use with worksheet unless one already exists.
5747
- drawing = Drawing.new(drawing_type, dimensions, 0, 0, name, nil, anchor, drawing_rel_index, 0, nil)
5766
+ drawing = Drawing.new(drawing_type, dimensions, 0, 0, name, nil, anchor, drawing_rel_index, 0, nil, 0)
5748
5767
  if !drawings?
5749
5768
  @drawings = Drawings.new
5750
5769
  @drawings.add_drawing_object(drawing)
@@ -6470,7 +6489,7 @@ module Writexlsx
6470
6489
  drawing_type = 2
6471
6490
 
6472
6491
  row, col, image, x_offset, y_offset,
6473
- x_scale, y_scale, url, tip, anchor = @images[index]
6492
+ x_scale, y_scale, url, tip, anchor, description, decorative = @images[index]
6474
6493
 
6475
6494
  width *= x_scale
6476
6495
  height *= y_scale
@@ -6485,7 +6504,7 @@ module Writexlsx
6485
6504
  height = (0.5 + (height * 9_525)).to_i
6486
6505
 
6487
6506
  # Create a Drawing object to use with worksheet unless one already exists.
6488
- drawing = Drawing.new(drawing_type, dimensions, width, height, name, nil, anchor, 0, 0, tip)
6507
+ drawing = Drawing.new(drawing_type, dimensions, width, height, name, nil, anchor, 0, 0, tip, decorative)
6489
6508
  if !drawings?
6490
6509
  drawings = Drawings.new
6491
6510
  drawings.embedded = 1
@@ -6498,6 +6517,10 @@ module Writexlsx
6498
6517
  end
6499
6518
  drawings.add_drawing_object(drawing)
6500
6519
 
6520
+ if description
6521
+ drawing.description = description
6522
+ end
6523
+
6501
6524
  if url
6502
6525
  rel_type = '/hyperlink'
6503
6526
  target_mode = 'External'
@@ -6651,7 +6674,7 @@ EOS
6651
6674
  shape.calc_position_emus(self)
6652
6675
 
6653
6676
  drawing_type = 3
6654
- drawing = Drawing.new(drawing_type, shape.dimensions, shape.width_emu, shape.height_emu, shape.name, shape, shape.anchor, drawing_rel_index, 0, nil)
6677
+ drawing = Drawing.new(drawing_type, shape.dimensions, shape.width_emu, shape.height_emu, shape.name, shape, shape.anchor, drawing_rel_index, 0, nil, 0)
6655
6678
  drawings.add_drawing_object(drawing)
6656
6679
  end
6657
6680
  public :prepare_shape
@@ -6741,6 +6764,8 @@ EOS
6741
6764
  chars.each { |c| encoded_password ^= c }
6742
6765
  encoded_password ^= count
6743
6766
  encoded_password ^= 0xCE4B
6767
+
6768
+ sprintf("%X", encoded_password)
6744
6769
  end
6745
6770
 
6746
6771
  #
@@ -7556,6 +7581,32 @@ EOS
7556
7581
  @writer.empty_tag('sheetProtection', attributes)
7557
7582
  end
7558
7583
 
7584
+ #
7585
+ # Write the <protectedRanges> element.
7586
+ #
7587
+ def write_protected_ranges
7588
+ return if @num_protected_ranges == 0
7589
+
7590
+ @writer.tag_elements('protectedRanges') do
7591
+ @protected_ranges.each do |protected_range|
7592
+ write_protected_range(*protected_range)
7593
+ end
7594
+ end
7595
+ end
7596
+
7597
+ #
7598
+ # Write the <protectedRange> element.
7599
+ #
7600
+ def write_protected_range(sqref, name, password)
7601
+ attributes = []
7602
+
7603
+ attributes << ['password', password] if password
7604
+ attributes << ['sqref', sqref]
7605
+ attributes << ['name', name]
7606
+
7607
+ @writer.empty_tag('protectedRange', attributes)
7608
+ end
7609
+
7559
7610
  #
7560
7611
  # Write the <drawing> elements.
7561
7612
  #
@@ -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)
@@ -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)