write_xlsx 0.64.1 → 0.65.0

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +10 -1
  3. data/examples/conditional_format.rb +251 -18
  4. data/examples/demo.rb +2 -3
  5. data/examples/macros.rb +42 -0
  6. data/examples/outline_collapsed.rb +160 -0
  7. data/examples/republic.png +0 -0
  8. data/examples/shape3.rb +2 -2
  9. data/examples/shape4.rb +5 -5
  10. data/examples/shape5.rb +6 -6
  11. data/examples/shape6.rb +6 -6
  12. data/examples/shape7.rb +11 -11
  13. data/examples/shape8.rb +10 -10
  14. data/examples/shape_all.rb +0 -0
  15. data/examples/vbaProject.bin +0 -0
  16. data/lib/write_xlsx/chart.rb +656 -56
  17. data/lib/write_xlsx/chartsheet.rb +26 -2
  18. data/lib/write_xlsx/format.rb +50 -27
  19. data/lib/write_xlsx/formats.rb +32 -0
  20. data/lib/write_xlsx/package/packager.rb +45 -238
  21. data/lib/write_xlsx/package/table.rb +9 -18
  22. data/lib/write_xlsx/package/xml_writer_simple.rb +26 -9
  23. data/lib/write_xlsx/sheets.rb +223 -0
  24. data/lib/write_xlsx/sparkline.rb +140 -4
  25. data/lib/write_xlsx/version.rb +1 -1
  26. data/lib/write_xlsx/workbook.rb +34 -121
  27. data/lib/write_xlsx/worksheet/data_validation.rb +291 -0
  28. data/lib/write_xlsx/worksheet/hyperlink.rb +111 -0
  29. data/lib/write_xlsx/worksheet/page_setup.rb +170 -0
  30. data/lib/write_xlsx/worksheet.rb +1112 -1334
  31. data/test/helper.rb +1 -1
  32. data/test/package/styles/test_styles_01.rb +1 -10
  33. data/test/package/styles/test_styles_02.rb +1 -10
  34. data/test/package/styles/test_styles_03.rb +1 -10
  35. data/test/package/styles/test_styles_04.rb +1 -10
  36. data/test/package/styles/test_styles_05.rb +1 -10
  37. data/test/package/styles/test_styles_06.rb +1 -10
  38. data/test/package/styles/test_styles_07.rb +1 -10
  39. data/test/package/styles/test_styles_08.rb +1 -10
  40. data/test/package/styles/test_styles_09.rb +1 -10
  41. data/test/perl_output/conditional_format.xlsx +0 -0
  42. data/test/perl_output/outline_collapsed.xlsx +0 -0
  43. data/test/perl_output/protection.xlsx +0 -0
  44. data/test/regression/test_chart_gap01.rb +47 -0
  45. data/test/regression/test_chart_gap02.rb +47 -0
  46. data/test/regression/test_chart_gap03.rb +47 -0
  47. data/test/regression/test_format05.rb +26 -0
  48. data/test/regression/test_rich_string12.rb +32 -0
  49. data/test/regression/xlsx_files/chart_gap01.xlsx +0 -0
  50. data/test/regression/xlsx_files/chart_gap02.xlsx +0 -0
  51. data/test/regression/xlsx_files/chart_gap03.xlsx +0 -0
  52. data/test/regression/xlsx_files/format05.xlsx +0 -0
  53. data/test/regression/xlsx_files/rich_string12.xlsx +0 -0
  54. data/test/test_example_match.rb +253 -20
  55. data/test/worksheet/test_set_column.rb +25 -0
  56. data/test/worksheet/test_worksheet_03.rb +1 -1
  57. data/test/worksheet/test_worksheet_04.rb +1 -1
  58. data/test/worksheet/test_write_array_formula_01.rb +7 -0
  59. data/test/worksheet/test_write_col_breaks.rb +2 -2
  60. data/test/worksheet/test_write_col_info.rb +8 -8
  61. data/test/worksheet/test_write_conditional_formatting.rb +4 -4
  62. data/test/worksheet/test_write_formula_does_not_change_formula_string.rb +18 -0
  63. data/test/worksheet/test_write_header_footer.rb +8 -3
  64. data/test/worksheet/test_write_hyperlink.rb +10 -5
  65. data/test/worksheet/test_write_merge_cells.rb +6 -6
  66. data/test/worksheet/test_write_page_set_up_pr.rb +1 -1
  67. data/test/worksheet/test_write_page_setup.rb +1 -1
  68. data/test/worksheet/test_write_row_breaks.rb +2 -2
  69. data/test/worksheet/test_write_row_element.rb +1 -1
  70. data/test/worksheet/test_write_sheet_pr.rb +2 -2
  71. data/test/worksheet/test_write_sheet_view.rb +0 -9
  72. data/test/worksheet/test_write_url.rb +19 -0
  73. data/test/worksheet/test_write_worksheet_attributes.rb +21 -0
  74. metadata +38 -5
  75. data/lib/write_xlsx/worksheet/print_style.rb +0 -51
  76. data/test/worksheet/test_write_worksheet.rb +0 -19
