axlsx 1.0.12 → 1.0.14
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/README.md +165 -134
- data/examples/example.rb +133 -153
- data/lib/axlsx/#cfb.xlsx# +0 -0
- data/lib/axlsx/content_type/content_type.rb +23 -0
- data/lib/axlsx/content_type/default.rb +37 -0
- data/lib/axlsx/content_type/override.rb +37 -0
- data/lib/axlsx/doc_props/app.rb +178 -0
- data/lib/axlsx/doc_props/core.rb +34 -0
- data/lib/axlsx/drawing/axis.rb +93 -0
- data/lib/axlsx/drawing/bar_3D_chart.rb +136 -0
- data/lib/axlsx/drawing/bar_series.rb +64 -0
- data/lib/axlsx/drawing/cat_axis.rb +63 -0
- data/lib/axlsx/drawing/cat_axis_data.rb +35 -0
- data/lib/axlsx/drawing/chart.rb +193 -0
- data/lib/axlsx/drawing/drawing.rb +137 -0
- data/lib/axlsx/drawing/graphic_frame.rb +52 -0
- data/lib/axlsx/drawing/line_3D_chart.rb +114 -0
- data/lib/axlsx/drawing/line_series.rb +46 -0
- data/lib/axlsx/drawing/marker.rb +61 -0
- data/lib/axlsx/drawing/one_cell_anchor.rb +89 -0
- data/lib/axlsx/drawing/pic.rb +153 -0
- data/lib/axlsx/drawing/picture_locking.rb +72 -0
- data/lib/axlsx/drawing/picture_locking.rb~ +36 -0
- data/lib/axlsx/drawing/pie_3D_chart.rb +41 -0
- data/lib/axlsx/drawing/pie_series.rb +56 -0
- data/lib/axlsx/drawing/scaling.rb +59 -0
- data/lib/axlsx/drawing/ser_axis.rb +45 -0
- data/lib/axlsx/drawing/series.rb +71 -0
- data/lib/axlsx/drawing/series_title.rb +22 -0
- data/lib/axlsx/drawing/title.rb +65 -0
- data/lib/axlsx/drawing/two_cell_anchor.rb +76 -0
- data/lib/axlsx/drawing/val_axis.rb +34 -0
- data/lib/axlsx/drawing/val_axis_data.rb +26 -0
- data/lib/axlsx/drawing/view_3D.rb +85 -0
- data/lib/axlsx/package.rb +223 -0
- data/lib/axlsx/rels/relationship.rb +44 -0
- data/lib/axlsx/rels/relationships.rb +25 -0
- data/lib/axlsx/stylesheet/border.rb +57 -0
- data/lib/axlsx/stylesheet/border_pr.rb +68 -0
- data/lib/axlsx/stylesheet/cell_alignment.rb +105 -0
- data/lib/axlsx/stylesheet/cell_protection.rb +36 -0
- data/lib/axlsx/stylesheet/cell_style.rb +65 -0
- data/lib/axlsx/stylesheet/color.rb +69 -0
- data/lib/axlsx/stylesheet/fill.rb +32 -0
- data/lib/axlsx/stylesheet/font.rb +139 -0
- data/lib/axlsx/stylesheet/gradient_fill.rb +76 -0
- data/lib/axlsx/stylesheet/gradient_stop.rb +33 -0
- data/lib/axlsx/stylesheet/num_fmt.rb +63 -0
- data/lib/axlsx/stylesheet/pattern_fill.rb +66 -0
- data/lib/axlsx/stylesheet/styles.rb +298 -0
- data/lib/axlsx/stylesheet/table_style.rb +47 -0
- data/lib/axlsx/stylesheet/table_style_element.rb +71 -0
- data/lib/axlsx/stylesheet/table_styles.rb +39 -0
- data/lib/axlsx/stylesheet/xf.rb +138 -0
- data/lib/axlsx/util/constants.rb +216 -0
- data/lib/axlsx/util/ms_off_crypto.rb +88 -0
- data/lib/axlsx/util/ms_off_crypto.rb~ +3 -0
- data/lib/axlsx/util/ms_offcrypto.rb~ +0 -0
- data/lib/axlsx/util/parser.rb +43 -0
- data/lib/axlsx/util/parser.rb~ +6 -0
- data/lib/axlsx/util/simple_typed_list.rb +160 -0
- data/lib/axlsx/util/validators.rb +132 -0
- data/lib/axlsx/version.rb +4 -0
- data/lib/axlsx/workbook/workbook.rb +161 -0
- data/lib/axlsx/workbook/worksheet/cell.rb +350 -0
- data/lib/axlsx/workbook/worksheet/row.rb +107 -0
- data/lib/axlsx/workbook/worksheet/worksheet.rb +335 -0
- data/lib/axlsx.rb +59 -0
- data/test/drawing/tc_axis.rb +1 -1
- data/test/drawing/tc_cat_axis.rb +1 -1
- data/test/drawing/tc_line_series.tc~ +34 -0
- data/test/drawing/tc_picture_locking.rb~ +77 -0
- data/test/tc_package.rb +10 -0
- data/test/workbook/worksheet/tc_cell.rb +15 -0
- data/test/workbook/worksheet/tc_worksheet.rb +22 -1
- metadata +117 -5
- data/examples/follow_20111202.xlsx +0 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
module Axlsx
|
|
3
|
+
|
|
4
|
+
# The Worksheet class represents a worksheet in the workbook.
|
|
5
|
+
class Worksheet
|
|
6
|
+
|
|
7
|
+
# The name of the worksheet
|
|
8
|
+
# @return [String]
|
|
9
|
+
attr_reader :name
|
|
10
|
+
|
|
11
|
+
# The workbook that owns this worksheet
|
|
12
|
+
# @return [Workbook]
|
|
13
|
+
attr_reader :workbook
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# The rows in this worksheet
|
|
17
|
+
# @note The recommended way to manage rows is Worksheet#add_row
|
|
18
|
+
# @return [SimpleTypedList]
|
|
19
|
+
# @see Worksheet#add_row
|
|
20
|
+
attr_reader :rows
|
|
21
|
+
|
|
22
|
+
# An array of content based calculated column widths.
|
|
23
|
+
# @note a single auto fit data item is a hash with :longest => [String] and :sz=> [Integer] members.
|
|
24
|
+
# @return [Array] of Hash
|
|
25
|
+
attr_reader :auto_fit_data
|
|
26
|
+
|
|
27
|
+
# An array of merged cell ranges e.d "A1:B3"
|
|
28
|
+
# Content and formatting is read from the first cell.
|
|
29
|
+
# @return Array
|
|
30
|
+
attr_reader :merged_cells
|
|
31
|
+
|
|
32
|
+
# An range that excel will apply an autfilter to "A1:B3"
|
|
33
|
+
# This will turn filtering on for the cells in the range.
|
|
34
|
+
# The first row is considered the header, while subsequent rows are considerd to be data.
|
|
35
|
+
# @return Array
|
|
36
|
+
attr_reader :auto_filter
|
|
37
|
+
|
|
38
|
+
# Creates a new worksheet.
|
|
39
|
+
# @note the recommended way to manage worksheets is Workbook#add_worksheet
|
|
40
|
+
# @see Workbook#add_worksheet
|
|
41
|
+
# @option options [String] name The name of this sheet.
|
|
42
|
+
def initialize(wb, options={})
|
|
43
|
+
@drawing = nil
|
|
44
|
+
@rows = SimpleTypedList.new Row
|
|
45
|
+
self.workbook = wb
|
|
46
|
+
@workbook.worksheets << self
|
|
47
|
+
@auto_fit_data = []
|
|
48
|
+
self.name = options[:name] || "Sheet" + (index+1).to_s
|
|
49
|
+
@magick_draw = Magick::Draw.new
|
|
50
|
+
@cols = SimpleTypedList.new Cell
|
|
51
|
+
@merged_cells = []
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Creates merge information for this worksheet.
|
|
55
|
+
# Cells can be merged by calling the merge_cells method on a worksheet.
|
|
56
|
+
# @example This would merge the three cells C1..E1 #
|
|
57
|
+
# worksheet.merge_cells "C1:E1"
|
|
58
|
+
# # you can also provide an array of cells to be merged
|
|
59
|
+
# worksheet.merge_cells worksheet.rows.first.cells[(2..4)]
|
|
60
|
+
# #alternatively you can do it from a single cell
|
|
61
|
+
# worksheet["C1"].merge worksheet["E1"]
|
|
62
|
+
# @param [Array, string]
|
|
63
|
+
def merge_cells(cells)
|
|
64
|
+
@merged_cells << if cells.is_a?(String)
|
|
65
|
+
cells
|
|
66
|
+
elsif cells.is_a?(Array)
|
|
67
|
+
cells = cells.sort { |x, y| x.r <=> y.r }
|
|
68
|
+
"#{cells.first.r}:#{cells.last.r}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# 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
|
|
75
|
+
# @return [Cell, Array]
|
|
76
|
+
def [](cell_def)
|
|
77
|
+
parts = cell_def.split(':')
|
|
78
|
+
first = name_to_cell parts[0]
|
|
79
|
+
|
|
80
|
+
if parts.size == 1
|
|
81
|
+
first
|
|
82
|
+
else
|
|
83
|
+
cells = []
|
|
84
|
+
last = name_to_cell(parts[1])
|
|
85
|
+
rows[(first.row.index..last.row.index)].each do |r|
|
|
86
|
+
r.cells[(first.index..last.index)].each do |c|
|
|
87
|
+
cells << c
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
cells
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# returns the column and row index for a named based cell
|
|
95
|
+
# @param [String] name The cell or cell range to return. "A1" will return the first cell of the first row.
|
|
96
|
+
# @return [Cell]
|
|
97
|
+
def name_to_cell(name)
|
|
98
|
+
col_index, row_index = *Axlsx::name_to_indices(name)
|
|
99
|
+
r = rows[row_index]
|
|
100
|
+
r.cells[col_index] if r
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# The name of the worksheet
|
|
104
|
+
# @param [String] v
|
|
105
|
+
def name=(v)
|
|
106
|
+
DataTypeValidator.validate "Worksheet.name", String, v
|
|
107
|
+
sheet_names = @workbook.worksheets.map { |s| s.name }
|
|
108
|
+
raise ArgumentError, (ERR_DUPLICATE_SHEET_NAME % v) if sheet_names.include?(v)
|
|
109
|
+
@name=v
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# The auto filter range for the worksheet
|
|
113
|
+
# @param [String] v
|
|
114
|
+
# @see auto_filter
|
|
115
|
+
def auto_filter=(v)
|
|
116
|
+
DataTypeValidator.validate "Worksheet.auto_filter", String, v
|
|
117
|
+
@auto_filter = v
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# The part name of this worksheet
|
|
121
|
+
# @return [String]
|
|
122
|
+
def pn
|
|
123
|
+
"#{WORKSHEET_PN % (index+1)}"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# The relationship part name of this worksheet
|
|
127
|
+
# @return [String]
|
|
128
|
+
def rels_pn
|
|
129
|
+
"#{WORKSHEET_RELS_PN % (index+1)}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# The relationship Id of thiw worksheet
|
|
133
|
+
# @return [String]
|
|
134
|
+
def rId
|
|
135
|
+
"rId#{index+1}"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# The index of this worksheet in the owning Workbook's worksheets list.
|
|
139
|
+
# @return [Integer]
|
|
140
|
+
def index
|
|
141
|
+
@workbook.worksheets.index(self)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# The drawing associated with this worksheet.
|
|
145
|
+
# @note the recommended way to work with drawings and charts is Worksheet#add_chart
|
|
146
|
+
# @return [Drawing]
|
|
147
|
+
# @see Worksheet#add_chart
|
|
148
|
+
def drawing
|
|
149
|
+
@drawing || @drawing = Axlsx::Drawing.new(self)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Adds a row to the worksheet and updates auto fit data
|
|
153
|
+
# @return [Row]
|
|
154
|
+
# @option options [Array] values
|
|
155
|
+
# @option options [Array, Symbol] types
|
|
156
|
+
# @option options [Array, Integer] style
|
|
157
|
+
def add_row(values=[], options={})
|
|
158
|
+
Row.new(self, values, options)
|
|
159
|
+
update_auto_fit_data @rows.last.cells
|
|
160
|
+
yield @rows.last if block_given?
|
|
161
|
+
@rows.last
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Set the style for cells in a specific row
|
|
165
|
+
# @param [Integer] index or range of indexes in the table
|
|
166
|
+
# @param [Integer] the cellXfs index
|
|
167
|
+
# @option options [Integer] col_offset only cells after this column will be updated.
|
|
168
|
+
# @note You can also specify the style in the add_row call
|
|
169
|
+
# @see Worksheet#add_row
|
|
170
|
+
# @see README.md for an example
|
|
171
|
+
def row_style(index, style, options={})
|
|
172
|
+
offset = options.delete(:col_offset) || 0
|
|
173
|
+
rs = @rows[index]
|
|
174
|
+
if rs.is_a?(Array)
|
|
175
|
+
rs.each { |r| r.cells[(offset..-1)].each { |c| c.style = style } }
|
|
176
|
+
else
|
|
177
|
+
rs.cells[(offset..-1)].each { |c| c.style = style }
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# returns the sheet data as columnw
|
|
182
|
+
def cols
|
|
183
|
+
@rows.transpose
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# Set the style for cells in a specific column
|
|
188
|
+
# @param [Integer] index the index of the column
|
|
189
|
+
# @param [Integer] the cellXfs index
|
|
190
|
+
# @option options [Integer] row_offset only cells after this column will be updated.
|
|
191
|
+
# @note You can also specify the style for specific columns in the call to add_row by using an array for the :styles option
|
|
192
|
+
# @see Worksheet#add_row
|
|
193
|
+
# @see README.md for an example
|
|
194
|
+
def col_style(index, style, options={})
|
|
195
|
+
offset = options.delete(:row_offset) || 0
|
|
196
|
+
@rows[(offset..-1)].each do |r|
|
|
197
|
+
cells = r.cells[index]
|
|
198
|
+
if cells.is_a?(Array)
|
|
199
|
+
cells.each { |c| c.style = style }
|
|
200
|
+
else
|
|
201
|
+
cells.style = style
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# 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.
|
|
207
|
+
# @param [Class] chart_type
|
|
208
|
+
# @option options [Array] start_at
|
|
209
|
+
# @option options [Array] end_at
|
|
210
|
+
# @option options [Cell, String] title
|
|
211
|
+
# @option options [Boolean] show_legend
|
|
212
|
+
# @option options [Integer] style
|
|
213
|
+
# @note each chart type also specifies additional options
|
|
214
|
+
# @see Chart
|
|
215
|
+
# @see Pie3DChart
|
|
216
|
+
# @see Bar3DChart
|
|
217
|
+
# @see Line3DChart
|
|
218
|
+
# @see README for examples
|
|
219
|
+
def add_chart(chart_type, options={})
|
|
220
|
+
chart = drawing.add_chart(chart_type, options)
|
|
221
|
+
yield chart if block_given?
|
|
222
|
+
chart
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Adds a media item to the worksheets drawing
|
|
226
|
+
# @param [Class] media_type
|
|
227
|
+
# @option options [] unknown
|
|
228
|
+
def add_image(options={})
|
|
229
|
+
image = drawing.add_image(options)
|
|
230
|
+
yield image if block_given?
|
|
231
|
+
image
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Serializes the worksheet document
|
|
235
|
+
# @return [String]
|
|
236
|
+
def to_xml
|
|
237
|
+
builder = Nokogiri::XML::Builder.new(:encoding => ENCODING) do |xml|
|
|
238
|
+
xml.worksheet(:xmlns => XML_NS,
|
|
239
|
+
:'xmlns:r' => XML_NS_R) {
|
|
240
|
+
if @auto_fit_data.size > 0
|
|
241
|
+
xml.cols {
|
|
242
|
+
@auto_fit_data.each_with_index do |col, index|
|
|
243
|
+
min_max = index+1
|
|
244
|
+
xml.col(:min=>min_max, :max=>min_max, :width => auto_width(col), :customWidth=>1)
|
|
245
|
+
end
|
|
246
|
+
}
|
|
247
|
+
end
|
|
248
|
+
xml.sheetData {
|
|
249
|
+
@rows.each do |row|
|
|
250
|
+
row.to_xml(xml)
|
|
251
|
+
end
|
|
252
|
+
}
|
|
253
|
+
xml.autoFilter :ref=>@auto_filter if @auto_filter
|
|
254
|
+
xml.mergeCells(:count=>@merged_cells.size) { @merged_cells.each { | mc | xml.mergeCell(:ref=>mc) } } unless @merged_cells.empty?
|
|
255
|
+
xml.drawing :"r:id"=>"rId1" if @drawing
|
|
256
|
+
}
|
|
257
|
+
end
|
|
258
|
+
builder.to_xml
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# The worksheet relationships. This is managed automatically by the worksheet
|
|
262
|
+
# @return [Relationships]
|
|
263
|
+
def relationships
|
|
264
|
+
r = Relationships.new
|
|
265
|
+
r << Relationship.new(DRAWING_R, "../#{@drawing.pn}") if @drawing
|
|
266
|
+
r
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
private
|
|
270
|
+
|
|
271
|
+
# assigns the owner workbook for this worksheet
|
|
272
|
+
def workbook=(v) DataTypeValidator.validate "Worksheet.workbook", Workbook, v; @workbook = v; end
|
|
273
|
+
|
|
274
|
+
# Updates auto fit data.
|
|
275
|
+
# 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
|
|
276
|
+
# attempt to identify the longest cell in the column. This is not 100% accurate as it needs to take into account
|
|
277
|
+
# any formatting that will be applied to the data, as well as the actual rendering size when the length and size is equal
|
|
278
|
+
# for two cells.
|
|
279
|
+
# @return [Array] of Cell objects
|
|
280
|
+
# @param [Array] cells an array of cells
|
|
281
|
+
def update_auto_fit_data(cells)
|
|
282
|
+
# TODO delay this until rendering. too much work when we dont know what they are going to do to the sheet.
|
|
283
|
+
styles = self.workbook.styles
|
|
284
|
+
cellXfs, fonts = styles.cellXfs, styles.fonts
|
|
285
|
+
sz = 11
|
|
286
|
+
cells.each_with_index do |item, index|
|
|
287
|
+
# ignore formula - there is no way for us to know the result
|
|
288
|
+
next if item.value.is_a?(String) && item.value.start_with?('=')
|
|
289
|
+
|
|
290
|
+
col = @auto_fit_data[index] || {:longest=>"", :sz=>sz}
|
|
291
|
+
cell_xf = cellXfs[item.style]
|
|
292
|
+
font = fonts[cell_xf.fontId || 0]
|
|
293
|
+
sz = item.sz || font.sz || fonts[0].sz
|
|
294
|
+
if (col[:longest].scan(/./mu).size * col[:sz]) < (item.value.to_s.scan(/./mu).size * sz)
|
|
295
|
+
col[:sz] = sz
|
|
296
|
+
col[:longest] = item.value.to_s
|
|
297
|
+
end
|
|
298
|
+
@auto_fit_data[index] = col
|
|
299
|
+
end
|
|
300
|
+
cells
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Determines the proper width for a column based on content.
|
|
304
|
+
# @note
|
|
305
|
+
# width = Truncate([!{Number of Characters} * !{Maximum Digit Width} + !{5 pixel padding}]/!{Maximum Digit Width}*256)/256
|
|
306
|
+
# @return [Float]
|
|
307
|
+
# @param [Hash] A hash of auto_fit_data
|
|
308
|
+
def auto_width(col)
|
|
309
|
+
mdw_count, font_scale, mdw = 0, col[:sz]/11.0, 6.0
|
|
310
|
+
mdw_count = col[:longest].scan(/./mu).reduce(0) do | count, char |
|
|
311
|
+
count +=1 if @magick_draw.get_type_metrics(char).max_advance >= mdw
|
|
312
|
+
count
|
|
313
|
+
end
|
|
314
|
+
((mdw_count * mdw + 5) / mdw * 256) / 256.0 * font_scale
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Something to look into:
|
|
318
|
+
# width calculation actually needs to be done agains the formatted value for items that apply a
|
|
319
|
+
# format
|
|
320
|
+
# def excel_format(cell)
|
|
321
|
+
# # The most common case.
|
|
322
|
+
# return time.value.to_s if cell.style == 0
|
|
323
|
+
#
|
|
324
|
+
# # The second most common case
|
|
325
|
+
# num_fmt = workbook.styles.cellXfs[items.style].numFmtId
|
|
326
|
+
# return value.to_s if num_fmt == 0
|
|
327
|
+
#
|
|
328
|
+
# format_code = workbook.styles.numFmts[num_fmt]
|
|
329
|
+
# # need to find some exceptionally fast way of parsing value according to
|
|
330
|
+
# # an excel format_code
|
|
331
|
+
# item.value.to_s
|
|
332
|
+
# end
|
|
333
|
+
|
|
334
|
+
end
|
|
335
|
+
end
|
data/lib/axlsx.rb
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
Encoding::default_internal = 'UTF-8' unless RUBY_VERSION < '1.9'
|
|
2
|
+
Encoding::default_external = 'UTF-8' unless RUBY_VERSION < '1.9'
|
|
3
|
+
|
|
4
|
+
require 'axlsx/version.rb'
|
|
5
|
+
|
|
6
|
+
require 'axlsx/util/simple_typed_list.rb'
|
|
7
|
+
require 'axlsx/util/constants.rb'
|
|
8
|
+
require 'axlsx/util/validators.rb'
|
|
9
|
+
|
|
10
|
+
require 'axlsx/util/ms_off_crypto.rb'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# to be included with parsable intitites.
|
|
14
|
+
#require 'axlsx/util/parser.rb'
|
|
15
|
+
|
|
16
|
+
require 'axlsx/stylesheet/styles.rb'
|
|
17
|
+
|
|
18
|
+
require 'axlsx/doc_props/app.rb'
|
|
19
|
+
require 'axlsx/doc_props/core.rb'
|
|
20
|
+
require 'axlsx/content_type/content_type.rb'
|
|
21
|
+
require 'axlsx/rels/relationships.rb'
|
|
22
|
+
|
|
23
|
+
require 'axlsx/drawing/drawing.rb'
|
|
24
|
+
require 'axlsx/workbook/workbook.rb'
|
|
25
|
+
require 'axlsx/package.rb'
|
|
26
|
+
|
|
27
|
+
#required gems
|
|
28
|
+
require 'nokogiri'
|
|
29
|
+
require 'active_support/core_ext/object/instance_variables'
|
|
30
|
+
require 'active_support/inflector'
|
|
31
|
+
require 'RMagick'
|
|
32
|
+
require 'zip/zip'
|
|
33
|
+
|
|
34
|
+
#core dependencies
|
|
35
|
+
require 'bigdecimal'
|
|
36
|
+
require 'time'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# 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.
|
|
40
|
+
module Axlsx
|
|
41
|
+
# determines the cell range for the items provided
|
|
42
|
+
def self.cell_range(items)
|
|
43
|
+
return "" unless items.first.is_a? Cell
|
|
44
|
+
ref = "'#{items.first.row.worksheet.name}'!" +
|
|
45
|
+
"#{items.first.r_abs}"
|
|
46
|
+
ref += ":#{items.last.r_abs}" if items.size > 1
|
|
47
|
+
ref
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.name_to_indices(name)
|
|
51
|
+
raise ArgumentError, 'invalid cell name' unless name.size > 1
|
|
52
|
+
v = name[/[A-Z]+/].reverse.chars.reduce({:base=>1, :i=>0}) do |v, c|
|
|
53
|
+
v[:i] += ((c.bytes.first - 65) + v[:base]); v[:base] *= 26; v
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
[v[:i]-1, ((name[/[1-9]+/]).to_i)-1]
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
end
|
data/test/drawing/tc_axis.rb
CHANGED
|
@@ -9,7 +9,7 @@ class TestAxis < Test::Unit::TestCase
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def test_initialization
|
|
12
|
-
assert_equal(@axis.axPos, :
|
|
12
|
+
assert_equal(@axis.axPos, :b, "axis position default incorrect")
|
|
13
13
|
assert_equal(@axis.tickLblPos, :nextTo, "tick label position default incorrect")
|
|
14
14
|
assert_equal(@axis.tickLblPos, :nextTo, "tick label position default incorrect")
|
|
15
15
|
assert_equal(@axis.crosses, :autoZero, "tick label position default incorrect")
|
data/test/drawing/tc_cat_axis.rb
CHANGED
|
@@ -9,7 +9,7 @@ class TestCatAxis < Test::Unit::TestCase
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def test_initialization
|
|
12
|
-
assert_equal(@axis.auto,
|
|
12
|
+
assert_equal(@axis.auto, 1, "axis auto default incorrect")
|
|
13
13
|
assert_equal(@axis.lblAlgn, :ctr, "label align default incorrect")
|
|
14
14
|
assert_equal(@axis.lblOffset, "100%", "label offset default incorrect")
|
|
15
15
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'axlsx.rb'
|
|
3
|
+
|
|
4
|
+
class TestLineSeries < Test::Unit::TestCase
|
|
5
|
+
|
|
6
|
+
def setup
|
|
7
|
+
p = Axlsx::Package.new
|
|
8
|
+
@ws = p.workbook.add_worksheet :name=>"hmmm"
|
|
9
|
+
chart = @ws.drawing.add_chart Axlsx::Bar3DChart, :title => "fishery"
|
|
10
|
+
@series = chart.add_series :data=>[0,1,2], :labels=>["zero", "one", "two"], :title=>"bob"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_initialize
|
|
14
|
+
assert_equal(@series.title, "bob", "series title has been applied")
|
|
15
|
+
assert_equal(@series.data, [0,1,2], "data option applied")
|
|
16
|
+
assert_equal(@series.labels, ["zero", "one","two"], "labels option applied")
|
|
17
|
+
assert_equal(@series.shape, :box, "series shape has been applied")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_data
|
|
21
|
+
assert_equal(@series.data, [0,1,2])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_labels
|
|
25
|
+
assert_equal(@series.labels, ["zero", "one", "two"])
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_shape
|
|
29
|
+
assert_raise(ArgumentError, "require valid shape") { @series.shape = :teardropt }
|
|
30
|
+
assert_nothing_raised("allow valid shape") { @series.shape = :cone }
|
|
31
|
+
assert(@series.shape == :cone)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'axlsx.rb'
|
|
3
|
+
|
|
4
|
+
class TestPictureLocking < Test::Unit::TestCase
|
|
5
|
+
def setup
|
|
6
|
+
@item = Axlsx::CellAlignment.new
|
|
7
|
+
end
|
|
8
|
+
def teardown
|
|
9
|
+
end
|
|
10
|
+
def test_initialiation
|
|
11
|
+
assert_equal(@item.horizontal, nil)
|
|
12
|
+
assert_equal(@item.vertical, nil)
|
|
13
|
+
assert_equal(@item.textRotation, nil)
|
|
14
|
+
assert_equal(@item.wrapText, nil)
|
|
15
|
+
assert_equal(@item.indent, nil)
|
|
16
|
+
assert_equal(@item.relativeIndent, nil)
|
|
17
|
+
assert_equal(@item.justifyLastLine, nil)
|
|
18
|
+
assert_equal(@item.shrinkToFit, nil)
|
|
19
|
+
assert_equal(@item.readingOrder, nil)
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_horizontal
|
|
24
|
+
assert_raise(ArgumentError) { @item.horizontal = :red }
|
|
25
|
+
assert_nothing_raised { @item.horizontal = :left }
|
|
26
|
+
assert_equal(@item.horizontal, :left )
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_vertical
|
|
30
|
+
assert_raise(ArgumentError) { @item.vertical = :red }
|
|
31
|
+
assert_nothing_raised { @item.vertical = :top }
|
|
32
|
+
assert_equal(@item.vertical, :top )
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_textRotation
|
|
36
|
+
assert_raise(ArgumentError) { @item.textRotation = -1 }
|
|
37
|
+
assert_nothing_raised { @item.textRotation = 5 }
|
|
38
|
+
assert_equal(@item.textRotation, 5 )
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_wrapText
|
|
42
|
+
assert_raise(ArgumentError) { @item.wrapText = -1 }
|
|
43
|
+
assert_nothing_raised { @item.wrapText = false }
|
|
44
|
+
assert_equal(@item.wrapText, false )
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_indent
|
|
48
|
+
assert_raise(ArgumentError) { @item.indent = -1 }
|
|
49
|
+
assert_nothing_raised { @item.indent = 5 }
|
|
50
|
+
assert_equal(@item.indent, 5 )
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_relativeIndent
|
|
54
|
+
assert_raise(ArgumentError) { @item.relativeIndent = :symbol }
|
|
55
|
+
assert_nothing_raised { @item.relativeIndent = 5 }
|
|
56
|
+
assert_equal(@item.relativeIndent, 5 )
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_justifyLastLine
|
|
60
|
+
assert_raise(ArgumentError) { @item.justifyLastLine = -1 }
|
|
61
|
+
assert_nothing_raised { @item.justifyLastLine = true }
|
|
62
|
+
assert_equal(@item.justifyLastLine, true )
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_shrinkToFit
|
|
66
|
+
assert_raise(ArgumentError) { @item.shrinkToFit = -1 }
|
|
67
|
+
assert_nothing_raised { @item.shrinkToFit = true }
|
|
68
|
+
assert_equal(@item.shrinkToFit, true )
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_readingOrder
|
|
72
|
+
assert_raise(ArgumentError) { @item.readingOrder = -1 }
|
|
73
|
+
assert_nothing_raised { @item.readingOrder = 2 }
|
|
74
|
+
assert_equal(@item.readingOrder, 2 )
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end
|
data/test/tc_package.rb
CHANGED
|
@@ -11,6 +11,16 @@ class TestPackage < Test::Unit::TestCase
|
|
|
11
11
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
def test_core_accessor
|
|
15
|
+
assert_equal(@package.core, @package.instance_values["core"])
|
|
16
|
+
assert_raise(NoMethodError) {@package.core = nil }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_app_accessor
|
|
20
|
+
assert_equal(@package.app, @package.instance_values["app"])
|
|
21
|
+
assert_raise(NoMethodError) {@package.app = nil }
|
|
22
|
+
end
|
|
23
|
+
|
|
14
24
|
def test_default_objects_are_created
|
|
15
25
|
assert(@package.instance_values["app"].is_a?(Axlsx::App), 'App object not created')
|
|
16
26
|
assert(@package.instance_values["core"].is_a?(Axlsx::Core), 'Core object not created')
|
|
@@ -175,5 +175,20 @@ class TestCell < Test::Unit::TestCase
|
|
|
175
175
|
assert_equal(@c.b, false)
|
|
176
176
|
end
|
|
177
177
|
|
|
178
|
+
def test_merge_with_string
|
|
179
|
+
assert_equal(@c.row.worksheet.merged_cells.size, 0)
|
|
180
|
+
@c.row.add_cell 2
|
|
181
|
+
@c.row.add_cell 3
|
|
182
|
+
@c.merge "A2"
|
|
183
|
+
assert_equal(@c.row.worksheet.merged_cells.last, "A1:A2")
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def test_merge_with_cell
|
|
187
|
+
assert_equal(@c.row.worksheet.merged_cells.size, 0)
|
|
188
|
+
@c.row.add_cell 2
|
|
189
|
+
@c.row.add_cell 3
|
|
190
|
+
@c.merge @row.cells.last
|
|
191
|
+
assert_equal(@c.row.worksheet.merged_cells.last, "A1:C1")
|
|
192
|
+
end
|
|
178
193
|
|
|
179
194
|
end
|
|
@@ -132,7 +132,28 @@ class TestWorksheet < Test::Unit::TestCase
|
|
|
132
132
|
|
|
133
133
|
def test_auto_width
|
|
134
134
|
assert(@ws.send(:auto_width, {:sz=>11, :longest=>"fisheries"}) > @ws.send(:auto_width, {:sz=>11, :longest=>"fish"}), "longer strings get a longer auto_width at the same font size")
|
|
135
|
-
|
|
135
|
+
|
|
136
|
+
assert(@ws.send(:auto_width, {:sz=>11, :longest=>"fish"}) < @ws.send(:auto_width, {:sz=>12, :longest=>"fish"}), "larger fonts produce longer with with same string")
|
|
137
|
+
|
|
136
138
|
end
|
|
137
139
|
|
|
140
|
+
def test_merge_cells
|
|
141
|
+
assert(@ws.merged_cells.is_a?(Array))
|
|
142
|
+
assert_equal(@ws.merged_cells.size, 0)
|
|
143
|
+
@ws.add_row [1,2,3]
|
|
144
|
+
@ws.add_row [4,5,6]
|
|
145
|
+
@ws.add_row [7,8,9]
|
|
146
|
+
@ws.merge_cells "A1:A2"
|
|
147
|
+
@ws.merge_cells "B2:C3"
|
|
148
|
+
@ws.merge_cells @ws.rows.last.cells[(0..1)]
|
|
149
|
+
assert_equal(@ws.merged_cells.size, 3)
|
|
150
|
+
assert_equal(@ws.merged_cells.last, "A3:B3")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def test_auto_filter
|
|
154
|
+
assert(@ws.auto_filter.nil?)
|
|
155
|
+
assert_raise(ArgumentError) { @ws.auto_filter = 123 }
|
|
156
|
+
@ws.auto_filter = "A1:D9"
|
|
157
|
+
assert_equal(@ws.auto_filter, "A1:D9")
|
|
158
|
+
end
|
|
138
159
|
end
|