axlsx 1.1.7 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. data/.yardopts +5 -2
  2. data/CHANGELOG.md +39 -0
  3. data/README.md +84 -46
  4. data/Rakefile +3 -2
  5. data/examples/basic_charts.rb +8 -0
  6. data/examples/chart_colors.rb +18 -3
  7. data/examples/example.rb +106 -46
  8. data/examples/example.xlsx +0 -0
  9. data/examples/example_streamed.xlsx +0 -0
  10. data/examples/extractive.pdf +0 -0
  11. data/examples/no-use_autowidth.xlsx +0 -0
  12. data/examples/scraping_html.rb +91 -0
  13. data/examples/shared_strings_example.xlsx +0 -0
  14. data/lib/axlsx/content_type/content_type.rb +2 -0
  15. data/lib/axlsx/content_type/default.rb +21 -12
  16. data/lib/axlsx/content_type/override.rb +22 -11
  17. data/lib/axlsx/doc_props/app.rb +36 -32
  18. data/lib/axlsx/doc_props/core.rb +9 -5
  19. data/lib/axlsx/drawing/ax_data_source.rb +7 -6
  20. data/lib/axlsx/drawing/axis.rb +48 -27
  21. data/lib/axlsx/drawing/bar_3D_chart.rb +49 -45
  22. data/lib/axlsx/drawing/bar_series.rb +1 -0
  23. data/lib/axlsx/drawing/cat_axis.rb +42 -38
  24. data/lib/axlsx/drawing/chart.rb +63 -52
  25. data/lib/axlsx/drawing/d_lbls.rb +100 -0
  26. data/lib/axlsx/drawing/drawing.rb +7 -4
  27. data/lib/axlsx/drawing/line_3D_chart.rb +3 -10
  28. data/lib/axlsx/drawing/num_data_source.rb +1 -1
  29. data/lib/axlsx/drawing/pie_3D_chart.rb +10 -7
  30. data/lib/axlsx/drawing/scatter_chart.rb +2 -8
  31. data/lib/axlsx/drawing/two_cell_anchor.rb +41 -9
  32. data/lib/axlsx/drawing/view_3D.rb +41 -31
  33. data/lib/axlsx/drawing/vml_drawing.rb +1 -1
  34. data/lib/axlsx/package.rb +11 -11
  35. data/lib/axlsx/rels/relationship.rb +3 -3
  36. data/lib/axlsx/stylesheet/styles.rb +1 -1
  37. data/lib/axlsx/util/constants.rb +4 -0
  38. data/lib/axlsx/util/simple_typed_list.rb +15 -8
  39. data/lib/axlsx/util/validators.rb +2 -2
  40. data/lib/axlsx/version.rb +2 -7
  41. data/lib/axlsx/workbook/defined_name.rb +174 -0
  42. data/lib/axlsx/workbook/defined_names.rb +21 -0
  43. data/lib/axlsx/workbook/workbook.rb +39 -14
  44. data/lib/axlsx/workbook/worksheet/auto_filter.rb +34 -0
  45. data/lib/axlsx/workbook/worksheet/cell.rb +25 -2
  46. data/lib/axlsx/workbook/worksheet/col.rb +15 -0
  47. data/lib/axlsx/workbook/worksheet/cols.rb +20 -0
  48. data/lib/axlsx/workbook/worksheet/comments.rb +8 -0
  49. data/lib/axlsx/workbook/worksheet/conditional_formattings.rb +25 -0
  50. data/lib/axlsx/workbook/worksheet/data_bar.rb +1 -1
  51. data/lib/axlsx/workbook/worksheet/data_validations.rb +28 -0
  52. data/lib/axlsx/workbook/worksheet/dimension.rb +65 -0
  53. data/lib/axlsx/workbook/worksheet/merged_cells.rb +35 -0
  54. data/lib/axlsx/workbook/worksheet/page_setup.rb +9 -0
  55. data/lib/axlsx/workbook/worksheet/protected_range.rb +46 -0
  56. data/lib/axlsx/workbook/worksheet/protected_ranges.rb +34 -0
  57. data/lib/axlsx/workbook/worksheet/row.rb +1 -1
  58. data/lib/axlsx/workbook/worksheet/sheet_data.rb +25 -0
  59. data/lib/axlsx/workbook/worksheet/sheet_pr.rb +24 -0
  60. data/lib/axlsx/workbook/worksheet/tables.rb +31 -0
  61. data/lib/axlsx/workbook/worksheet/worksheet.rb +300 -293
  62. data/lib/axlsx/workbook/worksheet/worksheet_comments.rb +57 -0
  63. data/lib/axlsx/workbook/worksheet/worksheet_drawing.rb +64 -0
  64. data/lib/axlsx.rb +21 -14
  65. data/test/content_type/tc_content_type.rb +1 -6
  66. data/test/doc_props/tc_core.rb +1 -1
  67. data/test/drawing/tc_axis.rb +8 -0
  68. data/test/drawing/tc_bar_3D_chart.rb +12 -12
  69. data/test/drawing/tc_bar_series.rb +1 -2
  70. data/test/drawing/tc_chart.rb +8 -6
  71. data/test/drawing/tc_d_lbls.rb +47 -0
  72. data/test/drawing/tc_drawing.rb +5 -4
  73. data/test/drawing/tc_line_series.rb +1 -1
  74. data/test/drawing/tc_pie_3D_chart.rb +4 -8
  75. data/test/drawing/tc_pie_series.rb +1 -1
  76. data/test/drawing/tc_scatter_series.rb +1 -1
  77. data/test/drawing/tc_series.rb +1 -1
  78. data/test/drawing/tc_view_3D.rb +18 -18
  79. data/test/tc_package.rb +18 -1
  80. data/test/workbook/tc_defined_name.rb +41 -0
  81. data/test/workbook/tc_workbook.rb +5 -3
  82. data/test/workbook/worksheet/table/tc_table.rb +0 -8
  83. data/test/workbook/worksheet/tc_cell.rb +2 -4
  84. data/test/workbook/worksheet/tc_page_setup.rb +20 -3
  85. data/test/workbook/worksheet/tc_protected_range.rb +17 -0
  86. data/test/workbook/worksheet/tc_row.rb +2 -2
  87. data/test/workbook/worksheet/tc_selection.rb +1 -1
  88. data/test/workbook/worksheet/tc_worksheet.rb +53 -34
  89. metadata +93 -101
  90. data/examples/axis-titles.xlsx +0 -0
  91. data/examples/basic_charts.xlsx +0 -0
  92. data/examples/chart_colors.xlsx +0 -0
  93. data/examples/charts.xlsx +0 -0
  94. data/examples/conditional_formatting/getting_barred.xlsx +0 -0
  95. data/examples/doc/_index.html +0 -84
  96. data/examples/doc/class_list.html +0 -47
  97. data/examples/doc/css/common.css +0 -1
  98. data/examples/doc/css/full_list.css +0 -55
  99. data/examples/doc/css/style.css +0 -322
  100. data/examples/doc/file_list.html +0 -46
  101. data/examples/doc/frames.html +0 -13
  102. data/examples/doc/index.html +0 -84
  103. data/examples/doc/js/app.js +0 -205
  104. data/examples/doc/js/full_list.js +0 -173
  105. data/examples/doc/js/jquery.js +0 -16
  106. data/examples/doc/method_list.html +0 -46
  107. data/examples/doc/top-level-namespace.html +0 -95
  108. data/examples/examples_saved.xlsx +0 -0
  109. data/examples/extractive.xlsx +0 -0
  110. data/examples/fish.xlsx +0 -0
  111. data/examples/pareto.rb +0 -28
  112. data/examples/pareto.xlsx +0 -0
  113. data/examples/pie_chart_excel.xlsx +0 -0
  114. data/examples/pie_chart_saved.xlsx +0 -0
  115. data/examples/sheet_protection.xlsx +0 -0
  116. data/examples/sheet_view.xlsx +0 -0
  117. data/examples/two_cell_anchor_image.xlsx +0 -0
  118. data/examples/~$example.xlsx +0 -0
  119. data/lib/axlsx/drawing/ax_data_source.rb~ +0 -55
  120. data/lib/axlsx/drawing/data_source.rb~ +0 -51
  121. data/lib/axlsx/drawing/hlink_click.rb~ +0 -0
  122. data/lib/axlsx/drawing/hyperlink.rb~ +0 -64
  123. data/lib/axlsx/drawing/num_data.rb~ +0 -51
  124. data/lib/axlsx/drawing/num_data_source.rb~ +0 -54
  125. data/lib/axlsx/drawing/num_val.rb~ +0 -40
  126. data/lib/axlsx/drawing/picture_locking.rb~ +0 -36
  127. data/lib/axlsx/drawing/ref.rb~ +0 -41
  128. data/lib/axlsx/drawing/str_data.rb~ +0 -58
  129. data/lib/axlsx/drawing/str_val.rb~ +0 -35
  130. data/lib/axlsx/drawing/vml_drawing.rb~ +0 -6
  131. data/lib/axlsx/drawing/vml_shape.rb~ +0 -61
  132. data/lib/axlsx/util/cbf.rb +0 -333
  133. data/lib/axlsx/util/cfb.rb~ +0 -201
  134. data/lib/axlsx/util/font_tables.rb~ +0 -0
  135. data/lib/axlsx/util/ms_off_crypto.rb +0 -189
  136. data/lib/axlsx/util/ms_off_crypto.rb~ +0 -3
  137. data/lib/axlsx/util/ms_offcrypto.rb~ +0 -0
  138. data/lib/axlsx/util/parser.rb~ +0 -6
  139. data/lib/axlsx/util/storage.rb~ +0 -0
  140. data/lib/axlsx/workbook/shared_strings_table.rb~ +0 -69
  141. data/lib/axlsx/workbook/worksheet/cfvo.rb~ +0 -0
  142. data/lib/axlsx/workbook/worksheet/col.rb~ +0 -0
  143. data/lib/axlsx/workbook/worksheet/color_scale.rb~ +0 -46
  144. data/lib/axlsx/workbook/worksheet/comment.rb~ +0 -91
  145. data/lib/axlsx/workbook/worksheet/comments.rb~ +0 -86
  146. data/lib/axlsx/workbook/worksheet/data_bar.rb~ +0 -0
  147. data/lib/axlsx/workbook/worksheet/icon_set.rb~ +0 -95
  148. data/lib/axlsx/workbook/worksheet/shared_strings_table.rb~ +0 -0
  149. data/lib/axlsx/workbook/worksheet/table.rb~ +0 -97
  150. data/lib/schema/dc.xsd~ +0 -118
  151. data/lib/schema/dcterms.xsd~ +0 -331
  152. data/lib/schema/opc-coreProperties.xsd~ +0 -50
  153. data/test/drawing/tc_data_source.rb~ +0 -30
  154. data/test/drawing/tc_num_data.rb~ +0 -35
  155. data/test/drawing/tc_num_val.rb~ +0 -29
  156. data/test/drawing/tc_str_data.rb~ +0 -30
  157. data/test/drawing/tc_str_val.rb~ +0 -26
  158. data/test/drawing/tc_vml_drawing.rb~ +0 -0
  159. data/test/workbook/worksheet/table/tc_table.rb~ +0 -72
  160. data/test/workbook/worksheet/tc_cfvo.rb~ +0 -20
  161. data/test/workbook/worksheet/tc_col.rb~ +0 -10
  162. data/test/workbook/worksheet/tc_color_scale.rb~ +0 -0
  163. data/test/workbook/worksheet/tc_data_bar.rb~ +0 -0
  164. data/test/workbook/worksheet/tc_icon_set.rb~ +0 -0
