axlsx 1.1.5 → 1.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/README.md +26 -7
  2. data/Rakefile +2 -1
  3. data/examples/chart_colors.xlsx +0 -0
  4. data/examples/data_validation.rb +50 -0
  5. data/examples/example.xlsx +0 -0
  6. data/examples/example_streamed.xlsx +0 -0
  7. data/examples/examples_saved.xlsx +0 -0
  8. data/examples/fish.xlsx +0 -0
  9. data/examples/no-use_autowidth.xlsx +0 -0
  10. data/examples/pareto.rb +28 -0
  11. data/examples/pareto.xlsx +0 -0
  12. data/examples/pie_chart.rb +16 -0
  13. data/examples/pie_chart.xlsx +0 -0
  14. data/examples/pie_chart_saved.xlsx +0 -0
  15. data/examples/shared_strings_example.xlsx +0 -0
  16. data/examples/sheet_protection.rb +10 -0
  17. data/examples/sheet_protection.xlsx +0 -0
  18. data/examples/two_cell_anchor_image.rb +11 -0
  19. data/examples/two_cell_anchor_image.xlsx +0 -0
  20. data/examples/~$pie_chart_saved.xlsx +0 -0
  21. data/lib/axlsx.rb +7 -0
  22. data/lib/axlsx/content_type/default.rb +15 -9
  23. data/lib/axlsx/content_type/override.rb +10 -6
  24. data/lib/axlsx/doc_props/app.rb +152 -99
  25. data/lib/axlsx/drawing/axis.rb +30 -23
  26. data/lib/axlsx/drawing/bar_series.rb +1 -1
  27. data/lib/axlsx/drawing/drawing.rb +7 -2
  28. data/lib/axlsx/drawing/pic.rb +44 -4
  29. data/lib/axlsx/drawing/two_cell_anchor.rb +6 -1
  30. data/lib/axlsx/drawing/vml_shape.rb +2 -5
  31. data/lib/axlsx/rels/relationships.rb +1 -1
  32. data/lib/axlsx/stylesheet/table_style.rb +3 -3
  33. data/lib/axlsx/util/simple_typed_list.rb +0 -13
  34. data/lib/axlsx/util/validators.rb +21 -0
  35. data/lib/axlsx/version.rb +1 -1
  36. data/lib/axlsx/workbook/workbook.rb +2 -0
  37. data/lib/axlsx/workbook/worksheet/cell.rb +3 -4
  38. data/lib/axlsx/workbook/worksheet/comment.rb +3 -9
  39. data/lib/axlsx/workbook/worksheet/data_validation.rb +245 -0
  40. data/lib/axlsx/workbook/worksheet/page_setup.rb +17 -3
  41. data/lib/axlsx/workbook/worksheet/row.rb +34 -18
  42. data/lib/axlsx/workbook/worksheet/sheet_protection.rb +224 -0
  43. data/lib/axlsx/workbook/worksheet/table.rb +2 -2
  44. data/lib/axlsx/workbook/worksheet/worksheet.rb +57 -22
  45. data/test/doc_props/tc_app.rb +31 -1
  46. data/test/drawing/tc_axis.rb +12 -2
  47. data/test/drawing/tc_chart.rb +21 -3
  48. data/test/drawing/tc_drawing.rb +6 -1
  49. data/test/drawing/tc_hyperlink.rb +0 -5
  50. data/test/drawing/tc_pic.rb +22 -2
  51. data/test/drawing/tc_scatter_chart.rb +6 -1
  52. data/test/drawing/tc_two_cell_anchor.rb +1 -2
  53. data/test/stylesheet/tc_styles.rb +3 -4
  54. data/test/stylesheet/tc_table_style.rb +8 -0
  55. data/test/stylesheet/tc_table_style_element.rb +10 -1
  56. data/test/tc_package.rb +43 -15
  57. data/test/util/tc_simple_typed_list.rb +13 -0
  58. data/test/util/tc_validators.rb +7 -7
  59. data/test/workbook/worksheet/table/tc_table.rb +3 -3
  60. data/test/workbook/worksheet/tc_cell.rb +15 -6
  61. data/test/workbook/worksheet/tc_col.rb +9 -0
  62. data/test/workbook/worksheet/tc_comment.rb +8 -7
  63. data/test/workbook/worksheet/tc_comments.rb +8 -1
  64. data/test/workbook/worksheet/tc_conditional_formatting.rb +44 -0
  65. data/test/workbook/worksheet/tc_data_bar.rb +1 -1
  66. data/test/workbook/worksheet/tc_data_validation.rb +265 -0
  67. data/test/workbook/worksheet/tc_page_setup.rb +22 -4
  68. data/test/workbook/worksheet/tc_row.rb +14 -2
  69. data/test/workbook/worksheet/tc_sheet_protection.rb +117 -0
  70. data/test/workbook/worksheet/tc_worksheet.rb +29 -4
  71. metadata +31 -10
