axlsx 1.0.17 → 1.0.18

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,61 +1,61 @@
1
1
  # encoding: UTF-8
2
2
  module Axlsx
3
- # A cell in a worksheet.
3
+ # A cell in a worksheet.
4
4
  # Cell stores inforamation requried to serialize a single worksheet cell to xml. You must provde the Row that the cell belongs to and the cells value. The data type will automatically be determed if you do not specify the :type option. The default style will be applied if you do not supply the :style option. Changing the cell's type will recast the value to the type specified. Altering the cell's value via the property accessor will also automatically cast the provided value to the cell's type.
5
5
  # @example Manually creating and manipulating Cell objects
6
- # ws = Workbook.new.add_worksheet
6
+ # ws = Workbook.new.add_worksheet
7
7
  # # This is the simple, and recommended way to create cells. Data types will automatically be determined for you.
8
8
  # ws.add_row :values => [1,"fish",Time.now]
9
9
  #
10
10
  # # but you can also do this
11
11
  # r = ws.add_row
12
12
  # r.add_cell 1
13
- #
13
+ #
14
14
  # # or even this
15
15
  # r = ws.add_row
16
16
  # c = Cell.new row, 1, :value=>integer
17
17
  #
18
18
  # # cells can also be accessed via Row#cells. The example here changes the cells type, which will automatically updated the value from 1 to 1.0
19
19
  # r.cells.last.type = :float
20
- #
20
+ #
21
21
  # @note The recommended way to generate cells is via Worksheet#add_row
22
- #
22
+ #
23
23
  # @see Worksheet#add_row
24
24
  class Cell
25
25
 
26
26
 
27
27
  # An array of available inline styes.
