axlsx 1.1.0 → 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 (214) hide show
  1. data/.yardopts +5 -2
  2. data/CHANGELOG.md +78 -0
  3. data/LICENSE +1 -1
  4. data/README.md +132 -331
  5. data/Rakefile +17 -5
  6. data/examples/basic_charts.rb +54 -0
  7. data/examples/chart_colors.rb +88 -0
  8. data/examples/conditional_formatting/example_conditional_formatting.rb +72 -0
  9. data/examples/conditional_formatting/getting_barred.rb +37 -0
  10. data/examples/conditional_formatting/hitting_the_high_notes.rb +37 -0
  11. data/examples/conditional_formatting/scaled_colors.rb +39 -0
  12. data/examples/conditional_formatting/stop_and_go.rb +37 -0
  13. data/examples/data_validation.rb +50 -0
  14. data/examples/example.rb +300 -152
  15. data/examples/example.xlsx +0 -0
  16. data/examples/example_streamed.xlsx +0 -0
  17. data/examples/extractive.pdf +0 -0
  18. data/examples/extractive.rb +45 -0
  19. data/examples/no-use_autowidth.xlsx +0 -0
  20. data/examples/sample.png +0 -0
  21. data/examples/scraping_html.rb +91 -0
  22. data/examples/shared_strings_example.xlsx +0 -0
  23. data/examples/sheet_protection.rb +10 -0
  24. data/examples/sheet_view.rb +34 -0
  25. data/examples/skydrive/real_example.rb +63 -0
  26. data/examples/two_cell_anchor_image.rb +11 -0
  27. data/lib/axlsx/content_type/content_type.rb +2 -0
  28. data/lib/axlsx/content_type/default.rb +30 -15
  29. data/lib/axlsx/content_type/override.rb +29 -14
  30. data/lib/axlsx/doc_props/app.rb +161 -104
  31. data/lib/axlsx/doc_props/core.rb +9 -5
  32. data/lib/axlsx/drawing/ax_data_source.rb +26 -0
  33. data/lib/axlsx/drawing/axis.rb +106 -35
  34. data/lib/axlsx/drawing/bar_3D_chart.rb +49 -45
  35. data/lib/axlsx/drawing/bar_series.rb +25 -7
  36. data/lib/axlsx/drawing/cat_axis.rb +50 -22
  37. data/lib/axlsx/drawing/chart.rb +69 -56
  38. data/lib/axlsx/drawing/d_lbls.rb +100 -0
  39. data/lib/axlsx/drawing/drawing.rb +28 -12
  40. data/lib/axlsx/drawing/graphic_frame.rb +2 -1
  41. data/lib/axlsx/drawing/line_3D_chart.rb +3 -10
  42. data/lib/axlsx/drawing/line_series.rb +20 -4
  43. data/lib/axlsx/drawing/num_data.rb +52 -0
  44. data/lib/axlsx/drawing/num_data_source.rb +61 -0
  45. data/lib/axlsx/drawing/num_val.rb +32 -0
  46. data/lib/axlsx/drawing/pic.rb +44 -4
  47. data/lib/axlsx/drawing/pie_3D_chart.rb +10 -7
  48. data/lib/axlsx/drawing/pie_series.rb +18 -4
  49. data/lib/axlsx/drawing/scatter_chart.rb +12 -18
  50. data/lib/axlsx/drawing/scatter_series.rb +28 -2
  51. data/lib/axlsx/drawing/ser_axis.rb +1 -1
  52. data/lib/axlsx/drawing/series.rb +1 -1
  53. data/lib/axlsx/drawing/str_data.rb +42 -0
  54. data/lib/axlsx/drawing/str_val.rb +33 -0
  55. data/lib/axlsx/drawing/title.rb +21 -9
  56. data/lib/axlsx/drawing/two_cell_anchor.rb +46 -9
  57. data/lib/axlsx/drawing/view_3D.rb +44 -31
  58. data/lib/axlsx/drawing/vml_drawing.rb +42 -0
  59. data/lib/axlsx/drawing/vml_shape.rb +125 -0
  60. data/lib/axlsx/package.rb +44 -16
  61. data/lib/axlsx/rels/relationship.rb +3 -3
  62. data/lib/axlsx/rels/relationships.rb +1 -11
  63. data/lib/axlsx/stylesheet/color.rb +0 -1
  64. data/lib/axlsx/stylesheet/dxf.rb +79 -0
  65. data/lib/axlsx/stylesheet/font.rb +2 -1
  66. data/lib/axlsx/stylesheet/gradient_fill.rb +1 -1
  67. data/lib/axlsx/stylesheet/styles.rb +143 -50
  68. data/lib/axlsx/stylesheet/table_style.rb +3 -3
  69. data/lib/axlsx/stylesheet/xf.rb +2 -0
  70. data/lib/axlsx/util/constants.rb +36 -0
  71. data/lib/axlsx/util/simple_typed_list.rb +15 -21
  72. data/lib/axlsx/util/validators.rb +122 -5
  73. data/lib/axlsx/version.rb +2 -7
  74. data/lib/axlsx/workbook/defined_name.rb +174 -0
  75. data/lib/axlsx/workbook/defined_names.rb +21 -0
  76. data/lib/axlsx/workbook/shared_strings_table.rb +12 -13
  77. data/lib/axlsx/workbook/workbook.rb +71 -13
  78. data/lib/axlsx/workbook/worksheet/auto_filter.rb +34 -0
  79. data/lib/axlsx/workbook/worksheet/cell.rb +45 -36
  80. data/lib/axlsx/workbook/worksheet/cfvo.rb +62 -0
  81. data/lib/axlsx/workbook/worksheet/col.rb +17 -1
  82. data/lib/axlsx/workbook/worksheet/color_scale.rb +76 -0
  83. data/lib/axlsx/workbook/worksheet/cols.rb +20 -0
  84. data/lib/axlsx/workbook/worksheet/comment.rb +101 -0
  85. data/lib/axlsx/workbook/worksheet/comments.rb +85 -0
  86. data/lib/axlsx/workbook/worksheet/conditional_formatting.rb +82 -0
  87. data/lib/axlsx/workbook/worksheet/conditional_formatting_rule.rb +216 -0
  88. data/lib/axlsx/workbook/worksheet/conditional_formattings.rb +25 -0
  89. data/lib/axlsx/workbook/worksheet/data_bar.rb +97 -0
  90. data/lib/axlsx/workbook/worksheet/data_validation.rb +245 -0
  91. data/lib/axlsx/workbook/worksheet/data_validations.rb +28 -0
  92. data/lib/axlsx/workbook/worksheet/dimension.rb +65 -0
  93. data/lib/axlsx/workbook/worksheet/icon_set.rb +83 -0
  94. data/lib/axlsx/workbook/worksheet/merged_cells.rb +35 -0
  95. data/lib/axlsx/workbook/worksheet/page_setup.rb +113 -0
  96. data/lib/axlsx/workbook/worksheet/pane.rb +144 -0
  97. data/lib/axlsx/workbook/worksheet/print_options.rb +63 -0
  98. data/lib/axlsx/workbook/worksheet/protected_range.rb +46 -0
  99. data/lib/axlsx/workbook/worksheet/protected_ranges.rb +34 -0
  100. data/lib/axlsx/workbook/worksheet/row.rb +69 -12
  101. data/lib/axlsx/workbook/worksheet/selection.rb +111 -0
  102. data/lib/axlsx/workbook/worksheet/sheet_data.rb +25 -0
  103. data/lib/axlsx/workbook/worksheet/sheet_pr.rb +24 -0
  104. data/lib/axlsx/workbook/worksheet/sheet_protection.rb +224 -0
  105. data/lib/axlsx/workbook/worksheet/sheet_view.rb +379 -0
  106. data/lib/axlsx/workbook/worksheet/table.rb +3 -3
  107. data/lib/axlsx/workbook/worksheet/tables.rb +31 -0
  108. data/lib/axlsx/workbook/worksheet/worksheet.rb +379 -233
  109. data/lib/axlsx/workbook/worksheet/worksheet_comments.rb +57 -0
  110. data/lib/axlsx/workbook/worksheet/worksheet_drawing.rb +64 -0
  111. data/lib/axlsx.rb +31 -16
  112. data/test/benchmark.rb +30 -38
  113. data/test/content_type/tc_content_type.rb +1 -6
  114. data/test/doc_props/tc_app.rb +31 -1
  115. data/test/doc_props/tc_core.rb +3 -1
  116. data/test/drawing/tc_axis.rb +46 -3
  117. data/test/drawing/tc_bar_3D_chart.rb +12 -12
  118. data/test/drawing/tc_bar_series.rb +17 -13
  119. data/test/drawing/tc_cat_axis.rb +3 -3
  120. data/test/drawing/tc_cat_axis_data.rb +23 -13
  121. data/test/drawing/tc_chart.rb +29 -11
  122. data/test/drawing/tc_d_lbls.rb +47 -0
  123. data/test/drawing/tc_data_source.rb +17 -0
  124. data/test/drawing/tc_drawing.rb +11 -5
  125. data/test/drawing/tc_hyperlink.rb +4 -5
  126. data/test/drawing/tc_line_series.rb +9 -11
  127. data/test/drawing/tc_named_axis_data.rb +27 -0
  128. data/test/drawing/tc_num_data.rb +31 -0
  129. data/test/drawing/tc_num_val.rb +29 -0
  130. data/test/drawing/tc_pic.rb +22 -2
  131. data/test/drawing/tc_pie_3D_chart.rb +4 -8
  132. data/test/drawing/tc_pie_series.rb +11 -13
  133. data/test/drawing/tc_scatter_chart.rb +6 -1
  134. data/test/drawing/tc_scatter_series.rb +6 -5
  135. data/test/drawing/tc_series.rb +1 -1
  136. data/test/drawing/tc_str_data.rb +18 -0
  137. data/test/drawing/tc_str_val.rb +21 -0
  138. data/test/drawing/tc_title.rb +16 -0
  139. data/test/drawing/tc_two_cell_anchor.rb +2 -3
  140. data/test/drawing/tc_view_3D.rb +18 -18
  141. data/test/drawing/tc_vml_drawing.rb +25 -0
  142. data/test/drawing/tc_vml_shape.rb +100 -0
  143. data/test/profile.rb +2 -1
  144. data/test/rels/tc_relationship.rb +1 -0
  145. data/test/stylesheet/tc_dxf.rb +81 -0
  146. data/test/stylesheet/tc_gradient_fill.rb +8 -0
  147. data/test/stylesheet/tc_pattern_fill.rb +7 -1
  148. data/test/stylesheet/tc_styles.rb +163 -3
  149. data/test/stylesheet/tc_table_style.rb +8 -0
  150. data/test/stylesheet/tc_table_style_element.rb +10 -1
  151. data/test/tc_helper.rb +1 -0
  152. data/test/tc_package.rb +93 -17
  153. data/test/util/tc_simple_typed_list.rb +13 -0
  154. data/test/util/tc_validators.rb +95 -9
  155. data/test/workbook/tc_defined_name.rb +41 -0
  156. data/test/workbook/tc_workbook.rb +5 -3
  157. data/test/workbook/worksheet/table/tc_table.rb +3 -11
  158. data/test/workbook/worksheet/tc_cell.rb +52 -18
  159. data/test/workbook/worksheet/tc_cfvo.rb +31 -0
  160. data/test/workbook/worksheet/tc_col.rb +9 -0
  161. data/test/workbook/worksheet/tc_color_scale.rb +29 -0
  162. data/test/workbook/worksheet/tc_comment.rb +57 -0
  163. data/test/workbook/worksheet/tc_comments.rb +57 -0
  164. data/test/workbook/worksheet/tc_conditional_formatting.rb +217 -0
  165. data/test/workbook/worksheet/tc_data_bar.rb +39 -0
  166. data/test/workbook/worksheet/tc_data_validation.rb +265 -0
  167. data/test/workbook/worksheet/tc_date_time_converter.rb +11 -5
  168. data/test/workbook/worksheet/tc_icon_set.rb +45 -0
  169. data/test/workbook/worksheet/tc_page_setup.rb +138 -0
  170. data/test/workbook/worksheet/tc_pane.rb +94 -0
  171. data/test/workbook/worksheet/tc_print_options.rb +72 -0
  172. data/test/workbook/worksheet/tc_protected_range.rb +17 -0
  173. data/test/workbook/worksheet/tc_row.rb +44 -13
  174. data/test/workbook/worksheet/tc_selection.rb +94 -0
  175. data/test/workbook/worksheet/tc_sheet_protection.rb +117 -0
  176. data/test/workbook/worksheet/tc_sheet_view.rb +214 -0
  177. data/test/workbook/worksheet/tc_worksheet.rb +147 -50
  178. metadata +216 -148
  179. data/examples/example.csv +0 -1000
  180. data/examples/example.rb~ +0 -112
  181. data/lib/axlsx/drawing/cat_axis_data.rb +0 -34
  182. data/lib/axlsx/drawing/hlink_click.rb~ +0 -0
  183. data/lib/axlsx/drawing/hyperlink.rb~ +0 -64
  184. data/lib/axlsx/drawing/named_axis_data.rb +0 -36
  185. data/lib/axlsx/drawing/picture_locking.rb~ +0 -36
  186. data/lib/axlsx/drawing/val_axis_data.rb +0 -27
  187. data/lib/axlsx/stylesheet/#num_fmt.rb# +0 -69
  188. data/lib/axlsx/util/cbf.rb +0 -333
  189. data/lib/axlsx/util/cfb.rb~ +0 -201
  190. data/lib/axlsx/util/ms_off_crypto.rb +0 -189
  191. data/lib/axlsx/util/ms_off_crypto.rb~ +0 -3
  192. data/lib/axlsx/util/ms_offcrypto.rb~ +0 -0
  193. data/lib/axlsx/util/parser.rb~ +0 -6
  194. data/lib/axlsx/util/storage.rb~ +0 -0
  195. data/lib/axlsx/workbook/shared_strings_table.rb~ +0 -69
  196. data/lib/axlsx/workbook/worksheet/col.rb~ +0 -0
  197. data/lib/axlsx/workbook/worksheet/shared_strings_table.rb~ +0 -0
  198. data/lib/axlsx/workbook/worksheet/table.rb~ +0 -97
  199. data/lib/schema/dc.xsd~ +0 -118
  200. data/lib/schema/dcterms.xsd~ +0 -331
  201. data/lib/schema/opc-coreProperties.xsd~ +0 -50
  202. data/test/#benchmark.txt# +0 -7
  203. data/test/#tc_helper.rb# +0 -3
  204. data/test/benchmark.rb~ +0 -0
  205. data/test/benchmark.txt +0 -6
  206. data/test/benchmark.txt~ +0 -6
  207. data/test/drawing/tc_val_axis_data.rb +0 -17
  208. data/test/example.csv +0 -1000
  209. data/test/example.xlsx +0 -0
  210. data/test/example_streamed.xlsx +0 -0
  211. data/test/tc_axlsx.rb~ +0 -0
  212. data/test/tc_helper.rb~ +0 -3
  213. data/test/workbook/worksheet/table/tc_table.rb~ +0 -72
  214. data/test/workbook/worksheet/tc_col.rb~ +0 -10
