murb/workbook

View on GitHub
lib/workbook/modules/cell.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true
# frozen_string_literal: true

require "workbook/modules/type_parser"
require "workbook/nil_value"
require "date"

module Workbook
  module Modules
    module Cell
      include Workbook::Modules::TypeParser

      CHARACTER_REPACEMENTS = {
        [/[().?,!=$:]/] => "",
        [/&/] => "amp",
        [/\+/] => "_plus_",
        [/\s/, "/_", "/", "\\"] => "_",
        ["–_", "-_", "+_", "-"] => "",
        ["__"] => "_",
        [">"] => "gt",
        ["<"] => "lt",
        ["á", "à", "â", "ä", "ã", "å"] => "a",
        ["Ã", "Ä", "Â", "À", "�?", "Å"] => "A",
        ["é", "è", "ê", "ë"] => "e",
        ["Ë", "É", "È", "Ê"] => "E",
        ["í", "ì", "î", "ï"] => "i",
        ["�?", "Î", "Ì", "�?"] => "I",
        ["ó", "ò", "ô", "ö", "õ"] => "o",
        ["Õ", "Ö", "Ô", "Ò", "Ó"] => "O",
        ["ú", "ù", "û", "ü"] => "u",
        ["Ú", "Û", "Ù", "Ü"] => "U",
        ["ç"] => "c",
        ["Ç"] => "C",
        ["š", "ś"] => "s",
        ["Š", "Ś"] => "S",
        ["ž", "ź"] => "z",
        ["Ž", "Ź"] => "Z",
        ["ñ"] => "n",
        ["Ñ"] => "N",
        ["#"] => "hash",
        ["*"] => "asterisk"
      }
      CLASS_CELLTYPE_MAPPING = {
        "BigDecimal" => :decimal,
        "Numeric" => :integer,
        "Integer" => :integer,
        "Fixnum" => :integer,
        "Float" => :float,
        "String" => :string,
        "Symbol" => :string,
        "Time" => :time,
        "Date" => :date,
        "DateTime" => :datetime,
        "ActiveSupport::TimeWithZone" => :datetime,
        "TrueClass" => :boolean,
        "FalseClass" => :boolean,
        "NilClass" => :nil,
        "Workbook::NilValue" => :nil
      }
      # Note that these types are sorted by 'importance'

      # Evaluates a value for class-validity
      #
      # @param [Numeric,String,Time,Date,TrueClass,FalseClass,NilClass,Object] value the value to evaluate
      # @return [Boolean] returns true when the value is a valid cell value
      def valid_value? value
        !CLASS_CELLTYPE_MAPPING[value.class.to_s].nil?
      end

      def formula
        @formula
      end

      def formula= f
        @formula = f
      end

      def row
        @row
      end

      def row= r
        @row = r
      end

      # Change the current value
      #
      # @param [Numeric,String,Time,Date,TrueClass,FalseClass,NilClass,Symbol] value a valid value
      def value= value
        if valid_value? value
          @value = value
          @to_sym = nil
        else
          raise ArgumentError, "value should be of a primitive type, e.g. a string, or an integer, not a #{value.class} (is_a? [TrueClass,FalseClass,Date,Time,Numeric,String, NilClass, Symbol])"
        end
      end

      # Returns column type, either :primary_key, :string, :text, :integer, :float, :decimal, :datetime, :date, :binary, :boolean
      #
      # @return [Symbol] the type of cell, compatible with Workbook::Column'types
      def cell_type
        CLASS_CELLTYPE_MAPPING[value.class.to_s]
      end

      # Returns the current value
      #
      # @return [Numeric,String,Time,Date,TrueClass,FalseClass,NilClass] a valid value
      def value
        @value
      end

      # Returns the column object for the cell
      #
      # @return [Workbook::Column] the column the cell belongs to
      def column
        table.columns[index]
      end

      # Returns the sheet its at.
      #
      # @return [Workbook::Table]
      def table
        row&.table
      end

      # Quick assessor to the book's template, if it exists
      #
      # @return [Workbook::Template]
      def template
        row&.template
      end

      # Change the current format
      #
      # @param [Workbook::Format, Hash] f set the formatting properties of this Cell, see Workbook::Format#initialize
      def format= f
        if f.is_a? Workbook::Format
          @workbook_format = f
        elsif f.is_a? Hash
          @workbook_format = Workbook::Format.new(f)
        elsif f.instance_of?(NilClass)
          @workbook_format = Workbook::Format.new
        end
      end

      # Returns current format
      #
      # @return [Workbook::Format] the current format
      def format
        # return @workbook_format if @workbook_format
        if row && template && row.header? && !defined?(@workbook_format)
          @workbook_format = template.create_or_find_format_by(:header)
        else
          @workbook_format ||= Workbook::Format.new
        end
        @workbook_format
      end

      # Tests for equality based on its value (formatting is irrelevant)
      #
      # @param [Workbook::Cell] other cell to compare against
      # @return [Boolean]
      def ==(other)
        if other.is_a? Cell
          other.value == value
        else
          other == value
        end
      end

      # returns true when the value of the cell is nil.
      # @return [Boolean]
      def nil?
        value.nil?
      end

      def nil_or_empty?
        value.nil? || value.strip.to_s == ""
      end

      def value_to_s
        value.to_s.downcase
      end

      # returns a symbol representation of the cell's value
      # @return [Symbol] a symbol representation
      # @example
      #
      #     <Workbook::Cell value="yet another value">.to_sym # returns :yet_another_value
      def to_sym
        @to_sym ||= ::Workbook::Cell.value_to_sym(value)
      end

      # Compare
      #
      # @param [Workbook::Cell] other cell to compare against (based on value), can compare different value-types using #compare_on_class
      # @return [Integer] -1, 0, 1
      def <=> other
        rv = nil
        begin
          rv = value <=> other.value
        rescue NoMethodError
          rv = compare_on_class other
        end
        if rv.nil?
          rv = compare_on_class other
        end
        rv
      end

      # Compare on class level
      #
      # @param [Workbook::Cell] other cell to compare against
      def compare_on_class other
        other_value = nil
        other_value = other.value if other
        self_value = importance_of_class value
        other_value = importance_of_class other_value
        self_value <=> other_value
      end

      # Returns the importance of a value's class
      #
      # @param value a potential value for a cell
      def importance_of_class value
        CLASS_CELLTYPE_MAPPING.keys.index value.class.to_s
      end

      # Returns whether special formatting is present on this cell
      #
      # @return [Boolean] index of the cell
      def format?
        format && (format.keys.count > 0)
      end

      # Returns the index of the cell within the row, returns nil if no row is present
      #
      # @return [Integer, NilClass] index of the cell
      def index
        row&.index self
      end

      # Returns the key (a Symbol) of the cell, based on its table's header
      #
      # @return [Symbol, NilClass] key of the cell, returns nil if the cell doesn't belong to a table
      def key
        table.header[index].to_sym if table
      end

      def inspect
        txt = "<Workbook::Cell @value=#{value}"
        txt += " @format=#{format}" if format?
        txt += " @cell_type=#{cell_type}"
        txt += ">"
        txt
      end

      # convert value to string, and in case of a Date or Time value, apply formatting
      # @return [String]
      def to_s
        if (is_a?(Date) || is_a?(Time)) && format[:number_format]
          value.strftime(format[:number_format])
        elsif instance_of?(Workbook::Cell)
          value.to_s
        else
          super
        end
      end

      def colspan= c
        @colspan = c
      end

      def rowspan= r
        @rowspan = r
      end

      def colspan
        @colspan.to_i if defined?(@colspan) && (@colspan.to_i > 1)
      end

      def rowspan
        @rowspan.to_i if defined?(@rowspan) && (@rowspan.to_i > 1)
      end
    end
  end
end