diff options
Diffstat (limited to 'lib/roo/excelx/sheet_doc.rb')
| -rwxr-xr-x | lib/roo/excelx/sheet_doc.rb | 134 |
1 files changed, 86 insertions, 48 deletions
diff --git a/lib/roo/excelx/sheet_doc.rb b/lib/roo/excelx/sheet_doc.rb index a705958..8b0c686 100755 --- a/lib/roo/excelx/sheet_doc.rb +++ b/lib/roo/excelx/sheet_doc.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'forwardable' require 'roo/excelx/extractor' @@ -5,7 +7,7 @@ module Roo class Excelx class SheetDoc < Excelx::Extractor extend Forwardable - delegate [:styles, :workbook, :shared_strings, :base_date] => :@shared + delegate [:workbook] => :@shared def initialize(path, relationships, shared, options = {}) super(path) @@ -19,7 +21,12 @@ module Roo end def hyperlinks(relationships) - @hyperlinks ||= extract_hyperlinks(relationships) + # If you're sure you're not going to need this hyperlinks you can discard it + @hyperlinks ||= if @options[:no_hyperlinks] + {} + else + extract_hyperlinks(relationships) + end end # Get the dimensions for the sheet. @@ -39,13 +46,10 @@ module Roo def each_cell(row_xml) return [] unless 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']) - hyperlinks(@relationships)[key] - end + coordinate = ::Roo::Utils.extract_coordinate(cell_element["r"]) + hyperlinks = hyperlinks(@relationships)[coordinate] - yield cell_from_xml(cell_element, hyperlinks) + yield cell_from_xml(cell_element, hyperlinks, coordinate) end end @@ -53,13 +57,13 @@ module Roo def cell_value_type(type, format) case type - when 's'.freeze + when 's' :shared - when 'b'.freeze + when 'b' :boolean - when 'str'.freeze + when 'str' :string - when 'inlineStr'.freeze + when 'inlineStr' :inlinestr else Excelx::Format.to_type(format) @@ -74,42 +78,58 @@ module Roo # </c> # hyperlink - a String for the hyperlink for the cell or nil when no # hyperlink is present. + # coordinate - a Roo::Excelx::Coordinate for the coordinate for the cell + # or nil to extract coordinate from cell_xml. + # empty_cell - an Optional Boolean value. # # Examples # - # cells_from_xml(<Nokogiri::XML::Element>, nil) + # cells_from_xml(<Nokogiri::XML::Element>, nil, nil) # # => <Excelx::Cell::String> # # 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? + def cell_from_xml(cell_xml, hyperlink, coordinate, empty_cell=true) + coordinate ||= ::Roo::Utils.extract_coordinate(cell_xml["r"]) + cell_xml_children = cell_xml.children + return create_empty_cell(coordinate, empty_cell) 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"].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) + content = +"" + cell.children.each do |inline_str| + if inline_str.name == 't' + content << inline_str.content + end + end + unless content.empty? + return Excelx::Cell.cell_class(:string).new(content, formula, style, hyperlink, coordinate) end when 'f' formula = cell.content when 'v' - return create_cell_from_value(value_type, cell, formula, format, style, hyperlink, base_date, coordinate) + format = style_format(style) + value_type = cell_value_type(cell_xml["t"], format) + + return create_cell_from_value(value_type, cell, formula, format, style, hyperlink, coordinate) end end - Excelx::Cell::Empty.new(coordinate) + create_empty_cell(coordinate) end - def create_cell_from_value(value_type, cell, formula, format, style, hyperlink, base_date, coordinate) + def create_empty_cell(coordinate, empty_cell) + if empty_cell + Excelx::Cell::Empty.new(coordinate) + end + end + + 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. @@ -125,11 +145,12 @@ module Roo # 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 @@ -148,35 +169,32 @@ module Roo 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] + hyperlinks.each_with_object({}) do |hyperlink, hash| + if relationship = relationships[hyperlink['id']] + target_link = relationship['Target'] + target_link += "##{hyperlink['location']}" if hyperlink['location'] + hash[::Roo::Utils.ref_to_key(hyperlink["ref"].to_s)] = target_link end - end.compact] + end end 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"].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] @@ -191,10 +209,14 @@ module Roo 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 = {} + empty_cell = @options[:empty_cell] + + doc.xpath('/worksheet/sheetData/row/c').each do |cell_xml| + coordinate = ::Roo::Utils.extract_coordinate(cell_xml["r"]) + cell = cell_from_xml(cell_xml, hyperlinks(relationships)[coordinate], coordinate, empty_cell) + extracted_cells[coordinate] = cell if cell + end expand_merged_ranges(extracted_cells) if @options[:expand_merged_ranges] @@ -203,9 +225,25 @@ module Roo def extract_dimensions Roo::Utils.each_element(@path, 'dimension') do |dimension| - return dimension.attributes['ref'].value + return dimension["ref"] 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 |