@@ -1,8 +1,10 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  require 'write_xlsx/package/xml_writer_simple'
3
3
  require 'write_xlsx/package/packager'
4
+ require 'write_xlsx/sheets'
4
5
  require 'write_xlsx/worksheet'
5
6
  require 'write_xlsx/chartsheet'
7
+ require 'write_xlsx/formats'
6
8
  require 'write_xlsx/format'
7
9
  require 'write_xlsx/shape'
8
10
  require 'write_xlsx/utility'
@@ -14,6 +16,8 @@ require 'digest/md5'
14
16
 
15
17
  module Writexlsx
16
18
 
19
+ OFFICE_URL = 'http://schemas.microsoft.com/office/' # :nodoc:
20
+
17
21
  # The WriteXLSX provides an object oriented interface to a new Excel workbook.
18
22
  # The following methods are available through a new workbook.
19
23
  #
@@ -34,12 +38,9 @@ module Writexlsx
34
38
 
35
39
  include Writexlsx::Utility
36
40
 
37
- BASE_NAME = { :sheet => 'Sheet', :chart => 'Chart'} # :nodoc:
38
-
39
41
  attr_writer :firstsheet # :nodoc:
40
42
  attr_reader :palette # :nodoc:
41
- attr_reader :font_count, :num_format_count, :border_count, :fill_count, :custom_colors # :nodoc:
42
- attr_reader :worksheets, :sheetnames, :charts, :drawings # :nodoc:
43
+ attr_reader :worksheets, :charts, :drawings # :nodoc:
43
44
  attr_reader :num_comment_files, :num_vml_files, :named_ranges # :nodoc:
44
45
  attr_reader :doc_properties # :nodoc:
45
46
  attr_reader :image_types, :images # :nodoc:
@@ -97,19 +98,12 @@ module Writexlsx
97
98
  @firstsheet = 0
98
99
  @selected = 0
99
100
  @fileclosed = false
100
- @sheet_name = 'Sheet'
101
- @chart_name = 'Chart'
102
- @sheetname_count = 0
103
- @chartname_count = 0
104
- @worksheets = []
101
+ @worksheets = Sheets.new
105
102
  @charts = []
106
103
  @drawings = []
107
- @sheetnames = []
108
- @formats = []
104
+ @formats = Formats.new
109
105
  @xf_formats = []
110
- @xf_format_indices = {}
111
106
  @dxf_formats = []
112
- @dxf_format_indices = {}
113
107
  @font_count = 0
114
108
  @num_format_count = 0
115
109
  @defined_names = []
@@ -265,7 +259,7 @@ module Writexlsx
265
259
  write_book_views
266
260
 
267
261
  # Write the worksheet names and ids.
268
- write_sheets
262
+ @worksheets.write_sheets(@writer)
269
263
 
270
264
  # Write the workbook defined names.
271
265
  write_defined_names