28
- INLINE_STYLES = ['value', 'type', 'font_name', 'charset',
29
- 'family', 'b', 'i', 'strike','outline',
30
- 'shadow', 'condense', 'extend', 'u',
28
+ INLINE_STYLES = ['value', 'type', 'font_name', 'charset',
29
+ 'family', 'b', 'i', 'strike','outline',
30
+ 'shadow', 'condense', 'extend', 'u',
31
31
  'vertAlign', 'sz', 'color', 'scheme']
32
32
 
33
33
 
34
34
  # The index of the cellXfs item to be applied to this cell.
35
- # @return [Integer]
35
+ # @return [Integer]
36
36
  # @see Axlsx::Styles
37
37
  attr_reader :style
38
38
 
39
39
  # The row this cell belongs to.
40
40
  # @return [Row]
41
41
  attr_reader :row
42
-
43
- # The cell's data type. Currently only four types are supported, :time, :float, :integer and :string.
44
- # Changing the type for a cell will recast the value into that type. If no type option is specified in the constructor, the type is
42
+
43
+ # The cell's data type. Currently only six types are supported, :date, :time, :float, :integer, :string and :boolean.
44
+ # Changing the type for a cell will recast the value into that type. If no type option is specified in the constructor, the type is
45
45
  # automatically determed.
46
46
  # @see Cell#cell_type_from_value
47
- # @return [Symbol] The type of data this cell's value is cast to.
48
- # @raise [ArgumentExeption] Cell.type must be one of [:time, :float, :integer, :string]
49
- # @note
47
+ # @return [Symbol] The type of data this cell's value is cast to.
48
+ # @raise [ArgumentExeption] Cell.type must be one of [:date, time, :float, :integer, :string, :boolean]
49
+ # @note
50
50
  # If the value provided cannot be cast into the type specified, type is changed to :string and the following logic is applied.
51
- # :string to :integer or :float, type coversions always return 0 or 0.0
51
+ # :string to :integer or :float, type conversions always return 0 or 0.0
52
52
  # :string, :integer, or :float to :time conversions always return the original value as a string and set the cells type to :string.
53
53
  # No support is currently implemented for parsing time strings.
54
54
  attr_reader :type
55
55
  # @see type
56
- def type=(v)
57
- RestrictionValidator.validate "Cell.type", [:time, :float, :integer, :string], v
58
- @type=v
56
+ def type=(v)
57
+ RestrictionValidator.validate "Cell.type", [:date, :time, :float, :integer, :string, :boolean], v
58
+ @type=v
59
59
  self.value = @value unless @value.nil?
60
60
  end
61
61
 
@@ -68,7 +68,7 @@ module Axlsx
68
68
  #TODO: consider doing value based type determination first?
69
69
  @value = cast_value(v)
70
70
  end
71
-
71
+
72
72
  # The inline font_name property for the cell
73
73
  # @return [String]
74
74
  attr_reader :font_name
@@ -139,7 +139,7 @@ module Axlsx
139
139
  # @return [Color]
140
140
  attr_reader :color
141
141
  # @param [String] The 8 character representation for an rgb color #FFFFFFFF"
142
- def color=(v)
142
+ def color=(v)
143
143
  @color = v.is_a?(Color) ? v : Color.new(:rgb=>v)
144
144
  end
145
145
 
@@ -164,7 +164,7 @@ module Axlsx
164
164
  def scheme=(v) RestrictionValidator.validate "Cell.schema", [:none, :major, :minor], v; @scheme = v; end
165
165
 
166
166
  # @param [Row] row The row this cell belongs to.
167
- # @param [Any] value The value associated with this cell.
167
+ # @param [Any] value The value associated with this cell.
168
168
  # @option options [Symbol] type The intended data type for this cell. If not specified the data type will be determined internally based on the vlue provided.
169
169
  # @option options [Integer] style The index of the cellXfs item to be applied to this cell. If not specified, the default style (0) will be applied.
170
170
  # @option options [String] font_name
@@ -183,9 +183,11 @@ module Axlsx
183
183
  # @option options [String] color an 8 letter rgb specification
184
184
  # @option options [Symbol] scheme must be one of :none, major, :minor
185
185
  def initialize(row, value="", options={})
186
- self.row=row
186
+ self.row=row
187
+ @font_name = @charset = @family = @b = @i = @strike = @outline = @shadow = nil
188
+ @condense = @u = @vertAlign = @sz = @color = @scheme = @extend = @ssti = nil
187
189
  @styles = row.worksheet.workbook.styles
188
- @row.cells << self
190
+ @row.cells << self
189
191
  options.each do |o|
190
192
  self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
191
193
  end
@@ -197,14 +199,14 @@ module Axlsx
197
199
  # The Shared Strings Table index for this cell
198
200
  # @return [Integer]
199
201
  attr_reader :ssti
200
-
202
+
201
203
  # equality comparison to test value, type and inline style attributes
202
204
  # this is how we work out if the cell needs to be added or already exists in the shared strings table
203
205
  def shareable(v)
204
206
 
205
207
  #using reject becase 1.8.7 select returns an array...
206
- v_hash = v.instance_values.reject { |k, v| !INLINE_STYLES.include?(k) }
207
- self_hash = self.instance_values.reject { |k, v| !INLINE_STYLES.include?(k) }
208
+ v_hash = v.instance_values.reject { |key, val| !INLINE_STYLES.include?(key) }
209
+ self_hash = self.instance_values.reject { |key, val| !INLINE_STYLES.include?(key) }
208
210
  # required as color is an object, and the comparison will fail even though both use the same color.
209
211
  v_hash['color'] = v_hash['color'].instance_values if v_hash['color']
210
212
  self_hash['color'] = self_hash['color'].instance_values if self_hash['color']
@@ -219,14 +221,14 @@ module Axlsx
219
221
 
220
222
  # @return [String] The alpha(column)numeric(row) reference for this sell.
221
223
  # @example Relative Cell Reference
222
- # ws.rows.first.cells.first.r #=> "A1"
224
+ # ws.rows.first.cells.first.r #=> "A1"
223
225
  def r
224
- "#{col_ref}#{@row.index+1}"
226
+ "#{col_ref}#{@row.index+1}"
225
227
  end
226
228
 
227
229
  # @return [String] The absolute alpha(column)numeric(row) reference for this sell.
228
230
  # @example Absolute Cell Reference
229
- # ws.rows.first.cells.first.r #=> "$A$1"
231
+ # ws.rows.first.cells.first.r #=> "$A$1"
230
232
  def r_abs
231
233
  "$#{r.split('').join('$')}"
232
234
  end
@@ -255,7 +257,7 @@ module Axlsx
255
257
  target.r
256
258
  end
257
259
  self.row.worksheet.merge_cells "#{self.r}:#{range_end}" unless range_end.nil?
258
- end
260
+ end
259
261
 
260
262
  # builds an xml text run based on this cells attributes. This is extracted from to_xml so that shared strings can use it.
261
263
  # @param [Nokogiri::XML::Builder] xml The document builder instance this output will be added to.
@@ -278,7 +280,7 @@ module Axlsx
278
280
  xml.sz(:val=>@sz) if @sz
279
281
  xml.u(:val=>@u) if @u
280
282
  # :baseline, :subscript, :superscript
281
- xml.vertAlign(:val=>@vertAlign) if @verAlign
283
+ xml.vertAlign(:val=>@vertAlign) if @vertAlign
282
284
  # :none, major, :minor
283
285
  xml.scheme(:val=>@scheme) if @scheme
284
286
  }