@@ -4,9 +4,44 @@ module Axlsx
4
4
  # The Worksheet class represents a worksheet in the workbook.
5
5
  class Worksheet
6
6
 
7
+ # definition of characters which are less than the maximum width of 0-9 in the default font for use in String#count.
8
+ # This is used for autowidth calculations
9
+ # @return [String]
10
+ def self.thin_chars
11
+ @thin_chars ||= "^.acefijklrstxyzFIJL()-"
12
+ end
13
+
14
+ # Creates a new worksheet.
15
+ # @note the recommended way to manage worksheets is Workbook#add_worksheet
16
+ # @see Workbook#add_worksheet
17
+ # @option options [String] name The name of this worksheet.
18
+ # @option options [Hash] page_margins A hash containing page margins for this worksheet. @see PageMargins
19
+ # @option options [Hash] print_options A hash containing print options for this worksheet. @see PrintOptions
20
+ # @option options [Boolean] show_gridlines indicates if gridlines should be shown for this sheet.
21
+ def initialize(wb, options={})
22
+ self.workbook = wb
23
+ @workbook.worksheets << self
24
+ @sheet_protection = nil
25
+
26
+ initialize_page_options(options)
27
+ options.each do |o|
28
+ self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
29
+ end
30
+ end
31
+
32
+ # Initalizes page margin, setup and print options
33
+ # @param [Hash] options Options passed in from the initializer
34
+ def initialize_page_options(options)
35
+ @page_margins = PageMargins.new options[:page_margins] if options[:page_margins]
36
+ @page_setup = PageSetup.new options[:page_setup] if options[:page_setup]
37
+ @print_options = PrintOptions.new options[:print_options] if options[:print_options]
38
+ end
39
+
7
40
  # The name of the worksheet