@@ -22,10 +22,14 @@ module Axlsx
22
22
  # * verticalDpi
23
23
 
24
24
  # Number of vertical pages to fit on.
25
- # @return [Integer]
25
+ # @note PageSetup#fit_to is the recomended way to manage page fitting as only specifying one of fit_to_width/fit_to_height will result in the counterpart
26
+ # being set to 1.
27
+ # @return [Integer]
26
28
  attr_reader :fit_to_height
27
29
 
28
30
  # Number of horizontal pages to fit on.
31
+ # @note PageSetup#fit_to is the recomended way to manage page fitting as only specifying one of width/height will result in the counterpart
32
+ # being set to 1.
29
33
  # @return [Integer]
30
34
  attr_reader :fit_to_width
31
35
 
@@ -45,7 +49,6 @@ module Axlsx
45
49
  # @return [Integer]
46
50
  attr_reader :scale
47
51
 
48
-
49
52
  # Creates a new PageSetup object
50
53
  # @option options [Integer] fit_to_height Number of vertical pages to fit on
51
54
  # @option options [Integer] fit_to_width Number of horizontal pages to fit on
@@ -77,13 +80,24 @@ module Axlsx
77
80
  def paper_width=(v); Axlsx::validate_number_with_unit(v); @paper_width = v; end
78
81
  # @see scale
79
82
  def scale=(v); Axlsx::validate_page_scale(v); @scale = v; end
83
+
84
+ # convenience method to achieve sanity when setting fit_to_width and fit_to_height
85
+ # as they both default to 1 if only their counterpart is specified.
86
+ # @note This method will overwrite any value you explicitly set via the fit_to_height or fit_to_width methods.
87
+ # @option options [Integer] width The number of pages to fit this worksheet on horizontally. Default 9999
88
+ # @option options [Integer] height The number of pages to fit this worksheet on vertically. Default 9999
89
+ def fit_to(options={})
90
+ self.fit_to_width = options[:width] || 9999
91
+ self.fit_to_height = options[:height] || 9999
92
+ [@fit_to_width, @fit_to_height]
93
+ end
80
94
 
81
95
  # Serializes the page settings element.
82
96
  # @param [String] str
83
97
  # @return [String]
84
98
  def to_xml_string(str = '')
85
99
  str << '<pageSetup '
86
- str << instance_values.map{ |k,v| k.gsub(/_(.)/){ $1.upcase } << %{="#{v}"} }.join(' ')
100
+ str << instance_values.reject{ |k, v| k == 'worksheet' }.map{ |k,v| k.gsub(/_(.)/){ $1.upcase } << %{="#{v}"} }.join(' ')
87
101
  str << '/>'
88
102
  end
89
103
  end
@@ -5,9 +5,16 @@ module Axlsx
5
5
  # @see Worksheet#add_row
6
6
  class Row
7
7
 