@@ -4,9 +4,62 @@ 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
45
+
46
+ # The sheet protection object for this workbook
47
+ # @return [SheetProtection]
48
+ # @see SheetProtection
49
+ def sheet_protection
50
+ @sheet_protection ||= SheetProtection.new
51
+ yield @sheet_protection if block_given?
52
+ @sheet_protection
53
+ end
54
+
55
+ # The sheet view object for this worksheet
56
+ # @return [SheetView]
57
+ # @see [SheetView]
58
+ def sheet_view
59
+ @sheet_view ||= SheetView.new
60
+ yield @sheet_view if block_given?
61
+ @sheet_view
62
+ end
10
63
 
11
64
  # The workbook that owns this worksheet
12
65
  # @return [Workbook]
@@ -14,49 +67,53 @@ module Axlsx
14
67
 
15
68
  # The tables in this worksheet
16
69
  # @return [Array] of Table
17
- attr_reader :tables
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
18
79
 
19
80
  # The rows in this worksheet
20
81
  # @note The recommended way to manage rows is Worksheet#add_row
21
82
  # @return [SimpleTypedList]
22
83
  # @see Worksheet#add_row
23
- attr_reader :rows
24
-
25
- # An array of content based calculated column widths.
26
- # @note a single auto fit data item is a hash with :longest => [String] and :sz=> [Integer] members.
27
- # @return [Array] of Hash
28
- attr_reader :auto_fit_data
84
+ def rows
85
+ @rows ||= SimpleTypedList.new Row
86
+ end
29
87
 