8
41
  # @return [String]
9
- attr_reader :name
42
+ def name
43
+ @name ||= "Sheet" + (index+1).to_s
44
+ end
10
45
 
11
46
  # The sheet protection object for this workbook
12
47
  # @return [SheetProtection]
@@ -16,7 +51,7 @@ module Axlsx
16
51
  yield @sheet_protection if block_given?
17
52
  @sheet_protection
18
53
  end
19
-
54
+
20
55
  # The sheet view object for this worksheet
21
56
  # @return [SheetView]
22
57
  # @see [SheetView]
@@ -32,48 +67,35 @@ module Axlsx
32
67
 
33
68
  # The tables in this worksheet
34
69
  # @return [Array] of Table
35
- attr_reader :tables
36
-
37
- attr_reader :comments
70
+ def tables
71
+ @tables ||= Tables.new self
72
+ end
73
+
74
+ # The a shortcut to the worksheet_comments list of comments
75
+ # @return [Array|SimpleTypedList]
76
+ def comments
77
+ worksheet_comments.comments if worksheet_comments.has_comments?
78
+ end
38
79
 
39
80
  # The rows in this worksheet
40
81
  # @note The recommended way to manage rows is Worksheet#add_row
41
82
  # @return [SimpleTypedList]
42
83
  # @see Worksheet#add_row
43
- attr_reader :rows
44
-
45
- # An array of content based calculated column widths.
46
- # @note a single auto fit data item is a hash with :longest => [String] and :sz=> [Integer] members.
47
- # @return [Array] of Hash
48
- attr_reader :auto_fit_data
84
+ def rows
85
+ @rows ||= SimpleTypedList.new Row
86
+ end
49
87
 
50
- # An array of merged cell ranges e.d "A1:B3"
51
- # Content and formatting is read from the first cell.
52
- # @return Array
53
- attr_reader :merged_cells
88
+ # returns the sheet data as columnw
89
+ def cols
90
+ @rows.transpose
91
+ end
54
92
 
55
93
  # An range that excel will apply an autfilter to "A1:B3"
56
94
  # This will turn filtering on for the cells in the range.
57
95
  # The first row is considered the header, while subsequent rows are considerd to be data.
58
- # @return Array
59
- attr_reader :auto_filter
60
-
61
- # Indicates if the worksheet should show gridlines or not
62
- # @return Boolean
63
- # @deprecated Use {SheetView#show_grid_lines} instead.
64
- def show_gridlines
65
- warn('axlsx::DEPRECIATED: Worksheet#show_gridlines has been depreciated. This value can get over SheetView#show_grid_lines.')
66
- sheet_view.show_grid_lines
67
- end
68
-
69
- # Indicates if the worksheet is selected in the workbook
70
- # It is possible to have more than one worksheet selected, however it might cause issues
71
- # in some older versions of excel when using copy and paste.
72
- # @return Boolean
73
- # @deprecated Use {SheetView#tab_selected} instead.
74
- def selected
75
- warn('axlsx::DEPRECIATED: Worksheet#selected has been depreciated. This value can get over SheetView#tab_selected.')
76
- sheet_view.tab_selected
96
+ # @return String
97
+ def auto_filter
98
+ @auto_filter ||= AutoFilter.new self
77
99
  end
78
100
 
79
101
  # Indicates if the worksheet will be fit by witdh or height to a specific number of pages.
@@ -81,14 +103,17 @@ module Axlsx
81
103
  # If you want the worksheet to fit on more pages (e.g. 2x2), set {PageSetup#fit_to_width} and {PageSetup#fit_to_height} accordingly.
82
104
  # @return Boolean
83
105
  # @see #page_setup
84
- def fit_to_page
85
- (@page_setup != nil && (@page_setup.fit_to_width != nil || @page_setup.fit_to_height != nil))
106
+ def fit_to_page?
107
+ return false unless self.instance_values.keys.include?('page_setup')
108
+ page_setup.fit_to_page?
86
109
  end
