axlsx 1.1.5 → 1.1.6

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 (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