Skip to content

Commit

Permalink
Merge f8316b2 into 3ed05e1
Browse files Browse the repository at this point in the history
  • Loading branch information
chopraanmol1 committed Jul 2, 2018
2 parents 3ed05e1 + f8316b2 commit e626c9f
Show file tree
Hide file tree
Showing 14 changed files with 166 additions and 95 deletions.
17 changes: 11 additions & 6 deletions lib/roo/excelx/cell.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,24 @@ def type
end

def self.create_cell(type, *values)
type_class = cell_class(type)
type_class && type_class.new(*values)
end

def self.cell_class(type)
case type
when :string
Cell::String.new(*values)
Cell::String
when :boolean
Cell::Boolean.new(*values)
Cell::Boolean
when :number
Cell::Number.new(*values)
Cell::Number
when :date
Cell::Date.new(*values)
Cell::Date
when :datetime
Cell::DateTime.new(*values)
Cell::DateTime
when :time
Cell::Time.new(*values)
Cell::Time
end
end

Expand Down
7 changes: 3 additions & 4 deletions lib/roo/excelx/cell/date.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ def initialize(value, formula, excelx_type, style, link, base_date, coordinate)

private

def create_date(base_date, value)
date = base_date + value.to_i
yyyy, mm, dd = date.strftime('%Y-%m-%d').split('-')
def create_datetime(_,_); end

::Date.new(yyyy.to_i, mm.to_i, dd.to_i)
def create_date(base_date, value)
base_date + value.to_i
end
end
end
Expand Down
19 changes: 7 additions & 12 deletions lib/roo/excelx/cell/datetime.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ module Roo
class Excelx
class Cell
class DateTime < Cell::Base
SECONDS_IN_DAY = 60 * 60 * 24

attr_reader :value, :formula, :format, :cell_value, :link, :coordinate

def initialize(value, formula, excelx_type, style, link, base_date, coordinate)
def initialize(value, formula, excelx_type, style, link, base_timestamp, coordinate)
super(value, formula, excelx_type, style, link, coordinate)
@type = :datetime
@format = excelx_type.last
@value = link? ? Roo::Link.new(link, value) : create_datetime(base_date, value)
@value = link? ? Roo::Link.new(link, value) : create_datetime(base_timestamp, value)
end

# Public: Returns formatted value for a datetime. Format's can be an
Expand Down Expand Up @@ -92,19 +94,12 @@ def parse_date_or_time_format(part)
'0' => '%1N' # Fractional Seconds: tenths.
}

def create_datetime(base_date, value)
date = base_date + value.to_f.round(6)
datetime_string = date.strftime('%Y-%m-%d %H:%M:%S.%N')
t = round_datetime(datetime_string)
def create_datetime(base_timestamp, value)
timestamp = (base_timestamp + (value.to_f.round(6) * SECONDS_IN_DAY)).round(0)
t = ::Time.at(timestamp).utc

::DateTime.civil(t.year, t.month, t.day, t.hour, t.min, t.sec)
end

def round_datetime(datetime_string)
/(?<yyyy>\d+)-(?<mm>\d+)-(?<dd>\d+) (?<hh>\d+):(?<mi>\d+):(?<ss>\d+.\d+)/ =~ datetime_string

::Time.new(yyyy, mm, dd, hh, mi, ss.to_r).round(0)
end
end
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/roo/excelx/cell/number.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
module Roo
class Excelx
class Cell
Expand Down
4 changes: 4 additions & 0 deletions lib/roo/excelx/coordinate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ def initialize(row, column)
@row = row
@column = column
end

def to_a
@array ||= [row, column].freeze
end
end
end
end
12 changes: 9 additions & 3 deletions lib/roo/excelx/format.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module Roo
class Excelx
module Format
extend self
EXCEPTIONAL_FORMATS = {
'h:mm am/pm' => :date,
'h:mm:ss am/pm' => :date
Expand Down Expand Up @@ -38,12 +40,17 @@ module Format
}

def to_type(format)
@to_type ||= {}
@to_type[format] ||= _to_type(format)
end

def _to_type(format)
format = format.to_s.downcase
if (type = EXCEPTIONAL_FORMATS[format])
type
elsif format.include?('#')
:float
elsif !format.match(/d+(?![\]])/).nil? || format.include?('y')
elsif format.include?('y') || !format.match(/d+(?![\]])/).nil?
if format.include?('h') || format.include?('s')
:datetime
else
Expand All @@ -58,7 +65,6 @@ def to_type(format)
end
end

module_function :to_type
end
end
end
end
4 changes: 4 additions & 0 deletions lib/roo/excelx/shared.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ def workbook
def base_date
workbook.base_date
end