30
- # An array of merged cell ranges e.d "A1:B3"
31
- # Content and formatting is read from the first cell.
32
- # @return Array
33
- attr_reader :merged_cells
88
+ # returns the sheet data as columnw
89
+ def cols
90
+ @rows.transpose
91
+ end
34
92
 
35
93
  # An range that excel will apply an autfilter to "A1:B3"
36
94
  # This will turn filtering on for the cells in the range.
37
95
  # The first row is considered the header, while subsequent rows are considerd to be data.
38
- # @return Array
39
- attr_reader :auto_filter
40
-
41
- # Indicates if the worksheet should show gridlines or not
42
- # @return Boolean
43
- attr_reader :show_gridlines
44
-
45
-
46
- # Indicates if the worksheet is selected in the workbook
47
- # It is possible to have more than one worksheet selected, however it might cause issues
48
- # in some older versions of excel when using copy and paste.
49
- # @return Boolean
50
- attr_reader :selected
96
+ # @return String
97
+ def auto_filter
98
+ @auto_filter ||= AutoFilter.new self
99
+ end
51
100
 
52
- # Indicates if the worksheet should print in a single page
101
+ # Indicates if the worksheet will be fit by witdh or height to a specific number of pages.
102
+ # To alter the width or height for page fitting, please use page_setup.fit_to_widht or page_setup.fit_to_height.
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.
53
104
  # @return Boolean
