roo 2.1.1 → 2.2.0

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.
@@ -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>