@@ -286,14 +288,14 @@ module Axlsx
286
288
  }
287
289
  else
288
290
  xml.t @value.to_s
289
- end
291
+ end
290
292
  end
291
293
 
292
294
  # Serializes the cell
293
295
  # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to.
294
296
  # @return [String] xml text for the cell
295
- def to_xml(xml)
296
- if @type == :string
297
+ def to_xml(xml)
298
+ if @type == :string
297
299
  #parse formula
298
300
  if @value.start_with?('=')
299
301
  xml.c(:r => r, :t=>:str, :s=>style) {
@@ -312,29 +314,31 @@ module Axlsx
312
314
  }
313
315
  end
314
316
  end
317
+ elsif @type == :date
318
+ # TODO: See if this is subject to the same restriction as Time below
319
+ v = DateTimeConverter::date_to_serial @value
320
+ xml.c(:r => r, :s => style) { xml.v v }
315
321
  elsif @type == :time
316
- # Using hardcoded offsets here as some operating systems will not except a 'negative' offset from the ruby epoc.
317
- epoc1900 = -2209021200 #Time.local(1900, 1, 1)
318
- epoc1904 = -2082877200 #Time.local(1904, 1, 1)
319
- epoc = Workbook.date1904 ? epoc1904 : epoc1900
320
- v = ((@value.localtime.to_f - epoc) /60.0/60.0/24.0).to_f
322
+ v = DateTimeConverter::time_to_serial @value
321
323
  xml.c(:r => r, :s => style) { xml.v v }
324
+ elsif @type == :boolean
325
+ xml.c(:r => r, :s => style, :t => :b) { xml.v value }
322
326
  else
323
327
  xml.c(:r => r, :s => style) { xml.v value }
324
328
  end
325
329
  end
326
330
 
327
- private
331
+ private
328
332
 
329
333
  # @see ssti
330
- def ssti=(v)
334
+ def ssti=(v)
331
335
  Axlsx::validate_unsigned_int(v)
332
336
  @ssti = v
333
337
  end
334
338
 
335
339
  # assigns the owning row for this cell.
336
340
  def row=(v) DataTypeValidator.validate "Cell.row", Row, v; @row=v end
337
-
341
+
338
342
  # converts the column index into alphabetical values.
339
343
  # @note This follows the standard spreadsheet convention of naming columns A to Z, followed by AA to AZ etc.
340
344
  # @return [String]
@@ -349,15 +353,21 @@ module Axlsx
349
353
  chars.reverse.join
350
354
  end
351
355
 
352
- # Determines the cell type based on the cell value.
356
+ # Determines the cell type based on the cell value.
353
357
  # @note This is only used when a cell is created but no :type option is specified, the following rules apply:
354
- # 1. If the value is an instance of Time, the type is set to :time
355
- # 2. :float and :integer types are determined by regular expression matching.
356
- # 3. Anything that does not meet either of the above is determined to be :string.
358
+ # 1. If the value is an instance of Date, the type is set to :date
359
+ # 2. If the value is an instance of Time, the type is set to :time
360
+ # 3. If the value is an instance of TrueClass or FalseClass, the type is set to :boolean
361
+ # 4. :float and :integer types are determined by regular expression matching.
362
+ # 5. Anything that does not meet either of the above is determined to be :string.
357
363
  # @return [Symbol] The determined type
358
- def cell_type_from_value(v)
359
- if v.is_a? Time
364
+ def cell_type_from_value(v)
365
+ if v.is_a?(Date)
366
+ :date
367
+ elsif v.is_a?(Time)
360
368
  :time
369
+ elsif v.is_a?(TrueClass) || v.is_a?(FalseClass)
370
+ :boolean
361
371
  elsif v.to_s.match(/\A[+-]?\d+?\Z/) #numeric
362
372
  :integer
363
373
  elsif v.to_s.match(/\A[+-]?\d+\.\d+?\Z/) #float
@@ -367,22 +377,27 @@ module Axlsx
367
377
  end
368
378
  end
369
379
 
370
- # Cast the value into this cells data type.
371
- # @note
380
+ # Cast the value into this cells data type.
381
+ # @note
372
382
  # About Time - Time in OOXML is *different* from what you might expect. The history as to why is interesting, but you can safely assume that if you are generating docs on a mac, you will want to specify Workbook.1904 as true when using time typed values.
373
383
  # @see Axlsx#date1904
374
384
  def cast_value(v)
375
- if (@type == :time && v.is_a?(Time)) || (@type == :time && v.respond_to?(:to_time))
385
+ if @type == :date
386
+ self.style = STYLE_DATE if self.style == 0
387
+ v
388
+ elsif (@type == :time && v.is_a?(Time)) || (@type == :time && v.respond_to?(:to_time))
376
389
  self.style = STYLE_DATE if self.style == 0
377
390
  v.respond_to?(:to_time) ? v.to_time : v
378
391
  elsif @type == :float
379
392
  v.to_f
380
393
  elsif @type == :integer
381
394
  v.to_i
395
+ elsif @type == :boolean
396
+ v ? 1 : 0
382
397
  else
383
398
  @type = :string
384
399
  v.to_s
385
400
  end
386
- end
401
+ end
387
402
  end