54
- attr_reader :fit_to_page
105
+ # @see #page_setup
106
+ def fit_to_page?
107
+ return false unless self.instance_values.keys.include?('page_setup')
108
+ page_setup.fit_to_page?
109
+ end
55
110
 
56
111
 
57
112
  # Column info for the sheet
58
113
  # @return [SimpleTypedList]
59
- attr_reader :column_info
114
+ def column_info
115
+ @column_info ||= Cols.new self
116
+ end
60
117
 
61
118
  # Page margins for printing the worksheet.
62
119
  # @example
@@ -78,44 +135,51 @@ module Axlsx
78
135
  @page_margins ||= PageMargins.new
79
136
  yield @page_margins if block_given?
80
137
  @page_margins
81
-
82
138
  end
83
139
 
84
- # Creates a new worksheet.
85
- # @note the recommended way to manage worksheets is Workbook#add_worksheet
86
- # @see Workbook#add_worksheet
87
- # @option options [String] name The name of this worksheet.
88
- # @option options [Hash] page_margins A hash containing page margins for this worksheet. @see PageMargins
89
- # @option options [Boolean] show_gridlines indicates if gridlines should be shown for this sheet.
90
- def initialize(wb, options={})
91
- self.workbook = wb
92
- @workbook.worksheets << self
93
-
94
- @drawing = @page_margins = @auto_filter = nil
95
- @merged_cells = []
96
- @auto_fit_data = []
97
-
98
- @selected = false
99
- @show_gridlines = true
100
- self.name = "Sheet" + (index+1).to_s
101
- @page_margins = PageMargins.new options[:page_margins] if options[:page_margins]
102
-
103
- @rows = SimpleTypedList.new Row
104
- @column_info = SimpleTypedList.new Col
105
- # @cols = SimpleTypedList.new Cell
106
- @tables = SimpleTypedList.new Table
107
-
108
- if self.workbook.use_autowidth
109
- require 'RMagick' unless defined?(Magick)
110
- @magick_draw = Magick::Draw.new
111
- else
112
- @magick_draw = nil
113
- end
114
-
115
- options.each do |o|
116
- self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
117
- end
140
+ # Page setup settings for printing the worksheet.
141
+ # @example
142
+ # wb = Axlsx::Package.new.workbook
143
+ #
144
+ # # using options when creating the worksheet.
145
+ # ws = wb.add_worksheet :page_setup => {:fit_to_width => 2, :orientation => :landscape}
146
+ #
147
+ # # use the set method of the page_setup object
148
+ # ws.page_setup.set(:paper_width => "297mm", :paper_height => "210mm")
149
+ #
150
+ # # setup page in a block
151
+ # ws.page_setup do |page|
152
+ # page.scale = 80
153
+ # page.orientation = :portrait
154
+ # end
155
+ # @see PageSetup#initialize
156
+ # @return [PageSetup]
157
+ def page_setup
158
+ @page_setup ||= PageSetup.new
159
+ yield @page_setup if block_given?
160
+ @page_setup
161
+ end
118
162
 
