axlsx 1.1.8 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +5 -2
- data/CHANGELOG.md +39 -0
- data/README.md +48 -46
- data/Rakefile +3 -3
- data/examples/basic_charts.rb +8 -0
- data/examples/example.rb +7 -1
- data/examples/example.xlsx +0 -0
- data/examples/example_streamed.xlsx +0 -0
- data/examples/no-use_autowidth.xlsx +0 -0
- data/examples/scraping_html.rb +91 -0
- data/examples/shared_strings_example.xlsx +0 -0
- data/lib/axlsx.rb +14 -8
- data/lib/axlsx/drawing/bar_3D_chart.rb +2 -8
- data/lib/axlsx/drawing/chart.rb +29 -25
- data/lib/axlsx/drawing/d_lbls.rb +100 -0
- data/lib/axlsx/drawing/drawing.rb +2 -0
- data/lib/axlsx/drawing/line_3D_chart.rb +2 -9
- data/lib/axlsx/drawing/pie_3D_chart.rb +3 -0
- data/lib/axlsx/drawing/scatter_chart.rb +2 -8
- data/lib/axlsx/drawing/two_cell_anchor.rb +38 -1
- data/lib/axlsx/util/simple_typed_list.rb +13 -6
- data/lib/axlsx/version.rb +2 -7
- data/lib/axlsx/workbook/defined_name.rb +174 -0
- data/lib/axlsx/workbook/defined_names.rb +21 -0
- data/lib/axlsx/workbook/workbook.rb +39 -13
- data/lib/axlsx/workbook/worksheet/auto_filter.rb +34 -0
- data/lib/axlsx/workbook/worksheet/cell.rb +24 -1
- data/lib/axlsx/workbook/worksheet/col.rb +15 -0
- data/lib/axlsx/workbook/worksheet/cols.rb +20 -0
- data/lib/axlsx/workbook/worksheet/comments.rb +8 -0
- data/lib/axlsx/workbook/worksheet/conditional_formattings.rb +25 -0
- data/lib/axlsx/workbook/worksheet/data_validations.rb +28 -0
- data/lib/axlsx/workbook/worksheet/dimension.rb +65 -0
- data/lib/axlsx/workbook/worksheet/merged_cells.rb +35 -0
- data/lib/axlsx/workbook/worksheet/protected_ranges.rb +34 -0
- data/lib/axlsx/workbook/worksheet/row.rb +1 -1
- data/lib/axlsx/workbook/worksheet/sheet_data.rb +25 -0
- data/lib/axlsx/workbook/worksheet/sheet_pr.rb +24 -0
- data/lib/axlsx/workbook/worksheet/tables.rb +31 -0
- data/lib/axlsx/workbook/worksheet/worksheet.rb +263 -380
- data/lib/axlsx/workbook/worksheet/worksheet_comments.rb +57 -0
- data/lib/axlsx/workbook/worksheet/worksheet_drawing.rb +64 -0
- data/test/drawing/tc_bar_series.rb +1 -1
- data/test/drawing/tc_chart.rb +7 -1
- data/test/drawing/tc_d_lbls.rb +47 -0
- data/test/drawing/tc_drawing.rb +5 -4
- data/test/drawing/tc_line_series.rb +1 -1
- data/test/drawing/tc_pie_3D_chart.rb +1 -1
- data/test/drawing/tc_pie_series.rb +1 -1
- data/test/drawing/tc_scatter_series.rb +1 -1
- data/test/drawing/tc_series.rb +1 -1
- data/test/tc_package.rb +16 -1
- data/test/workbook/tc_defined_name.rb +41 -0
- data/test/workbook/tc_workbook.rb +5 -3
- data/test/workbook/worksheet/table/tc_table.rb +0 -8
- data/test/workbook/worksheet/tc_cell.rb +2 -4
- data/test/workbook/worksheet/tc_protected_range.rb +0 -1
- data/test/workbook/worksheet/tc_row.rb +2 -2
- data/test/workbook/worksheet/tc_worksheet.rb +19 -21
- metadata +48 -7
@@ -134,7 +134,7 @@ module Axlsx
|
|
134
134
|
# @return [Cell]
|
135
135
|
def add_cell(value="", options={})
|
136
136
|
c = Cell.new(self, value, options)
|
137
|
-
worksheet.send(:update_column_info, self.cells, []
|
137
|
+
worksheet.send(:update_column_info, self.cells, [])
|
138
138
|
c
|
139
139
|
end
|
140
140
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Axlsx
|
2
|
+
|
3
|
+
# This class manages the serialization of rows for worksheets
|
4
|
+
class SheetData
|
5
|
+
|
6
|
+
# Creates a new SheetData object
|
7
|
+
# @param [Worksheet] worksheet The worksheet that owns this sheet data.
|
8
|
+
def initialize(worksheet)
|
9
|
+
raise ArgumentError, "you must provide a worksheet" unless worksheet.is_a?(Worksheet)
|
10
|
+
@worksheet = worksheet
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :worksheet
|
14
|
+
|
15
|
+
# Serialize the sheet data
|
16
|
+
# @param [String] str the string this objects serializaton will be concacted to.
|
17
|
+
# @return [String]
|
18
|
+
def to_xml_string(str = '')
|
19
|
+
str << '<sheetData>'
|
20
|
+
worksheet.rows.each_with_index{ |row, index| row.to_xml_string(index, str) }
|
21
|
+
str << '</sheetData>'
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Axlsx
|
2
|
+
|
3
|
+
# The SheetPr class manages serialization fo a worksheet's sheetPr element.
|
4
|
+
# Only fit_to_page is implemented
|
5
|
+
class SheetPr
|
6
|
+
|
7
|
+
# Creates a new SheetPr object
|
8
|
+
# @param [Worksheet] worksheet The worksheet that owns this SheetPr object
|
9
|
+
def initialize(worksheet)
|
10
|
+
raise ArgumentError, "you must provide a worksheet" unless worksheet.is_a?(Worksheet)
|
11
|
+
@worksheet = worksheet
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :worksheet
|
15
|
+
|
16
|
+
# Serialize the object
|
17
|
+
# @param [String] str serialized output will be appended to this object if provided.
|
18
|
+
# @return [String]
|
19
|
+
def to_xml_string(str = '')
|
20
|
+
return unless worksheet.fit_to_page?
|
21
|
+
str << "<sheetPr><pageSetUpPr fitToPage=\"%s\"></pageSetUpPr></sheetPr>" % worksheet.fit_to_page?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Axlsx
|
2
|
+
|
3
|
+
# A simple, self serializing class for storing tables
|
4
|
+
class Tables < SimpleTypedList
|
5
|
+
|
6
|
+
# creates a new Tables object
|
7
|
+
def initialize(worksheet)
|
8
|
+
raise ArgumentError, "you must provide a worksheet" unless worksheet.is_a?(Worksheet)
|
9
|
+
super Table
|
10
|
+
@worksheet = worksheet
|
11
|
+
end
|
12
|
+
|
13
|
+
# The worksheet that owns this collection of tables
|
14
|
+
# @return [Worksheet]
|
15
|
+
attr_reader :worksheet
|
16
|
+
|
17
|
+
# returns the relationships required by this collection
|
18
|
+
def relationships
|
19
|
+
return [] if empty?
|
20
|
+
map{ |table| Relationship.new(TABLE_R, "../#{table.pn}") }
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_xml_string(str = "")
|
24
|
+
return if empty?
|
25
|
+
str << "<tableParts count='#{size}'>"
|
26
|
+
@list.each { |table| str << "<tablePart r:id='#{table.rId}'/>" }
|
27
|
+
str << '</tableParts>'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -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
|
-
|
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]
|
@@ -17,12 +52,6 @@ module Axlsx
|
|
17
52
|
@sheet_protection
|
18
53
|
end
|
19
54
|
|
20
|
-
# A collection of protected ranges in the worksheet
|
21
|
-
# @note The recommended way to manage protected ranges is with Worksheet#protect_range
|
22
|
-
# @see Worksheet#protect_range
|
23
|
-
# @return [SimpleTypedList] The protected ranges for this worksheet
|
24
|
-
attr_reader :protected_ranges
|
25
|
-
|
26
55
|
# The sheet view object for this worksheet
|
27
56
|
# @return [SheetView]
|
28
57
|
# @see [SheetView]
|
@@ -38,50 +67,35 @@ module Axlsx
|
|
38
67
|
|
39
68
|
# The tables in this worksheet
|
40
69
|
# @return [Array] of Table
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
46
79
|
|
47
80
|
# The rows in this worksheet
|
48
81
|
# @note The recommended way to manage rows is Worksheet#add_row
|
49
82
|
# @return [SimpleTypedList]
|
50
83
|
# @see Worksheet#add_row
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# @note a single auto fit data item is a hash with :longest => [String] and :sz=> [Integer] members.
|
55
|
-
# @return [Array] of Hash
|
56
|
-
attr_reader :auto_fit_data
|
84
|
+
def rows
|
85
|
+
@rows ||= SimpleTypedList.new Row
|
86
|
+
end
|
57
87
|
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
|
88
|
+
# returns the sheet data as columnw
|
89
|
+
def cols
|
90
|
+
@rows.transpose
|
91
|
+
end
|
62
92
|
|
63
93
|
# An range that excel will apply an autfilter to "A1:B3"
|
64
94
|
# This will turn filtering on for the cells in the range.
|
65
95
|
# The first row is considered the header, while subsequent rows are considerd to be data.
|
66
|
-
# @return
|
67
|
-
|
68
|
-
|
69
|
-
# Indicates if the worksheet should show gridlines or not
|
70
|
-
# @return Boolean
|
71
|
-
# @deprecated Use {SheetView#show_grid_lines} instead.
|
72
|
-
def show_gridlines
|
73
|
-
warn('axlsx::DEPRECIATED: Worksheet#show_gridlines has been depreciated. This value can get over SheetView#show_grid_lines.')
|
74
|
-
sheet_view.show_grid_lines
|
75
|
-
end
|
76
|
-
|
77
|
-
# Indicates if the worksheet is selected in the workbook
|
78
|
-
# It is possible to have more than one worksheet selected, however it might cause issues
|
79
|
-
# in some older versions of excel when using copy and paste.
|
80
|
-
# @return Boolean
|
81
|
-
# @deprecated Use {SheetView#tab_selected} instead.
|
82
|
-
def selected
|
83
|
-
warn('axlsx::DEPRECIATED: Worksheet#selected has been depreciated. This value can get over SheetView#tab_selected.')
|
84
|
-
sheet_view.tab_selected
|
96
|
+
# @return String
|
97
|
+
def auto_filter
|
98
|
+
@auto_filter ||= AutoFilter.new self
|
85
99
|
end
|
86
100
|
|
87
101
|
# Indicates if the worksheet will be fit by witdh or height to a specific number of pages.
|
@@ -90,14 +104,16 @@ module Axlsx
|
|
90
104
|
# @return Boolean
|
91
105
|
# @see #page_setup
|
92
106
|
def fit_to_page?
|
93
|
-
return false unless
|
94
|
-
|
107
|
+
return false unless self.instance_values.keys.include?('page_setup')
|
108
|
+
page_setup.fit_to_page?
|
95
109
|
end
|
96
110
|
|
97
111
|
|
98
112
|
# Column info for the sheet
|
99
113
|
# @return [SimpleTypedList]
|
100
|
-
|
114
|
+
def column_info
|
115
|
+
@column_info ||= Cols.new self
|
116
|
+
end
|
101
117
|
|
102
118
|
# Page margins for printing the worksheet.
|
103
119
|
# @example
|
@@ -166,77 +182,12 @@ module Axlsx
|
|
166
182
|
@print_options
|
167
183
|
end
|
168
184
|
|
169
|
-
# definition of characters which are less than the maximum width of 0-9 in the default font for use in String#count.
|
170
|
-
# This is used for autowidth calculations
|
171
|
-
# @return [String]
|
172
|
-
def self.thin_chars
|
173
|
-
@thin_chars ||= "^.acefijklrstxyzFIJL()-"
|
174
|
-
end
|
175
|
-
|
176
|
-
# Creates a new worksheet.
|
177
|
-
# @note the recommended way to manage worksheets is Workbook#add_worksheet
|
178
|
-
# @see Workbook#add_worksheet
|
179
|
-
# @option options [String] name The name of this worksheet.
|
180
|
-
# @option options [Hash] page_margins A hash containing page margins for this worksheet. @see PageMargins
|
181
|
-
# @option options [Hash] print_options A hash containing print options for this worksheet. @see PrintOptions
|
182
|
-
# @option options [Boolean] show_gridlines indicates if gridlines should be shown for this sheet.
|
183
|
-
def initialize(wb, options={})
|
184
|
-
self.workbook = wb
|
185
|
-
@workbook.worksheets << self
|
186
|
-
@page_marging = @page_setup = @print_options = nil
|
187
|
-
@drawing = @page_margins = @auto_filter = @sheet_protection = @sheet_view = nil
|
188
|
-
@merged_cells = []
|
189
|
-
@auto_fit_data = []
|
190
|
-
@conditional_formattings = []
|
191
|
-
@data_validations = []
|
192
|
-
@comments = Comments.new(self)
|
193
|
-
self.name = "Sheet" + (index+1).to_s
|
194
|
-
@page_margins = PageMargins.new options[:page_margins] if options[:page_margins]
|
195
|
-
@page_setup = PageSetup.new options[:page_setup] if options[:page_setup]
|
196
|
-
@print_options = PrintOptions.new options[:print_options] if options[:print_options]
|
197
|
-
@rows = SimpleTypedList.new Row
|
198
|
-
@column_info = SimpleTypedList.new Col
|
199
|
-
@protected_ranges = SimpleTypedList.new ProtectedRange
|
200
|
-
@tables = SimpleTypedList.new Table
|
201
|
-
|
202
|
-
options.each do |o|
|
203
|
-
self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
185
|
# convinience method to access all cells in this worksheet
|
208
186
|
# @return [Array] cells
|
209
187
|
def cells
|
210
188
|
rows.flatten
|
211
189
|
end
|
212
190
|
|
213
|
-
# Add conditional formatting to this worksheet.
|
214
|
-
#
|
215
|
-
# @param [String] cells The range to apply the formatting to
|
216
|
-
# @param [Array|Hash] rules An array of hashes (or just one) to create Conditional formatting rules from.
|
217
|
-
# @example This would format column A whenever it is FALSE.
|
218
|
-
# # for a longer example, see examples/example_conditional_formatting.rb (link below)
|
219
|
-
# worksheet.add_conditional_formatting( "A1:A1048576", { :type => :cellIs, :operator => :equal, :formula => "FALSE", :dxfId => 1, :priority => 1 }
|
220
|
-
#
|
221
|
-
# @see ConditionalFormattingRule#initialize
|
222
|
-
# @see file:examples/example_conditional_formatting.rb
|
223
|
-
def add_conditional_formatting(cells, rules)
|
224
|
-
cf = ConditionalFormatting.new( :sqref => cells )
|
225
|
-
cf.add_rules rules
|
226
|
-
@conditional_formattings << cf
|
227
|
-
end
|
228
|
-
|
229
|
-
# Add data validation to this worksheet.
|
230
|
-
#
|
231
|
-
# @param [String] cells The cells the validation will apply to.
|
232
|
-
# @param [hash] data_validation options defining the validation to apply.
|
233
|
-
# @see examples/data_validation.rb for an example
|
234
|
-
def add_data_validation(cells, data_validation)
|
235
|
-
dv = DataValidation.new(data_validation)
|
236
|
-
dv.sqref = cells
|
237
|
-
@data_validations << dv
|
238
|
-
end
|
239
|
-
|
240
191
|
# Creates merge information for this worksheet.
|
241
192
|
# Cells can be merged by calling the merge_cells method on a worksheet.
|
242
193
|
# @example This would merge the three cells C1..E1 #
|
@@ -247,12 +198,7 @@ module Axlsx
|
|
247
198
|
# worksheet["C1"].merge worksheet["E1"]
|
248
199
|
# @param [Array, string] cells
|
249
200
|
def merge_cells(cells)
|
250
|
-
|
251
|
-
cells
|
252
|
-
elsif cells.is_a?(Array)
|
253
|
-
cells = cells.sort { |x, y| [x.index, x.row.index] <=> [y.index, y.row.index] }
|
254
|
-
"#{cells.first.r}:#{cells.last.r}"
|
255
|
-
end
|
201
|
+
merged_cells.add cells
|
256
202
|
end
|
257
203
|
|
258
204
|
# Adds a new protected cell range to the worksheet. Note that protected ranges are only in effect when sheet protection is enabled.
|
@@ -260,23 +206,21 @@ module Axlsx
|
|
260
206
|
# @return [ProtectedRange]
|
261
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.
|
262
208
|
def protect_range(cells)
|
263
|
-
|
264
|
-
cells
|
265
|
-
elsif cells.is_a?(SimpleTypedList)
|
266
|
-
cells = cells.sort { |x, y| [x.index, x.row.index] <=> [y.index, y.row.index] }
|
267
|
-
"#{cells.first.r}:#{cells.last.r}"
|
268
|
-
end
|
269
|
-
@protected_ranges << ProtectedRange.new(:sqref => sqref, :name => 'Range#{@protected_ranges.size}')
|
270
|
-
@protected_ranges.last
|
209
|
+
protected_ranges.add_range(cells)
|
271
210
|
end
|
272
211
|
|
273
|
-
# The
|
212
|
+
# The dimensions of a worksheet. This is not actually a required element by the spec,
|
274
213
|
# but at least a few other document readers expect this for conversion
|
275
|
-
# @return [
|
214
|
+
# @return [Dimension]
|
276
215
|
def dimension
|
277
|
-
|
278
|
-
|
279
|
-
|
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
|
280
224
|
end
|
281
225
|
|
282
226
|
# Indicates if gridlines should be shown in the sheet.
|
@@ -298,6 +242,23 @@ module Axlsx
|
|
298
242
|
sheet_view.tab_selected = v
|
299
243
|
end
|
300
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
|
301
262
|
|
302
263
|
# (see #fit_to_page)
|
303
264
|
# @return [Boolean]
|
@@ -306,33 +267,12 @@ module Axlsx
|
|
306
267
|
fit_to_page?
|
307
268
|
end
|
308
269
|
|
309
|
-
|
310
|
-
# returns the column and row index for a named based cell
|
311
|
-
# @param [String] name The cell or cell range to return. "A1" will return the first cell of the first row.
|
312
|
-
# @return [Cell]
|
313
|
-
def name_to_cell(name)
|
314
|
-
col_index, row_index = *Axlsx::name_to_indices(name)
|
315
|
-
r = rows[row_index]
|
316
|
-
r.cells[col_index] if r
|
317
|
-
end
|
318
|
-
|
319
270
|
# The name of the worksheet
|
320
271
|
# The name of a worksheet must be unique in the workbook, and must not exceed 31 characters
|
321
|
-
# @param [String]
|
322
|
-
def name=(
|
323
|
-
|
324
|
-
|
325
|
-
raise ArgumentError, (ERR_SHEET_NAME_COLON_FORBIDDEN % v) if v.include? ':'
|
326
|
-
v = Axlsx::coder.encode(v)
|
327
|
-
sheet_names = @workbook.worksheets.map { |s| s.name }
|
328
|
-
raise ArgumentError, (ERR_DUPLICATE_SHEET_NAME % v) if sheet_names.include?(v)
|
329
|
-
@name=v
|
330
|
-
end
|
331
|
-
|
332
|
-
# The absolute auto filter range
|
333
|
-
# @see auto_filter
|
334
|
-
def abs_auto_filter
|
335
|
-
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)
|
336
276
|
end
|
337
277
|
|
338
278
|
# The auto filter range for the worksheet
|
@@ -340,7 +280,7 @@ module Axlsx
|
|
340
280
|
# @see auto_filter
|
341
281
|
def auto_filter=(v)
|
342
282
|
DataTypeValidator.validate "Worksheet.auto_filter", String, v
|
343
|
-
|
283
|
+
auto_filter.range = v
|
344
284
|
end
|
345
285
|
|
346
286
|
# The part name of this worksheet
|
@@ -372,7 +312,7 @@ module Axlsx
|
|
372
312
|
# @return [Drawing]
|
373
313
|
# @see Worksheet#add_chart
|
374
314
|
def drawing
|
375
|
-
|
315
|
+
worksheet_drawing.drawing
|
376
316
|
end
|
377
317
|
|
378
318
|
# Adds a row to the worksheet and updates auto fit data.
|
@@ -416,73 +356,39 @@ module Axlsx
|
|
416
356
|
# @option options [Float] height the row's height (in points)
|
417
357
|
def add_row(values=[], options={})
|
418
358
|
Row.new(self, values, options)
|
419
|
-
update_column_info @rows.last.cells, options.delete(:widths) ||
|
420
|
-
# update_auto_fit_data @rows.last.cells, options.delete(:widths) || []
|
359
|
+
update_column_info @rows.last.cells, options.delete(:widths) || []
|
421
360
|
yield @rows.last if block_given?
|
422
361
|
@rows.last
|
423
362
|
end
|
424
363
|
|
425
364
|
alias :<< :add_row
|
426
365
|
|
427
|
-
#
|
428
|
-
#
|
429
|
-
# @param [
|
430
|
-
# @param [Hash]
|
431
|
-
# @
|
432
|
-
#
|
433
|
-
#
|
434
|
-
#
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
end
|
443
|
-
end
|
444
|
-
|
445
|
-
# returns the sheet data as columnw
|
446
|
-
def cols
|
447
|
-
@rows.transpose
|
448
|
-
end
|
449
|
-
|
450
|
-
|
451
|
-
# Set the style for cells in a specific column
|
452
|
-
# @param [Integer] index the index of the column
|
453
|
-
# @param [Integer] style the cellXfs index
|
454
|
-
# @param [Hash] options
|
455
|
-
# @option [Integer] :row_offset only cells after this column will be updated.
|
456
|
-
# @note You can also specify the style for specific columns in the call to add_row by using an array for the :styles option
|
457
|
-
# @see Worksheet#add_row
|
458
|
-
# @see README.md for an example
|
459
|
-
def col_style(index, style, options={})
|
460
|
-
offset = options.delete(:row_offset) || 0
|
461
|
-
@rows[(offset..-1)].each do |r|
|
462
|
-
cells = r.cells[index]
|
463
|
-
next unless cells
|
464
|
-
if cells.is_a?(Array)
|
465
|
-
cells.each { |c| c.style = style }
|
466
|
-
else
|
467
|
-
cells.style = style
|
468
|
-
end
|
469
|
-
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
|
470
381
|
end
|
471
382
|
|
472
|
-
#
|
473
|
-
#
|
474
|
-
#
|
475
|
-
# @
|
476
|
-
#
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
next if value == nil
|
482
|
-
Axlsx::validate_unsigned_numeric(value) unless value == nil
|
483
|
-
@column_info[index] ||= Col.new index+1, index+1
|
484
|
-
@column_info[index].width = value
|
485
|
-
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
|
486
392
|
end
|
487
393
|
|
488
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.
|
@@ -499,68 +405,84 @@ module Axlsx
|
|
499
405
|
# @see Line3DChart
|
500
406
|
# @see README for examples
|
501
407
|
def add_chart(chart_type, options={})
|
502
|
-
chart =
|
408
|
+
chart = worksheet_drawing.add_chart(chart_type, options)
|
503
409
|
yield chart if block_given?
|
504
410
|
chart
|
505
411
|
end
|
506
412
|
|
507
413
|
# needs documentation
|
508
414
|
def add_table(ref, options={})
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
table
|
415
|
+
tables << Table.new(ref, self, options)
|
416
|
+
yield tables.last if block_given?
|
417
|
+
tables.last
|
513
418
|
end
|
514
419
|
|
515
|
-
|
516
|
-
# Shortcut to comments#add_comment
|
420
|
+
# Shortcut to worsksheet_comments#add_comment
|
517
421
|
def add_comment(options={})
|
518
|
-
|
422
|
+
worksheet_comments.add_comment(options)
|
519
423
|
end
|
520
424
|
|
521
425
|
# Adds a media item to the worksheets drawing
|
522
426
|
# @option [Hash] options options passed to drawing.add_image
|
523
427
|
def add_image(options={})
|
524
|
-
image =
|
428
|
+
image = worksheet_drawing.add_image(options)
|
525
429
|
yield image if block_given?
|
526
430
|
image
|
527
431
|
end
|
528
432
|
|
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
|
+
|
529
476
|
# Serializes the worksheet object to an xml string
|
530
477
|
# This intentionally does not use nokogiri for performance reasons
|
531
478
|
# @return [String]
|
532
479
|
def to_xml_string
|
533
480
|
str = '<?xml version="1.0" encoding="UTF-8"?>'
|
534
481
|
str << worksheet_node
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
str << cols_node
|
539
|
-
str << sheet_data_node
|
540
|
-
|
541
|
-
str << auto_filter_node
|
542
|
-
@sheet_protection.to_xml_string(str) if @sheet_protection
|
543
|
-
str << protected_ranges_node
|
544
|
-
str << merged_cells_node
|
545
|
-
@print_options.to_xml_string(str) if @print_options
|
546
|
-
page_margins.to_xml_string(str) if @page_margins
|
547
|
-
page_setup.to_xml_string(str) if @page_setup
|
548
|
-
str << drawing_node
|
549
|
-
str << legacy_drawing_node
|
550
|
-
str << table_parts_node
|
551
|
-
str << conditional_formattings_node
|
552
|
-
str << data_validations_node
|
482
|
+
serializable_parts.each do |item|
|
483
|
+
item.to_xml_string(str) if item
|
484
|
+
end
|
553
485
|
str << '</worksheet>'
|
554
|
-
# User reported that when parsing some old data that had control characters excel chokes.
|
555
|
-
# All of the following are defined as illegal xml characters in the xml spec, but for now I am only dealing with control
|
556
|
-
# characters. Thanks to asakusarb and @hsbt's flash of code on the screen!
|
557
|
-
# [#x1-#x8], [#xB-#xC], [#xE-#x1F], [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF],
|
558
|
-
# [#x1FFFE-#x1FFFF], [#x2FFFE-#x2FFFF], [#x3FFFE-#x3FFFF],
|
559
|
-
# [#x4FFFE-#x4FFFF], [#x5FFFE-#x5FFFF], [#x6FFFE-#x6FFFF],
|
560
|
-
# [#x7FFFE-#x7FFFF], [#x8FFFE-#x8FFFF], [#x9FFFE-#x9FFFF],
|
561
|
-
# [#xAFFFE-#xAFFFF], [#xBFFFE-#xBFFFF], [#xCFFFE-#xCFFFF],
|
562
|
-
# [#xDFFFE-#xDFFFF], [#xEFFFE-#xEFFFF], [#xFFFFE-#xFFFFF],
|
563
|
-
# [#x10FFFE-#x10FFFF].
|
564
486
|
str.gsub(/[[:cntrl:]]/,'')
|
565
487
|
end
|
566
488
|
|
@@ -568,15 +490,9 @@ module Axlsx
|
|
568
490
|
# @return [Relationships]
|
569
491
|
def relationships
|
570
492
|
r = Relationships.new
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
r << Relationship.new(VML_DRAWING_R, "../#{@comments.vml_drawing.pn}") if @comments.size > 0
|
576
|
-
r << Relationship.new(COMMENT_R, "../#{@comments.pn}") if @comments.size > 0
|
577
|
-
r << Relationship.new(COMMENT_R_NULL, "NULL") if @comments.size > 0
|
578
|
-
|
579
|
-
r << Relationship.new(DRAWING_R, "../#{@drawing.pn}") if @drawing
|
493
|
+
r + [tables.relationships,
|
494
|
+
worksheet_comments.relationships,
|
495
|
+
worksheet_drawing.relationship].flatten.compact || []
|
580
496
|
r
|
581
497
|
end
|
582
498
|
|
@@ -585,163 +501,130 @@ module Axlsx
|
|
585
501
|
# @return [Cell, Array]
|
586
502
|
def [] (cell_def)
|
587
503
|
return rows[cell_def] if cell_def.is_a?(Integer)
|
588
|
-
|
589
|
-
parts = cell_def.split(':')
|
590
|
-
first = name_to_cell parts[0]
|
504
|
+
parts = cell_def.split(':').map{ |part| name_to_cell part }
|
591
505
|
if parts.size == 1
|
592
|
-
first
|
506
|
+
parts.first
|
593
507
|
else
|
594
|
-
|
595
|
-
last = name_to_cell(parts[1])
|
596
|
-
rows[(first.row.index..last.row.index)].each do |r|
|
597
|
-
r.cells[(first.index..last.index)].each do |c|
|
598
|
-
cells << c
|
599
|
-
end
|
600
|
-
end
|
601
|
-
cells
|
508
|
+
range(*parts)
|
602
509
|
end
|
603
510
|
end
|
604
511
|
|
605
|
-
|
606
|
-
|
607
|
-
#
|
608
|
-
|
609
|
-
|
610
|
-
|
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
|
611
519
|
end
|
612
520
|
|
613
|
-
#
|
614
|
-
#
|
615
|
-
|
616
|
-
|
617
|
-
|
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
|
618
532
|
end
|
619
533
|
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
534
|
+
|
535
|
+
private
|
536
|
+
|
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)
|
625
544
|
end
|
626
545
|
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
546
|
+
|
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]
|
633
554
|
end
|
634
555
|
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
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
|
562
|
+
end
|
563
|
+
end
|
564
|
+
cells
|
640
565
|
end
|
641
566
|
|
642
|
-
#
|
643
|
-
# @
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
@
|
648
|
-
|
649
|
-
end
|
650
|
-
|
651
|
-
# Helper method for parsing out the protectedRanges node
|
652
|
-
# @return [String]
|
653
|
-
def protected_ranges_node
|
654
|
-
return '' if @protected_ranges.empty?
|
655
|
-
str = '<protectedRanges>'
|
656
|
-
@protected_ranges.each { |pr| pr.to_xml_string(str) }
|
657
|
-
str << '</protectedRanges>'
|
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
|
658
574
|
end
|
659
575
|
|
660
|
-
#
|
661
|
-
# @return [
|
662
|
-
def
|
663
|
-
|
664
|
-
str = "<mergeCells count='#{@merged_cells.size}'>"
|
665
|
-
@merged_cells.each { |merged_cell| str << "<mergeCell ref='#{merged_cell}'></mergeCell>" }
|
666
|
-
str << '</mergeCells>'
|
576
|
+
# conditional formattings
|
577
|
+
# @return [Array]
|
578
|
+
def conditional_formattings
|
579
|
+
@conditional_formattings ||= ConditionalFormattings.new self
|
667
580
|
end
|
668
581
|
|
669
|
-
#
|
670
|
-
# @return [
|
671
|
-
def
|
672
|
-
|
673
|
-
"<drawing r:id='rId" << (relationships.index{ |r| r.Type == DRAWING_R } + 1).to_s << "'/>"
|
582
|
+
# data validations array
|
583
|
+
# @return [Array]
|
584
|
+
def data_validations
|
585
|
+
@data_validations ||= DataValidations.new self
|
674
586
|
end
|
675
587
|
|
676
|
-
#
|
677
|
-
# @return [
|
678
|
-
def
|
679
|
-
|
680
|
-
"<legacyDrawing r:id='rId" << (relationships.index{ |r| r.Type == VML_DRAWING_R } + 1).to_s << "'/>"
|
588
|
+
# merged cells array
|
589
|
+
# @return [Array]
|
590
|
+
def merged_cells
|
591
|
+
@merged_cells ||= MergedCells.new self
|
681
592
|
end
|
682
593
|
|
683
|
-
|
594
|
+
|
595
|
+
# Helper method for parsingout the root node for worksheet
|
684
596
|
# @return [String]
|
685
|
-
def
|
686
|
-
|
687
|
-
str = "<tableParts count='#{@tables.size}'>"
|
688
|
-
@tables.each { |table| str << "<tablePart r:id='#{table.rId}'/>" }
|
689
|
-
str << '</tableParts>'
|
597
|
+
def worksheet_node
|
598
|
+
"<worksheet xmlns=\"%s\" xmlns:r=\"%s\">" % [XML_NS, XML_NS_R]
|
690
599
|
end
|
691
600
|
|
692
|
-
|
693
|
-
|
694
|
-
def conditional_formattings_node
|
695
|
-
return '' if @conditional_formattings.size == 0
|
696
|
-
str = ''
|
697
|
-
@conditional_formattings.each { |conditional_formatting| str << conditional_formatting.to_xml_string }
|
698
|
-
str
|
601
|
+
def sheet_data
|
602
|
+
@sheet_data ||= SheetData.new self
|
699
603
|
end
|
700
604
|
|
701
|
-
|
702
|
-
|
703
|
-
def data_validations_node
|
704
|
-
return '' if @data_validations.size == 0
|
705
|
-
str = "<dataValidations count='#{@data_validations.size}'>"
|
706
|
-
@data_validations.each { |data_validation| str << data_validation.to_xml_string }
|
707
|
-
str << '</dataValidations>'
|
605
|
+
def worksheet_drawing
|
606
|
+
@worksheet_drawing ||= WorksheetDrawing.new self
|
708
607
|
end
|
709
608
|
|
609
|
+
# The comments associated with this worksheet
|
610
|
+
# @return [SimpleTypedList]
|
611
|
+
def worksheet_comments
|
612
|
+
@worksheet_comments ||= WorksheetComments.new self
|
613
|
+
end
|
710
614
|
|
711
|
-
# assigns the owner workbook for this worksheet
|
712
615
|
def workbook=(v) DataTypeValidator.validate "Worksheet.workbook", Workbook, v; @workbook = v; end
|
713
616
|
|
714
|
-
|
715
|
-
# TODO this needs cleanup!
|
716
|
-
def update_column_info(cells, widths=[], style=[])
|
717
|
-
styles = self.workbook.styles
|
718
|
-
cellXfs, fonts = styles.cellXfs, styles.fonts
|
719
|
-
sz = 11
|
720
|
-
|
617
|
+
def update_column_info(cells, widths=[])
|
721
618
|
cells.each_with_index do |cell, index|
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
col.width = width if [Integer, Float, Fixnum].include?(width.class)
|
726
|
-
c_style = style[index] if [Integer, Fixnum].include?(style[index].class)
|
727
|
-
next if width == :ignore || (cell.value.is_a?(String) && cell.value.start_with?('=') || cell.value == nil)
|
728
|
-
if self.workbook.use_autowidth
|
729
|
-
cell_xf = cellXfs[(c_style || 0)]
|
730
|
-
font = fonts[(cell_xf.fontId || 0)]
|
731
|
-
sz = cell.sz || font.sz || sz
|
732
|
-
col.width = [(col.width || 0), calculate_width(cell.value.to_s, sz)].max
|
733
|
-
end
|
619
|
+
col = find_or_create_column_info(index)
|
620
|
+
next if widths[index] == :ignore
|
621
|
+
col.update_width(cell, widths[index], workbook.use_autowidth)
|
734
622
|
end
|
735
623
|
end
|
736
624
|
|
737
|
-
|
738
|
-
|
739
|
-
# - scaling is not linear as font sizes increst
|
740
|
-
# - different fonts have different mdw and char widths
|
741
|
-
def calculate_width(text, sz)
|
742
|
-
mdw = 1.78 #This is the widest width of 0..9 in arial@10px)
|
743
|
-
font_scale = (sz/10.0).to_f
|
744
|
-
((text.count(Worksheet.thin_chars) * mdw + 5) / mdw * 256) / 256.0 * font_scale
|
625
|
+
def find_or_create_column_info(index)
|
626
|
+
column_info[index] ||= Col.new(index + 1, index + 1)
|
745
627
|
end
|
628
|
+
|
746
629
|
end
|
747
630
|
end
|