ooxl 0.0.1.4.24 → 0.0.1.5.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8eb9194a8f21b6a1ff814c7307581b87039174af
4
- data.tar.gz: 19327c4f196fa7be33de042dd02a5637855d9326
3
+ metadata.gz: 72289ba7eb6dba7ef0e3feec1b329f59d5ed9547
4
+ data.tar.gz: 568bfdd6c97d43698e665eac15f0e453fac151e3
5
5
  SHA512:
6
- metadata.gz: 2fa656c463f7278a8ba232e890eca33137a65b6065a0936b90a4017c3efaab77c2e4b9921fcc780b80912b55b2dcfd332a43a0ebd4bab2fea8bc38e256c8385f
7
- data.tar.gz: e8ef1b7a8c5a9705690ba0b512f550faa2c785a87f796a0b85f7fce407559ddcdbacb6695345ad58503f1c3daf0206133b687c6e05ad9f26820d07517d451d29
6
+ metadata.gz: 4a8c5d321345da254a3c397dccc77d090435408f895c7b16b2877d6d24f59721c9cb1caffd4e39337e8ebec02495f15e3c0133f0e0faa6d58cb5f602ee419951
7
+ data.tar.gz: e4e7aca211e24fb842b959b431850671191aed6292f7f5ef7cd8c31267564dde3ab467c9aca19351f2f3012c1d24e7b9cee6a8a231510b4073b1b7b077ef480e
data/.gitignore CHANGED
File without changes
data/.rspec CHANGED
File without changes
File without changes
data/Gemfile CHANGED
File without changes
data/README.md CHANGED
File without changes
data/Rakefile CHANGED
File without changes
File without changes
data/bin/setup CHANGED
File without changes
File without changes
File without changes
@@ -33,9 +33,11 @@ class OOXL
33
33
  end
34
34
 
35
35
  def sheet(sheet_name)
36
- sheet_index = @workbook.sheets.index { |sheet| sheet[:name] == sheet_name}
37
- raise "No #{sheet_name} in workbook." if sheet_index.nil?
38
- sheet = @sheets.fetch((sheet_index+1).to_s)
36
+ sheet_meta = @workbook.sheets.find { |sheet| sheet[:name] == sheet_name }
37
+ raise "No #{sheet_name} in workbook." if sheet_meta.nil?
38
+
39
+ sheet_index = sheet_meta[:sheet_id]
40
+ sheet = @sheets.fetch(sheet_index)
39
41
 
40
42
  # shared variables
41
43
  sheet.name = sheet_name
@@ -73,8 +75,7 @@ class OOXL
73
75
  end
74
76
 
75
77
  def fetch_comments(sheet_index)
76
- final_sheet_index = sheet_index+1
77
- relationship = @relationships[final_sheet_index.to_s]
78
+ relationship = @relationships[sheet_index]
78
79
  @comments[relationship.comment_id] if relationship.present?
79
80
  end
80
81
 
@@ -1,12 +1,13 @@
1
- class OOXL
1
+ class OOXL
2
2
  module Util
3
- COLUMN_LETTERS = ('A'..'ZZZZ').to_a
4
- def letter_equivalent(index)
5
- COLUMN_LETTERS.fetch(index)
3
+ COLUMN_LETTERS = [nil] + ('A'..'ZZZZ').to_a
4
+
5
+ def letter_index(col_letter)
6
+ column_letter_to_number(col_letter) - 1
6
7
  end
7
8
 
8
- def letter_index(letter)
9
- COLUMN_LETTERS.index { |c_letter| c_letter == letter}
9
+ def letter_equivalent(col_index)
10
+ column_number_to_letter(col_index + 1)
10
11
  end
11
12
 
12
13
  def to_column_letter(reference)
@@ -14,13 +15,17 @@ class OOXL
14
15
  end
15
16
 
16
17
  def uniform_reference(ref)
17
- ref.to_s[/[A-Z]/] ? letter_index(ref) + 1 : ref
18
+ ref.to_s[/[A-Z]/] ? column_letter_to_number(ref) : ref
18
19
  end