def base_timestamp
workbook.base_timestamp
end
end
end
end
10 changes: 5 additions & 5 deletions lib/roo/excelx/shared_strings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ def extract_shared_strings
shared_string = ''
si.children.each do |elem|
case elem.name
when 'r'
when 'r'.freeze
elem.children.each do |r_elem|
shared_string << r_elem.content if r_elem.name == 't'
end
when 't'
when 't'.freeze
shared_string = elem.content
end
end
Expand All @@ -69,13 +69,13 @@ def extract_html
html_string = '<html>'
si.children.each do |elem|
case elem.name
when 'r'
when 'r'.freeze
html_string << extract_html_r(elem)
when 't'
when 't'.freeze
html_string << elem.content
end # case elem.name
end # si.children.each do |elem|
html_string << '</html>'
html_string << '</html>'.freeze
end # doc.xpath('/sst/si').map do |si|
end # def extract_html

Expand Down
10 changes: 6 additions & 4 deletions lib/roo/excelx/sheet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,16 @@ def each_row(options = {}, &block)

def row(row_number)
first_column.upto(last_column).map do |col|
cells[[row_number, col]]
end.map { |cell| cell && cell.value }
cell = cells[[row_number, col]]
cell && cell.value
end
end

def column(col_number)
first_row.upto(last_row).map do |row|
cells[[row, col_number]]
end.map { |cell| cell && cell.value }
cell = cells[[row, col_number]]
cell && cell.value
end
end

# returns the number of the first non-empty row
Expand Down
93 changes: 57 additions & 36 deletions lib/roo/excelx/sheet_doc.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# frozen_string_literal: true
require 'forwardable'
require 'roo/excelx/extractor'

module Roo
class Excelx
class SheetDoc < Excelx::Extractor
extend Forwardable
delegate [:styles, :workbook, :shared_strings, :base_date] => :@shared
delegate [:workbook, :shared_strings] => :@shared

def initialize(path, relationships, shared, options = {})
super(path)
Expand Down Expand Up @@ -41,7 +42,7 @@ def each_cell(row_xml)
row_xml.children.each do |cell_element|
# If you're sure you're not going to need this hyperlinks you can discard it
hyperlinks = unless @options[:no_hyperlinks]
key = ::Roo::Utils.ref_to_key(cell_element['r'])
key = ::Roo::Utils.ref_to_key(cell_element['r'.freeze])
hyperlinks(@relationships)[key]
end

Expand Down Expand Up @@ -82,34 +83,41 @@ def cell_value_type(type, format)
#
# Returns a type of <Excelx::Cell>.
def cell_from_xml(cell_xml, hyperlink)
coordinate = extract_coordinate(cell_xml['r'])
return Excelx::Cell::Empty.new(coordinate) if cell_xml.children.empty?
coordinate = ::Roo::Utils.extract_coordinate(cell_xml['r'.freeze])
cell_xml_children = cell_xml.children
return Excelx::Cell::Empty.new(coordinate) if cell_xml_children.empty?

# NOTE: This is error prone, to_i will silently turn a nil into a 0.
# This works by coincidence because Format[0] is General.
style = cell_xml['s'].to_i
format = styles.style_format(style)
value_type = cell_value_type(cell_xml['t'], format)
style = cell_xml['s'.freeze].to_i
formula = nil

cell_xml.children.each do |cell|
cell_xml_children.each do |cell|
case cell.name
when 'is'
content_arr = cell.search('t').map(&:content)
unless content_arr.empty?
return Excelx::Cell.create_cell(:string, content_arr.join(''), formula, style, hyperlink, coordinate)
when 'is'.freeze
content = +""
cell.children.each do |inline_str|
if inline_str.name == 't'.freeze
content << inline_str.content
end
end
when 'f'
unless content.empty?
return Excelx::Cell.cell_class(:string).new(content, formula, style, hyperlink, coordinate)
end
when 'f'.freeze
formula = cell.content
when 'v'
return create_cell_from_value(value_type, cell, formula, format, style, hyperlink, base_date, coordinate)
when 'v'.freeze
format = style_format(style)
value_type = cell_value_type(cell_xml['t'.freeze], format)

return create_cell_from_value(value_type, cell, formula, format, style, hyperlink, coordinate)
end
end

Excelx::Cell::Empty.new(coordinate)
end

