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.
- checksums.yaml +4 -4
- data/README.rdoc +10 -1
- data/examples/conditional_format.rb +251 -18
- data/examples/demo.rb +2 -3
- data/examples/macros.rb +42 -0
- data/examples/outline_collapsed.rb +160 -0
- data/examples/republic.png +0 -0
- data/examples/shape3.rb +2 -2
- data/examples/shape4.rb +5 -5
- data/examples/shape5.rb +6 -6
- data/examples/shape6.rb +6 -6
- data/examples/shape7.rb +11 -11
- data/examples/shape8.rb +10 -10
- data/examples/shape_all.rb +0 -0
- data/examples/vbaProject.bin +0 -0
- data/lib/write_xlsx/chart.rb +656 -56
- data/lib/write_xlsx/chartsheet.rb +26 -2
- data/lib/write_xlsx/format.rb +50 -27
- data/lib/write_xlsx/formats.rb +32 -0
- data/lib/write_xlsx/package/packager.rb +45 -238
- data/lib/write_xlsx/package/table.rb +9 -18
- data/lib/write_xlsx/package/xml_writer_simple.rb +26 -9
- data/lib/write_xlsx/sheets.rb +223 -0
- data/lib/write_xlsx/sparkline.rb +140 -4
- data/lib/write_xlsx/version.rb +1 -1
- data/lib/write_xlsx/workbook.rb +34 -121
- data/lib/write_xlsx/worksheet/data_validation.rb +291 -0
- data/lib/write_xlsx/worksheet/hyperlink.rb +111 -0
- data/lib/write_xlsx/worksheet/page_setup.rb +170 -0
- data/lib/write_xlsx/worksheet.rb +1112 -1334
- data/test/helper.rb +1 -1
- data/test/package/styles/test_styles_01.rb +1 -10
- data/test/package/styles/test_styles_02.rb +1 -10
- data/test/package/styles/test_styles_03.rb +1 -10
- data/test/package/styles/test_styles_04.rb +1 -10
- data/test/package/styles/test_styles_05.rb +1 -10
- data/test/package/styles/test_styles_06.rb +1 -10
- data/test/package/styles/test_styles_07.rb +1 -10
- data/test/package/styles/test_styles_08.rb +1 -10
- data/test/package/styles/test_styles_09.rb +1 -10
- data/test/perl_output/conditional_format.xlsx +0 -0
- data/test/perl_output/outline_collapsed.xlsx +0 -0
- data/test/perl_output/protection.xlsx +0 -0
- data/test/regression/test_chart_gap01.rb +47 -0
- data/test/regression/test_chart_gap02.rb +47 -0
- data/test/regression/test_chart_gap03.rb +47 -0
- data/test/regression/test_format05.rb +26 -0
- data/test/regression/test_rich_string12.rb +32 -0
- data/test/regression/xlsx_files/chart_gap01.xlsx +0 -0
- data/test/regression/xlsx_files/chart_gap02.xlsx +0 -0
- data/test/regression/xlsx_files/chart_gap03.xlsx +0 -0
- data/test/regression/xlsx_files/format05.xlsx +0 -0
- data/test/regression/xlsx_files/rich_string12.xlsx +0 -0
- data/test/test_example_match.rb +253 -20
- data/test/worksheet/test_set_column.rb +25 -0
- data/test/worksheet/test_worksheet_03.rb +1 -1
- data/test/worksheet/test_worksheet_04.rb +1 -1
- data/test/worksheet/test_write_array_formula_01.rb +7 -0
- data/test/worksheet/test_write_col_breaks.rb +2 -2
- data/test/worksheet/test_write_col_info.rb +8 -8
- data/test/worksheet/test_write_conditional_formatting.rb +4 -4
- data/test/worksheet/test_write_formula_does_not_change_formula_string.rb +18 -0
- data/test/worksheet/test_write_header_footer.rb +8 -3
- data/test/worksheet/test_write_hyperlink.rb +10 -5
- data/test/worksheet/test_write_merge_cells.rb +6 -6
- data/test/worksheet/test_write_page_set_up_pr.rb +1 -1
- data/test/worksheet/test_write_page_setup.rb +1 -1
- data/test/worksheet/test_write_row_breaks.rb +2 -2
- data/test/worksheet/test_write_row_element.rb +1 -1
- data/test/worksheet/test_write_sheet_pr.rb +2 -2
- data/test/worksheet/test_write_sheet_view.rb +0 -9
- data/test/worksheet/test_write_url.rb +19 -0
- data/test/worksheet/test_write_worksheet_attributes.rb +21 -0
- metadata +38 -5
- data/lib/write_xlsx/worksheet/print_style.rb +0 -51
- data/test/worksheet/test_write_worksheet.rb +0 -19
data/lib/write_xlsx/workbook.rb
CHANGED
@@ -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 :
|
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
|
-
@
|
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
|
-
@
|
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
|
-
|
426
|
-
@xf_format_indices,
|
427
|
-
@dxf_format_indices,
|
428
|
-
properties
|
429
|
-
]
|
417
|
+
format = Format.new(@formats, properties)
|
430
418
|
|
431
|
-
format
|
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 =
|
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
|
950
|
-
@
|
935
|
+
def chartsheet_count
|
936
|
+
@worksheets.chartsheet_count
|
951
937
|
end
|
952
938
|
|
953
|
-
def
|
954
|
-
|
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',
|
1224
|
-
'uri',
|
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
|
-
@
|
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
|
-
|
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
|