weshatheleopard/rubyXL

View on GitHub
lib/rubyXL/objects/sheet_data.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'rubyXL/objects/ooxml_object'
require 'rubyXL/objects/simple_types'
require 'rubyXL/objects/text'
require 'rubyXL/objects/formula'
require 'rubyXL/cell'

module RubyXL
  # http://msdn.microsoft.com/en-us/library/documentformat.openxml.spreadsheet.cellvalues(v=office.14).aspx
  module DataType
    SHARED_STRING = 's'
    RAW_STRING    = 'str'
    INLINE_STRING = 'inlineStr'
    ERROR         = 'e'
    BOOLEAN       = 'b'
    NUMBER        = 'n'
    DATE          = 'd' # Only available in Office2010.
  end

  # http://www.datypic.com/sc/ooxml/e-ssml_v-1.html
  class CellValue < OOXMLObject
    define_attribute(:_, :string, :accessor => :value)
    define_attribute(:'xml:space', %w{ preserve })
    define_element_name 'v'

    def before_write_xml
      preserve_whitespace
      true
    end
  end

  # http://www.datypic.com/sc/ooxml/e-ssml_c-2.html
  class Cell < OOXMLObject
    NUMBER_REGEXP = /\A-?\d+((?:\.\d+)?(?:e[+-]?\d+)?)?\Z/i

    define_attribute(:r,   :ref)
    define_attribute(:s,   :int,  :default => 0, :accessor => :style_index)
    define_attribute(:t,   RubyXL::ST_CellType,  :accessor => :datatype, :default => 'n')
    define_attribute(:cm,  :int,  :default => 0)
    define_attribute(:vm,  :int,  :default => 0)
    define_attribute(:ph,  :bool, :default => false)
    define_child_node(RubyXL::Formula,   :accessor => :formula)
    define_child_node(RubyXL::CellValue, :accessor => :value_container)
    define_child_node(RubyXL::RichText) # is
    define_element_name 'c'

    attr_accessor :worksheet

    def index_in_collection
      r.col_range.begin
    end

    def row
      r && r.first_row
    end

    def row=(v)
      self.r = RubyXL::Reference.new(v, column || 0)
    end

    def column
      r && r.first_col
    end

    def column=(v)
      self.r = RubyXL::Reference.new(row || 0, v)
    end

    def raw_value
      value_container && value_container.value
    end

    def raw_value=(v)
      self.value_container ||= RubyXL::CellValue.new
      value_container.value = v
    end

    def get_cell_xf
      workbook.stylesheet.cell_xfs[self.style_index || 0]
    end

    def get_cell_font
      workbook.stylesheet.fonts[get_cell_xf.font_id]
    end

    def get_cell_border
      workbook.stylesheet.borders[get_cell_xf.border_id]
    end

    def number_format
      workbook.stylesheet.get_number_format_by_id(get_cell_xf.num_fmt_id)
    end

    def is_date?
      return false unless # Only fully numeric values can be dates
        case raw_value
        when Numeric then true
        when String  then raw_value =~ NUMBER_REGEXP
        else false
        end

      num_fmt = self.number_format
      num_fmt && num_fmt.is_date_format?
    end

    # Gets massaged value of the cell, converting datatypes to those known to Ruby (that includes
    # stripping any special formatting from RichText).
    def value(args = {})
      r = self.raw_value

      case datatype
      when RubyXL::DataType::SHARED_STRING then workbook.shared_strings_container[r.to_i].to_s
      when RubyXL::DataType::INLINE_STRING then is.to_s
      when RubyXL::DataType::RAW_STRING    then raw_value
      else
        if is then is.to_s
        elsif is_date? then workbook.num_to_date(r.to_f)
        elsif r.is_a?(String) && (r =~ NUMBER_REGEXP) then # Numeric
          if Regexp.last_match(1) != '' then r.to_f
          else r.to_i
          end
        else r
        end
      end
    end

    def inspect
      str = "#<#{self.class}(#{row},#{column}): #{raw_value.inspect}"
      str << " =#{self.formula.expression}" if self.formula
      str << ", datatype=#{self.datatype.inspect}, style_index=#{self.style_index.inspect}>"
      return str
    end

    include LegacyCell
  end

#TODO#<row r="1" spans="1:1" x14ac:dyDescent="0.25">

  # http://www.datypic.com/sc/ooxml/e-ssml_row-1.html
  class Row < OOXMLObject
    define_attribute(:r,            :int)
    define_attribute(:spans,        :string)
    define_attribute(:s,            :int,   :default => 0, :accessor => :style_index)
    define_attribute(:customFormat, :bool,  :default => false)
    define_attribute(:ht,           :double)
    define_attribute(:hidden,       :bool,  :default => false)
    define_attribute(:customHeight, :bool,  :default => false)
    define_attribute(:outlineLevel, :int,   :default => 0)
    define_attribute(:collapsed,    :bool,  :default => false)
    define_attribute(:thickTop,     :bool,  :default => false)
    define_attribute(:thickBot,     :bool,  :default => false)
    define_attribute(:ph,           :bool,  :default => false)
    define_child_node(RubyXL::Cell, :collection => true, :accessor => :cells)
    define_element_name 'row'

    attr_accessor :worksheet

    def before_write_xml
      !(cells.nil? || cells.empty?)
    end

    def index_in_collection
      r - 1
    end

    def [](ind)
      cells[ind]
    end

    def size
      cells.size
    end

    def insert_cell_shift_right(c, col_index)
      cells.insert(col_index, c)
      update_cell_coords(col_index)
    end

    def delete_cell_shift_left(col_index)
      cells.delete_at(col_index)
      update_cell_coords(col_index)
    end

    def update_cell_coords(start_from_index)
      cells.drop(start_from_index).each_with_index { |cell, i|
        next if cell.nil?
        cell.column = start_from_index + i
      }
    end
    private :update_cell_coords

    def xf
      @worksheet.workbook.cell_xfs[self.style_index || 0]
    end

    def get_fill_color
      @worksheet.workbook.get_fill_color(xf)
    end

    def get_font
      @worksheet.workbook.fonts[xf.font_id]
    end

    DEFAULT_HEIGHT = 13
  end

  # http://www.datypic.com/sc/ooxml/e-ssml_sheetData-1.html
  class SheetData < OOXMLObject
    define_child_node(RubyXL::Row, :collection => true, :accessor => :rows)
    define_element_name 'sheetData'

    def [](ind)
      rows[ind]
    end

    def size
      rows.size
    end
  end
end