ooxl 0.0.1.4.25 → 0.0.1.5.4

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
- SHA1:
3
- metadata.gz: f48104f80b87661ce3cd77977bcfe3512243feb2
4
- data.tar.gz: 5ff369822f8474348b63df4e23d381e3da28e762
2
+ SHA256:
3
+ metadata.gz: feba34a102bb13498ce6cfbbdad7ae2d9f02e4d5f872a92a1bd80a5f74c59d83
4
+ data.tar.gz: 94ab7441bbbe828a0a4fe0f3d98b97f62d2ae25f6aa052ce657f1567b868abca
5
5
  SHA512:
6
- metadata.gz: 6c30e22c9ab498315d84958b9fc1640ab938daed2c58892d59d70740862fb996d49688e7e958be7ef421b50b0ed00b6d339d9e26c8e845ac149bd3580c1622c9
7
- data.tar.gz: 036dd1cfe76161b9301de7d963df9421bb69fffd73923b22362ba63e538645292b91160fbd5108837a47bf8b0f2f444ef74210116bc266e4e3b4d9b3e457d82d
6
+ metadata.gz: 8653a2a872e73055569eff3f7114a24ca76769f0251dd71e52bfb2a15e5c81f51fd6f4fd9ad8ce9d046099c2f07a44f16ab61e7e1d02380f98b6474e132571d4
7
+ data.tar.gz: 8e12f9a5b9f101a01ab33b991c6923f32daa926e16c3ffd2a9ce984b2a94d7e9a9e3a3cf8714b709051004af336fbd7d132c92e5f7de14bddc9bb5f9f0dc50fc
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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "bundler/setup"
4
- require "ooxml_excel"
4
+ require "./lib/ooxl"
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +10,5 @@ require "ooxml_excel"
10
10
  # require "pry"
11
11
  # Pry.start
12
12
 
13
- require "irb"
14
- IRB.start
13
+ require "pry"
14
+ Pry.start
data/bin/setup CHANGED
File without changes
File without changes
File without changes
@@ -3,20 +3,33 @@ class OOXL
3
3
  include ListHelper
4
4
  attr_reader :filename
5
5
 
6
- def initialize(spreadsheet_filepath, options={})
6
+ def initialize(filepath = nil, contents: nil, **options)
7
7
  @workbook = nil
8
8
  @sheets = {}
9
9
  @styles = []
10
10
  @comments = {}
11
- @relationships = {}
11
+ @workbook_relationships = nil
12
+ @sheet_relationships = {}
12
13
  @options = options
13
14
  @tables = []
14
- @filename = File.basename(spreadsheet_filepath)
15
- parse_spreadsheet_contents(spreadsheet_filepath)
15
+
16
+ @filename = filepath && File.basename(filepath)
17
+ if contents.present?
18
+ parse_spreadsheet_contents(contents)
19
+ elsif filepath.present?
20
+ parse_spreadsheet_file(filepath)
21
+ else
22
+ raise 'no file path or contents were provided'
23
+ end
16
24
  end
17
25
 
18
26
  def self.open(spreadsheet_filepath, options={})
19
- new(spreadsheet_filepath, options)
27
+ new(spreadsheet_filepath, **options)
28
+ end
29
+
30
+ def self.parse(spreadsheet_contents, options={})
31
+ spreadsheet_contents.force_encoding('ASCII-8BIT') if spreadsheet_contents.respond_to?(:force_encoding)
32
+ new(nil, contents: spreadsheet_contents, **options)
20
33
  end
21
34
 
22
35
  def sheets(skip_hidden: false)
@@ -33,9 +46,11 @@ class OOXL
33
46
  end
34
47
 
35
48
  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)
49
+ sheet_meta = @workbook.sheets.find { |sheet| sheet[:name] == sheet_name }
50
+ raise "No #{sheet_name} in workbook." if sheet_meta.nil?
51
+
52
+ sheet_index = @workbook_relationships[sheet_meta[:relationship_id]].scan(/\d+/).first
53
+ sheet = @sheets.fetch(sheet_index)
39
54
 
40
55
  # shared variables
41
56
  sheet.name = sheet_name
@@ -73,38 +88,46 @@ class OOXL
73
88
  end
74
89
 
75
90
  def fetch_comments(sheet_index)
76
- final_sheet_index = sheet_index+1
77
- relationship = @relationships[final_sheet_index.to_s]
91
+ relationship = @sheet_relationships[sheet_index]
78
92
  @comments[relationship.comment_id] if relationship.present?
79
93
  end
80
94
 
81
- def parse_spreadsheet_contents(spreadsheet)
95
+ def parse_spreadsheet_file(file_path)
96
+ Zip::File.open(file_path) { |zip| parse_zip(zip) }
97
+ end
98
+
99
+ def parse_spreadsheet_contents(file_contents)
100
+ # open_buffer works for strings and IO streams
101
+ Zip::File.open_buffer(file_contents) { |zip| parse_zip(zip) }
102
+ end
103
+
104
+ def parse_zip(spreadsheet_zip)
82
105
  shared_strings = []