@@ -305,7 +299,6 @@ module Writexlsx
305
299
  name = check_sheetname(name)
306
300
  worksheet = Worksheet.new(self, @worksheets.size, name)
307
301
  @worksheets << worksheet
308
- @sheetnames << name
309
302
  worksheet
310
303
  end
311
304
 
@@ -403,7 +396,6 @@ module Writexlsx
403
396
  chartsheet = Chartsheet.new(self, @worksheets.size, sheetname)
404
397
  chartsheet.chart = chart
405
398
  @worksheets << chartsheet
406
- @sheetnames << sheetname
407
399
  end
408
400
  @charts << chart
409
401
  ptrue?(embedded) ? chart : chartsheet
@@ -422,15 +414,9 @@ module Writexlsx
422
414
  # Format properties and how to set them.
423
415
  #
424
416
  def add_format(properties = {})
425
- init_data = [
426
- @xf_format_indices,
427
- @dxf_format_indices,
428
- properties
429
- ]
417
+ format = Format.new(@formats, properties)
430
418
 
431
- format = Format.new(*init_data)
432
-
433
- @formats.push(format) # Store format reference
419
+ @formats.formats.push(format) # Store format reference
434
420
 
435
421
  format
436
422
  end
@@ -716,7 +702,7 @@ module Writexlsx
716
702
  if name =~ /^(.*)!(.*)$/
717
703
  sheetname = $1
718
704
  name = $2
719
- sheet_index = get_sheet_index(sheetname)
705
+ sheet_index = @worksheets.index_by_name(sheetname)
720
706
  else
721
707
  sheet_index = -1 # Use -1 to indicate global names.
722
708
  end
@@ -946,12 +932,21 @@ module Writexlsx
946
932
  @shared_strings.empty?
947
933
  end
948
934
 
949
- def xf_formats # :nodoc:
950
- @xf_formats.dup
935
+ def chartsheet_count
936
+ @worksheets.chartsheet_count
951
937
  end
952
938
 
953
- def dxf_formats # :nodoc:
954
- @dxf_formats.dup
939
+ def style_properties
940
+ [
941
+ @xf_formats,
942
+ @palette,
943
+ @font_count,
944
+ @num_format_count,
945
+ @border_count,
946
+ @fill_count,
947
+ @custom_colors,
948
+ @dxf_formats
949
+ ]
955
950
  end
956
951
 
957
952
  private
@@ -1037,52 +1032,11 @@ module Writexlsx
1037
1032
  # invalid characters and if the name is unique in the workbook.
1038
1033
  #
1039
1034
  def check_sheetname(name) #:nodoc:
1040
- make_and_check_sheet_chart_name(:sheet, name)
1035
+ @worksheets.make_and_check_sheet_chart_name(:sheet, name)
1041
1036
  end
1042
1037
 
1043
1038
  def check_chart_sheetname(name)
1044
- make_and_check_sheet_chart_name(:chart, name)
1045
- end
1046
-
1047
- def make_and_check_sheet_chart_name(type, name)
1048
- count = sheet_chart_count_increment(type)
1049
- name = "#{BASE_NAME[type]}#{count}" unless ptrue?(name)
1050
-
1051
- check_valid_sheetname(name)
1052
- name
1053
- end
1054
-
1055
- def sheet_chart_count_increment(type)
1056
- case type
1057
- when :sheet
1058
- @sheetname_count += 1
1059
- when :chart
1060
- @chartname_count += 1
1061
- end
1062
- end
1063
-
1064
- def check_valid_sheetname(name)
1065
- # Check that sheet name is <= 31. Excel limit.
1066
- raise "Sheetname #{name} must be <= #{SHEETNAME_MAX} chars" if name.length > SHEETNAME_MAX
1067
-
1068
- # Check that sheetname doesn't contain any invalid characters
1069
- invalid_char = /[\[\]:*?\/\\]/
1070
- if name =~ invalid_char
1071
- raise 'Invalid character []:*?/\\ in worksheet name: ' + name
1072
- end
1073
-
1074
- # Check that the worksheet name doesn't already exist since this is a fatal
1075
- # error in Excel 97. The check must also exclude case insensitive matches.
1076
- unless is_sheetname_uniq?(name)
1077
- raise "Worksheet name '#{name}', with case ignored, is already used."
1078
- end
1079
- end
1080
-
1081
- def is_sheetname_uniq?(name)
1082
- @worksheets.each do |worksheet|
1083
- return false if name.downcase == worksheet.name.downcase
1084
- end
1085
- true
1039
+ @worksheets.make_and_check_sheet_chart_name(:chart, name)
1086
1040
  end