87
110
 
88
111
 
89
112
  # Column info for the sheet
90
113
  # @return [SimpleTypedList]
91
- attr_reader :column_info
114
+ def column_info
115
+ @column_info ||= Cols.new self
116
+ end
92
117
 
93
118
  # Page margins for printing the worksheet.
94
119
  # @example
@@ -110,7 +135,6 @@ module Axlsx
110
135
  @page_margins ||= PageMargins.new
111
136
  yield @page_margins if block_given?
112
137
  @page_margins
113
-
114
138
  end
115
139
 
116
140
  # Page setup settings for printing the worksheet.
@@ -158,77 +182,12 @@ module Axlsx
158
182
  @print_options
159
183
  end
160
184
 
161
- # definition of characters which are less than the maximum width of 0-9 in the default font for use in String#count.
162
- # This is used for autowidth calculations
163
- # @return [String]
164
- def self.thin_chars
165
- @thin_chars ||= "^.acefijklrstxyzFIJL()-"
166
- end
167
-
168
- # Creates a new worksheet.
169
- # @note the recommended way to manage worksheets is Workbook#add_worksheet
170
- # @see Workbook#add_worksheet
171
- # @option options [String] name The name of this worksheet.
172
- # @option options [Hash] page_margins A hash containing page margins for this worksheet. @see PageMargins
173
- # @option options [Hash] print_options A hash containing print options for this worksheet. @see PrintOptions
174
- # @option options [Boolean] show_gridlines indicates if gridlines should be shown for this sheet.
175
- def initialize(wb, options={})
176
- self.workbook = wb
177
- @workbook.worksheets << self
178
- @page_marging = @page_setup = @print_options = nil
179
- @drawing = @page_margins = @auto_filter = @sheet_protection = @sheet_view = nil
180
- @merged_cells = []
181
- @auto_fit_data = []
182
- @conditional_formattings = []
183
- @data_validations = []
184
- @comments = Comments.new(self)
185
- self.name = "Sheet" + (index+1).to_s
186
- @page_margins = PageMargins.new options[:page_margins] if options[:page_margins]
187
- @page_setup = PageSetup.new options[:page_setup] if options[:page_setup]
188
- @print_options = PrintOptions.new options[:print_options] if options[:print_options]
189
- @rows = SimpleTypedList.new Row
190
- @column_info = SimpleTypedList.new Col
191
- # @cols = SimpleTypedList.new Cell
192
- @tables = SimpleTypedList.new Table
193
-
194
- options.each do |o|
195
- self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
196
- end
197
- end
198
-
199
185
  # convinience method to access all cells in this worksheet
200
186
  # @return [Array] cells
201
187
  def cells
202
188
  rows.flatten
203
189
  end
204
190
 
205
- # Add conditional formatting to this worksheet.
206
- #
207
- # @param [String] cells The range to apply the formatting to
208
- # @param [Array|Hash] rules An array of hashes (or just one) to create Conditional formatting rules from.
209
- # @example This would format column A whenever it is FALSE.
210
- # # for a longer example, see examples/example_conditional_formatting.rb (link below)
211
- # worksheet.add_conditional_formatting( "A1:A1048576", { :type => :cellIs, :operator => :equal, :formula => "FALSE", :dxfId => 1, :priority => 1 }
212
- #
213
- # @see ConditionalFormattingRule#initialize
214
- # @see file:examples/example_conditional_formatting.rb
215
- def add_conditional_formatting(cells, rules)
216
- cf = ConditionalFormatting.new( :sqref => cells )
217
- cf.add_rules rules
218
- @conditional_formattings << cf
219
- end
220
-
221
- # Add data validation to this worksheet.
222
- #
223
- # @param [String] cells The cells the validation will apply to.
224
- # @param [hash] data_validation options defining the validation to apply.
225
- # @see examples/data_validation.rb for an example
226
- def add_data_validation(cells, data_validation)
227
- dv = DataValidation.new(data_validation)
228
- dv.sqref = cells
229
- @data_validations << dv
230
- end
231
-
232
191
  # Creates merge information for this worksheet.
233
192
  # Cells can be merged by calling the merge_cells method on a worksheet.
234
193
  # @example This would merge the three cells C1..E1 #
@@ -237,24 +196,31 @@ module Axlsx
237
196
  # worksheet.merge_cells worksheet.rows.first.cells[(2..4)]
238
197
  # #alternatively you can do it from a single cell
239
198
  # worksheet["C1"].merge worksheet["E1"]
240
- # @param [Array, string]
199
+ # @param [Array, string] cells
241
200
  def merge_cells(cells)
242
- @merged_cells << if cells.is_a?(String)
243
- cells
244
- elsif cells.is_a?(Array)
245
- cells = cells.sort { |x, y| [x.index, x.row.index] <=> [y.index, y.row.index] }
246
- "#{cells.first.r}:#{cells.last.r}"
247
- end
201
+ merged_cells.add cells
248
202
  end
249
203
 
204
+ # Adds a new protected cell range to the worksheet. Note that protected ranges are only in effect when sheet protection is enabled.
205
+ # @param [String|Array] cells The string reference for the cells to protect or an array of cells.
206
+ # @return [ProtectedRange]
207
+ # @note When using an array of cells, a contiguous range is created from the minimum top left to the maximum top bottom of the cells provided.
208
+ def protect_range(cells)
209
+ protected_ranges.add_range(cells)
210
+ end
250
211
 
251
- # The demensions of a worksheet. This is not actually a required element by the spec,
212
+ # The dimensions of a worksheet. This is not actually a required element by the spec,
252
213
  # but at least a few other document readers expect this for conversion
253
- # @return [String] the A1:B2 style reference for the first and last row column intersection in the workbook
214
+ # @return [Dimension]
254
215
  def dimension