388
403
  end
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+ require "date"
3
+
4
+ module Axlsx
5
+ # The DateTimeConverter class converts both data and time types to their apprpriate excel serializations
6
+ class DateTimeConverter
7
+
8
+ # The date_to_serial method converts Date objects to the equivelant excel serialized forms
9
+ # @param [Date] date the date to be serialized
10
+ # @return [Numeric]
11
+ def self.date_to_serial(date)
12
+ epoch = Axlsx::Workbook::date1904 ? Date.new(1904) : Date.new(1899, 12, 30)
13
+ (date-epoch).to_f
14
+ end
15
+
16
+ # The time_to_serial methond converts a Time object its excel serialized form.
17
+ # @param [Time] time the time to be serialized
18
+ # @return [Numeric]
19
+ def self.time_to_serial(time)
20
+ # Using hardcoded offsets here as some operating systems will not except
21
+ # a 'negative' offset from the ruby epoch.
22
+ epoch1900 = -2209161600 # Time.utc(1899, 12, 30).to_i
23
+ epoch1904 = -2082844800 # Time.utc(1904, 1, 1).to_i
24
+ seconds_per_day = 86400 # 60*60*24
25
+ epoch = Axlsx::Workbook::date1904 ? epoch1904 : epoch1900
26
+ (time.to_f - epoch)/seconds_per_day
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,94 @@
1
+ module Axlsx
2
+ # PageMargins specify the margins when printing a worksheet.
3
+ #
4
+ # For compatibility, PageMargins serialize to an empty string, unless at least one custom margin value
5
+ # has been specified. Otherwise, it serializes to a PageMargin element specifying all 6 margin values
6
+ # (using default values for margins that have not been specified explicitly).
7
+ #
8
+ # @note The recommended way to manage page margins is via Worksheet#page_margins
9
+ # @see Worksheet#page_margins
10
+ # @see Worksheet#initialize
11
+ class PageMargins
12
+
13
+ # Default left and right margin (in inches)
14
+ DEFAULT_LEFT_RIGHT = 0.75
15
+
16
+ # Default top and bottom margins (in inches)
17
+ DEFAULT_TOP_BOTTOM = 1.00
18
+
19
+ # Default header and footer margins (in inches)
20
+ DEFAULT_HEADER_FOOTER = 0.50
21
+
22
+ # Left margin (in inches)
23
+ # @return [Float]
24
+ attr_reader :left
25
+
26
+ # Right margin (in inches)
27
+ # @return [Float]
28
+ attr_reader :right
29
+
30
+ # Top margin (in inches)
31
+ # @return [Float]
32
+ attr_reader :top
33
+
34
+ # Bottom margin (in inches)
35
+ # @return [Float]
36
+ attr_reader :bottom
37
+
38
+ # Header margin (in inches)
39
+ # @return [Float]
40
+ attr_reader :header
41
+
42
+ # Footer margin (in inches)
43
+ # @return [Float]
44
+ attr_reader :footer
45
+
46
+ # Creates a new PageMargins object
47
+ # @option options [Numeric] left The left margin in inches
48
+ # @option options [Numeric] right The right margin in inches
49
+ # @option options [Numeric] bottom The bottom margin in inches
50
+ # @option options [Numeric] top The top margin in inches
51
+ # @option options [Numeric] header The header margin in inches
52
+ # @option options [Numeric] footer The footer margin in inches
53
+ def initialize(options={})
54
+ # Default values taken from MS Excel for Mac 2011
55
+ @left = @right = DEFAULT_LEFT_RIGHT
56
+ @top = @bottom = DEFAULT_TOP_BOTTOM
57
+ @header = @footer = DEFAULT_HEADER_FOOTER
58
+
59
+ options.each do |o|
60
+ self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
61
+ end
62
+ end
63
+
64
+ # Set some or all margins at once.
65
+ # @param [Hash] margins the margins to set (possible keys are :left, :right, :top, :bottom, :header and :footer).
66
+ def set(margins)
67
+ margins.select do |k, v|
68
+ next unless [:left, :right, :top, :bottom, :header, :footer].include? k
69
+ send("#{k}=", v)
70
+ end
71
+ end
72
+
73
+ # @see left
74
+ def left=(v); Axlsx::validate_unsigned_numeric(v); @left = v end
75
+ # @see right
76
+ def right=(v); Axlsx::validate_unsigned_numeric(v); @right = v end
77
+ # @see top
78
+ def top=(v); Axlsx::validate_unsigned_numeric(v); @top = v end
79
+ # @see bottom
80
+ def bottom=(v); Axlsx::validate_unsigned_numeric(v); @bottom = v end
81
+ # @see header
82
+ def header=(v); Axlsx::validate_unsigned_numeric(v); @header = v end
83
+ # @see footer
84
+ def footer=(v); Axlsx::validate_unsigned_numeric(v); @footer = v end
85
+
86
+ # Serializes the page margins element
87
+ # @note For compatibility, this is a noop unless custom margins have been specified.
88
+ # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to.
89
+ # @see #custom_margins_specified?
90
+ def to_xml(xml)
91
+ xml.pageMargins :left => left, :right => right, :top => top, :bottom => bottom, :header => header, :footer => footer
92
+ end
93
+ end
94
+ end
@@ -13,12 +13,14 @@ module Axlsx
13
13
  # @return [SimpleTypedList]
14
14
  attr_reader :cells
15
15
 
16
+ # The height of this row in points, if set explicitly.
17
+ # @return [Float]
18
+ attr_reader :height
19
+
16
20
  # TODO 18.3.1.73
17
21
  # collapsed
18
22
  # customFormat
19
- # customHeight
20
23
  # hidden
21
- # ht (height)
22
24
  # outlineLevel
23
25
  # ph
24
26
  # s (style)