163
+ # Options for printing the worksheet.
164
+ # @example
165
+ # wb = Axlsx::Package.new.workbook
166
+ # # using options when creating the worksheet.
167
+ # ws = wb.add_worksheet :print_options => {:grid_lines => true, :horizontal_centered => true}
168
+ #
169
+ # # use the set method of the page_margins object
170
+ # ws.print_options.set(:headings => true)
171
+ #
172
+ # # set page margins in a block
173
+ # ws.print_options do |options|
174
+ # options.horizontal_centered = true
175
+ # options.vertical_centered = true
176
+ # end
177
+ # @see PrintOptions#initialize
178
+ # @return [PrintOptions]
179
+ def print_options
180
+ @print_options ||= PrintOptions.new
181
+ yield @print_options if block_given?
182
+ @print_options
119
183
  end
120
184
 
121
185
  # convinience method to access all cells in this worksheet
@@ -132,73 +196,83 @@ module Axlsx
132
196
  # worksheet.merge_cells worksheet.rows.first.cells[(2..4)]
133
197
  # #alternatively you can do it from a single cell
134
198
  # worksheet["C1"].merge worksheet["E1"]
135
- # @param [Array, string]
199
+ # @param [Array, string] cells
136
200
  def merge_cells(cells)
137
- @merged_cells << if cells.is_a?(String)
138
- cells
139
- elsif cells.is_a?(Array)
140
- cells = cells.sort { |x, y| x.r <=> y.r }
141
- "#{cells.first.r}:#{cells.last.r}"
142
- end
201
+ merged_cells.add cells
143
202
  end
144
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
145
211
 
146
- # 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,
147
213
  # but at least a few other document readers expect this for conversion