8
- # A list of serilizable attributes.
9
- SERIALIZABLE_ATTRIBUTES = [:hidden, :outlineLevel, :collapsed, :style]
8
+ # No support is provided for the following attributes
9
+ # spans
10
+ # thickTop
11
+ # thickBottom
10
12
 
13
+
14
+ # A list of serilizable attributes.
15
+ # @note height(ht) and customHeight are manages separately for now. Have a look at Row#height
16
+ SERIALIZABLE_ATTRIBUTES = [:hidden, :outlineLevel, :collapsed, :s, :customFormat, :ph]
17
+
11
18
  # The worksheet this row belongs to
12
19
  # @return [Worksheet]
13
20
  attr_reader :worksheet
@@ -16,36 +23,40 @@ module Axlsx
16
23
  # @return [SimpleTypedList]
17
24
  attr_reader :cells
18
25
 
19
- # The height of this row in points, if set explicitly.
26
+ # Row height measured in point size. There is no margin padding on row height.
20
27
  # @return [Float]
21
28
  attr_reader :height
22
29
 
23
- # Flag indicating if the outlining of the affected column(s) is in the collapsed state.
30
+ # Flag indicating if the outlining of row.
24
31
  # @return [Boolean]
25
32
  attr_reader :collapsed
26
33
 
27
- # Flag indicating if the affected column(s) are hidden on this worksheet.
34
+ # Flag indicating if the the row is hidden.
28
35
  # @return [Boolean]
29
36
  attr_reader :hidden
30
37
 
31
- # Outline level of affected column(s). Range is 0 to 7.
38
+ # Outlining level of the row, when outlining is on
32
39
  # @return [Integer]
33
40
  attr_reader :outlineLevel
34
41
 
35
- # Default style for the affected column(s). Affects cells not yet allocated in the column(s). In other words, this style applies to new columns.
42
+ # The style applied ot the row. This affects the entire row.
36
43
  # @return [Integer]
37
- attr_reader :style
38
-
39
- # TODO 18.3.1.73
40
- # customFormat
41
- # # hidden
42
- # ph
43
- # # s (style)
44
- # spans
45
- # thickTop
46
- # thickBottom
44
+ attr_reader :s
47
45
 
46
+ # indicates that a style has been applied directly to the row via Row#s
47
+ # @return [Boolean]
48
+ attr_reader :customFormat
48
49
 
50
+ # indicates if the row should show phonetic
51
+ # @return [Boolean]
52
+ attr_reader :ph
53
+
54
+ # NOTE removing this from the api as it is actually incorrect.
55
+ # having a method to style a row's cells is fine, but it is not an attribute on the row.
56
+ # The proper attribute is ':s'
57
+ # attr_reader style
58
+ #
59
+
49
60
  # Creates a new row. New Cell objects are created based on the values, types and style options.
50
61
  # A new cell is created for each item in the values array. style and types options are applied as follows:
51
62
  # If the types option is defined and is a symbol it is applied to all the cells created.
@@ -81,6 +92,12 @@ module Axlsx
81
92
  Axlsx.validate_boolean(v)
82
93
  @hidden = v
83
94
  end
95
+
96
+ # @see Row#ph
97
+ def ph=(v) Axlsx.validate_boolean(v); @ph = v end
98
+
99
+ # @see Row#s
100
+ def s=(v) Axlsx.validate_unsigned_numeric(v); @s = v; @customFormat = true end
84
101
 
85
102
  # @see Row#outline
86
103
  def outlineLevel=(v)
@@ -88,7 +105,6 @@ module Axlsx
88
105
  @outlineLevel = v
89
106
  end
90
107
 
91
-
92
108
  # The index of this row in the worksheet
93
109
  # @return [Integer]
94
110
  def index