@@ -37,28 +39,33 @@ module Axlsx
37
39
  # If the style option is not defined, the default style (0) is applied to each cell.
38
40
  # @param [Worksheet] worksheet
39
41
  # @option options [Array] values
40
- # @option options [Array, Symbol] types
41
- # @option options [Array, Integer] style
42
+ # @option options [Array, Symbol] types
43
+ # @option options [Array, Integer] style
44
+ # @option options [Float] height the row's height (in points)
42
45
  # @see Row#array_to_cells
43
46
  # @see Cell
44
47
  def initialize(worksheet, values=[], options={})
48
+ @height = nil
45
49
  self.worksheet = worksheet
46
50
  @cells = SimpleTypedList.new Cell
47
51
  @worksheet.rows << self
52
+ self.height = options.delete(:height) if options[:height]
48
53
  array_to_cells(values, options)
49
54
  end
50
55
 
51
56
  # The index of this row in the worksheet
52
57
  # @return [Integer]
53
- def index
58
+ def index
54
59
  worksheet.rows.index(self)
55
60
  end
56
-
61
+
57
62
  # Serializes the row
58
63
  # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to.
59
64
  # @return [String]
60
65
  def to_xml(xml)
61
- xml.row(:r => index+1) { @cells.each { |cell| cell.to_xml(xml) } }
66
+ attrs = {:r => index+1}
67
+ attrs.merge!(:customHeight => 1, :ht => height) if custom_height?
68
+ xml.row(attrs) { |ixml| @cells.each { |cell| cell.to_xml(ixml) } }
62
69
  end
63
70
 
64
71
  # Adds a singel sell to the row based on the data provided and updates the worksheet's autofit data.
@@ -68,7 +75,7 @@ module Axlsx
68
75
  update_auto_fit_data
69
76
  c
70
77
  end
71
-
78
+
72
79
  # sets the style for every cell in this row
73
80
  def style=(style)
74
81
  cells.each_with_index do | cell, index |
@@ -78,17 +85,27 @@ module Axlsx
78
85
  end
79
86
 
80
87
  # returns the cells in this row as an array
81
- # This lets us transpose the rows into columns
88
+ # This lets us transpose the rows into columns
82
89
  # @return [Array]
83
90
  def to_ary
84
91
  @cells.to_ary
85
92
  end
86
93
 
94
+ # @see height
95
+ def height=(v); Axlsx::validate_unsigned_numeric(v) unless v.nil?; @height = v end
96
+
97
+ # true if the row height has been manually set
98
+ # @return [Boolean]
99
+ # @see #height
100
+ def custom_height?
101
+ @height != nil
102
+ end
103
+
87
104
  private
88
105
 
89
106
  # assigns the owning worksheet for this row
90
107
  def worksheet=(v) DataTypeValidator.validate "Row.worksheet", Worksheet, v; @worksheet=v; end
91
-
108
+
92
109
  # Tell the worksheet to update autofit data for the columns based on this row's cells.
93
110
  # @return [SimpleTypedList]
94
111
  def update_auto_fit_data
@@ -103,13 +120,13 @@ module Axlsx
103
120
  # If the style option is defined and is an Integer, it is applied to all cells created.
104
121
  # If the style option is an array, style is applied by index for each cell.
105
122
  # @option options [Array] values
106
- # @option options [Array, Symbol] types
107
- # @option options [Array, Integer] style
123
+ # @option options [Array, Symbol] types
124
+ # @option options [Array, Integer] style
108
125
  def array_to_cells(values, options={})
109
126
  values = values
110
127
  DataTypeValidator.validate 'Row.array_to_cells', Array, values
111
128
  types, style = options.delete(:types), options.delete(:style)
112
- values.each_with_index do |value, index|
129
+ values.each_with_index do |value, index|
113
130
  cell_style = style.is_a?(Array) ? style[index] : style
114
131
  options[:style] = cell_style if cell_style
115
132
  cell_type = types.is_a?(Array)? types[index] : types
@@ -118,5 +135,5 @@ module Axlsx
118
135
  end
119
136
  end
120
137
  end
121
-
138
+
122
139
  end