axlsx 1.0.16 → 1.0.18
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.
- data/.yardopts +1 -0
- data/CHANGELOG.md +7 -0
- data/README.md +105 -46
- data/examples/example.rb +172 -142
- data/lib/axlsx/content_type/content_type.rb +4 -3
- data/lib/axlsx/content_type/default.rb +1 -0
- data/lib/axlsx/content_type/override.rb +1 -0
- data/lib/axlsx/doc_props/app.rb +1 -1
- data/lib/axlsx/doc_props/core.rb +1 -0
- data/lib/axlsx/drawing/axis.rb +1 -0
- data/lib/axlsx/drawing/bar_3D_chart.rb +1 -0
- data/lib/axlsx/drawing/bar_series.rb +1 -0
- data/lib/axlsx/drawing/cat_axis.rb +1 -0
- data/lib/axlsx/drawing/cat_axis_data.rb +1 -0
- data/lib/axlsx/drawing/chart.rb +4 -4
- data/lib/axlsx/drawing/drawing.rb +1 -0
- data/lib/axlsx/drawing/graphic_frame.rb +1 -0
- data/lib/axlsx/drawing/hyperlink.rb +1 -1
- data/lib/axlsx/drawing/line_3D_chart.rb +1 -0
- data/lib/axlsx/drawing/line_series.rb +1 -0
- data/lib/axlsx/drawing/marker.rb +1 -0
- data/lib/axlsx/drawing/one_cell_anchor.rb +1 -0
- data/lib/axlsx/drawing/pic.rb +2 -1
- data/lib/axlsx/drawing/picture_locking.rb +1 -0
- data/lib/axlsx/drawing/pie_3D_chart.rb +1 -0
- data/lib/axlsx/drawing/pie_series.rb +1 -0
- data/lib/axlsx/drawing/scaling.rb +1 -0
- data/lib/axlsx/drawing/ser_axis.rb +1 -0
- data/lib/axlsx/drawing/series.rb +1 -0
- data/lib/axlsx/drawing/series_title.rb +1 -0
- data/lib/axlsx/drawing/title.rb +1 -0
- data/lib/axlsx/drawing/two_cell_anchor.rb +1 -0
- data/lib/axlsx/drawing/val_axis.rb +1 -0
- data/lib/axlsx/drawing/val_axis_data.rb +1 -0
- data/lib/axlsx/drawing/view_3D.rb +1 -0
- data/lib/axlsx/package.rb +86 -38
- data/lib/axlsx/rels/relationship.rb +4 -3
- data/lib/axlsx/rels/relationships.rb +1 -0
- data/lib/axlsx/stylesheet/border.rb +1 -0
- data/lib/axlsx/stylesheet/border_pr.rb +1 -0
- data/lib/axlsx/stylesheet/cell_alignment.rb +1 -0
- data/lib/axlsx/stylesheet/cell_protection.rb +1 -0
- data/lib/axlsx/stylesheet/cell_style.rb +1 -0
- data/lib/axlsx/stylesheet/color.rb +1 -0
- data/lib/axlsx/stylesheet/fill.rb +1 -0
- data/lib/axlsx/stylesheet/font.rb +8 -0
- data/lib/axlsx/stylesheet/gradient_fill.rb +1 -1
- data/lib/axlsx/stylesheet/gradient_stop.rb +1 -1
- data/lib/axlsx/stylesheet/num_fmt.rb +1 -0
- data/lib/axlsx/stylesheet/pattern_fill.rb +1 -0
- data/lib/axlsx/stylesheet/styles.rb +12 -9
- data/lib/axlsx/stylesheet/table_style.rb +1 -0
- data/lib/axlsx/stylesheet/table_style_element.rb +1 -0
- data/lib/axlsx/stylesheet/table_styles.rb +1 -0
- data/lib/axlsx/stylesheet/xf.rb +1 -0
- data/lib/axlsx/util/cbf.rb +1 -0
- data/lib/axlsx/util/constants.rb +23 -14
- data/lib/axlsx/util/ms_off_crypto.rb +1 -1
- data/lib/axlsx/util/parser.rb +1 -0
- data/lib/axlsx/util/simple_typed_list.rb +2 -1
- data/lib/axlsx/util/storage.rb +1 -0
- data/lib/axlsx/util/validators.rb +11 -2
- data/lib/axlsx/version.rb +2 -1
- data/lib/axlsx/workbook/shared_strings_table.rb +71 -0
- data/lib/axlsx/workbook/shared_strings_table.rb~ +69 -0
- data/lib/axlsx/workbook/workbook.rb +47 -25
- data/lib/axlsx/workbook/worksheet/cell.rb +131 -78
- data/lib/axlsx/workbook/worksheet/date_time_converter.rb +29 -0
- data/lib/axlsx/workbook/worksheet/page_margins.rb +94 -0
- data/lib/axlsx/workbook/worksheet/row.rb +44 -12
- data/lib/axlsx/workbook/worksheet/shared_strings_table.rb~ +0 -0
- data/lib/axlsx/workbook/worksheet/worksheet.rb +158 -44
- data/lib/axlsx.rb +16 -10
- data/test/content_type/tc_content_type.rb +2 -0
- data/test/content_type/tc_default.rb +2 -0
- data/test/content_type/tc_override.rb +1 -0
- data/test/drawing/tc_chart.rb +2 -2
- data/test/stylesheet/tc_cell_alignment.rb +8 -3
- data/test/stylesheet/tc_font.rb +8 -0
- data/test/stylesheet/tc_styles.rb +6 -2
- data/test/tc_package.rb +31 -0
- data/test/workbook/tc_shared_strings_table.rb +39 -0
- data/test/workbook/tc_shared_strings_table.rb~ +8 -0
- data/test/workbook/tc_workbook.rb +8 -0
- data/test/workbook/worksheet/tc_cell.rb +52 -9
- data/test/workbook/worksheet/tc_date_time_converter.rb +127 -0
- data/test/workbook/worksheet/tc_date_time_converter.rb~ +69 -0
- data/test/workbook/worksheet/tc_page_margins.rb +100 -0
- data/test/workbook/worksheet/tc_row.rb +36 -0
- data/test/workbook/worksheet/tc_worksheet.rb +97 -3
- metadata +33 -40
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
#
|
|
1
|
+
# encoding: UTF-8
|
|
2
2
|
module Axlsx
|
|
3
|
-
|
|
4
|
-
# The Worksheet class represents a worksheet in the workbook.
|
|
3
|
+
|
|
4
|
+
# The Worksheet class represents a worksheet in the workbook.
|
|
5
5
|
class Worksheet
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
# The name of the worksheet
|
|
8
8
|
# @return [String]
|
|
9
9
|
attr_reader :name
|
|
@@ -21,7 +21,7 @@ module Axlsx
|
|
|
21
21
|
|
|
22
22
|
# An array of content based calculated column widths.
|
|
23
23
|
# @note a single auto fit data item is a hash with :longest => [String] and :sz=> [Integer] members.
|
|
24
|
-
# @return [Array] of Hash
|
|
24
|
+
# @return [Array] of Hash
|
|
25
25
|
attr_reader :auto_fit_data
|
|
26
26
|
|
|
27
27
|
# An array of merged cell ranges e.d "A1:B3"
|
|
@@ -34,46 +34,87 @@ module Axlsx
|
|
|
34
34
|
# The first row is considered the header, while subsequent rows are considerd to be data.
|
|
35
35
|
# @return Array
|
|
36
36
|
attr_reader :auto_filter
|
|
37
|
-
|
|
37
|
+
|
|
38
|
+
# Page margins for printing the worksheet.
|
|
39
|
+
# @example
|
|
40
|
+
# wb = Axlsx::Package.new.workbook
|
|
41
|
+
# # using options when creating the worksheet.
|
|
42
|
+
# ws = wb.add_worksheet :page_margins => {:left => 1.9, :header => 0.1}
|
|
43
|
+
#
|
|
44
|
+
# # use the set method of the page_margins object
|
|
45
|
+
# ws.page_margins.set(:bottom => 3, :footer => 0.7)
|
|
46
|
+
#
|
|
47
|
+
# # set page margins in a block
|
|
48
|
+
# ws.page_margins do |margins|
|
|
49
|
+
# margins.right = 6
|
|
50
|
+
# margins.top = 0.2
|
|
51
|
+
# end
|
|
52
|
+
# @see PageMargins#initialize
|
|
53
|
+
# @return [PageMargins]
|
|
54
|
+
def page_margins
|
|
55
|
+
@page_margins ||= PageMargins.new
|
|
56
|
+
yield @page_margins if block_given?
|
|
57
|
+
@page_margins
|
|
58
|
+
end
|
|
59
|
+
|
|
38
60
|
# Creates a new worksheet.
|
|
39
61
|
# @note the recommended way to manage worksheets is Workbook#add_worksheet
|
|
40
62
|
# @see Workbook#add_worksheet
|
|
41
|
-
# @option options [String] name The name of this
|
|
63
|
+
# @option options [String] name The name of this worksheet.
|
|
64
|
+
# @option options [Hash] page_margins A hash containing page margins for this worksheet. @see PageMargins
|
|
42
65
|
def initialize(wb, options={})
|
|
43
|
-
@drawing = nil
|
|
66
|
+
@drawing = @page_margins = @auto_filter = nil
|
|
44
67
|
@rows = SimpleTypedList.new Row
|
|
45
68
|
self.workbook = wb
|
|
46
69
|
@workbook.worksheets << self
|
|
47
70
|
@auto_fit_data = []
|
|
48
71
|
self.name = options[:name] || "Sheet" + (index+1).to_s
|
|
72
|
+
|
|
49
73
|
@magick_draw = Magick::Draw.new
|
|
50
74
|
@cols = SimpleTypedList.new Cell
|
|
51
75
|
@merged_cells = []
|
|
76
|
+
|
|
77
|
+
@page_margins = PageMargins.new options[:page_margins] if options[:page_margins]
|
|
52
78
|
end
|
|
53
79
|
|
|
54
|
-
#
|
|
80
|
+
# convinience method to access all cells in this worksheet
|
|
81
|
+
# @return [Array] cells
|
|
82
|
+
def cells
|
|
83
|
+
rows.flatten
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Creates merge information for this worksheet.
|
|
55
87
|
# Cells can be merged by calling the merge_cells method on a worksheet.
|
|
56
|
-
# @example This would merge the three cells C1..E1 #
|
|
88
|
+
# @example This would merge the three cells C1..E1 #
|
|
57
89
|
# worksheet.merge_cells "C1:E1"
|
|
58
90
|
# # you can also provide an array of cells to be merged
|
|
59
91
|
# worksheet.merge_cells worksheet.rows.first.cells[(2..4)]
|
|
60
92
|
# #alternatively you can do it from a single cell
|
|
61
93
|
# worksheet["C1"].merge worksheet["E1"]
|
|
62
|
-
# @param [Array, string]
|
|
94
|
+
# @param [Array, string]
|
|
63
95
|
def merge_cells(cells)
|
|
64
96
|
@merged_cells << if cells.is_a?(String)
|
|
65
97
|
cells
|
|
66
98
|
elsif cells.is_a?(Array)
|
|
67
99
|
cells = cells.sort { |x, y| x.r <=> y.r }
|
|
68
100
|
"#{cells.first.r}:#{cells.last.r}"
|
|
69
|
-
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# The demensions of a worksheet. This is not actually a required element by the spec,
|
|
106
|
+
# but at least a few other document readers expect this for conversion
|
|
107
|
+
# @return [String] the A1:B2 style reference for the first and last row column intersection in the workbook
|
|
108
|
+
def dimension
|
|
109
|
+
"#{rows.first.cells.first.r}:#{rows.last.cells.last.r}"
|
|
70
110
|
end
|
|
71
111
|
|
|
72
112
|
|
|
73
113
|
# Returns the cell or cells defined using excel style A1:B3 references.
|
|
74
|
-
# @param [String] cell_def the string defining the cell or range of cells
|
|
114
|
+
# @param [String|Integer] cell_def the string defining the cell or range of cells, or the rownumber
|
|
75
115
|
# @return [Cell, Array]
|
|
76
116
|
def [](cell_def)
|
|
117
|
+
return rows[cell_def - 1] if cell_def.is_a? Integer
|
|
77
118
|
parts = cell_def.split(':')
|
|
78
119
|
first = name_to_cell parts[0]
|
|
79
120
|
|
|
@@ -101,18 +142,20 @@ module Axlsx
|
|
|
101
142
|
end
|
|
102
143
|
|
|
103
144
|
# The name of the worksheet
|
|
145
|
+
# The name of a worksheet must be unique in the workbook, and must not exceed 31 characters
|
|
104
146
|
# @param [String] v
|
|
105
|
-
def name=(v)
|
|
147
|
+
def name=(v)
|
|
106
148
|
DataTypeValidator.validate "Worksheet.name", String, v
|
|
149
|
+
raise ArgumentError, (ERR_SHEET_NAME_TOO_LONG % v) if v.size > 31
|
|
107
150
|
sheet_names = @workbook.worksheets.map { |s| s.name }
|
|
108
|
-
raise ArgumentError, (ERR_DUPLICATE_SHEET_NAME % v) if sheet_names.include?(v)
|
|
109
|
-
@name=v
|
|
151
|
+
raise ArgumentError, (ERR_DUPLICATE_SHEET_NAME % v) if sheet_names.include?(v)
|
|
152
|
+
@name=v
|
|
110
153
|
end
|
|
111
154
|
|
|
112
155
|
# The auto filter range for the worksheet
|
|
113
156
|
# @param [String] v
|
|
114
157
|
# @see auto_filter
|
|
115
|
-
def auto_filter=(v)
|
|
158
|
+
def auto_filter=(v)
|
|
116
159
|
DataTypeValidator.validate "Worksheet.auto_filter", String, v
|
|
117
160
|
@auto_filter = v
|
|
118
161
|
end
|
|
@@ -149,18 +192,54 @@ module Axlsx
|
|
|
149
192
|
@drawing || @drawing = Axlsx::Drawing.new(self)
|
|
150
193
|
end
|
|
151
194
|
|
|
152
|
-
# Adds a row to the worksheet and updates auto fit data
|
|
195
|
+
# Adds a row to the worksheet and updates auto fit data.
|
|
196
|
+
# @example - put a vanilla row in your spreadsheet
|
|
197
|
+
# ws.add_row [1, 'fish on my pl', '8']
|
|
198
|
+
#
|
|
199
|
+
# @example - specify a fixed width for a column in your spreadsheet
|
|
200
|
+
# # The first column will ignore the content of this cell when calculating column autowidth.
|
|
201
|
+
# # The second column will include this text in calculating the columns autowidth
|
|
202
|
+
# # The third cell will set a fixed with of 80 for the column.
|
|
203
|
+
# # If you need to un-fix a column width, use :auto. That will recalculate the column width based on all content in the column
|
|
204
|
+
#
|
|
205
|
+
# ws.add_row ['I wish', 'for a fish', 'on my fish wish dish'], :widths=>[:ignore, :auto, 80]
|
|
206
|
+
#
|
|
207
|
+
# @example - specify a fixed height for a row
|
|
208
|
+
# ws.add_row ['I wish', 'for a fish', 'on my fish wish dish'], :height => 40
|
|
209
|
+
#
|
|
210
|
+
# @example - create and use a style for all cells in the row
|
|
211
|
+
# blue = ws.styles.add_style :color => "#00FF00"
|
|
212
|
+
# ws.add_row [1, 2, 3], :style=>blue
|
|
213
|
+
#
|
|
214
|
+
# @example - only style some cells
|
|
215
|
+
# blue = ws.styles.add_style :color => "#00FF00"
|
|
216
|
+
# red = ws.styles.add_style :color => "#FF0000"
|
|
217
|
+
# big = ws.styles.add_style :sz => 40
|
|
218
|
+
# ws.add_row ["red fish", "blue fish", "one fish", "two fish"], :style=>[red, blue, nil, big] # the last nil is optional
|
|
219
|
+
#
|
|
220
|
+
#
|
|
221
|
+
# @example - force the second cell to be a float value
|
|
222
|
+
# ws.add_row [3, 4, 5], :types => [nil, :float]
|
|
223
|
+
#
|
|
224
|
+
# @example - use << alias
|
|
225
|
+
# ws << [3, 4, 5], :types => [nil, :float]
|
|
226
|
+
#
|
|
227
|
+
# @see Worksheet#column_widths
|
|
153
228
|
# @return [Row]
|
|
154
229
|
# @option options [Array] values
|
|
155
|
-
# @option options [Array, Symbol] types
|
|
156
|
-
# @option options [Array, Integer] style
|
|
230
|
+
# @option options [Array, Symbol] types
|
|
231
|
+
# @option options [Array, Integer] style
|
|
232
|
+
# @option options [Array] widths each member of the widths array will affect how auto_fit behavies.
|
|
233
|
+
# @option options [Float] height the row's height (in points)
|
|
157
234
|
def add_row(values=[], options={})
|
|
158
235
|
Row.new(self, values, options)
|
|
159
|
-
update_auto_fit_data @rows.last.cells
|
|
236
|
+
update_auto_fit_data @rows.last.cells, options.delete(:widths) || []
|
|
160
237
|
yield @rows.last if block_given?
|
|
161
238
|
@rows.last
|
|
162
239
|
end
|
|
163
240
|
|
|
241
|
+
alias :<< :add_row
|
|
242
|
+
|
|
164
243
|
# Set the style for cells in a specific row
|
|
165
244
|
# @param [Integer] index or range of indexes in the table
|
|
166
245
|
# @param [Integer] the cellXfs index
|
|
@@ -193,7 +272,7 @@ module Axlsx
|
|
|
193
272
|
# @see README.md for an example
|
|
194
273
|
def col_style(index, style, options={})
|
|
195
274
|
offset = options.delete(:row_offset) || 0
|
|
196
|
-
@rows[(offset..-1)].each do |r|
|
|
275
|
+
@rows[(offset..-1)].each do |r|
|
|
197
276
|
cells = r.cells[index]
|
|
198
277
|
next unless cells
|
|
199
278
|
if cells.is_a?(Array)
|
|
@@ -203,15 +282,30 @@ module Axlsx
|
|
|
203
282
|
end
|
|
204
283
|
end
|
|
205
284
|
end
|
|
206
|
-
|
|
207
|
-
#
|
|
285
|
+
|
|
286
|
+
# This is a helper method that Lets you specify a fixed width for multiple columns in a worksheet in one go.
|
|
287
|
+
# Axlsx is sparse, so if you have not set data for a column, you cannot set the width.
|
|
288
|
+
# Setting a fixed column width to nil will revert the behaviour back to calculating the width for you.
|
|
289
|
+
# @example This would set the first and third column widhts but leave the second column in autofit state.
|
|
290
|
+
# ws.column_widths 7.2, nil, 3
|
|
291
|
+
# @note For updating only a single column it is probably easier to just set ws.auto_fit_data[col_index][:fixed] directly
|
|
292
|
+
# @param [Integer|Float|Fixnum|nil] values
|
|
293
|
+
def column_widths(*args)
|
|
294
|
+
args.each_with_index do |value, index|
|
|
295
|
+
raise ArgumentError, "Invalid column specification" unless index < @auto_fit_data.size
|
|
296
|
+
Axlsx::validate_unsigned_numeric(value) unless value == nil
|
|
297
|
+
@auto_fit_data[index][:fixed] = value
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# 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.
|
|
208
302
|
# @param [Class] chart_type
|
|
209
303
|
# @option options [Array] start_at
|
|
210
304
|
# @option options [Array] end_at
|
|
211
305
|
# @option options [Cell, String] title
|
|
212
306
|
# @option options [Boolean] show_legend
|
|
213
|
-
# @option options [Integer] style
|
|
214
|
-
# @note each chart type also specifies additional options
|
|
307
|
+
# @option options [Integer] style
|
|
308
|
+
# @note each chart type also specifies additional options
|
|
215
309
|
# @see Chart
|
|
216
310
|
# @see Pie3DChart
|
|
217
311
|
# @see Bar3DChart
|
|
@@ -236,8 +330,19 @@ module Axlsx
|
|
|
236
330
|
# @return [String]
|
|
237
331
|
def to_xml
|
|
238
332
|
builder = Nokogiri::XML::Builder.new(:encoding => ENCODING) do |xml|
|
|
239
|
-
xml.worksheet(:xmlns => XML_NS,
|
|
333
|
+
xml.worksheet(:xmlns => XML_NS,
|
|
240
334
|
:'xmlns:r' => XML_NS_R) {
|
|
335
|
+
# another patch for the folks at rubyXL as thier parser depends on this optional element.
|
|
336
|
+
xml.dimension :ref=>dimension unless rows.size == 0
|
|
337
|
+
# this is required by rubyXL, spec says who cares - but it seems they didnt notice
|
|
338
|
+
# however, it also seems to be causing some odd [Grouped] stuff in excel 2011 - so
|
|
339
|
+
# removing until I understand it better.
|
|
340
|
+
# xml.sheetViews {
|
|
341
|
+
# xml.sheetView(:tabSelected => 1, :workbookViewId => 0) {
|
|
342
|
+
# xml.selection :activeCell=>"A1", :sqref => "A1"
|
|
343
|
+
# }
|
|
344
|
+
# }
|
|
345
|
+
|
|
241
346
|
if @auto_fit_data.size > 0
|
|
242
347
|
xml.cols {
|
|
243
348
|
@auto_fit_data.each_with_index do |col, index|
|
|
@@ -253,7 +358,8 @@ module Axlsx
|
|
|
253
358
|
}
|
|
254
359
|
xml.autoFilter :ref=>@auto_filter if @auto_filter
|
|
255
360
|
xml.mergeCells(:count=>@merged_cells.size) { @merged_cells.each { | mc | xml.mergeCell(:ref=>mc) } } unless @merged_cells.empty?
|
|
256
|
-
xml
|
|
361
|
+
page_margins.to_xml(xml) if @page_margins
|
|
362
|
+
xml.drawing :"r:id"=>"rId1" if @drawing
|
|
257
363
|
}
|
|
258
364
|
end
|
|
259
365
|
builder.to_xml(:save_with => 0)
|
|
@@ -267,28 +373,35 @@ module Axlsx
|
|
|
267
373
|
r
|
|
268
374
|
end
|
|
269
375
|
|
|
270
|
-
private
|
|
376
|
+
private
|
|
271
377
|
|
|
272
378
|
# assigns the owner workbook for this worksheet
|
|
273
379
|
def workbook=(v) DataTypeValidator.validate "Worksheet.workbook", Workbook, v; @workbook = v; end
|
|
274
380
|
|
|
275
|
-
# Updates auto fit data.
|
|
276
|
-
# We store an auto_fit_data item for each column. when a row is added we multiple the font size by the length of the text to
|
|
381
|
+
# Updates auto fit data.
|
|
382
|
+
# We store an auto_fit_data item for each column. when a row is added we multiple the font size by the length of the text to
|
|
277
383
|
# attempt to identify the longest cell in the column. This is not 100% accurate as it needs to take into account
|
|
278
|
-
# any formatting that will be applied to the data, as well as the actual rendering size when the length and size is equal
|
|
384
|
+
# any formatting that will be applied to the data, as well as the actual rendering size when the length and size is equal
|
|
279
385
|
# for two cells.
|
|
386
|
+
|
|
280
387
|
# @return [Array] of Cell objects
|
|
281
388
|
# @param [Array] cells an array of cells
|
|
282
|
-
|
|
389
|
+
# @param [Array] widths an array of cell widths @see Worksheet#add_row
|
|
390
|
+
def update_auto_fit_data(cells, widths=[])
|
|
283
391
|
# TODO delay this until rendering. too much work when we dont know what they are going to do to the sheet.
|
|
284
392
|
styles = self.workbook.styles
|
|
285
393
|
cellXfs, fonts = styles.cellXfs, styles.fonts
|
|
286
394
|
sz = 11
|
|
287
395
|
cells.each_with_index do |item, index|
|
|
288
|
-
|
|
289
|
-
|
|
396
|
+
col = @auto_fit_data[index] ||= {:longest=>"", :sz=>sz, :fixed=>nil}
|
|
397
|
+
width = widths[index]
|
|
398
|
+
# set fixed width and skip if numeric width is given
|
|
399
|
+
col[:fixed] = width if [Integer, Float, Fixnum].include?(width.class)
|
|
400
|
+
# ignore default column widths and formula
|
|
401
|
+
next if width == :ignore || (item.value.is_a?(String) && item.value.start_with?('='))
|
|
402
|
+
# make sure we can turn that fixed with off!
|
|
403
|
+
col[:fixed] = nil if width == :auto
|
|
290
404
|
|
|
291
|
-
col = @auto_fit_data[index] || {:longest=>"", :sz=>sz}
|
|
292
405
|
cell_xf = cellXfs[item.style]
|
|
293
406
|
font = fonts[cell_xf.fontId || 0]
|
|
294
407
|
sz = item.sz || font.sz || fonts[0].sz
|
|
@@ -296,17 +409,18 @@ module Axlsx
|
|
|
296
409
|
col[:sz] = sz
|
|
297
410
|
col[:longest] = item.value.to_s
|
|
298
411
|
end
|
|
299
|
-
@auto_fit_data[index] = col
|
|
300
412
|
end
|
|
301
413
|
cells
|
|
302
414
|
end
|
|
303
|
-
|
|
415
|
+
|
|
304
416
|
# Determines the proper width for a column based on content.
|
|
305
|
-
# @note
|
|
417
|
+
# @note
|
|
306
418
|
# width = Truncate([!{Number of Characters} * !{Maximum Digit Width} + !{5 pixel padding}]/!{Maximum Digit Width}*256)/256
|
|
307
419
|
# @return [Float]
|
|
308
|
-
# @param [Hash] A hash of auto_fit_data
|
|
420
|
+
# @param [Hash] A hash of auto_fit_data
|
|
309
421
|
def auto_width(col)
|
|
422
|
+
return col[:fixed] unless col[:fixed] == nil
|
|
423
|
+
|
|
310
424
|
mdw_count, font_scale, mdw = 0, col[:sz]/11.0, 6.0
|
|
311
425
|
mdw_count = col[:longest].scan(/./mu).reduce(0) do | count, char |
|
|
312
426
|
count +=1 if @magick_draw.get_type_metrics(char).max_advance >= mdw
|
|
@@ -316,21 +430,21 @@ module Axlsx
|
|
|
316
430
|
end
|
|
317
431
|
|
|
318
432
|
# Something to look into:
|
|
319
|
-
# width calculation actually needs to be done agains the formatted value for items that apply a
|
|
433
|
+
# width calculation actually needs to be done agains the formatted value for items that apply a
|
|
320
434
|
# format
|
|
321
435
|
# def excel_format(cell)
|
|
322
436
|
# # The most common case.
|
|
323
437
|
# return time.value.to_s if cell.style == 0
|
|
324
438
|
#
|
|
325
|
-
# # The second most common case
|
|
439
|
+
# # The second most common case
|
|
326
440
|
# num_fmt = workbook.styles.cellXfs[items.style].numFmtId
|
|
327
441
|
# return value.to_s if num_fmt == 0
|
|
328
|
-
#
|
|
442
|
+
#
|
|
329
443
|
# format_code = workbook.styles.numFmts[num_fmt]
|
|
330
444
|
# # need to find some exceptionally fast way of parsing value according to
|
|
331
445
|
# # an excel format_code
|
|
332
446
|
# item.value.to_s
|
|
333
|
-
# end
|
|
447
|
+
# end
|
|
334
448
|
|
|
335
449
|
end
|
|
336
450
|
end
|
data/lib/axlsx.rb
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
Encoding::default_external = 'UTF-8' unless RUBY_VERSION < '1.9'
|
|
3
|
-
|
|
1
|
+
# encoding: UTF-8
|
|
4
2
|
require 'axlsx/version.rb'
|
|
5
3
|
|
|
6
4
|
require 'axlsx/util/simple_typed_list.rb'
|
|
7
5
|
require 'axlsx/util/constants.rb'
|
|
8
6
|
require 'axlsx/util/validators.rb'
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
require 'axlsx/util/storage.rb'
|
|
8
|
+
require 'axlsx/util/cbf.rb'
|
|
9
|
+
require 'axlsx/util/ms_off_crypto.rb'
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
# to be included with parsable intitites.
|
|
@@ -27,8 +25,6 @@ require 'axlsx/package.rb'
|
|
|
27
25
|
|
|
28
26
|
#required gems
|
|
29
27
|
require 'nokogiri'
|
|
30
|
-
require 'active_support/core_ext/object/instance_variables'
|
|
31
|
-
require 'active_support/inflector'
|
|
32
28
|
require 'RMagick'
|
|
33
29
|
require 'zip/zip'
|
|
34
30
|
|
|
@@ -36,6 +32,16 @@ require 'zip/zip'
|
|
|
36
32
|
require 'bigdecimal'
|
|
37
33
|
require 'time'
|
|
38
34
|
|
|
35
|
+
#if object does not have this already, I am borrowing it from active_support.
|
|
36
|
+
# I am a very big fan of activesupports instance_values method, but do not want to require nor include the entire
|
|
37
|
+
# library just for this one method.
|
|
38
|
+
if !Object.respond_to?(:instance_values)
|
|
39
|
+
Object.send :public # patch for 1.8.7 as it uses private scope
|
|
40
|
+
Object.send :define_method, :instance_values do
|
|
41
|
+
Hash[instance_variables.map { |name| [name.to_s[1..-1], instance_variable_get(name)] }]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
39
45
|
|
|
40
46
|
# xlsx generation with charts, images, automated column width, customizable styles and full schema validation. Axlsx excels at helping you generate beautiful Office Open XML Spreadsheet documents without having to understand the entire ECMA specification. Check out the README for some examples of how easy it is. Best of all, you can validate your xlsx file before serialization so you know for sure that anything generated is going to load on your client's machine.
|
|
41
47
|
module Axlsx
|
|
@@ -50,8 +56,8 @@ module Axlsx
|
|
|
50
56
|
|
|
51
57
|
def self.name_to_indices(name)
|
|
52
58
|
raise ArgumentError, 'invalid cell name' unless name.size > 1
|
|
53
|
-
v = name[/[A-Z]+/].reverse.chars.reduce({:base=>1, :i=>0}) do |
|
|
54
|
-
|
|
59
|
+
v = name[/[A-Z]+/].reverse.chars.reduce({:base=>1, :i=>0}) do |val, c|
|
|
60
|
+
val[:i] += ((c.bytes.first - 65) + val[:base]); val[:base] *= 26; val
|
|
55
61
|
end
|
|
56
62
|
|
|
57
63
|
[v[:i]-1, ((name[/[1-9]+/]).to_i)-1]
|
data/test/drawing/tc_chart.rb
CHANGED
|
@@ -41,9 +41,9 @@ class TestChart < Test::Unit::TestCase
|
|
|
41
41
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
def
|
|
44
|
+
def test_end_at
|
|
45
45
|
@chart.end_at 25, 90
|
|
46
|
-
assert_equal(@chart.graphic_frame.anchor.
|
|
46
|
+
assert_equal(@chart.graphic_frame.anchor.to.col, 25)
|
|
47
47
|
assert_equal(@chart.graphic_frame.anchor.to.row, 90)
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -5,8 +5,7 @@ class TestCellAlignment < Test::Unit::TestCase
|
|
|
5
5
|
def setup
|
|
6
6
|
@item = Axlsx::CellAlignment.new
|
|
7
7
|
end
|
|
8
|
-
|
|
9
|
-
end
|
|
8
|
+
|
|
10
9
|
def test_initialiation
|
|
11
10
|
assert_equal(@item.horizontal, nil)
|
|
12
11
|
assert_equal(@item.vertical, nil)
|
|
@@ -17,7 +16,13 @@ class TestCellAlignment < Test::Unit::TestCase
|
|
|
17
16
|
assert_equal(@item.justifyLastLine, nil)
|
|
18
17
|
assert_equal(@item.shrinkToFit, nil)
|
|
19
18
|
assert_equal(@item.readingOrder, nil)
|
|
20
|
-
|
|
19
|
+
options = { :horizontal => :left, :vertical => :top, :textRotation => 3,
|
|
20
|
+
:wrapText => true, :indent => 2, :relativeIndent => 5,
|
|
21
|
+
:justifyLastLine => true, :shrinkToFit => true, :readingOrder => 2 }
|
|
22
|
+
ca = Axlsx::CellAlignment.new options
|
|
23
|
+
options.each do |key, value|
|
|
24
|
+
assert_equal(ca.send(key.to_sym),value)
|
|
25
|
+
end
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
def test_horizontal
|
data/test/stylesheet/tc_font.rb
CHANGED
|
@@ -17,6 +17,7 @@ class TestFont < Test::Unit::TestCase
|
|
|
17
17
|
assert_equal(@item.family, nil)
|
|
18
18
|
assert_equal(@item.b, nil)
|
|
19
19
|
assert_equal(@item.i, nil)
|
|
20
|
+
assert_equal(@item.u, nil)
|
|
20
21
|
assert_equal(@item.strike, nil)
|
|
21
22
|
assert_equal(@item.outline, nil)
|
|
22
23
|
assert_equal(@item.shadow, nil)
|
|
@@ -61,6 +62,13 @@ class TestFont < Test::Unit::TestCase
|
|
|
61
62
|
assert_nothing_raised { @item.i = true }
|
|
62
63
|
assert_equal(@item.i, true)
|
|
63
64
|
end
|
|
65
|
+
|
|
66
|
+
# def u=(v) Axlsx::validate_boolean v; @u = v end
|
|
67
|
+
def test_u
|
|
68
|
+
assert_raise(ArgumentError) { @item.u = -7 }
|
|
69
|
+
assert_nothing_raised { @item.u = true }
|
|
70
|
+
assert_equal(@item.u, true)
|
|
71
|
+
end
|
|
64
72
|
|
|
65
73
|
# def strike=(v) Axlsx::validate_boolean v; @strike = v end
|
|
66
74
|
def test_strike
|
|
@@ -42,10 +42,14 @@ class TestStyles < Test::Unit::TestCase
|
|
|
42
42
|
|
|
43
43
|
assert(xf.alignment.is_a?(Axlsx::CellAlignment), "alignment was created")
|
|
44
44
|
assert_equal(xf.alignment.horizontal, :left, "horizontal alignment applied")
|
|
45
|
-
assert_equal(xf.applyProtection, 1, "protection applied")
|
|
46
45
|
assert_equal(xf.protection.hidden, true, "hidden protection set")
|
|
47
46
|
assert_equal(xf.protection.locked, true, "cell locking set")
|
|
48
|
-
assert_raise(ArgumentError, "should reject invalid borderId") { @styles.add_style :border => 2 }
|
|
47
|
+
assert_raise(ArgumentError, "should reject invalid borderId") { @styles.add_style :border => 2 }
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
assert_equal(xf.applyProtection, 1, "protection applied")
|
|
51
|
+
assert_equal(xf.applyBorder, true, "border applied")
|
|
52
|
+
assert_equal(xf.applyNumberFormat, true, "border applied")
|
|
49
53
|
|
|
50
54
|
end
|
|
51
55
|
|
data/test/tc_package.rb
CHANGED
|
@@ -21,6 +21,13 @@ class TestPackage < Test::Unit::TestCase
|
|
|
21
21
|
assert_raise(NoMethodError) {@package.app = nil }
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
def test_use_shared_strings
|
|
25
|
+
assert_equal(@package.use_shared_strings, nil)
|
|
26
|
+
assert_raise(ArgumentError) {@package.use_shared_strings 9}
|
|
27
|
+
assert_nothing_raised {@package.use_shared_strings = true}
|
|
28
|
+
assert_equal(@package.use_shared_strings, @package.workbook.use_shared_strings)
|
|
29
|
+
end
|
|
30
|
+
|
|
24
31
|
def test_default_objects_are_created
|
|
25
32
|
assert(@package.instance_values["app"].is_a?(Axlsx::App), 'App object not created')
|
|
26
33
|
assert(@package.instance_values["core"].is_a?(Axlsx::Core), 'Core object not created')
|
|
@@ -69,10 +76,34 @@ class TestPackage < Test::Unit::TestCase
|
|
|
69
76
|
|
|
70
77
|
#no mystery parts
|
|
71
78
|
assert_equal(p.size, 12)
|
|
79
|
+
|
|
80
|
+
end
|
|
72
81
|
|
|
82
|
+
def test_shared_strings_requires_part
|
|
83
|
+
@package.use_shared_strings = true
|
|
84
|
+
p = @package.send(:parts)
|
|
85
|
+
assert_equal(p.select{ |part| part[:entry] =~/xl\/sharedStrings.xml/}.size, 1, "shared strings table missing")
|
|
73
86
|
end
|
|
74
87
|
|
|
75
88
|
def test_workbook_is_a_workbook
|
|
76
89
|
assert @package.workbook.is_a? Axlsx::Workbook
|
|
77
90
|
end
|
|
91
|
+
|
|
92
|
+
def test_base_content_types
|
|
93
|
+
ct = @package.send(:base_content_types)
|
|
94
|
+
assert(ct.select { |c| c.ContentType == Axlsx::RELS_CT }.size == 1, "rels content type missing")
|
|
95
|
+
assert(ct.select { |c| c.ContentType == Axlsx::XML_CT }.size == 1, "xml content type missing")
|
|
96
|
+
assert(ct.select { |c| c.ContentType == Axlsx::APP_CT }.size == 1, "app content type missing")
|
|
97
|
+
assert(ct.select { |c| c.ContentType == Axlsx::CORE_CT }.size == 1, "core content type missing")
|
|
98
|
+
assert(ct.select { |c| c.ContentType == Axlsx::STYLES_CT }.size == 1, "styles content type missing")
|
|
99
|
+
assert(ct.select { |c| c.ContentType == Axlsx::WORKBOOK_CT }.size == 1, "workbook content type missing")
|
|
100
|
+
assert(ct.size == 6)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def test_content_type_added_with_shared_strings
|
|
104
|
+
@package.use_shared_strings = true
|
|
105
|
+
ct = @package.send(:content_types)
|
|
106
|
+
assert(ct.select { |ct| ct.ContentType == Axlsx::SHARED_STRINGS_CT }.size == 1)
|
|
107
|
+
end
|
|
108
|
+
|
|
78
109
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'axlsx.rb'
|
|
3
|
+
|
|
4
|
+
class TestSharedStringsTable < Test::Unit::TestCase
|
|
5
|
+
|
|
6
|
+
def setup
|
|
7
|
+
@p = Axlsx::Package.new :use_shared_strings=>true
|
|
8
|
+
ws = @p.workbook.add_worksheet
|
|
9
|
+
ws.add_row ['a', 1, 'b']
|
|
10
|
+
ws.add_row ['b', 1, 'c']
|
|
11
|
+
ws.add_row ['c', 1, 'd']
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_workbook_has_shared_strings
|
|
15
|
+
assert(@p.workbook.shared_strings.is_a?(Axlsx::SharedStringsTable), "shared string table was not created")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_count
|
|
19
|
+
sst = @p.workbook.shared_strings
|
|
20
|
+
assert_equal(sst.count, 6)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_unique_count
|
|
24
|
+
sst = @p.workbook.shared_strings
|
|
25
|
+
assert_equal(sst.unique_count, 4)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_valid_document
|
|
29
|
+
schema = Nokogiri::XML::Schema(File.open(Axlsx::SML_XSD))
|
|
30
|
+
doc = Nokogiri::XML(@p.workbook.shared_strings.to_xml)
|
|
31
|
+
errors = []
|
|
32
|
+
schema.validate(doc).each do |error|
|
|
33
|
+
puts error.message
|
|
34
|
+
errors << error
|
|
35
|
+
end
|
|
36
|
+
assert_equal(errors.size, 0, "sharedStirngs.xml Invalid" + errors.map{ |e| e.message }.to_s)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
@@ -18,6 +18,12 @@ class TestWorkbook < Test::Unit::TestCase
|
|
|
18
18
|
assert_equal(Axlsx::Workbook.date1904, @wb.date1904)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
def test_shared_strings
|
|
22
|
+
assert_equal(@wb.use_shared_strings, nil)
|
|
23
|
+
assert_raise(ArgumentError) {@wb.use_shared_strings = 'bpb'}
|
|
24
|
+
assert_nothing_raised {@wb.use_shared_strings = :true}
|
|
25
|
+
end
|
|
26
|
+
|
|
21
27
|
def test_add_worksheet
|
|
22
28
|
assert(@wb.worksheets.empty?, "worbook has no worksheets by default")
|
|
23
29
|
ws = @wb.add_worksheet(:name=>"bob")
|
|
@@ -30,6 +36,8 @@ class TestWorkbook < Test::Unit::TestCase
|
|
|
30
36
|
assert(@wb.relationships.size == 1)
|
|
31
37
|
@wb.add_worksheet
|
|
32
38
|
assert(@wb.relationships.size == 2)
|
|
39
|
+
@wb.use_shared_strings = true
|
|
40
|
+
assert(@wb.relationships.size == 3)
|
|
33
41
|
end
|
|
34
42
|
|
|
35
43
|
def test_to_xml
|