def create_cell_from_value(value_type, cell, formula, format, style, hyperlink, base_date, coordinate)
def create_cell_from_value(value_type, cell, formula, format, style, hyperlink, coordinate)
# NOTE: format.to_s can replace excelx_type as an argument for
# Cell::Time, Cell::DateTime, Cell::Date or Cell::Number, but
# it will break some brittle tests.
Expand All @@ -125,11 +133,12 @@ def create_cell_from_value(value_type, cell, formula, format, style, hyperlink,
# 3. formula
case value_type
when :shared
value = shared_strings.use_html?(cell.content.to_i) ? shared_strings.to_html[cell.content.to_i] : shared_strings[cell.content.to_i]
Excelx::Cell.create_cell(:string, value, formula, style, hyperlink, coordinate)
cell_content = cell.content.to_i
value = shared_strings.use_html?(cell_content) ? shared_strings.to_html[cell_content] : shared_strings[cell_content]
Excelx::Cell.cell_class(:string).new(value, formula, style, hyperlink, coordinate)
when :boolean, :string
value = cell.content
Excelx::Cell.create_cell(value_type, value, formula, style, hyperlink, coordinate)
Excelx::Cell.cell_class(value_type).new(value, formula, style, hyperlink, coordinate)
when :time, :datetime
cell_content = cell.content.to_f
# NOTE: A date will be a whole number. A time will have be > 1. And
Expand All @@ -148,26 +157,21 @@ def create_cell_from_value(value_type, cell, formula, format, style, hyperlink,
else
:date
end
Excelx::Cell.create_cell(cell_type, cell.content, formula, excelx_type, style, hyperlink, base_date, coordinate)
base_value = cell_type == :date ? base_date : base_timestamp
Excelx::Cell.cell_class(cell_type).new(cell_content, formula, excelx_type, style, hyperlink, base_value, coordinate)
when :date
Excelx::Cell.create_cell(value_type, cell.content, formula, excelx_type, style, hyperlink, base_date, coordinate)
Excelx::Cell.cell_class(:date).new(cell.content, formula, excelx_type, style, hyperlink, base_date, coordinate)
else
Excelx::Cell.create_cell(:number, cell.content, formula, excelx_type, style, hyperlink, coordinate)
Excelx::Cell.cell_class(:number).new(cell.content, formula, excelx_type, style, hyperlink, coordinate)
end
end

def extract_coordinate(coordinate)
row, column = ::Roo::Utils.split_coordinate(coordinate)

Excelx::Coordinate.new(row, column)
end

def extract_hyperlinks(relationships)
return {} unless (hyperlinks = doc.xpath('/worksheet/hyperlinks/hyperlink'))

Hash[hyperlinks.map do |hyperlink|
if hyperlink.attribute('id') && (relationship = relationships[hyperlink.attribute('id').text])
[::Roo::Utils.ref_to_key(hyperlink.attributes['ref'].to_s), relationship.attribute('Target').text]
if hyperlink.attribute('id'.freeze) && (relationship = relationships[hyperlink.attribute('id'.freeze).text])
[::Roo::Utils.ref_to_key(hyperlink.attributes['ref'.freeze].to_s), relationship.attribute('Target'.freeze).text]
end
end.compact]
end
Expand All @@ -176,7 +180,7 @@ def expand_merged_ranges(cells)
# Extract merged ranges from xml
merges = {}
doc.xpath('/worksheet/mergeCells/mergeCell').each do |mergecell_xml|
tl, br = mergecell_xml['ref'].split(/:/).map { |ref| ::Roo::Utils.ref_to_key(ref) }
tl, br = mergecell_xml['ref'.freeze].split(/:/).map { |ref| ::Roo::Utils.ref_to_key(ref) }
for row in tl[0]..br[0] do
for col in tl[1]..br[1] do
next if row == tl[0] && col == tl[1]
Expand All @@ -191,10 +195,11 @@ def expand_merged_ranges(cells)
end

def extract_cells(relationships)
extracted_cells = Hash[doc.xpath('/worksheet/sheetData/row/c').map do |cell_xml|
key = ::Roo::Utils.ref_to_key(cell_xml['r'])
[key, cell_from_xml(cell_xml, hyperlinks(relationships)[key])]
end]
extracted_cells = {}
doc.xpath('/worksheet/sheetData/row/c').each do |cell_xml|
key = ::Roo::Utils.ref_to_key(cell_xml['r'.freeze])
extracted_cells[key] = cell_from_xml(cell_xml, hyperlinks(relationships)[key])
end

expand_merged_ranges(extracted_cells) if @options[:expand_merged_ranges]

Expand All @@ -203,9 +208,25 @@ def extract_cells(relationships)

def extract_dimensions
Roo::Utils.each_element(@path, 'dimension') do |dimension|
return dimension.attributes['ref'].value
return dimension.attributes['ref'.freeze].value
end
end

def style_format(style)
@shared.styles.style_format(style)
end

def base_date
@shared.base_date
end

def base_timestamp
@shared.base_timestamp
end

def shared_strings
@shared.shared_strings
end
end
end
end
4 changes: 4 additions & 0 deletions lib/roo/excelx/workbook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ def defined_names
end]
end

def base_timestamp
@base_timestamp ||= base_date.to_datetime.to_time.to_i
end

def base_date
@base_date ||=
begin
Expand Down
Loading

0 comments on commit e626c9f

Please sign in to comment.