roo 2.1.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,94 @@
1
+ module Roo
2
+ class Excelx
3
+ class Cell
4
+ class Base
5
+ attr_reader :cell_type, :cell_value, :value
6
+
7
+ # FIXME: I think style should be deprecated. Having a style attribute
8
+ # for a cell doesn't really accomplish much. It seems to be used
9
+ # when you want to export to excelx.
10
+ attr_reader :style
11
+
12
+
13
+ # FIXME: Updating a cell's value should be able tochange the cell's type,
14
+ # but that isn't currently possible. This will cause weird bugs
15
+ # when one changes the value of a Number cell to a String. e.g.
16
+ #
17
+ # cell = Cell::Number(*args)
18
+ # cell.value = 'Hello'
19
+ # cell.formatted_value # => Some unexpected value
20
+ #
21
+ # Here are two possible solutions to such issues:
22
+ # 1. Don't allow a cell's value to be updated. Use a method like
23
+ # `Sheet.update_cell` instead. The simple solution.
24
+ # 2. When `cell.value = ` is called, use injection to try and
25
+ # change the type of cell on the fly. But deciding what type
26
+ # of value to pass to `cell.value=`. isn't always obvious. e.g.
27
+ # `cell.value = Time.now` should convert a cell to a DateTime,
28
+ # not a Time cell. Time cells would be hard to recognize because
29
+ # they are integers. This approach would require a significant
30
+ # change to the code as written. The complex solution.
31
+ #
32
+ # If the first solution is used, then this method should be
33
+ # deprecated.
34
+ attr_writer :value
35
+
36
+ def initialize(value, formula, excelx_type, style, link, coordinate)
37
+ @link = !!link
38
+ @cell_value = value
39
+ @cell_type = excelx_type
40
+ @formula = formula
41
+ @style = style
42
+ @coordinate = coordinate
43
+ @type = :base
44
+ @value = link? ? Roo::Link.new(link, value) : value
45
+ end
46
+
47
+ def type
48
+ if formula?
49
+ :formula
50
+ elsif link?
51
+ :link
52
+ else
53
+ @type
54
+ end
55
+ end
56
+
57
+ def formula?
58
+ !!@formula
59
+ end
60
+
61
+ def link?
62
+ !!@link
63
+ end
64
+
65
+ alias_method :formatted_value, :value
66
+
67
+ def to_s
68
+ formatted_value
69
+ end
70
+
71
+ # DEPRECATED: Please use link instead.
72
+ def hyperlink
73
+ warn '[DEPRECATION] `hyperlink` is deprecated. Please use `link` instead.'
74
+ end
75
+
76
+ # DEPRECATED: Please use cell_value instead.
77
+ def excelx_value
78
+ warn '[DEPRECATION] `excelx_value` is deprecated. Please use `cell_value` instead.'
79
+ cell_value
80
+ end
81
+
82
+ # DEPRECATED: Please use cell_type instead.
83
+ def excelx_type
84
+ warn '[DEPRECATION] `excelx_type` is deprecated. Please use `cell_type` instead.'
85
+ cell_type
86
+ end
87
+
88
+ def empty?
89
+ false
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,27 @@
1
+ module Roo
2
+ class Excelx
3
+ class Cell
4
+ class Boolean < Cell::Base
5
+ attr_reader :value, :formula, :format, :cell_type, :cell_value, :link, :coordinate
6
+
7
+ def initialize(value, formula, style, link, coordinate)
8
+ super(value, formula, nil, style, link, coordinate)
9
+ @type = @cell_type = :boolean
10
+ @value = link? ? Roo::Link.new(link, value) : create_boolean(value)
11
+ end
12
+
13
+ def formatted_value
14
+ value ? 'TRUE'.freeze : 'FALSE'.freeze
15
+ end
16
+
17
+ private
18
+
19
+ def create_boolean(value)
20
+ # FIXME: Using a boolean will cause methods like Base#to_csv to fail.
21
+ # Roo is using some method to ignore false/nil values.
22
+ value.to_i == 1 ? true : false
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ require 'date'
2
+
3
+ module Roo
4
+ class Excelx
5
+ class Cell
6
+ class Date < Roo::Excelx::Cell::DateTime
7
+ attr_reader :value, :formula, :format, :cell_type, :cell_value, :link, :coordinate
8
+
9
+ def initialize(value, formula, excelx_type, style, link, base_date, coordinate)
10
+ # NOTE: Pass all arguments to the parent class, DateTime.
11
+ super
12
+ @type = :date
13
+ @format = excelx_type.last
14
+ @value = link? ? Roo::Link.new(link, value) : create_date(base_date, value)
15
+ end
16
+
17
+ private
18
+
19
+ def create_date(base_date, value)
20
+ date = base_date + value.to_i
21
+ yyyy, mm, dd = date.strftime('%Y-%m-%d').split('-')
22
+
23
+ ::Date.new(yyyy.to_i, mm.to_i, dd.to_i)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,101 @@
1
+ require 'date'
2
+
3
+ module Roo
4
+ class Excelx
5
+ class Cell
6
+ class DateTime < Cell::Base
7
+ attr_reader :value, :formula, :format, :cell_value, :link, :coordinate
8
+
9
+ def initialize(value, formula, excelx_type, style, link, base_date, coordinate)
10
+ super(value, formula, excelx_type, style, link, coordinate)
11
+ @type = :datetime
12
+ @format = excelx_type.last
13
+ @value = link? ? Roo::Link.new(link, value) : create_datetime(base_date, value)
14
+ end
15
+
16
+ # Public: Returns formatted value for a datetime. Format's can be an
17
+ # standard excel format, or a custom format.
18
+ #
19
+ # Standard formats follow certain conventions. Date fields for
20
+ # days, months, and years are separated with hyhens or
21
+ # slashes ("-", /") (e.g. 01-JAN, 1/13/15). Time fields for
22
+ # hours, minutes, and seconds are separated with a colon (e.g.
23
+ # 12:45:01).
24
+ #
25
+ # If a custom format follows those conventions, then the custom
26
+ # format will be used for the a cell's formatted value.
27
+ # Otherwise, the formatted value will be in the following
28
+ # format: 'YYYY-mm-dd HH:MM:SS' (e.g. "2015-07-10 20:33:15").
29
+ #
30
+ # Examples
31
+ # formatted_value #=> '01-JAN'
32
+ #
33
+ # Returns a String representation of a cell's value.
34
+ def formatted_value
35
+ date_regex = /(?<date>[dmy]+[\-\/][dmy]+([\-\/][dmy]+)?)/
36
+ time_regex = /(?<time>(\[?[h]\]?+:)?[m]+(:?ss|:?s)?)/
37
+
38
+ formatter = @format.downcase.split(' ').map do |part|
39
+ if part[date_regex] == part
40
+ part.gsub(/#{DATE_FORMATS.keys.join('|')}/, DATE_FORMATS)
41
+ elsif part[time_regex]
42
+ part.gsub(/#{TIME_FORMATS.keys.join('|')}/, TIME_FORMATS)
43
+ else
44
+ warn 'Unable to parse custom format. Using "YYYY-mm-dd HH:MM:SS" format.'
45
+ return @value.strftime('%F %T')
46
+ end
47
+ end.join(' ')
48
+
49
+ @value.strftime(formatter)
50
+ end
51
+
52
+ private
53
+
54
+ DATE_FORMATS = {
55
+ 'yyyy'.freeze => '%Y'.freeze, # Year: 2000
56
+ 'yy'.freeze => '%y'.freeze, # Year: 00
57
+ # mmmmm => J-D
58
+ 'mmmm'.freeze => '%B'.freeze, # Month: January
59
+ 'mmm'.freeze => '%^b'.freeze, # Month: JAN
60
+ 'mm'.freeze => '%m'.freeze, # Month: 01
61
+ 'm'.freeze => '%-m'.freeze, # Month: 1
62
+ 'dddd'.freeze => '%A'.freeze, # Day of the Week: Sunday
63
+ 'ddd'.freeze => '%^a'.freeze, # Day of the Week: SUN
64
+ 'dd'.freeze => '%d'.freeze, # Day of the Month: 01
65
+ 'd'.freeze => '%-d'.freeze, # Day of the Month: 1
66
+ # '\\\\'.freeze => ''.freeze, # NOTE: Fixes a custom format's output.
67
+ }
68
+
69
+ TIME_FORMATS = {
70
+ 'hh'.freeze => '%H'.freeze, # Hour (24): 01
71
+ 'h'.freeze => '%-k'.freeze, # Hour (24): 1
72
+ # 'hh'.freeze => '%I'.freeze, # Hour (12): 08
73
+ # 'h'.freeze => '%-l'.freeze, # Hour (12): 8
74
+ 'mm'.freeze => '%M'.freeze, # Minute: 01
75
+ # FIXME: is this used? Seems like 'm' is used for month, not minute.
76
+ 'm'.freeze => '%-M'.freeze, # Minute: 1
77
+ 'ss'.freeze => '%S'.freeze, # Seconds: 01
78
+ 's'.freeze => '%-S'.freeze, # Seconds: 1
79
+ 'am/pm'.freeze => '%p'.freeze, # Meridian: AM
80
+ '000'.freeze => '%3N'.freeze, # Fractional Seconds: thousandth.
81
+ '00'.freeze => '%2N'.freeze, # Fractional Seconds: hundredth.
82
+ '0'.freeze => '%1N'.freeze, # Fractional Seconds: tenths.
83
+ }
84
+
85
+ def create_datetime(base_date, value)
86
+ date = base_date + value.to_f.round(6)
87
+ datetime_string = date.strftime('%Y-%m-%d %H:%M:%S.%N')
88
+ t = round_datetime(datetime_string)
89
+
90
+ ::DateTime.civil(t.year, t.month, t.day, t.hour, t.min, t.sec)
91
+ end
92
+
93
+ def round_datetime(datetime_string)
94
+ /(?<yyyy>\d+)-(?<mm>\d+)-(?<dd>\d+) (?<hh>\d+):(?<mi>\d+):(?<ss>\d+.\d+)/ =~ datetime_string
95
+
96
+ ::Time.new(yyyy.to_i, mm.to_i, dd.to_i, hh.to_i, mi.to_i, ss.to_r).round(0)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,19 @@
1
+
2
+ module Roo
3
+ class Excelx
4
+ class Cell
5
+ class Empty < Cell::Base
6
+ attr_reader :value, :formula, :format, :cell_type, :cell_value, :hyperlink, :coordinate
7
+
8
+ def initialize(coordinate)
9
+ @value = @formula = @format = @cell_type = @cell_value = @hyperlink = nil
10
+ @coordinate = coordinate
11
+ end
12
+
13
+ def empty?
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,80 @@
1
+ module Roo
2
+ class Excelx
3
+ class Cell
4
+ class Number < Cell::Base
5
+ attr_reader :value, :formula, :format, :cell_value, :link, :coordinate
6
+
7
+ def initialize(value, formula, excelx_type, style, link, coordinate)
8
+ super
9
+ # FIXME: change @type to number. This will break brittle tests.
10
+ # FIXME: Excelx_type is an array, but the first value isn't used.
11
+ @type = :float
12
+ @format = excelx_type.last
13
+ @value = link? ? Roo::Link.new(link, value) : create_numeric(value)
14
+ end
15
+
16
+ def create_numeric(number)
17
+ case @format
18
+ when /%/
19
+ Float(number)
20
+ when /\.0/
21
+ Float(number)
22
+ else
23
+ number.include?('.') ? Float(number) : Integer(number)
24
+ end
25
+ end
26
+
27
+ def formatted_value
28
+ formatter = formats[@format]
29
+ if formatter.is_a? Proc
30
+ formatter.call(@cell_value)
31
+ else
32
+ Kernel.format(formatter, @cell_value)
33
+ end
34
+ end
35
+
36
+ def formats
37
+ # FIXME: numbers can be other colors besides red:
38
+ # [BLACK], [BLUE], [CYAN], [GREEN], [MAGENTA], [RED], [WHITE], [YELLOW], [COLOR n]
39
+ {
40
+ 'General' => '%.0f',
41
+ '0' => '%.0f',
42
+ '0.00' => '%.2f',
43
+ '#,##0' => proc do |number|
44
+ Kernel.format('%.0f', number).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
45
+ end,
46
+ '#,##0.00' => proc do |number|
47
+ Kernel.format('%.2f', number).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
48
+ end,
49
+ '0%' => proc do |number|
50
+ Kernel.format('%d%', number.to_f * 100)
51
+ end,
52
+ '0.00%' => proc do |number|
53
+ Kernel.format('%.2f%', number.to_f * 100)
54
+ end,
55
+ '0.00E+00' => '%.2E',
56
+ '#,##0 ;(#,##0)' => proc do |number|
57
+ formatter = number.to_i > 0 ? '%.0f' : '(%.0f)'
58
+ Kernel.format(formatter, number.to_f.abs).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
59
+ end,
60
+ '#,##0 ;[Red](#,##0)' => proc do |number|
61
+ formatter = number.to_i > 0 ? '%.0f' : '[Red](%.0f)'
62
+ Kernel.format(formatter, number.to_f.abs).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
63
+ end,
64
+ '#,##0.00;(#,##0.00)' => proc do |number|
65
+ formatter = number.to_i > 0 ? '%.2f' : '(%.2f)'
66
+ Kernel.format(formatter, number.to_f.abs).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
67
+ end,
68
+ '#,##0.00;[Red](#,##0.00)' => proc do |number|
69
+ formatter = number.to_i > 0 ? '%.2f' : '[Red](%.2f)'
70
+ Kernel.format(formatter, number.to_f.abs).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
71
+ end,
72
+ # FIXME: not quite sure what the format should look like in this case.
73
+ '##0.0E+0' => '%.1E',
74
+ '@' => proc { |number| number }
75
+ }
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,19 @@
1
+ module Roo
2
+ class Excelx
3
+ class Cell
4
+ class String < Cell::Base
5
+ attr_reader :value, :formula, :format, :cell_type, :cell_value, :link, :coordinate
6
+
7
+ def initialize(value, formula, style, link, coordinate)
8
+ super(value, formula, nil, style, link, coordinate)
9
+ @type = @cell_type = :string
10
+ @value = link? ? Roo::Link.new(link, value) : value
11
+ end
12
+
13
+ def empty?
14
+ value.empty?
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ require 'date'
2
+
3
+ module Roo
4
+ class Excelx
5
+ class Cell
6
+ class Time < Roo::Excelx::Cell::DateTime
7
+ attr_reader :value, :formula, :format, :cell_value, :link, :coordinate
8
+
9
+ def initialize(value, formula, excelx_type, style, link, base_date, coordinate)
10
+ # NOTE: Pass all arguments to DateTime super class.
11
+ super
12
+ @type = :time
13
+ @format = excelx_type.last
14
+ @datetime = create_datetime(base_date, value)
15
+ @value = link? ? Roo::Link.new(link, value) : (value.to_f * 86_400).to_i
16
+ end
17
+
18
+ def formatted_value
19
+ formatter = @format.gsub(/#{TIME_FORMATS.keys.join('|')}/, TIME_FORMATS)
20
+ @datetime.strftime(formatter)
21
+ end
22
+
23
+ alias_method :to_s, :formatted_value
24
+
25
+ private
26
+
27
+ def create_datetime(base_date, value)
28
+ date = base_date + value.to_f.round(6)
29
+ datetime_string = date.strftime('%Y-%m-%d %H:%M:%S.%N')
30
+ t = round_datetime(datetime_string)
31
+
32
+ ::DateTime.civil(t.year, t.month, t.day, t.hour, t.min, t.sec)
33
+ end
34
+
35
+ def round_datetime(datetime_string)
36
+ /(?<yyyy>\d+)-(?<mm>\d+)-(?<dd>\d+) (?<hh>\d+):(?<mi>\d+):(?<ss>\d+.\d+)/ =~ datetime_string
37
+
38
+ ::Time.new(yyyy.to_i, mm.to_i, dd.to_i, hh.to_i, mi.to_i, ss.to_r).round(0)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -20,3 +20,36 @@ module Roo
20
20
  end
21
21
  end
22
22
  end
23
+ # xl/comments1.xml
24
+ # <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
25
+ # <comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
26
+ # <authors>
27
+ # <author />
28
+ # </authors>
29
+ # <commentList>
30
+ # <comment ref="B4" authorId="0">
31
+ # <text>
32
+ # <r>
33
+ # <rPr>
34
+ # <sz val="10" />
35
+ # <rFont val="Arial" />
36
+ # <family val="2" />
37
+ # </rPr>
38
+ # <t>Comment for B4</t>
39
+ # </r>
40
+ # </text>
41
+ # </comment>
42
+ # <comment ref="B5" authorId="0">
43
+ # <text>
44
+ # <r>
45
+ # <rPr>
46
+ # <sz val="10" />
47
+ # <rFont val="Arial" />
48
+ # <family val="2" />
49
+ # </rPr>
50
+ # <t>Comment for B5</t>
51
+ # </r>
52
+ # </text>
53
+ # </comment>
54
+ # </commentList>
55
+ # </comments>