255
- dim_start = rows.first.cells.first == nil ? 'A1' : rows.first.cells.first.r
256
- dim_end = rows.last.cells.last == nil ? 'AA:200' : rows.last.cells.last.r
257
- "#{dim_start}:#{dim_end}"
216
+ @dimension ||= Dimension.new self
217
+ end
218
+
219
+ # The sheet properties for this workbook.
220
+ # Currently only pageSetUpPr -> fitToPage is implemented
221
+ # @return [SheetPr]
222
+ def sheet_pr
223
+ @sheet_pr ||= SheetPr.new self
258
224
  end
259
225
 
260
226
  # Indicates if gridlines should be shown in the sheet.
@@ -276,39 +242,37 @@ module Axlsx
276
242
  sheet_view.tab_selected = v
277
243
  end
278
244
 
245
+ # Indicates if the worksheet should show gridlines or not
246
+ # @return Boolean
247
+ # @deprecated Use {SheetView#show_grid_lines} instead.
248
+ def show_gridlines
249
+ warn('axlsx::DEPRECIATED: Worksheet#show_gridlines has been depreciated. This value can get over SheetView#show_grid_lines.')
250
+ sheet_view.show_grid_lines
251
+ end
252
+
253
+ # Indicates if the worksheet is selected in the workbook
254
+ # It is possible to have more than one worksheet selected, however it might cause issues
255
+ # in some older versions of excel when using copy and paste.
256
+ # @return Boolean
257
+ # @deprecated Use {SheetView#tab_selected} instead.
258
+ def selected
259
+ warn('axlsx::DEPRECIATED: Worksheet#selected has been depreciated. This value can get over SheetView#tab_selected.')
260
+ sheet_view.tab_selected
261
+ end
279
262
 
280
263
  # (see #fit_to_page)
281
264
  # @return [Boolean]
282
265
  def fit_to_page=(v)
283
266
  warn('axlsx::DEPRECIATED: Worksheet#fit_to_page has been depreciated. This value will automatically be set for you when you use PageSetup#fit_to.')
284
- fit_to_page
285
- end
286
-
287
-
288
- # returns the column and row index for a named based cell
289
- # @param [String] name The cell or cell range to return. "A1" will return the first cell of the first row.
290
- # @return [Cell]
291
- def name_to_cell(name)
292
- col_index, row_index = *Axlsx::name_to_indices(name)
293
- r = rows[row_index]
294
- r.cells[col_index] if r
267
+ fit_to_page?
295
268
  end
296
269
 
297
270
  # The name of the worksheet
298
271
  # The name of a worksheet must be unique in the workbook, and must not exceed 31 characters
299
- # @param [String] v
300
- def name=(v)
301
- DataTypeValidator.validate "Worksheet.name", String, v
302
- raise ArgumentError, (ERR_SHEET_NAME_TOO_LONG % v) if v.size > 31
303
- sheet_names = @workbook.worksheets.map { |s| s.name }
304
- raise ArgumentError, (ERR_DUPLICATE_SHEET_NAME % v) if sheet_names.include?(v)
305
- @name=v
306
- end
307
-
308
- # The absolute auto filter range
309
- # @see auto_filter
310
- def abs_auto_filter
311
- Axlsx.cell_range(@auto_filter.split(':').collect { |name| name_to_cell(name)}) if @auto_filter
272
+ # @param [String] name
273
+ def name=(name)
274
+ validate_sheet_name name
275
+ @name=Axlsx::coder.encode(name)
312
276
  end
313
277
 
314
278
  # The auto filter range for the worksheet
@@ -316,7 +280,7 @@ module Axlsx
316
280
  # @see auto_filter
317
281
  def auto_filter=(v)
318
282
  DataTypeValidator.validate "Worksheet.auto_filter", String, v
319
- @auto_filter = v
283
+ auto_filter.range = v
320
284
  end
321
285
 
322
286
  # The part name of this worksheet
@@ -348,7 +312,7 @@ module Axlsx
348
312
  # @return [Drawing]
349
313
  # @see Worksheet#add_chart
350
314
  def drawing
351
- @drawing || @drawing = Axlsx::Drawing.new(self)
315
+ worksheet_drawing.drawing
352
316
  end
353
317
 
354
318
  # Adds a row to the worksheet and updates auto fit data.
@@ -392,71 +356,39 @@ module Axlsx
392
356
  # @option options [Float] height the row's height (in points)
393
357
  def add_row(values=[], options={})
394
358
  Row.new(self, values, options)
395
- update_column_info @rows.last.cells, options.delete(:widths) ||[], options.delete(:style) || []
396
- # update_auto_fit_data @rows.last.cells, options.delete(:widths) || []
359
+ update_column_info @rows.last.cells, options.delete(:widths) || []
397
360
  yield @rows.last if block_given?
398
361
  @rows.last
399
362
  end
400
363
 
401
364
  alias :<< :add_row
402
365
 
