write_xlsx 0.64.1 → 0.65.0

Sign up to get free protection for your applications and to get access to all the features.
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