require 'fast_xs'
class XlsxWriter
class Cell
class << self
# 0 -> A (zero based!)
def column_letter(i)
result = []
while i >= 26 do
result << ABC[i % 26]
i /= 26
end
result << ABC[result.empty? ? i : i - 1]
result.reverse.join
end
# backwards compatibility
alias :excel_column_letter :column_letter
def type(value, proposed = nil)
hint = if proposed
proposed
elsif value.is_a?(String) and value =~ TRUE_FALSE_PATTERN
:Boolean
else
value.class.name.to_sym
end
case hint
when :NilClass
:String
when :Fixnum
:Integer
when :Float, :Rational, :BigDecimal
:Decimal
when :TrueClass, :FalseClass
:Boolean
else
hint
end
end
def style_number(type, faded = false)
style_number = STYLE_NUMBER[type] or raise("Don't know style number for #{type.inspect}. Must be #{STYLE_NUMBER.keys.map(&:inspect).join(', ')}.")
if faded
style_number * 2 + 1
else
style_number * 2
end
end
def type_name(type)
TYPE_NAME[type] or raise "Don't know type name for #{type.inspect}. Must be #{TYPE_NAME.keys.map(&:inspect).join(', ')}."
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(value, type = nil)
if (w = ((character_width(value, type).to_f*MAX_DIGIT_WIDTH+5)/MAX_DIGIT_WIDTH*256)/256) < MAX_REASONABLE_WIDTH
w
else
MAX_REASONABLE_WIDTH
end
end
def character_width(value, type = nil)
if type.nil?
type = Cell.type(value)
end
case type
when :String, :Integer
value.to_s.length
when :Decimal
# -1000000.5
round(value, 2).to_s.length + 2
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
else
raise "Don't know character width for #{type.inspect}."
end
end
def escape(value, type = nil)
if type.nil?
type = Cell.type(value)
end
case type
when :Integer
value.to_s
when :Decimal, :Currency
case value
when BIG_DECIMAL
value.to_s('F')
when Rational
value.to_f.to_s
else
value.to_s
end
when :Date
# doesn't work for DateTimes or Times yet
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
end
when :Boolean
value.to_s.downcase == 'true' ? 1 : 0
else
value.fast_xs
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
MAX_DIGIT_WIDTH = 5
MAX_REASONABLE_WIDTH = 75
DATE_LENGTH = 'YYYY-MM-DD'.length
BOOLEAN_LENGTH = 'FALSE'.length + 1
JAN_1_1900 = Time.parse('1899-12-30 00:00:00 UTC')
TRUE_FALSE_PATTERN = %r{^true|false$}i
BIG_DECIMAL = defined?(BigDecimal) ? BigDecimal : Struct.new
STYLE_NUMBER = {
:String => 0,
:Boolean => 0,
:Currency => 1,
:Date => 2,
:Integer => 3,
:Decimal => 4,
}
TYPE_NAME = {
:String => :s,
:Boolean => :b,
:Currency => :n,
:Date => :n,
:Integer => :n,
:Decimal => :n,
}
attr_reader :row
attr_reader :x
attr_reader :y
attr_reader :value
attr_reader :type
def initialize(row, raw_value, x, y)
@row = row
@x = x
@y = y
if raw_value.is_a?(Hash)
@value = raw_value[:value]
@type = Cell.type @value, raw_value[:type]
@faded_query = raw_value[:faded]
else
@value = raw_value
@type = Cell.type value
end
end
def faded?
@faded_query == true
end
def empty?
return @empty_query if defined?(@empty_query)
@empty_query = (value.nil? or (value.is_a?(String) and value.empty?) or (value == false and row.sheet.document.quiet_booleans?))
end
def to_xml
if empty?
%{}
else
%{#{escaped_value}}
end
end
def pixel_width
@pixel_width ||= Cell.pixel_width value, type
end
def escaped_value
@escaped_value ||= begin
if type == :String
row.sheet.document.shared_strings.ndx value
else
Cell.escape value
end
end
end
end
end