@@ -0,0 +1,224 @@
1
+ # encoding: UTF-8
2
+ module Axlsx
3
+
4
+ # The SheetProtection object manages worksheet protection options per sheet.
5
+ class SheetProtection
6
+
7
+ # If 1 or true then AutoFilters should not be allowed to operate when the sheet is protected.
8
+ # If 0 or false then AutoFilters should be allowed to operate when the sheet is protected.
9
+ # @return [Boolean]
10
+ # default true
11
+ attr_reader :auto_filter
12
+
13
+ # If 1 or true then deleting columns should not be allowed when the sheet is protected.
14
+ # If 0 or false then deleting columns should be allowed when the sheet is protected.
15
+ # @return [Boolean]
16
+ # default true
17
+ attr_reader :delete_columns
18
+
19
+ # If 1 or true then deleting rows should not be allowed when the sheet is protected.
20
+ # If 0 or false then deleting rows should be allowed when the sheet is protected.
21
+ # @return [Boolean]
22
+ # default true
23
+ attr_reader :delete_rows
24
+
25
+ # If 1 or true then formatting cells should not be allowed when the sheet is protected.
26
+ # If 0 or false then formatting cells should be allowed when the sheet is protected.
27
+ # @return [Boolean]
28
+ # default true
29
+ attr_reader :format_cells
30
+
31
+ # If 1 or true then formatting columns should not be allowed when the sheet is protected.
32
+ # If 0 or false then formatting columns should be allowed when the sheet is protected.
33
+ # @return [Boolean]
34
+ # default true
35
+ attr_reader :format_columns
36
+
37
+ # If 1 or true then formatting rows should not be allowed when the sheet is protected.
38
+ # If 0 or false then formatting rows should be allowed when the sheet is protected.
39
+ # @return [Boolean]
40
+ # default true
41
+ attr_reader :format_rows
42
+
43
+ # If 1 or true then inserting columns should not be allowed when the sheet is protected.
44
+ # If 0 or false then inserting columns should be allowed when the sheet is protected.
45
+ # @return [Boolean]
46
+ # default true
47
+ attr_reader :insert_columns
48
+
49
+ # If 1 or true then inserting hyperlinks should not be allowed when the sheet is protected.
50
+ # If 0 or false then inserting hyperlinks should be allowed when the sheet is protected.
51
+ # @return [Boolean]
52
+ # default true
53
+ attr_reader :insert_hyperlinks
54
+
55
+ # If 1 or true then inserting rows should not be allowed when the sheet is protected.
56
+ # If 0 or false then inserting rows should be allowed when the sheet is protected.
57
+ # @return [Boolean]
58
+ # default true
59
+ attr_reader :insert_rows
60
+
61
+ # If 1 or true then editing of objects should not be allowed when the sheet is protected.
62
+ # If 0 or false then objects are allowed to be edited when the sheet is protected.
63
+ # @return [Boolean]
64
+ # default false
65
+ attr_reader :objects
66
+
67
+ # If 1 or true then PivotTables should not be allowed to operate when the sheet is protected.
68
+ # If 0 or false then PivotTables should be allowed to operate when the sheet is protected.
69
+ # @return [Boolean]
70
+ # default true
71
+ attr_reader :pivot_tables
72
+
73
+ # Specifies the salt which was prepended to the user-supplied password before it was hashed using the hashing algorithm
74
+ # @return [String]
75
+ attr_reader :salt_value
76
+
77
+ # If 1 or true then Scenarios should not be edited when the sheet is protected.
78
+ # If 0 or false then Scenarios are allowed to be edited when the sheet is protected.
79
+ # @return [Boolean]
80
+ # default false
81
+ attr_reader :scenarios
82
+
83
+ # If 1 or true then selection of locked cells should not be allowed when the sheet is protected.
84
+ # If 0 or false then selection of locked cells should be allowed when the sheet is protected.
85
+ # @return [Boolean]
86
+ # default false
87
+ attr_reader :select_locked_cells
88
+
89
+ # If 1 or true then selection of unlocked cells should not be allowed when the sheet is protected.
90
+ # If 0 or false then selection of unlocked cells should be allowed when the sheet is protected.
91
+ # @return [Boolean]
92
+ # default false
93
+ attr_reader :select_unlocked_cells
94
+
95
+ # If 1 or true then the sheet is protected.
96
+ # If 0 or false then the sheet is not protected.
97
+ # @return [Boolean]
98
+ # default true
99
+ attr_reader :sheet
100
+
101
+ # If 1 or true then sorting should not be allowed when the sheet is protected.
102
+ # If 0 or false then sorting should be allowed when the sheet is protected.
103
+ # @return [Boolean]
104
+ # default true
105
+ attr_reader :sort
106
+
107
+ # Password hash
108
+ # @return [String]
109
+ # default nil
110
+ attr_reader :password
111
+
112
+ # Creates a new SheetProtection instance
113
+ # @option options [Boolean] sheet @see SheetProtection#sheet
114
+ # @option options [Boolean] objects @see SheetProtection#objects
115
+ # @option options [Boolean] scenarios @see SheetProtection#scenarios
116
+ # @option options [Boolean] format_cells @see SheetProtection#objects
117
+ # @option options [Boolean] format_columns @see SheetProtection#format_columns
118
+ # @option options [Boolean] format_rows @see SheetProtection#format_rows
119
+ # @option options [Boolean] insert_columns @see SheetProtection#insert_columns
120
+ # @option options [Boolean] insert_rows @see SheetProtection#insert_rows
121
+ # @option options [Boolean] insert_hyperlinks @see SheetProtection#insert_hyperlinks
122
+ # @option options [Boolean] delete_columns @see SheetProtection#delete_columns
123
+ # @option options [Boolean] delete_rows @see SheetProtection#delete_rows
124
+ # @option options [Boolean] select_locked_cells @see SheetProtection#select_locked_cells
125
+ # @option options [Boolean] sort @see SheetProtection#sort
126
+ # @option options [Boolean] auto_filter @see SheetProtection#auto_filter
127
+ # @option options [Boolean] pivot_tables @see SheetProtection#pivot_tables
128
+ # @option options [Boolean] select_unlocked_cells @see SheetProtection#select_unlocked_cells
129
+ # @option options [String] password. The password required for unlocking. @see SheetProtection#password=
130
+ # @option options [Boolean] objects @see SheetProtection#objects
131
+ def initialize(options={})
132
+ @objects = @scenarios = @select_locked_cells = @select_unlocked_cells = false
133
+ @sheet = @format_cells = @format_rows = @format_columns = @insert_columns = @insert_rows = @insert_hyperlinks = @delete_columns = @delete_rows = @sort = @auto_filter = @pivot_tables = true
134
+ @password = nil
135
+
136
+ options.each do |o|
137
+ self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
138
+ end
139
+ end
140
+
141
+
142
+ # create validating setters for boolean values
143
+ # @return [Boolean]
144
+ [:sheet, :objects, :scenarios, :select_locked_cells, :sort,
145
+ :select_unlocked_cells, :format_cells, :format_rows, :format_columns,
146
+ :insert_columns, :insert_rows, :insert_hyperlinks, :delete_columns,
147
+ :delete_rows, :auto_filter, :pivot_tables].each do |f_name|
148
+ define_method "#{f_name.to_s}=".to_sym do |v|
149
+ Axlsx::validate_boolean(v)
150
+ instance_variable_set "@#{f_name.to_s}".to_sym, v
151
+ end
152
+ end
153
+
154
+ # This block is intended to implement the salt_value, hash_value and spin count as per the ECMA-376 standard.
155
+ # However, it does not seem to actually work in EXCEL - instead they are using their old retro algorithm shown below
156
+ # defined in the transitional portion of the speck. I am leaving this code in in the hope that someday Ill be able to
157
+ # figure out why it does not work, and if Excel even supports it.
158
+ # def propper_password=(v)
159
+ # @algorithm_name = v == nil ? nil : 'SHA-1'
160
+ # @salt_value = @spin_count = @hash_value = v if v == nil
161
+ # return if v == nil
162
+ # require 'digest/sha1'
163
+ # @spin_count = 10000
164
+ # @salt_value = Digest::SHA1.hexdigest(rand(36**8).to_s(36))
165
+ # @spin_count.times do |count|
166
+ # @hash_value = Digest::SHA1.hexdigest((@hash_value ||= (@salt_value + v.to_s)) + Array(count).pack('V'))
167
+ # end
168
+ # end
169
+
170
+
171
+
172
+ # encodes password for protection locking
173
+ def password=(v)
174
+ return if v == nil
175
+ @password = create_password_hash(v)
176
+ end
177
+
178
+ # Serialize the object
179
+ # @param [String] str
180
+ # @return [String]
181
+ def to_xml_string(str = '')
182
+ str << '<sheetProtection '
183
+ str << instance_values.map{ |k,v| k.gsub(/_(.)/){ $1.upcase } << %{="#{v.to_s}"} }.join(' ')
184
+ str << '/>'
185
+ end
186
+
187
+ private
188
+ # Creates a password hash for a given password
189
+ # @return [String]
190
+ def create_password_hash(password)
191
+ encoded_password = encode_password(password)
192
+
193
+ password_as_hex = [encoded_password].pack("v")
194
+ password_as_string = password_as_hex.unpack("H*").first.upcase
195
+
196
+ password_as_string[2..3] + password_as_string[0..1]
197
+ end
198
+
199
+
200
+ # Encodes a given password
201
+ # Based on the algorithm provided by Daniel Rentz of OpenOffice.
202
+ # http://www.openoffice.org/sc/excelfileformat.pdf, Revision 1.42, page 115 (21.05.2012)
203
+ # @return [String]
204
+ def encode_password(password)
205
+ i = 0
206
+ chars = password.split(//)
207
+ count = chars.size
208
+
209
+ chars.collect! do |char|
210
+ i += 1
211
+ char = char.unpack('c')[0] << i #ord << i
212
+ low_15 = char & 0x7fff
213
+ high_15 = char & 0x7fff << 15
214
+ high_15 = high_15 >> 15
215
+ char = low_15 | high_15
216
+ end
217
+
218
+ encoded_password = 0x0000
219
+ chars.each { |c| encoded_password ^= c }
220
+ encoded_password ^= count
221
+ encoded_password ^= 0xCE4B
222
+ end
223
+ end
224
+ end
@@ -19,8 +19,8 @@ module Axlsx
19
19
  attr_reader :style
20
20
 
21
21
  # Creates a new Table object
22
- # @param [String] ref The reference to the table data.
23
- # @param [Sheet] ref The sheet containing the table data.
22
+ # @param [String] ref The reference to the table data like 'A1:G24'.
23
+ # @param [Worksheet] sheet The sheet containing the table data.
24
24
  # @option options [Cell, String] name
25
25
  # @option options [TableStyle] style
26
26
  def initialize(ref, sheet, options={})
@@ -8,6 +8,15 @@ module Axlsx
8
8
  # @return [String]
9
9
  attr_reader :name
10
10
 
11
+ # The sheet protection object for this workbook
12
+ # @return [SheetProtection]
13
+ # @see SheetProtection
14
+ def sheet_protection
15
+ @sheet_protection ||= SheetProtection.new
16
+ yield @sheet_protection if block_given?
17
+ @sheet_protection
18
+ end
19
+
11
20
  # The workbook that owns this worksheet
12
21
  # @return [Workbook]
13
22
  attr_reader :workbook
@@ -51,9 +60,14 @@ module Axlsx
51
60
  # @return Boolean
52
61
  attr_reader :selected
53
62
 
54
- # Indicates if the worksheet should print in a single page
63
+ # Indicates if the worksheet will be fit by witdh or height to a specific number of pages.
64
+ # To alter the width or height for page fitting, please use page_setup.fit_to_widht or page_setup.fit_to_height.
65
+ # If you want the worksheet to fit on more pages (e.g. 2x2), set {PageSetup#fit_to_width} and {PageSetup#fit_to_height} accordingly.
55
66
  # @return Boolean
56
- attr_reader :fit_to_page
67
+ # @see #page_setup
68
+ def fit_to_page
69
+ (@page_setup != nil && (@page_setup.fit_to_width != nil || @page_setup.fit_to_height != nil))
70
+ end
57
71
 
58
72
 
59
73
  # Column info for the sheet
@@ -88,7 +102,7 @@ module Axlsx
88
102
  # wb = Axlsx::Package.new.workbook
89
103
  #
90
104
  # # using options when creating the worksheet.
91
- # ws = wb.add_worksheet :page_setup => {:fit_to_width => 1, :orientation => :landscape}
105
+ # ws = wb.add_worksheet :page_setup => {:fit_to_width => 2, :orientation => :landscape}
92
106
  #
93
107
  # # use the set method of the page_setup object
94
108
  # ws.page_setup.set(:paper_width => "297mm", :paper_height => "210mm")
@@ -110,15 +124,15 @@ module Axlsx
110
124
  # @example
111
125
  # wb = Axlsx::Package.new.workbook
112
126
  # # using options when creating the worksheet.
113
- # ws = wb.add_worksheet :print_options => {:gridLines => true, :horizontalCentered => true}
127
+ # ws = wb.add_worksheet :print_options => {:grid_lines => true, :horizontal_centered => true}
114
128
  #
115
129
  # # use the set method of the page_margins object
116
130
  # ws.print_options.set(:headings => true)
117
131
  #
118
132
  # # set page margins in a block
119
133
  # ws.print_options do |options|
120
- # options.horizontalCentered = true
121
- # options.verticalCentered = true
134
+ # options.horizontal_centered = true
135
+ # options.vertical_centered = true
122
136
  # end
123
137
  # @see PrintOptions#initialize
124
138
  # @return [PrintOptions]
@@ -146,16 +160,17 @@ module Axlsx
146
160
  self.workbook = wb
147
161
  @workbook.worksheets << self
148
162
  @page_marging = @page_setup = @print_options = nil
149
- @drawing = @page_margins = @auto_filter = nil
163
+ @drawing = @page_margins = @auto_filter = @sheet_protection = nil
150
164
  @merged_cells = []
151
165
  @auto_fit_data = []
152
166
  @conditional_formattings = []
167
+ @data_validations = []
153
168
  @comments = Comments.new(self)
154
169
  @selected = false
155
170
  @show_gridlines = true
156
171
  self.name = "Sheet" + (index+1).to_s
157
172
  @page_margins = PageMargins.new options[:page_margins] if options[:page_margins]
158
- @page_setup = PageSetup.new options[:page_setup] if options[:page_setup]
173
+ @page_setup = PageSetup.new options[:page_setup] if options[:page_setup]
159
174
  @print_options = PrintOptions.new options[:print_options] if options[:print_options]
160
175
  @rows = SimpleTypedList.new Row
161
176
  @column_info = SimpleTypedList.new Col
@@ -188,6 +203,17 @@ module Axlsx
188
203
  cf.add_rules rules
189
204
  @conditional_formattings << cf
190
205
  end
206
+
207
+ # Add data validation to this worksheet.
208
+ #
209
+ # @param [String] cells The cells the validation will apply to.
210
+ # @param [hash] data_validation options defining the validation to apply.
211
+ # @see examples/data_validation.rb for an example
212
+ def add_data_validation(cells, data_validation)
213
+ dv = DataValidation.new(data_validation)
214
+ dv.sqref = cells
215
+ @data_validations << dv
216
+ end
191
217
 
192
218
  # Creates merge information for this worksheet.
193
219
  # Cells can be merged by calling the merge_cells method on a worksheet.
@@ -202,7 +228,7 @@ module Axlsx
202
228
  @merged_cells << if cells.is_a?(String)
203
229
  cells
204
230
  elsif cells.is_a?(Array)
205
- cells = cells.sort { |x, y| x.r <=> y.r }
231
+ cells = cells.sort { |x, y| [x.index, x.row.index] <=> [y.index, y.row.index] }
206
232
  "#{cells.first.r}:#{cells.last.r}"
207
233
  end
208
234
  end
@@ -233,12 +259,11 @@ module Axlsx
233
259
  end
234
260
 
235
261
 
236
- # Indicates if the worksheet should print in a single page.
237
- # This is true by default.
262
+ # (see #fit_to_page)
238
263
  # @return [Boolean]
239
264
  def fit_to_page=(v)
240
- Axlsx::validate_boolean v
241
- @fit_to_page = v
265
+ warn('axlsx::DEPRECIATED: Worksheet#fit_to_page has been depreciated. This value will automatically be set for you when you use PageSetup#fit_to.')
266
+ fit_to_page
242
267
  end
243
268
 
244
269
 
@@ -462,6 +487,7 @@ module Axlsx
462
487
  # @param [String] str
463
488
  # @return [String]
464
489
  def to_xml_string
490
+ rels = relationships
465
491
  str = '<?xml version="1.0" encoding="UTF-8"?>'
466
492
  str.concat "<worksheet xmlns=\"%s\" xmlns:r=\"%s\">" % [XML_NS, XML_NS_R]
467
493
  str.concat "<sheetPr><pageSetUpPr fitToPage=\"%s\"></pageSetUpPr></sheetPr>" % fit_to_page if fit_to_page
@@ -477,19 +503,28 @@ module Axlsx
477
503
  @rows.each_with_index { |row, index| row.to_xml_string(index, str) }
478
504
  str.concat '</sheetData>'
479
505
  str.concat "<autoFilter ref='%s'></autoFilter>" % @auto_filter if @auto_filter
506
+ @sheet_protection.to_xml_string(str) if @sheet_protection
480
507
  str.concat "<mergeCells count='%s'>%s</mergeCells>" % [@merged_cells.size, @merged_cells.reduce('') { |memo, obj| memo += "<mergeCell ref='%s'></mergeCell>" % obj } ] unless @merged_cells.empty?
481
508
  print_options.to_xml_string(str) if @print_options
482
509
  page_margins.to_xml_string(str) if @page_margins
483
510
  page_setup.to_xml_string(str) if @page_setup
484
- str.concat "<drawing r:id='rId1'></drawing>" if @drawing
485
- unless @tables.empty?
486
- str.concat "<tableParts count='%s'>%s</tableParts>" % [@tables.size, @tables.reduce('') { |memo, obj| memo += "<tablePart r:id='%s'/>" % obj.rId }]
487
- end
488
- @conditional_formattings.each do |cf|
489
- str.concat cf.to_xml_string
490
- end
491
- str << '<legacyDrawing r:id="rId1"/>' if @comments.size > 0
492
- str + '</worksheet>'
511
+ str << "<drawing r:id='rId" << (rels.index{ |r| r.Type == DRAWING_R } + 1).to_s << "'/>" if @drawing
512
+ str << "<legacyDrawing r:id='rId" << (rels.index{ |r| r.Type == VML_DRAWING_R } + 1).to_s << "'/>" if @comments.size > 0
513
+ unless @tables.empty?
514
+ str.concat "<tableParts count='%s'>%s</tableParts>" % [@tables.size, @tables.reduce('') { |memo, obj| memo += "<tablePart r:id='%s'/>" % obj.rId }]
515
+ end
516
+ @conditional_formattings.each do |cf|
517
+ str.concat cf.to_xml_string
518
+ end
519
+
520
+ unless @data_validations.empty?
521
+ str.concat "<dataValidations count=\"#{@data_validations.size}\">"
522
+ @data_validations.each do |df|
523
+ str.concat df.to_xml_string
524
+ end
525
+ str.concat '</dataValidations>'
526
+ end
527
+ str + '</worksheet>'
493
528
  end
494
529
 
495
530
  # The worksheet relationships. This is managed automatically by the worksheet