lib/xlsx_writer/cell.rb in xlsx_writer-0.1.2 vs lib/xlsx_writer/cell.rb in xlsx_writer-0.2.0

- old
+ new

@@ -62,11 +62,10 @@ alias :excel_currency :excel_number alias :excel_integer :excel_number alias :excel_decimal :excel_number # doesn't necessarily work for times yet - JAN_1_1900 = ::Time.parse('1900-01-01') def excel_date(value) if value.is_a?(::String) ((::Time.parse(str) - JAN_1_1900) / 86_400).round elsif value.respond_to?(:to_date) (value.to_date - JAN_1_1900.to_date).to_i @@ -74,65 +73,106 @@ end def excel_boolean(value) value ? 1 : 0 end + + # width = Truncate([{Number of Characters} * {Maximum Digit Width} + {5 pixel padding}]/{Maximum Digit Width}*256)/256 + # Using the Calibri font as an example, the maximum digit width of 11 point font size is 7 pixels (at 96 dpi). In fact, each digit is the same width for this font. Therefore if the cell width is 8 characters wide, the value of this attribute shall be Truncate([8*7+5]/7*256)/256 = 8.7109375. + def pixel_width(character_width) + [ + ((character_width.to_f*MAX_DIGIT_WIDTH+5)/MAX_DIGIT_WIDTH*256)/256, + MAX_REASONABLE_WIDTH + ].min + end + + def calculate_type(value) + if value.is_a?(::Date) + :Date + elsif value.is_a?(::Integer) + :Integer + elsif value.is_a?(::Float) or (defined?(::BigDecimal) and value.is_a?(::BigDecimal)) or (defined?(::Decimal) and value.is_a?(::Decimal)) + :Decimal + elsif value.is_a?(::Numeric) + :Number + else + :String + end + end + + def character_width(value, calculated_type = nil) + calculated_type ||= calculate_type(value) + case calculated_type + when :String + value.to_s.length + when :Number, :Integer, :Decimal + # -1000000.5 + len = round(value, 2).to_s.length + len += 2 if calculated_type == :Decimal + len += 1 if value < 0 + len + when :Currency + # (1,000,000.50) + len = round(value, 2).to_s.length + log_base(value.abs, 1e3).floor + len += 2 if value < 0 + len + when :Date + DATE_LENGTH + when :Boolean + BOOLEAN_LENGTH + end + end + + if ::RUBY_VERSION >= '1.9' + def round(number, precision) + number.round precision + end + def log_base(number, base) + ::Math.log number, base + end + else + def round(number, precision) + (number * (10 ** precision).to_i).round / (10 ** precision).to_f + end + # http://blog.vagmim.com/2010/01/logarithm-to-any-base-in-ruby.html + def log_base(number, base) + ::Math.log(number) / ::Math.log(base) + end + end end ABC = ('A'..'Z').to_a - - attr_reader :row - attr_reader :data - - def initialize(row, data) - @row = row - @data = data.is_a?(::Hash) ? data.symbolize_keys : data - end - - # width = Truncate([{Number of Characters} * {Maximum Digit Width} + {5 pixel padding}]/{Maximum Digit Width}*256)/256 - # Using the Calibri font as an example, the maximum digit width of 11 point font size is 7 pixels (at 96 dpi). In fact, each digit is the same width for this font. Therefore if the cell width is 8 characters wide, the value of this attribute shall be Truncate([8*7+5]/7*256)/256 = 8.7109375. MAX_DIGIT_WIDTH = 5 MAX_REASONABLE_WIDTH = 75 - def pixel_width - @pixel_width ||= [ - ((character_width.to_f*MAX_DIGIT_WIDTH+5)/MAX_DIGIT_WIDTH*256)/256, - MAX_REASONABLE_WIDTH - ].min - end - DATE_LENGTH = 'YYYY-MM-DD'.length BOOLEAN_LENGTH = 'FALSE'.length - def character_width - @character_width ||= case calculated_type - when :String - value.to_s.length - when :Number, :Integer, :Decimal - # -1000000.5 - len = value.round(2).to_s.length - len += 2 if calculated_type == :Decimal - len += 1 if value < 0 - len - when :Currency - # (1,000,000.50) - len = value.round(2).to_s.length + ::Math.log(value.abs, 1_000).floor - len += 2 if value < 0 - len - when :Date - DATE_LENGTH - when :Boolean - BOOLEAN_LENGTH + JAN_1_1900 = ::Time.parse '1900-01-01' + + attr_reader :row + attr_reader :value + attr_reader :pixel_width + attr_reader :excel_type + attr_reader :excel_style_number + attr_reader :excel_value + + def initialize(row, data) + @row = row + if data.is_a?(::Hash) + data = data.symbolize_keys + calculated_type = data[:type] + @value = data[:value] + else + @value = data + calculated_type = Cell.calculate_type @value end + character_width = Cell.character_width @value, calculated_type + @pixel_width = Cell.pixel_width character_width + @excel_type = Cell.excel_type calculated_type + @excel_style_number = Cell.excel_style_number calculated_type + @excel_value = Cell.send "excel_#{calculated_type.to_s.underscore}", @value end - - def unstyled? - !styled? - end - - def styled? - data.is_a?(::Hash) - end - + def to_xml if value.blank? %{<c r="#{excel_column_letter}#{row.ndx}" s="0" t="inlineStr" />} elsif excel_type == :inlineStr %{<c r="#{excel_column_letter}#{row.ndx}" s="#{excel_style_number}" t="#{excel_type}"><is><t>#{excel_value}</t></is></c>} @@ -142,41 +182,8 @@ end # 0 -> A (zero based!) def excel_column_letter Cell.excel_column_letter row.cells.index(self) - end - - # detect dates here, even if we're not styled - def excel_type - Cell.excel_type calculated_type - end - - def excel_style_number - Cell.excel_style_number calculated_type - end - - def calculated_type - @calculated_type ||= if styled? - data[:type] - elsif value.is_a?(::Date) - :Date - elsif value.is_a?(::Integer) - :Integer - elsif value.is_a?(::Float) or (defined?(::BigDecimal) and value.is_a?(::BigDecimal)) or (defined?(::Decimal) and value.is_a?(::Decimal)) - :Decimal - elsif value.is_a?(::Numeric) - :Number - else - :String - end - end - - def value - styled? ? data[:value] : data - end - - def excel_value - Cell.send "excel_#{calculated_type.to_s.underscore}", value end end end