148
- # @return [String] the A1:B2 style reference for the first and last row column intersection in the workbook
214
+ # @return [Dimension]
149
215
  def dimension
150
- "#{rows.first.cells.first.r}:#{rows.last.cells.last.r}"
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
151
224
  end
152
225
 
153
226
  # Indicates if gridlines should be shown in the sheet.
154
227
  # This is true by default.
155
228
  # @return [Boolean]
229
+ # @deprecated Use {SheetView#show_grid_lines=} instead.
156
230
  def show_gridlines=(v)
231
+ warn('axlsx::DEPRECIATED: Worksheet#show_gridlines= has been depreciated. This value can be set over SheetView#show_grid_lines=.')
157
232
  Axlsx::validate_boolean v
158
- @show_gridlines = v
233
+ sheet_view.show_grid_lines = v
159
234
  end
160
235
 
161
236
  # @see selected
162
237
  # @return [Boolean]
238
+ # @deprecated Use {SheetView#tab_selected=} instead.
163
239
  def selected=(v)
240
+ warn('axlsx::DEPRECIATED: Worksheet#selected= has been depreciated. This value can be set over SheetView#tab_selected=.')
164
241
  Axlsx::validate_boolean v
165
- @selected = v
242
+ sheet_view.tab_selected = v
166
243
  end
167
244
 
168
-
169
- # Indicates if the worksheet should print in a single page.
170
- # This is true by default.
171
- # @return [Boolean]
172
- def fit_to_page=(v)
173
- Axlsx::validate_boolean v
174
- @fit_to_page = v
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
175
251
  end
176
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
177
262
 
178
- # returns the column and row index for a named based cell
179
- # @param [String] name The cell or cell range to return. "A1" will return the first cell of the first row.
180
- # @return [Cell]
181
- def name_to_cell(name)
182
- col_index, row_index = *Axlsx::name_to_indices(name)
183
- r = rows[row_index]
184
- r.cells[col_index] if r
263
+ # (see #fit_to_page)
264
+ # @return [Boolean]
265
+ def fit_to_page=(v)
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.')
267
+ fit_to_page?
185
268
  end
186
269
 
187
270
  # The name of the worksheet
188
271
  # The name of a worksheet must be unique in the workbook, and must not exceed 31 characters
189
- # @param [String] v
190
- def name=(v)
191
- DataTypeValidator.validate "Worksheet.name", String, v
192
- raise ArgumentError, (ERR_SHEET_NAME_TOO_LONG % v) if v.size > 31
193
- sheet_names = @workbook.worksheets.map { |s| s.name }
194
- raise ArgumentError, (ERR_DUPLICATE_SHEET_NAME % v) if sheet_names.include?(v)
195
- @name=v
196
- end
197
-
198
- # The absolute auto filter range
199
- # @see auto_filter
200
- def abs_auto_filter
201
- 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)
202
276
  end
203
277
 
204
278
  # The auto filter range for the worksheet
@@ -206,7 +280,7 @@ module Axlsx
206
280
  # @see auto_filter
207
281
  def auto_filter=(v)
208
282
  DataTypeValidator.validate "Worksheet.auto_filter", String, v
209
- @auto_filter = v
283
+ auto_filter.range = v
210
284
  end
211
285
 
212
286
  # The part name of this worksheet
@@ -238,7 +312,7 @@ module Axlsx
238
312
  # @return [Drawing]
239
313
  # @see Worksheet#add_chart
240
314
  def drawing
241
- @drawing || @drawing = Axlsx::Drawing.new(self)
315
+ worksheet_drawing.drawing
242
316
  end
243
317
 
244
318
  # Adds a row to the worksheet and updates auto fit data.
@@ -282,70 +356,39 @@ module Axlsx
282
356
  # @option options [Float] height the row's height (in points)
283
357
  def add_row(values=[], options={})
284
358
  Row.new(self, values, options)
285
- update_column_info @rows.last.cells, options.delete(:widths) ||[], options.delete(:style) || []
286
- # update_auto_fit_data @rows.last.cells, options.delete(:widths) || []
359
+ update_column_info @rows.last.cells, options.delete(:widths) || []
287
360
  yield @rows.last if block_given?
288
361
  @rows.last
289
362
  end
290
363
 
291
364
  alias :<< :add_row
292
365
 