403
- # Set the style for cells in a specific row
404
- # @param [Integer] index or range of indexes in the table
405
- # @param [Integer] the cellXfs index
406
- # @option options [Integer] col_offset only cells after this column will be updated.
407
- # @note You can also specify the style in the add_row call
408
- # @see Worksheet#add_row
409
- # @see README.md for an example
410
- def row_style(index, style, options={})
411
- offset = options.delete(:col_offset) || 0
412
- rs = @rows[index]
413
- if rs.is_a?(Array)
414
- rs.each { |r| r.cells[(offset..-1)].each { |c| c.style = style } }
415
- else
416
- rs.cells[(offset..-1)].each { |c| c.style = style }
417
- end
418
- end
419
-
420
- # returns the sheet data as columnw
421
- def cols
422
- @rows.transpose
423
- end
424
-
425
-
426
- # Set the style for cells in a specific column
427
- # @param [Integer] index the index of the column
428
- # @param [Integer] the cellXfs index
429
- # @option options [Integer] row_offset only cells after this column will be updated.
430
- # @note You can also specify the style for specific columns in the call to add_row by using an array for the :styles option
431
- # @see Worksheet#add_row
432
- # @see README.md for an example
433
- def col_style(index, style, options={})
434
- offset = options.delete(:row_offset) || 0
435
- @rows[(offset..-1)].each do |r|
436
- cells = r.cells[index]
437
- next unless cells
438
- if cells.is_a?(Array)
439
- cells.each { |c| c.style = style }
440
- else
441
- cells.style = style
442
- end
443
- end
366
+ # Add conditional formatting to this worksheet.
367
+ #
368
+ # @param [String] cells The range to apply the formatting to
369
+ # @param [Array|Hash] rules An array of hashes (or just one) to create Conditional formatting rules from.
370
+ # @example This would format column A whenever it is FALSE.
371
+ # # for a longer example, see examples/example_conditional_formatting.rb (link below)
372
+ # worksheet.add_conditional_formatting( "A1:A1048576", { :type => :cellIs, :operator => :equal, :formula => "FALSE", :dxfId => 1, :priority => 1 }
373
+ #
374
+ # @see ConditionalFormattingRule#initialize
375
+ # @see file:examples/example_conditional_formatting.rb
376
+ def add_conditional_formatting(cells, rules)
377
+ cf = ConditionalFormatting.new( :sqref => cells )
378
+ cf.add_rules rules
379
+ conditional_formattings << cf
380
+ conditional_formattings
444
381
  end
445
382
 
446
- # This is a helper method that Lets you specify a fixed width for multiple columns in a worksheet in one go.
447
- # Axlsx is sparse, so if you have not set data for a column, you cannot set the width.
448
- # Setting a fixed column width to nil will revert the behaviour back to calculating the width for you.
449
- # @example This would set the first and third column widhts but leave the second column in autofit state.
450
- # ws.column_widths 7.2, nil, 3
451
- # @note For updating only a single column it is probably easier to just set the width of the ws.column_info[col_index].width directly
452
- # @param [Integer|Float|Fixnum|nil] values
453
- def column_widths(*args)
454
- args.each_with_index do |value, index|
455
- next if value == nil
456
- Axlsx::validate_unsigned_numeric(value) unless value == nil
457
- @column_info[index] ||= Col.new index+1, index+1
458
- @column_info[index].width = value
459
- end
383
+ # Add data validation to this worksheet.
384
+ #
385
+ # @param [String] cells The cells the validation will apply to.
386
+ # @param [hash] data_validation options defining the validation to apply.
387
+ # @see examples/data_validation.rb for an example
388
+ def add_data_validation(cells, data_validation)
389
+ dv = DataValidation.new(data_validation)
390
+ dv.sqref = cells
391
+ data_validations << dv
460
392
  end
461
393
 
462
394
  # Adds a chart to this worksheets drawing. This is the recommended way to create charts for your worksheet. This method wraps the complexity of dealing with ooxml drawing, anchors, markers graphic frames chart objects and all the other dirty details.
@@ -473,90 +405,94 @@ module Axlsx
473
405
  # @see Line3DChart
474
406
  # @see README for examples
475
407
  def add_chart(chart_type, options={})
476
- chart = drawing.add_chart(chart_type, options)
408
+ chart = worksheet_drawing.add_chart(chart_type, options)
477
409
  yield chart if block_given?
478
410
  chart
479
411
  end
480
412
 
481
413
  # needs documentation
482
414
  def add_table(ref, options={})
483
- table = Table.new(ref, self, options)
484
- @tables << table
485
- yield table if block_given?
486
- table
415
+ tables << Table.new(ref, self, options)
416
+ yield tables.last if block_given?
417
+ tables.last
487
418
  end
488
419
 
489
-
490
- # Shortcut to comments#add_comment
420
+ # Shortcut to worsksheet_comments#add_comment
491
421
  def add_comment(options={})
492
- @comments.add_comment(options)
422
+ worksheet_comments.add_comment(options)
493
423
  end
494
424
 
495
425
  # Adds a media item to the worksheets drawing
496
- # @param [Class] media_type
497
- # @option options [] unknown
426
+ # @option [Hash] options options passed to drawing.add_image
498
427
  def add_image(options={})
499
- image = drawing.add_image(options)
428
+ image = worksheet_drawing.add_image(options)
500
429
  yield image if block_given?
501
430
  image
502
431
  end
503
432
 
504
- # Serializes the object
505
- # @param [String] str
433
+ # This is a helper method that Lets you specify a fixed width for multiple columns in a worksheet in one go.
434
+ # Axlsx is sparse, so if you have not set data for a column, you cannot set the width.
435
+ # Setting a fixed column width to nil will revert the behaviour back to calculating the width for you on the next call to add_row.
436
+ # @example This would set the first and third column widhts but leave the second column in autofit state.
437
+ # ws.column_widths 7.2, nil, 3
438
+ # @note For updating only a single column it is probably easier to just set the width of the ws.column_info[col_index].width directly
439
+ # @param [Integer|Float|Fixnum|nil] widths
440
+ def column_widths(*widths)
441
+ widths.each_with_index do |value, index|
442
+ next if value == nil
443
+ Axlsx::validate_unsigned_numeric(value) unless value == nil
444
+ find_or_create_column_info(index).width = value
445
+ end
446
+ end
447
+
448
+ # Set the style for cells in a specific column
449
+ # @param [Integer] index the index of the column
450
+ # @param [Integer] style the cellXfs index
451
+ # @param [Hash] options
452
+ # @option [Integer] :row_offset only cells after this column will be updated.
453
+ # @note You can also specify the style for specific columns in the call to add_row by using an array for the :styles option
454
+ # @see Worksheet#add_row
455
+ # @see README.md for an example
456
+ def col_style(index, style, options={})
457
+ offset = options.delete(:row_offset) || 0
458
+ cells = @rows[(offset..-1)].map { |row| row.cells[index] }.flatten.compact
459
+ cells.each { |cell| cell.style = style }
460
+ end
461
+
462
+ # Set the style for cells in a specific row
463
+ # @param [Integer] index or range of indexes in the table
464
+ # @param [Integer] style the cellXfs index
465
+ # @param [Hash] options the options used when applying the style
466
+ # @option [Integer] :col_offset only cells after this column will be updated.
467
+ # @note You can also specify the style in the add_row call
468
+ # @see Worksheet#add_row
469
+ # @see README.md for an example
470
+ def row_style(index, style, options={})
471
+ offset = options.delete(:col_offset) || 0
472
+ cells = cols[(offset..-1)].map { |column| column[index] }.flatten.compact
473
+ cells.each { |cell| cell.style = style }
474
+ end
475
+
476
+ # Serializes the worksheet object to an xml string
477
+ # This intentionally does not use nokogiri for performance reasons
506
478
  # @return [String]