19
20
 
20
21
  def node_value_extractor(node)
21
22
  node.try(:value)
22
23
  end
23
24
 
25
+ def column_number_to_letter(index)
26
+ COLUMN_LETTERS.fetch(index)
27
+ end
28
+
24
29
  def column_letter_to_number(column_letter)
25
30
  pow = column_letter.length - 1
26
31
  result = 0
@@ -1,3 +1,3 @@
1
1
  class OOXL
2
- VERSION = "0.0.1.4.24"
2
+ VERSION = "0.0.1.5.3"
3
3
  end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -10,20 +10,18 @@ class OOXL
10
10
  end
11
11
 
12
12
  def [](id)
13
- cell = if id.is_a?(String)
14
- cells.find { |row| row.id == id}
13
+ if id.is_a?(String)
14
+ cell(id)
15
15
  else
16
- cells[id]
16
+ cells[id] || BlankCell.new(id)
17
17
  end
18
- (cell.present?) ? cell : BlankCell.new(id)
19
18
  end
20
19
 
21
20
  def cells
22
21
  if @options[:padded_cells]
23
22
  unless @cells.blank?
24
23
  'A'.upto(@cells.last.column).map do |column_letter|
25
- cell = @cells.find { |cell| cell.column == column_letter}
26
- (cell.blank?) ? BlankCell.new("#{column_letter}#{id}") : cell
24
+ cell(column_letter)
27
25
  end
28
26
  end
29
27
  else
@@ -32,8 +30,14 @@ class OOXL
32
30
  end
33
31
 
34
32
  def cell(cell_id)
35
- cell_final_id = cell_id[/[A-Z]{1,}\d+/] ? cell_id : "#{cell_id}#{id}"
36
- cells.find { |cell| cell.id == cell_final_id}
33
+ cell_final_id = cell_id[/[A-Z]+\d+/] ? cell_id : "#{cell_id}#{id}"
34
+ cell_id_map[cell_final_id]
35
+ end
36
+
37
+ def cell_id_map
38
+ @cell_id_map ||= cells.each_with_object(Hash.new { |_, k| BlankCell.new(k) }) do |cell, result|
39
+ result[cell.id] = cell
40
+ end
37
41
  end
38
42
 
39
43
  def each
@@ -41,12 +45,16 @@ class OOXL
41
45
  end
42
46
 
43
47
  def self.load_from_node(row_node, shared_strings, styles, options)