293
- # Set the style for cells in a specific row
294
- # @param [Integer] index or range of indexes in the table
295
- # @param [Integer] the cellXfs index
296
- # @option options [Integer] col_offset only cells after this column will be updated.
297
- # @note You can also specify the style in the add_row call
298
- # @see Worksheet#add_row
299
- # @see README.md for an example
300
- def row_style(index, style, options={})
301
- offset = options.delete(:col_offset) || 0
302
- rs = @rows[index]
303
- if rs.is_a?(Array)
304
- rs.each { |r| r.cells[(offset..-1)].each { |c| c.style = style } }
305
- else
306
- rs.cells[(offset..-1)].each { |c| c.style = style }
307
- end
308
- end
309
-
310
- # returns the sheet data as columnw
311
- def cols
312
- @rows.transpose
313
- end
314
-
315
-
316
- # Set the style for cells in a specific column
317
- # @param [Integer] index the index of the column
318
- # @param [Integer] the cellXfs index
319
- # @option options [Integer] row_offset only cells after this column will be updated.
320
- # @note You can also specify the style for specific columns in the call to add_row by using an array for the :styles option
321
- # @see Worksheet#add_row
322
- # @see README.md for an example
323
- def col_style(index, style, options={})
324
- offset = options.delete(:row_offset) || 0
325
- @rows[(offset..-1)].each do |r|
326
- cells = r.cells[index]
327
- next unless cells
328
- if cells.is_a?(Array)
329
- cells.each { |c| c.style = style }
330
- else
331
- cells.style = style
332
- end
333
- 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
334
381
  end
335
382
 
336
- # This is a helper method that Lets you specify a fixed width for multiple columns in a worksheet in one go.
337
- # Axlsx is sparse, so if you have not set data for a column, you cannot set the width.
338
- # Setting a fixed column width to nil will revert the behaviour back to calculating the width for you.
339
- # @example This would set the first and third column widhts but leave the second column in autofit state.
340
- # ws.column_widths 7.2, nil, 3
341
- # @note For updating only a single column it is probably easier to just set ws.auto_fit_data[col_index][:fixed] directly
342
- # @param [Integer|Float|Fixnum|nil] values
343
- def column_widths(*args)
344
- args.each_with_index do |value, index|
345
- raise ArgumentError, "Invalid column specification" unless index < @column_info.size
346
- Axlsx::validate_unsigned_numeric(value) unless value == nil
347
- @column_info[index].width = value
348
- 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
349
392
  end
350
393
 
351
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.
@@ -362,123 +405,226 @@ module Axlsx
362
405
  # @see Line3DChart
363
406
  # @see README for examples
364
407
  def add_chart(chart_type, options={})
365
- chart = drawing.add_chart(chart_type, options)
408
+ chart = worksheet_drawing.add_chart(chart_type, options)
366
409
  yield chart if block_given?
367
410
  chart
368
411
  end
369
412
 
370
413
  # needs documentation
371
414
  def add_table(ref, options={})
372
- table = Table.new(ref, self, options)
373
- @tables << table
374
- yield table if block_given?
375
- table
415
+ tables << Table.new(ref, self, options)
416
+ yield tables.last if block_given?
417
+ tables.last
418
+ end
419
+
420
+ # Shortcut to worsksheet_comments#add_comment
421
+ def add_comment(options={})
422
+ worksheet_comments.add_comment(options)
376
423
  end
377
424
 
378
425
  # Adds a media item to the worksheets drawing
379
- # @param [Class] media_type
380
- # @option options [] unknown
426
+ # @option [Hash] options options passed to drawing.add_image
381
427
  def add_image(options={})
382
- image = drawing.add_image(options)
428
+ image = worksheet_drawing.add_image(options)
383
429
  yield image if block_given?
384
430
  image
385
431
  end
386
432
 
387
- # Serializes the object
388
- # @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
389
478
  # @return [String]
390
479
  def to_xml_string
391
480
  str = '<?xml version="1.0" encoding="UTF-8"?>'
392
- str.concat "<worksheet xmlns=\"%s\" xmlns:r=\"%s\">" % [XML_NS, XML_NS_R]
393
- str.concat "<sheetPr><pageSetUpPr fitToPage=\"%s\"></pageSetUpPr></sheetPr>" % fit_to_page if fit_to_page
394
- str.concat "<dimension ref=\"%s\"></dimension>" % dimension unless rows.size == 0
395
- str.concat "<sheetViews><sheetView tabSelected='%s' workbookViewId='0' showGridLines='%s'><selection activeCell=\"A1\" sqref=\"A1\"/></sheetView></sheetViews>" % [@selected, show_gridlines]
396
-
397
- if @column_info.size > 0
398
- str << "<cols>"
399
- @column_info.each { |col| col.to_xml_string(str) }
400
- str.concat '</cols>'
481
+ str << worksheet_node
482
+ serializable_parts.each do |item|
483
+ item.to_xml_string(str) if item
401
484
  end