507
479
  def to_xml_string
508
- rels = relationships
509
480
  str = '<?xml version="1.0" encoding="UTF-8"?>'
510
- str.concat "<worksheet xmlns=\"%s\" xmlns:r=\"%s\">" % [XML_NS, XML_NS_R]
511
- str.concat "<sheetPr><pageSetUpPr fitToPage=\"%s\"></pageSetUpPr></sheetPr>" % fit_to_page if fit_to_page
512
- str.concat "<dimension ref=\"%s\"></dimension>" % dimension unless rows.size == 0
513
- @sheet_view.to_xml_string(str) if @sheet_view
514
- if @column_info.size > 0
515
- str << "<cols>"
516
- @column_info.each { |col| col.to_xml_string(str) }
517
- str.concat '</cols>'
481
+ str << worksheet_node
482
+ serializable_parts.each do |item|
483
+ item.to_xml_string(str) if item
518
484
  end
519
- str.concat '<sheetData>'
520
- @rows.each_with_index { |row, index| row.to_xml_string(index, str) }
521
- str.concat '</sheetData>'
522
- str.concat "<autoFilter ref='%s'></autoFilter>" % @auto_filter if @auto_filter
523
- @sheet_protection.to_xml_string(str) if @sheet_protection
524
- 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?
525
- print_options.to_xml_string(str) if @print_options
526
- page_margins.to_xml_string(str) if @page_margins
527
- page_setup.to_xml_string(str) if @page_setup
528
- str << "<drawing r:id='rId" << (rels.index{ |r| r.Type == DRAWING_R } + 1).to_s << "'/>" if @drawing
529
- str << "<legacyDrawing r:id='rId" << (rels.index{ |r| r.Type == VML_DRAWING_R } + 1).to_s << "'/>" if @comments.size > 0
530
- unless @tables.empty?
531
- str.concat "<tableParts count='%s'>%s</tableParts>" % [@tables.size, @tables.reduce('') { |memo, obj| memo += "<tablePart r:id='%s'/>" % obj.rId }]
532
- end
533
- @conditional_formattings.each do |cf|
534
- str.concat cf.to_xml_string
535
- end
536
-
537
- unless @data_validations.empty?
538
- str.concat "<dataValidations count=\"#{@data_validations.size}\">"
539
- @data_validations.each do |df|
540
- str.concat df.to_xml_string
541
- end
542
- str.concat '</dataValidations>'
543
- end
544
- str + '</worksheet>'
485
+ str << '</worksheet>'
486
+ str.gsub(/[[:cntrl:]]/,'')
545
487
  end
546
488
 
547
489
  # The worksheet relationships. This is managed automatically by the worksheet
548
490
  # @return [Relationships]
549
491
  def relationships
550
492
  r = Relationships.new
551
- @tables.each do |table|
552
- r << Relationship.new(TABLE_R, "../#{table.pn}")
553
- end
554
-
555
- r << Relationship.new(VML_DRAWING_R, "../#{@comments.vml_drawing.pn}") if @comments.size > 0
556
- r << Relationship.new(COMMENT_R, "../#{@comments.pn}") if @comments.size > 0
557
- r << Relationship.new(COMMENT_R_NULL, "NULL") if @comments.size > 0
558
-
559
- r << Relationship.new(DRAWING_R, "../#{@drawing.pn}") if @drawing
493
+ r + [tables.relationships,
494
+ worksheet_comments.relationships,
495
+ worksheet_drawing.relationship].flatten.compact || []
560
496
  r
561
497
  end
562
498
 
@@ -565,59 +501,130 @@ module Axlsx
565
501
  # @return [Cell, Array]
566
502
  def [] (cell_def)
567
503
  return rows[cell_def] if cell_def.is_a?(Integer)
568
-
569
- parts = cell_def.split(':')
570
- first = name_to_cell parts[0]
504
+ parts = cell_def.split(':').map{ |part| name_to_cell part }
571
505
  if parts.size == 1
572
- first
506
+ parts.first
573
507
  else
574
- cells = []
575
- last = name_to_cell(parts[1])
576
- rows[(first.row.index..last.row.index)].each do |r|
577
- r.cells[(first.index..last.index)].each do |c|
578
- cells << c
579
- end
580
- end
581
- cells
508
+ range(*parts)
582
509
  end
583
510
  end
584
511
 