83
- Zip::File.open(spreadsheet) do |spreadsheet_zip|
84
- spreadsheet_zip.each do |entry|
85
- case entry.name
86
- when /xl\/worksheets\/sheet(\d+)?\.xml/
87
- sheet_id = entry.name.scan(/xl\/worksheets\/sheet(\d+)?\.xml/).flatten.first
88
- @sheets[sheet_id] = OOXL::Sheet.new(entry.get_input_stream.read, shared_strings, @options)
89
- when /xl\/styles\.xml/
90
- @styles = OOXL::Styles.load_from_stream(entry.get_input_stream.read)
91
- when /xl\/comments(\d+)?\.xml/
92
- comment_id = entry.name.scan(/xl\/comments(\d+)\.xml/).flatten.first
93
- @comments[comment_id] = OOXL::Comments.load_from_stream(entry.get_input_stream.read)
94
- when "xl/sharedStrings.xml"
95
- Nokogiri.XML(entry.get_input_stream.read).remove_namespaces!.xpath('sst/si').each do |shared_string_node|
96
- shared_strings << shared_string_node.xpath('r/t|t').map { |value_node| value_node.text}.join('')
97
- end
98
- when /xl\/tables\/.*?/i
99
- @tables << OOXL::Table.new(entry.get_input_stream.read)
100
- when "xl/workbook.xml"
101
- @workbook = OOXL::Workbook.load_from_stream(entry.get_input_stream.read)
102
- when /xl\/worksheets\/_rels\/sheet\d+\.xml\.rels/
103
- sheet_id = entry.name.scan(/sheet(\d+)/).flatten.first
104
- @relationships[sheet_id] = Relationships.new(entry.get_input_stream.read)
105
- else
106
- # unsupported for now..
106
+ spreadsheet_zip.each do |entry|
107
+ case entry.name
108
+ when /xl\/worksheets\/sheet(\d+)?\.xml/
109
+ sheet_id = entry.name.scan(/xl\/worksheets\/sheet(\d+)?\.xml/).flatten.first
110
+ @sheets[sheet_id] = OOXL::Sheet.new(entry.get_input_stream.read, shared_strings, @options)
111
+ when /xl\/styles\.xml/
112
+ @styles = OOXL::Styles.load_from_stream(entry.get_input_stream.read)
113
+ when /xl\/comments(\d+)?\.xml/
114
+ comment_id = entry.name.scan(/xl\/comments(\d+)\.xml/).flatten.first
115
+ @comments[comment_id] = OOXL::Comments.load_from_stream(entry.get_input_stream.read)
116
+ when "xl/sharedStrings.xml"
117
+ Nokogiri.XML(entry.get_input_stream.read).remove_namespaces!.xpath('sst/si').each do |shared_string_node|
118
+ shared_strings << shared_string_node.xpath('r/t|t').map { |value_node| value_node.text}.join('')
107
119
  end
120
+ when /xl\/tables\/.*?/i
121
+ @tables << OOXL::Table.new(entry.get_input_stream.read)
122
+ when "xl/workbook.xml"
123
+ @workbook = OOXL::Workbook.load_from_stream(entry.get_input_stream.read)
124
+ when /xl\/worksheets\/_rels\/sheet\d+\.xml\.rels/
125
+ sheet_id = entry.name.scan(/sheet(\d+)/).flatten.first
126
+ @sheet_relationships[sheet_id] = Relationships.new(entry.get_input_stream.read)
127
+ when /xl\/_rels\/workbook\.xml\.rels/
128
+ @workbook_relationships = Relationships.new(entry.get_input_stream.read)
129
+ else
130
+ # unsupported for now..
108
131
  end
109
132
  end
110
133
  end
@@ -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.25"
2
+ VERSION = "0.0.1.5.4"
3
3
  end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,29 +1,41 @@
1
1
  class OOXL
2
2
  class Relationships
3
3
  SUPPORTED_TYPES = ['http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments']
4
+
4
5
  def initialize(relationships_node)
5
- @types = {}
6
+ @relationships = []
6
7
  parse_relationships(relationships_node)
7
8
  end
8
9
 
9
10
  def comment_id
10
- @types['comments']
11
+ comment_target = by_type('comments').first
12
+ comment_target && extract_file_reference(comment_target)
13
+ end
14
+
15
+ def [](id)
16
+ @relationships.find { |rel| rel.id == id }&.target
17
+ end
18
+
19
+ def by_type(type)
20
+ @relationships.select { |rel| rel.type == type }.map(&:target)
11
21
  end
12
22
 
13
23
  private
24
+
14
25
  def parse_relationships(relationships_node)
15
26
  relationships_node = Nokogiri.XML(relationships_node).remove_namespaces!
16
27
  relationships_node.xpath('//Relationship').each do |relationship_node|
17
28
  relationship_type = relationship_node.attributes["Type"].value
18
29
  target = relationship_node.attributes["Target"].value
19
- if supported_type?(relationship_type)
20
- @types[extract_type(relationship_type)] = extract_file_reference(target)
21
- end
30
+ id = extract_number(relationship_node.attributes["Id"].value)
31
+ type = extract_type(relationship_type)
32
+ target = relationship_node.attributes["Target"].value
33
+ @relationships << Relationship.new(id, type, target)
22
34
  end
23
35
  end
24
36
 
25
- def supported_type?(type)
26
- SUPPORTED_TYPES.include?(type)
37
+ def extract_number(str)
38
+ str.scan(/(\d+)/).flatten.first
27
39
  end
28
40
 
29
41
  def extract_type(type)
@@ -34,6 +46,7 @@ class OOXL
34
46
  file.scan(/(\d+)\.[\w]/).flatten.first
35
47
  end
36
48
 
49
+ Relationship = Struct.new(:id, :type, :target)
37
50
  end
38
51
  end
39
52
 
@@ -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)
@@ -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.25
4
+ version: 0.0.1.5.4
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-03-27 00:00:00.000000000 Z
11
+ date: 2020-10-20 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,8 @@ 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.4.5.1
158
- signing_key:
171
+ rubygems_version: 3.0.3
172
+ signing_key:
159
173
  specification_version: 4
160
174
  summary: OOXL Excel - Parse Excel Spreadsheets (xlsx, xlsm).
161
175
  test_files: []