44
- new(id: row_node.attributes["r"].try(:value),
48
+ new(id: extract_id(row_node),
45
49
  spans: row_node.attributes["spans"].try(:value),
46
50
  height: row_node.attributes["ht"].try(:value),
47
51
  cells: row_node.xpath('c').map { |cell_node| OOXL::Cell.load_from_node(cell_node, shared_strings, styles)},
48
52
  options: options )
49
53
  end
54
+
55
+ def self.extract_id(row_node)
56
+ row_node.attributes["r"].try(:value)
57
+ end
50
58
  end
51
59
  end
52
60
 
@@ -0,0 +1,128 @@
1
+ class OOXL
2
+ class RowCache
3
+ include Enumerable
4
+
5
+ attr_accessor :styles
6
+
7
+ # built on-demand -- use rows instead
8
+ attr_reader :row_cache
9
+
10
+ # built on-demand -- use fetch_row_by_id instead
11
+ attr_reader :row_id_map
12
+
13
+ delegate :size, to: :row_nodes
14
+
15
+ def initialize(sheet_xml, shared_strings, options = {})
16
+ @shared_strings = shared_strings
17
+ @sheet_xml = sheet_xml
18
+ @options = options
19
+ @row_cache = []
20
+ @row_id_map = {}
21
+ end
22
+
23
+ def [](id)
24
+ fetch_row_by_id(id)
25
+ end
26
+
27
+ alias_method :row, :[]
28
+
29
+ def each(&block)
30
+ if @options[:padded_rows]
31
+ padded_rows(&block)
32
+ else
33
+ rows(&block)
34
+ end
35
+ end
36
+
37
+ def rows(&block)
38
+ # track yield count to know if caller broke out of loop
39
+ rows_yielded = 0
40
+ row_cache.each do |r|
41
+ yield r if block_given?
42
+ rows_yielded += 1
43
+ end
44
+
45
+ if !all_rows_loaded? && rows_yielded == row_cache.count
46
+ parse_more_rows(&block)
47
+ end
48
+
49
+ row_cache
50
+ end
51
+
52
+ def row_range(start_index, end_index)
53
+ return enum_for(:row_range, start_index, end_index) unless block_given?
54
+
55
+ rows do |row|
56
+ row_id = row.id.to_i
57
+ next if row_id < start_index
58
+ break if row_id > end_index
59
+
60
+ yield row
61
+ end
62
+ end
63
+
64
+ def max_row_index
65
+ return 0 if row_nodes.empty?
66
+
67
+ if all_rows_loaded?
68
+ row_cache.last.id.to_i
69
+ else
70
+ Row.extract_id(row_nodes.last).to_i
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def parse_more_rows
77
+ row_nodes.drop(row_cache.count).each do |row_node|
78
+ row = parse_row(row_node)
79
+ row_cache << row
80
+ row_id_map[row.id] = row
81
+ yield row if block_given?
82
+ end
83
+ end
84
+
85
+ def all_rows_loaded?
86
+ row_cache.count == row_nodes.count
87
+ end
88
+
89
+ def fetch_row_by_id(row_id)
90
+ row_id = row_id.to_s
91
+ return row_id_map[row_id] if all_rows_loaded? || row_id_map.key?(row_id)
92
+
93
+ parse_more_rows do |row|
94
+ return row if row.id == row_id
95
+ end
96
+
97
+ nil
98
+ end
99
+
100
+ def padded_rows
101
+ real_rows_yielded = 0
102
+ yielded_rows = []
103
+ (1..Float::INFINITY).each do |row_index|
104
+ row = row(row_index)
105
+ if row.blank?
106
+ row = Row.new(id: row_index.to_s, cells: [])
107
+ else
108
+ real_rows_yielded += 1
109
+ end
110
+
111
+ yielded_rows << row
112
+ yield row if block_given?
113
+
114
+ break if real_rows_yielded == row_cache.count && all_rows_loaded?
115
+ end
116
+ yielded_rows
117
+ end
118
+
119
+ def row_nodes
120
+ @row_nodes ||= @sheet_xml.xpath('//sheetData/row')
121
+ end
122
+
123
+ def parse_row(row_node)
124
+ Row.load_from_node(row_node, @shared_strings, @styles, @options)
125
+ end
126
+ end
127
+ end
128
+
@@ -1,10 +1,14 @@
1
1
  require_relative 'sheet/data_validation'
2
+ require_relative 'row_cache'
3
+
2
4
  class OOXL
3
5
  class Sheet
4
6
  include OOXL::Util
5
7
  include Enumerable
6
- attr_reader :columns, :data_validations, :shared_strings
7
- attr_accessor :comments, :styles, :defined_names, :name
8
+
9
+ attr_reader :columns, :data_validations, :shared_strings, :styles
10
+ attr_accessor :comments, :defined_names, :name
11
+ delegate :[], :each, :rows, :row, to: :@row_cache
8
12
 
9
13
  def initialize(xml, shared_strings, options={})
10
14
  @xml = Nokogiri.XML(xml).remove_namespaces!
@@ -12,8 +16,8 @@ class OOXL
12
16
  @comments = {}
13
17
  @defined_names = {}
14
18
  @styles = []
15
- @loaded_cache = {}
16
19
  @options = options
20
+ @row_cache = RowCache.new(@xml, @shared_strings, options)
17
21
  end
18
22
 
19
23
  def code_name
@@ -41,70 +45,35 @@ class OOXL
41
45
  end
42
46
  end
43
47
 
44
- def [](id)
45
- if id.is_a?(String)
46
- rows.find { |row| row.id == id}
47
- else
48
- rows[id]
49
- end
50
- end
51
48
 
52
- def row(index, stream: false)
53
- if @loaded_cache[:rows] || !stream
54
- rows.find { |row| row.id == index.to_s}
55
- else
56
- found_row = nil
57
- rows do |row|
58
- if row.id == index.to_s
59
- found_row = row
60
- break
61
- end
62
- end
63
- found_row
64
- end
49
+ # DEPRECATED: stream is no longer separate
50
+ def stream_row(index)
51
+ row(index)
65
52
  end
66
53
 
67
54
  # test mode
68
55
  def cells_by_column(column_letter)
69
- columns = []
70
- rows.each do |row|
71
- columns << row.cells.find { |cell| to_column_letter(cell.id) == column_letter}
56
+ rows.map do |row|
57
+ row.cells.find { |cell| to_column_letter(cell.id) == column_letter}
72
58
  end
73
- columns
74
59
  end
75
60
 
76
- def cell(cell_id, stream: false)
77
- column_letter, row_index = cell_id.partition(/\d+/)
78
- current_row = row(row_index, stream: stream)
79
- current_row.cell(column_letter) unless current_row.nil?
61
+ def last_column(row_index=1)
62
+ @last_column ||= {}
63
+ @last_column[row_index] ||= begin
64
+ cells = row(row_index).try(:cells)
65
+ cells.last.column if cells.present?
66
+ end
80
67
  end
81
68
 
82
- def formula(cell_id, stream: false)
83
- cell(cell_id, stream: stream).try(:formula)
69
+ def cell(cell_id)
70
+ column_letter, row_index = cell_id.partition(/\d+/)
71
+ current_row = row(row_index)
72
+ current_row.cell(column_letter) unless current_row.nil?
84
73
  end
85
74
 
86
- def rows
87
- @rows ||= begin
88
- all_rows = @xml.xpath('//sheetData/row').map do |row_node|
89
- row = Row.load_from_node(row_node, @shared_strings, @styles, @options)
90
- yield row if block_given?
91
- row
92
- end
93
- @loaded_cache[:rows] = true
94
- all_rows
95
- end
96
- end
97
-
98
- def each
99
- if @options[:padded_rows]
100
- last_row_index = rows.last.id.to_i
101
- (1.upto(last_row_index)).each do |row_index|
102
- row = row(row_index)
103
- yield (row.blank?) ? Row.new(id: "#{row_index}", cells: []) : row
104
- end
105
- else
106
- rows { |row| yield row }
107
- end
75
+ def formula(cell_id)
76
+ cell(cell_id).try(:formula)
108
77
  end
109
78
 
110
79
  def font(cell_reference)
@@ -115,7 +84,6 @@ class OOXL
115
84
  cell(cell_reference).try(:fill)
116
85
  end
117
86
 
118
-
119
87
  def data_validations
120
88
  @data_validations ||= begin
121
89
 
@@ -134,6 +102,11 @@ class OOXL
134
102
  end
135
103
  end
136
104
 
105
+ def styles=(styles)
106
+ @styles = styles
107
+ @row_cache.styles = styles
108
+ end
109
+
137
110
  # a shortcut for:
138
111
  # formula = data_validation('A1').formula
139
112
  # ooxl.named_range(formula)
@@ -143,7 +116,7 @@ class OOXL
143
116
  if data_validation.formula[/[\s\$\,\:]/]
144
117
  (data_validation.formula[/\$/].present?) ? "#{name}!#{data_validation.formula}" : data_validation.formula
145
118
  else
146
- @defined_names.fetch(data_validation.formula)
119
+ @defined_names[data_validation.formula]
147
120
  end
148
121
  end
149
122
  end
@@ -155,57 +128,62 @@ class OOXL
155
128
  # cell_range values separated by comma
156
129
  if cell_range.include?(":")
157
130
  cell_letters = cell_range.gsub(/[\d]/, '').split(':')
158
- start_index, end_index = cell_range[/[A-Z]{1,}\d+/] ? cell_range.gsub(/[^\d:]/, '').split(':').map(&:to_i) : [1, rows.size]
159
- # This will allow values from this pattern
160
- # 'SheetName!A1:C3'
161
- # The number after the cell letter will be the index
162
- # 1 => start_index
163
- # 3 => end_index
164
- # Expected output would be: [['value', 'value', 'value'], ['value', 'value', 'value'], ['value', 'value', 'value']]
131
+ start_index, end_index = cell_range[/[A-Z]{1,}\d+/] ? cell_range.gsub(/[^\d:]/, '').split(':').map(&:to_i) : [1, @row_cache.max_row_index]
165
132
  if cell_letters.uniq.size > 1
166
- start_index.upto(end_index).map do |row_index|
167
- (letter_index(cell_letters.first)..letter_index(cell_letters.last)).map do |cell_index|
168
- row = fetch_row_by_id(row_index.to_s)
169
- next if row.blank?
170
-
171
- cell_letter = letter_equivalent(cell_index)
172
- row["#{cell_letter}#{row_index}"].value
173
- end
174
- end
133
+ list_values_from_rectangle(cell_letters, start_index, end_index)
175
134
  else
176
- cell_letter = cell_letters.uniq.first
177
- (start_index..end_index).to_a.map do |row_index|
178
- row = fetch_row_by_id(row_index.to_s)
179
- next if row.blank?
180
- row["#{cell_letter}#{row_index}"].value
181
- end
135
+ list_values_from_column(cell_letters.uniq.first, start_index, end_index)
182
136
  end
183
137
  else
184
138
  # when only one value: B2
185
- row_index = cell_range.gsub(/[^\d:]/, '').split(':').map(&:to_i).first
186
- row = fetch_row_by_id(row_index.to_s)
187
- return if row.blank?
188
- [row[cell_range].value]
139
+ list_values_from_cell(cell_range)
189
140
  end
190
141
  end
191
142
  alias_method :list_values_from_formula, :list_values_from_cell_range
192
143
 
193
- def self.load_from_stream(xml_stream, shared_strings)
194
- self.new(Nokogiri.XML(xml_stream).remove_namespaces!, shared_strings)
144
+ # This will allow values from this pattern
145
+ # 'SheetName!A1:C3'
146
+ # The number after the cell letter will be the index
147
+ # 1 => start_index
148
+ # 3 => end_index
149
+ # Expected output would be: [['value', 'value', 'value'], ['value', 'value', 'value'], ['value', 'value', 'value']]
150
+ def list_values_from_rectangle(cell_letters, start_index, end_index)
151
+ start_col = column_letter_to_number(cell_letters.first)
152
+ end_col = column_letter_to_number(cell_letters.last)
153
+ @row_cache.row_range(start_index, end_index).map do |row|
154
+ (start_col..end_col).map do |col_index|
155
+ col_letter = column_number_to_letter(col_index)
156
+ row["#{col_letter}#{row.id}"].value
157
+ end
158
+ end
159
+ end
160
+
161
+ def list_values_from_column(column_letter, start_index, end_index)
162
+ @row_cache.row_range(start_index, end_index).map do |row|
163
+ row["#{column_letter}#{row.id}"].value
164
+ end
165
+ end
166
+
167
+ def list_values_from_cell(cell_ref)
168
+ row_index = cell_ref.gsub(/[^\d:]/, '').split(':').map(&:to_i).first
169
+ row = row(row_index)
170
+ return if row.blank?
171
+ [row[cell_ref].value]
195
172
  end
196
173
 
197
174
  def in_merged_cells?(cell_id)
198
175
  column_letter, column_index = cell_id.partition(/\d+/)
199
- range = merged_cells_range.find { |column_range, index_range| column_range.cover?(column_letter) && index_range.cover?(column_index) }
176
+ range = merged_cells.find { |column_range, index_range| column_range.cover?(column_letter) && index_range.cover?(column_index) }
200
177
  range.present?
201
178
  end
202
179
 
203
- private
204
- def fetch_row_by_id(row_id)
205
- rows.find { |row| row.id == row_id.to_s}
180
+ def self.load_from_stream(xml_stream, shared_strings)
181
+ self.new(xml_stream, shared_strings)
206
182
  end
207
183
 
208
- def merged_cells_range
184
+ private
185
+
186
+ def merged_cells
209
187
  @merged_cells ||= @xml.xpath('//mergeCells/mergeCell').map do |merged_cell|
210
188
  # <mergeCell ref="Q381:R381"/>
211
189
  start_reference, end_reference = merged_cell.attributes["ref"].try(:value).split(':')
File without changes
File without changes
File without changes
@@ -26,9 +26,10 @@ Gem::Specification.new do |spec|
26
26
  spec.require_paths = ["lib"]
27
27
  spec.add_dependency 'activesupport'
28
28
  spec.add_dependency 'nokogiri', '~> 1'
29
- spec.add_dependency 'rubyzip', '~> 1.0', '< 2.0.0'
29
+ spec.add_dependency 'rubyzip', '~> 1.3.0', '< 2.0.0'
30
30
 
31
- spec.add_development_dependency "bundler", "~> 1.12"
31
+ spec.add_development_dependency "bundler"
32
+ spec.add_development_dependency "pry-byebug"
32
33
  spec.add_development_dependency "rake", "~> 10.0"
33
34
  spec.add_development_dependency "rspec", "~> 3.0"
34
35
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ooxl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.4.24
4
+ version: 0.0.1.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Mones
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-02-28 00:00:00.000000000 Z
11
+ date: 2020-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -44,7 +44,7 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.0'
47
+ version: 1.3.0
48
48
  - - "<"
49
49
  - !ruby/object:Gem::Version
50
50
  version: 2.0.0
@@ -54,7 +54,7 @@ dependencies:
54
54
  requirements:
55
55
  - - "~>"
56
56
  - !ruby/object:Gem::Version
57
- version: '1.0'
57
+ version: 1.3.0
58
58
  - - "<"
59
59
  - !ruby/object:Gem::Version
60
60
  version: 2.0.0
@@ -62,16 +62,30 @@ dependencies:
62
62
  name: bundler
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - "~>"
65
+ - - ">="
66
66
  - !ruby/object:Gem::Version
67
- version: '1.12'
67
+ version: '0'
68
68
  type: :development
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - "~>"
72
+ - - ">="
73
73
  - !ruby/object:Gem::Version
74
- version: '1.12'
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: pry-byebug
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
75
89
  - !ruby/object:Gem::Dependency
76
90
  name: rake
77
91
  requirement: !ruby/object:Gem::Requirement
@@ -129,6 +143,7 @@ files:
129
143
  - lib/ooxl/xl_objects/number_formatting.rb
130
144
  - lib/ooxl/xl_objects/relationships.rb
131
145
  - lib/ooxl/xl_objects/row.rb
146
+ - lib/ooxl/xl_objects/row_cache.rb
132
147
  - lib/ooxl/xl_objects/sheet.rb
133
148
  - lib/ooxl/xl_objects/sheet/data_validation.rb
134
149
  - lib/ooxl/xl_objects/styles.rb
@@ -138,7 +153,7 @@ files:
138
153
  homepage: https://github.com/halcjames/ooxl
139
154
  licenses: []
140
155
  metadata: {}
141
- post_install_message:
156
+ post_install_message:
142
157
  rdoc_options: []
143
158
  require_paths:
144
159
  - lib
@@ -153,9 +168,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
168
  - !ruby/object:Gem::Version
154
169
  version: '0'
155
170
  requirements: []
156
- rubyforge_project:
157
- rubygems_version: 2.6.8
158
- signing_key:
171
+ rubyforge_project:
172
+ rubygems_version: 2.6.12
173
+ signing_key:
159
174
  specification_version: 4
160
175
  summary: OOXL Excel - Parse Excel Spreadsheets (xlsx, xlsm).
161
176
  test_files: []