1087
1041
 
1088
1042
  #
@@ -1184,29 +1138,6 @@ module Writexlsx
1184
1138
  @writer.empty_tag('workbookView', attributes)
1185
1139
  end
1186
1140
 
1187
- def write_sheets #:nodoc:
1188
- @writer.tag_elements('sheets') do
1189
- id_num = 1
1190
- @worksheets.each do |sheet|
1191
- write_sheet(sheet.name, id_num, sheet.hidden?)
1192
- id_num += 1
1193
- end
1194
- end
1195
- end
1196
-
1197
- def write_sheet(name, sheet_id, hidden = false) #:nodoc:
1198
- attributes = [
1199
- 'name', name,
1200
- 'sheetId', sheet_id
1201
- ]
1202
-
1203
- if hidden
1204
- attributes << 'state' << 'hidden'
1205
- end
1206
- attributes << 'r:id' << "rId#{sheet_id}"
1207
- @writer.empty_tag_encoded('sheet', attributes)
1208
- end
1209
-
1210
1141
  def write_calc_pr #:nodoc:
1211
1142
  attributes = ['calcId', 124519]
1212
1143
  @writer.empty_tag('calcPr', attributes)
@@ -1219,9 +1150,10 @@ module Writexlsx
1219
1150
 
1220
1151
  def write_ext #:nodoc:
1221
1152
  tag = 'ext'
1153
+ uri = "#{OFFICE_URL}mac/excel/2008/main"
1222
1154
  attributes = [
1223
- 'xmlns:mx', 'http://schemas.microsoft.com/office/mac/excel/2008/main',
1224
- 'uri', 'http://schemas.microsoft.com/office/mac/excel/2008/main'
1155
+ 'xmlns:mx', uri,
1156
+ 'uri', uri
1225
1157
  ]
1226
1158
  @writer.tag_elements(tag, attributes) { write_mx_arch_id }
1227
1159
  end
@@ -1289,8 +1221,7 @@ module Writexlsx
1289
1221
  add_chart_data
1290
1222
 
1291
1223
  # Package the workbook.
1292
- packager = Package::Packager.new
1293
- packager.add_workbook(self)
1224
+ packager = Package::Packager.new(self)
1294
1225
  packager.set_package_dir(@tempdir)
1295
1226
  packager.create_package
1296
1227
 
@@ -1328,7 +1259,7 @@ module Writexlsx
1328
1259
  # formats.
1329
1260
  #
1330
1261
  def prepare_formats #:nodoc:
1331
- @formats.each do |format|
1262
+ @formats.formats.each do |format|
1332
1263
  xf_index = format.xf_index
1333
1264
  dxf_index = format.dxf_index
1334
1265
 
@@ -1562,8 +1493,7 @@ module Writexlsx
1562
1493
  # Add a font format for cell comments.
1563
1494
  if comment_files > 0