402
- str.concat '<sheetData>'
403
- @rows.each_with_index { |row, index| row.to_xml_string(index, str) }
404
- str.concat '</sheetData>'
405
- page_margins.to_xml_string(str) if @page_margins
406
- str.concat "<autoFilter ref='%s'></autoFilter>" % @auto_filter if @auto_filter
407
- str.concat "<mergeCells count='%s'>%s</mergeCells>" % [@merged_cells.size, @merged_cells.reduce('') { |memo, obj| "<mergeCell ref='%s'></mergeCell>" % obj } ] unless @merged_cells.empty?
408
- str.concat "<drawing r:id='rId1'></drawing>" if @drawing
409
- unless @tables.empty?
410
- str.concat "<tableParts count='%s'>%s</tableParts>" % [@tables.size, @tables.reduce('') { |memo, obj| memo += "<tablePart r:id='%s'/>" % obj.rId }]
411
- end
412
- str + '</worksheet>'
485
+ str << '</worksheet>'
486
+ str.gsub(/[[:cntrl:]]/,'')
413
487
  end
414
488
 
415
489
  # The worksheet relationships. This is managed automatically by the worksheet
416
490
  # @return [Relationships]
417
491
  def relationships
418
- r = Relationships.new
419
- @tables.each do |table|
420
- r << Relationship.new(TABLE_R, "../#{table.pn}")
421
- end
422
- r << Relationship.new(DRAWING_R, "../#{@drawing.pn}") if @drawing
423
- r
492
+ r = Relationships.new
493
+ r + [tables.relationships,
494
+ worksheet_comments.relationships,
495
+ worksheet_drawing.relationship].flatten.compact || []
496
+ r
424
497
  end
425
498
 
426
499
  # Returns the cell or cells defined using excel style A1:B3 references.
427
500
  # @param [String|Integer] cell_def the string defining the cell or range of cells, or the rownumber
428
501
  # @return [Cell, Array]
429
-
430
502
  def [] (cell_def)
431
503
  return rows[cell_def] if cell_def.is_a?(Integer)
432
-
433
- parts = cell_def.split(':')
434
- first = name_to_cell parts[0]
504
+ parts = cell_def.split(':').map{ |part| name_to_cell part }
435
505
  if parts.size == 1
436
- first
506
+ parts.first
437
507
  else
438
- cells = []
439
- last = name_to_cell(parts[1])
440
- rows[(first.row.index..last.row.index)].each do |r|
441
- r.cells[(first.index..last.index)].each do |c|
442
- cells << c
443
- end
444
- end
445
- cells
508
+ range(*parts)
446
509
  end
447
510
  end
448
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
+
449
535
  private
450
536
 
451
- # assigns the owner workbook for this worksheet
452
- 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
453
545
 
454
546
 
455
- def update_column_info(cells, widths=[], style=[])
456
- styles = self.workbook.styles
457
- cellXfs, fonts = styles.cellXfs, styles.fonts
458
- sz = 11
459
- cells.each_with_index do |cell, index|
460
- @column_info[index] ||= Col.new index+1, index+1
461
- col = @column_info[index]
462
- width = widths[index]
463
- col.width = width if [Integer, Float, Fixnum].include?(width.class)
464
- c_style = style[index] if [Integer, Fixnum].include?(style[index].class)
465
- next if width == :ignore || col.width || (cell.value.is_a?(String) && cell.value.start_with?('='))
466
- if self.workbook.use_autowidth
467
- cell_xf = cellXfs[(c_style || 0)]
468
- font = fonts[(cell_xf.fontId || 0)]
469
- sz = cell.sz || font.sz || sz
470
- col.width = [(col.width || 0), calculate_width(cell.value.to_s, sz)].max
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
555
+
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
471
562
  end
472
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
592
+ end
593
+
594
+
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
607
+ end
608
+
609
+ # The comments associated with this worksheet
610
+ # @return [SimpleTypedList]
611
+ def worksheet_comments
612
+ @worksheet_comments ||= WorksheetComments.new self
473
613
  end
474
614
 
475
- def calculate_width(text, sz)
476
- mdw_count, font_scale, mdw = 0, sz/11.0, 6.0
477
- mdw_count = text.scan(/./mu).reduce(0) do | count, char |
478
- count +=1 if @magick_draw.get_type_metrics(char).max_advance >= mdw
479
- count
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)
480
622
  end
481
- ((mdw_count * mdw + 5) / mdw * 256) / 256.0 * font_scale
482
623
  end
624
+
625
+ def find_or_create_column_info(index)
626
+ column_info[index] ||= Col.new(index + 1, index + 1)
627
+ end
628
+
483
629
  end
484
630
  end