512
+ # returns the column and row index for a named based cell
513
+ # @param [String] name The cell or cell range to return. "A1" will return the first cell of the first row.
514
+ # @return [Cell]
515
+ def name_to_cell(name)
516
+ col_index, row_index = *Axlsx::name_to_indices(name)
517
+ r = rows[row_index]
518
+ r.cells[col_index] if r
519
+ end
520
+
521
+ # shortcut method to access styles direclty from the worksheet
522
+ # This lets us do stuff like:
523
+ # @example
524
+ # p = Axlsx::Package.new
525
+ # p.workbook.add_worksheet(:name => 'foo') do |sheet|
526
+ # my_style = sheet.styles.add_style { :bg_color => "FF0000" }
527
+ # sheet.add_row ['Oh No!'], :styles => my_style
528
+ # end
529
+ # p.serialize 'foo.xlsx'
530
+ def styles
531
+ @styles ||= self.workbook.styles
532
+ end
533
+
534
+
585
535
  private
586
536
 
587
- # assigns the owner workbook for this worksheet
588
- def workbook=(v) DataTypeValidator.validate "Worksheet.workbook", Workbook, v; @workbook = v; end
537
+ def validate_sheet_name(name)
538
+ DataTypeValidator.validate "Worksheet.name", String, name
539
+ raise ArgumentError, (ERR_SHEET_NAME_TOO_LONG % name) if name.size > 31
540
+ raise ArgumentError, (ERR_SHEET_NAME_COLON_FORBIDDEN % name) if name.include? ':'
541
+ name = Axlsx::coder.encode(name)
542
+ sheet_names = @workbook.worksheets.map { |s| s.name }
543
+ raise ArgumentError, (ERR_DUPLICATE_SHEET_NAME % name) if sheet_names.include?(name)
544
+ end
589
545
 
590
546
 
591
- # TODO this needs cleanup!
592
- def update_column_info(cells, widths=[], style=[])
593
- styles = self.workbook.styles
594
- cellXfs, fonts = styles.cellXfs, styles.fonts
595
- sz = 11
547
+ def serializable_parts
548
+ [sheet_pr, dimension, sheet_view, column_info,
549
+ sheet_data, @sheet_protection, protected_ranges,
550
+ auto_filter, merged_cells, conditional_formattings,
551
+ data_validations, print_options, page_margins,
552
+ page_setup, worksheet_drawing, worksheet_comments,
553
+ tables]
554
+ end
596
555
 
597
- cells.each_with_index do |cell, index|
598
- @column_info[index] ||= Col.new index+1, index+1
599
- col = @column_info[index]
600
- width = widths[index]
601
- col.width = width if [Integer, Float, Fixnum].include?(width.class)
602
- c_style = style[index] if [Integer, Fixnum].include?(style[index].class)
603
- next if width == :ignore || (cell.value.is_a?(String) && cell.value.start_with?('=') || cell.value == nil)
604
- if self.workbook.use_autowidth
605
- cell_xf = cellXfs[(c_style || 0)]
606
- font = fonts[(cell_xf.fontId || 0)]
607
- sz = cell.sz || font.sz || sz
608
- col.width = [(col.width || 0), calculate_width(cell.value.to_s, sz)].max
556
+ def range(*cell_def)
557
+ first, last = cell_def
558
+ cells = []
559
+ rows[(first.row.index..last.row.index)].each do |r|
560
+ r.cells[(first.index..last.index)].each do |c|
561
+ cells << c
609
562
  end
610
563
  end
564
+ cells
565
+ end
566
+
567
+ # A collection of protected ranges in the worksheet
568
+ # @note The recommended way to manage protected ranges is with Worksheet#protect_range
569
+ # @see Worksheet#protect_range
570
+ # @return [SimpleTypedList] The protected ranges for this worksheet
571
+ def protected_ranges
572
+ @protected_ranges ||= ProtectedRanges.new self
573
+ # SimpleTypedList.new ProtectedRange
574
+ end
575
+
576
+ # conditional formattings
577
+ # @return [Array]
578
+ def conditional_formattings
579
+ @conditional_formattings ||= ConditionalFormattings.new self
580
+ end
581
+
582
+ # data validations array
583
+ # @return [Array]
584
+ def data_validations
585
+ @data_validations ||= DataValidations.new self
586
+ end
587
+
588
+ # merged cells array
589
+ # @return [Array]
590
+ def merged_cells
591
+ @merged_cells ||= MergedCells.new self
611
592
  end
612
593
 
613
594
 
614
- # This is still not perfect...
615
- # - scaling is not linear as font sizes increst
616
- # - different fonts have different mdw and char widths
617
- def calculate_width(text, sz)
618
- mdw = 1.78 #This is the widest width of 0..9 in arial@10px)
619
- font_scale = (sz/10.0).to_f
620
- ((text.count(Worksheet.thin_chars) * mdw + 5) / mdw * 256) / 256.0 * font_scale
595
+ # Helper method for parsingout the root node for worksheet
596
+ # @return [String]
597
+ def worksheet_node
598
+ "<worksheet xmlns=\"%s\" xmlns:r=\"%s\">" % [XML_NS, XML_NS_R]
599
+ end
600
+
601
+ def sheet_data
602
+ @sheet_data ||= SheetData.new self
603
+ end
604
+
605
+ def worksheet_drawing
606
+ @worksheet_drawing ||= WorksheetDrawing.new self
621
607
  end
608
+
609
+ # The comments associated with this worksheet
610
+ # @return [SimpleTypedList]
611
+ def worksheet_comments
612
+ @worksheet_comments ||= WorksheetComments.new self
613
+ end
614
+
615
+ def workbook=(v) DataTypeValidator.validate "Worksheet.workbook", Workbook, v; @workbook = v; end
616
+
617
+ def update_column_info(cells, widths=[])
618
+ cells.each_with_index do |cell, index|
619
+ col = find_or_create_column_info(index)
620
+ next if widths[index] == :ignore
621
+ col.update_width(cell, widths[index], workbook.use_autowidth)
622
+ end
623
+ end
624
+
625
+ def find_or_create_column_info(index)
626
+ column_info[index] ||= Col.new(index + 1, index + 1)
627
+ end
628
+
622
629
  end
623
630
  end