1564
1495
  format = Format.new(
1565
- @xf_format_indices,
1566
- @dxf_format_indices,
1496
+ @formats,
1567
1497
  :font => 'Tahoma',
1568
1498
  :size => 8,
1569
1499
  :color_indexed => 81,
@@ -1572,7 +1502,7 @@ module Writexlsx
1572
1502
 
1573
1503
  format.get_xf_index
1574
1504
 
1575
- @formats << format
1505
+ @formats.formats << format
1576
1506
  end
1577
1507
  end
1578
1508
 
@@ -1728,7 +1658,7 @@ module Writexlsx
1728
1658
 
1729
1659
  drawing_id += 1
1730
1660
 
1731
- (0 .. chart_count - 1).each do |index|
1661
+ sheet.charts.each_with_index do |chart, index|
1732
1662
  chart_ref_id += 1
1733
1663
  sheet.prepare_chart(index, chart_ref_id, drawing_id)
1734
1664
  end
@@ -1758,23 +1688,6 @@ module Writexlsx
1758
1688
  @drawing_count = drawing_id
1759
1689
  end
1760
1690
 
1761
- #
1762
- # Convert a sheet name to its index. Return undef otherwise.
1763
- #
1764
- def get_sheet_index(sheetname) #:nodoc:
1765
- sheet_count = @sheetnames.size
1766
- sheet_index = nil
1767
-
1768
- sheetname.sub!(/^'/, '')
1769
- sheetname.sub!(/'$/, '')
1770
-
1771
- ( 0 .. sheet_count - 1 ).each do |i|
1772
- sheet_index = i if sheetname == @sheetnames[i]
1773
- end
1774
-
1775
- sheet_index
1776
- end
1777
-
1778
1691
  #
1779
1692
  # Extract information from the image file such as dimension, type, filename,
1780
1693
  # and extension. Also keep track of previously seen images to optimise out
@@ -0,0 +1,291 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Writexlsx
4
+ class Worksheet
5
+ class DataValidation # :nodoc:
6
+ include Writexlsx::Utility
7
+
8
+ attr_reader :value, :source, :minimum, :maximum, :validate, :criteria
9
+ attr_reader :error_type, :cells, :other_cells
10
+ attr_reader :ignore_blank, :dropdown, :show_input, :show_error
11
+ attr_reader :error_title, :error_message, :input_title, :input_message
12
+
13
+ def initialize(*args)
14
+ # Check for a cell reference in A1 notation and substitute row and column.
15
+ row1, col1, row2, col2, options = row_col_notation(args)
16
+ if row2.respond_to?(:keys)
17
+ options_to_instance_variable(row2.dup)
18
+ row2, col2 = row1, col1
19
+ elsif options.respond_to?(:keys)
20
+ options_to_instance_variable(options.dup)
21
+ else
22
+ raise WriteXLSXInsufficientArgumentError
23
+ end
24
+ raise WriteXLSXInsufficientArgumentError if [row1, col1, row2, col2].include?(nil)
25
+ check_for_valid_input_params
26
+
27
+ check_dimensions(row1, col1)
28
+ check_dimensions(row2, col2)
29
+ @cells = [[row1, col1, row2, col2]]
30
+
31
+ @value = @source if @source
32
+ @value = @minimum if @minimum
33
+
34
+ @validate = valid_validation_type[@validate.downcase]
35
+ if @validate == 'none'
36
+ @validate_none = true
37
+ return
38
+ end
39
+ if ['list', 'custom'].include?(@validate)
40
+ @criteria = 'between'
41
+ @maximum = nil
42
+ end
43
+
44
+ check_criteria_required
45
+ check_valid_citeria_types
46
+ @criteria = valid_criteria_type[@criteria.downcase]
47
+
48
+ check_maximum_value_when_criteria_is_between_or_notbetween
49
+ @error_type = has_key?(:error_type) ? error_type_hash[@error_type.downcase] : 0
50
+
51
+ convert_date_time_value_if_required
52
+ set_some_defaults
53
+
54
+ # A (for now) undocumented parameter to pass additional cell ranges.
55
+ @other_cells.each { |cells| @cells << cells } if has_key?(:other_cells)
56
+ end
57
+
58
+ def options_to_instance_variable(params)
59
+ params.each do |k, v|
60
+ instance_variable_set("@#{k}", v)
61
+ end
62
+ end
63
+
64
+ def keys
65
+ self.instance_variables.collect { |v| v.to_s.sub(/@/, '').to_sym }
66
+ end
67
+
68
+ def validate_none?
69
+ @validate_none
70
+ end
71
+
72
+ #
73
+ # Write the <dataValidation> element.
74
+ #
75
+ def write_data_validation(writer) #:nodoc:
76
+ @writer = writer
77
+ @writer.tag_elements('dataValidation', attributes) do
78
+ # Write the formula1 element.
79
+ write_formula_1(@value)
80
+ # Write the formula2 element.
81
+ write_formula_2(@maximum) if @maximum
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ #
88
+ # Write the <formula1> element.
89
+ #
90
+ def write_formula_1(formula) #:nodoc:
91
+ # Convert a list array ref into a comma separated string.
92
+ formula = %!"#{formula.join(',')}"! if formula.kind_of?(Array)
93
+
94
+ formula = formula.sub(/^=/, '') if formula.respond_to?(:sub)
95
+
96
+ @writer.data_element('formula1', formula)
97
+ end
98
+
99
+ #
100
+ # Write the <formula2> element.
101
+ #
102
+ def write_formula_2(formula) #:nodoc:
103
+ formula = formula.sub(/^=/, '') if formula.respond_to?(:sub)
104
+
105
+ @writer.data_element('formula2', formula)
106
+ end
107
+
108
+ def attributes
109
+ sqref = ''
110
+ attributes = []
111
+
112
+ # Set the cell range(s) for the data validation.
113
+ @cells.each do |cells|
114
+ # Add a space between multiple cell ranges.
115
+ sqref += ' ' if sqref != ''
116
+
117
+ row_first, col_first, row_last, col_last = cells
118
+
119
+ # Swap last row/col for first row/col as necessary
120
+ row_first, row_last = row_last, row_first if row_first > row_last
121
+ col_first, col_last = col_last, col_first if col_first > col_last
122
+
123
+ # If the first and last cell are the same write a single cell.
124
+ if row_first == row_last && col_first == col_last
125
+ sqref += xl_rowcol_to_cell(row_first, col_first)
126
+ else
127
+ sqref += xl_range(row_first, row_last, col_first, col_last)
128
+ end
129
+ end
130
+
131
+ attributes << 'type' << @validate
132
+ attributes << 'operator' << @criteria if @criteria != 'between'
133
+
134
+ if @error_type
135
+ attributes << 'errorStyle' << 'warning' if @error_type == 1
136
+ attributes << 'errorStyle' << 'information' if @error_type == 2
137
+ end
138
+ attributes << 'allowBlank' << 1 if @ignore_blank != 0
139
+ attributes << 'showDropDown' << 1 if @dropdown == 0
140
+ attributes << 'showInputMessage' << 1 if @show_input != 0
141
+ attributes << 'showErrorMessage' << 1 if @show_error != 0
142
+
143
+ attributes << 'errorTitle' << @error_title if @error_title
144
+ attributes << 'error' << @error_message if @error_message
145
+ attributes << 'promptTitle' << @input_title if @input_title
146
+ attributes << 'prompt' << @input_message if @input_message
147
+ attributes << 'sqref' << sqref
148
+ end
149
+
150
+ def has_key?(key)
151
+ keys.index(key)
152
+ end
153
+
154
+ def set_some_defaults
155
+ @ignore_blank ||= 1
156
+ @dropdown ||= 1
157
+ @show_input ||= 1
158
+ @show_error ||= 1
159
+ end
160
+
161
+ def check_for_valid_input_params
162
+ check_parameter(self, valid_validation_parameter, 'data_validation')
163
+
164
+ unless has_key?(:validate)
165
+ raise WriteXLSXOptionParameterError, "Parameter :validate is required in data_validation()"
166
+ end
167
+ unless valid_validation_type.has_key?(@validate.downcase)
168
+ raise WriteXLSXOptionParameterError,
169
+ "Unknown validation type '#{@validate}' for parameter :validate in data_validation()"
170
+ end
171
+ if @error_type && !error_type_hash.has_key?(@error_type.downcase)
172
+ raise WriteXLSXOptionParameterError,
173
+ "Unknown criteria type '#param[:error_type}' for parameter :error_type in data_validation()"
174
+ end
175
+ end
176
+
177
+ def check_criteria_required
178
+ unless has_key?(:criteria)
179
+ raise WriteXLSXOptionParameterError, "Parameter :criteria is required in data_validation()"
180
+ end
181
+ end
182
+
183
+ def check_maximum_value_when_criteria_is_between_or_notbetween
184
+ if @criteria == 'between' || @criteria == 'notBetween'
185
+ unless has_key?(:maximum)
186
+ raise WriteXLSXOptionParameterError,
187
+ "Parameter :maximum is required in data_validation() when using :between or :not between criteria"
188
+ end
189
+ else
190
+ @maximum = nil
191
+ end
192
+ end
193
+
194
+ def check_valid_citeria_types
195
+ unless valid_criteria_type.has_key?(@criteria.downcase)
196
+ raise WriteXLSXOptionParameterError,
197
+ "Unknown criteria type '#{@criteria}' for parameter :criteria in data_validation()"
198
+ end
199
+ end
200
+
201
+ def convert_date_time_value_if_required
202
+ @date_1904 = date_1904?
203
+ if @validate == 'date' || @validate == 'time'
204
+ unless convert_date_time_value(:value) && convert_date_time_value(:maximum)
205
+ raise WriteXLSXOptionParameterError, "Invalid date/time value."
206
+ end
207
+ end
208
+ end
209
+
210
+ def error_type_hash
211
+ {'stop' => 0, 'warning' => 1, 'information' => 2}
212
+ end
213
+
214
+ def valid_validation_type # :nodoc:
215
+ {
216
+ 'any' => 'none',
217
+ 'any value' => 'none',
218
+ 'whole number' => 'whole',
219
+ 'whole' => 'whole',
220
+ 'integer' => 'whole',
221
+ 'decimal' => 'decimal',
222
+ 'list' => 'list',
223
+ 'date' => 'date',
224
+ 'time' => 'time',
225
+ 'text length' => 'textLength',
226
+ 'length' => 'textLength',
227
+ 'custom' => 'custom'
228
+ }
229
+ end
230
+
231
+ # List of valid input parameters.
232
+ def valid_validation_parameter
233
+ [
234
+ :validate,
235
+ :criteria,
236
+ :value,
237
+ :source,
238
+ :minimum,
239
+ :maximum,
240
+ :ignore_blank,
241
+ :dropdown,
242
+ :show_input,
243
+ :input_title,
244
+ :input_message,
245
+ :show_error,
246
+ :error_title,
247
+ :error_message,
248
+ :error_type,
249
+ :other_cells
250
+ ]
251
+ end
252
+
253
+ # List of valid criteria types.
254
+ def valid_criteria_type # :nodoc:
255
+ {
256
+ 'between' => 'between',
257
+ 'not between' => 'notBetween',
258
+ 'equal to' => 'equal',
259
+ '=' => 'equal',
260
+ '==' => 'equal',
261
+ 'not equal to' => 'notEqual',
262
+ '!=' => 'notEqual',
263
+ '<>' => 'notEqual',
264
+ 'greater than' => 'greaterThan',
265
+ '>' => 'greaterThan',
266
+ 'less than' => 'lessThan',
267
+ '<' => 'lessThan',
268
+ 'greater than or equal to' => 'greaterThanOrEqual',
269
+ '>=' => 'greaterThanOrEqual',
270
+ 'less than or equal to' => 'lessThanOrEqual',
271
+ '<=' => 'lessThanOrEqual'
272
+ }
273
+ end
274
+
275
+ def convert_date_time_value(key) # :nodoc:
276
+ value = instance_variable_get("@#{key}")
277
+ if value && value =~ /T/
278
+ date_time = convert_date_time(value)
279
+ instance_variable_set("@#{key}", date_time) if date_time
280
+ date_time
281
+ else
282
+ true
283
+ end
284
+ end
285
+
286
+ def date_1904?
287
+ @date_1904
288
+ end
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,111 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Writexlsx
4
+ class Worksheet
5
+ class Hyperlink # :nodoc:
6
+ include Writexlsx::Utility
7
+
8
+ attr_reader :url, :link_type, :str, :url_str
9
+ attr_accessor :tip, :display
10
+
11
+ def initialize(url, str = nil)
12
+ link_type = 1
13
+
14
+ # Remove the URI scheme from internal links.
15
+ if url =~ /^internal:/
16
+ url = url.sub(/^internal:/, '')
17
+ link_type = 2
18
+ # Remove the URI scheme from external links.
19
+ elsif url =~ /^external:/
20
+ url = url.sub(/^external:/, '')
21
+ link_type = 3
22
+ end
23
+
24
+ # The displayed string defaults to the url string.
25
+ str ||= url.dup
26
+
27
+ # For external links change the directory separator from Unix to Dos.
28
+ if link_type == 3
29
+ url = url.gsub(%r|/|, '\\')
30
+ str.gsub!(%r|/|, '\\')
31
+ end
32
+
33
+ # Strip the mailto header.
34
+ str.sub!(/^mailto:/, '')
35
+
36
+ # Copy string for use in hyperlink elements.
37
+ url_str = str.dup
38
+
39
+ # External links to URLs and to other Excel workbooks have slightly
40
+ # different characteristics that we have to account for.
41
+ if link_type == 1
42
+ # Escape URL unless it looks already escaped.
43
+ unless url =~ /%[0-9a-fA-F]{2}/
44
+ # Escape the URL escape symbol.
45
+ url = url.gsub(/%/, "%25")
46
+
47
+ # Escape whitespae in URL.
48
+ url = url.gsub(/[\s\x00]/, '%20')
49
+
50
+ # Escape other special characters in URL.
51
+ re = /(["<>\[\]`^{}])/
52
+ while re =~ url
53
+ match = $~[1]
54
+ url = url.sub(re, sprintf("%%%x", match.ord))
55
+ end
56
+ end
57
+
58
+ # Ordinary URL style external links don't have a "location" string.
59
+ url_str = nil
60
+ elsif link_type == 3
61
+ # External Workbook links need to be modified into the right format.
62
+ # The URL will look something like 'c:\temp\file.xlsx#Sheet!A1'.
63
+ # We need the part to the left of the # as the URL and the part to
64
+ # the right as the "location" string (if it exists).
65
+ url, url_str = url.split(/#/)
66
+
67
+ # Add the file:/// URI to the url if non-local.
68
+ if url =~ %r![:]! || # Windows style "C:/" link.
69
+ url =~ %r!^\\\\! # Network share.
70
+ url = "file:///#{url}"
71
+ end
72
+
73
+ # Convert a ./dir/file.xlsx link to dir/file.xlsx.
74
+ url = url.sub(%r!^.\\!, '')
75
+
76
+ # Treat as a default external link now that the data has been modified.
77
+ link_type = 1
78
+ end
79
+
80
+ # Excel limits escaped URL to 255 characters.
81
+ if url.bytesize > 255
82
+ raise "URL '#{url}' > 255 characters, it exceeds Excel's limit for URLS."
83
+ end
84
+ @url = url
85
+ @link_type = link_type
86
+ @str = str
87
+ @url_str = url_str
88
+ end
89
+
90
+ def write_external_attributes(row, col, id)
91
+ ref = xl_rowcol_to_cell(row, col)
92
+
93
+ attributes = ['ref', ref, 'r:id', "rId#{id}"]
94
+
95
+ attributes << 'location' << url_str if url_str
96
+ attributes << 'display' << display if display
97
+ attributes << 'tooltip' << tip if tip
98
+ attributes
99
+ end
100
+
101
+ def write_internal_attributes(row, col)
102
+ ref = xl_rowcol_to_cell(row, col)
103
+
104
+ attributes = ['ref', ref, 'location', url]
105
+
106
+ attributes << 'tooltip' << tip if tip
107
+ attributes << 'display' << str
108
+ end
109
+ end
110
+ end
111
+ end