module DBF module Column # Raised if length is less than 1 class LengthError < StandardError; end # Raised if name is empty class NameError < StandardError; end class Base attr_reader :table, :name, :type, :length, :decimal # Initialize a new DBF::Column # # @param [String] name # @param [String] type # @param [Fixnum] length # @param [Fixnum] decimal def initialize(table, name, type, length, decimal) @table = table @name = clean(name) @type = type @length = length @decimal = decimal @version = table.version @encoding = table.encoding unless length >= 0 raise LengthError, 'field length must be 0 or greater' end if @name.empty? raise NameError, 'column name cannot be empty' end end # Cast value to native type # # @param [String] value # @return [Fixnum, Float, Date, DateTime, Boolean, String] def type_cast(value) return nil if length == 0 case type when 'N' then unpack_number(value) when 'I' then unpack_unsigned_long(value) when 'F' then value.to_f when 'Y' then (unpack_unsigned_long(value) / 10000.0).to_f when 'D' then decode_date(value) when 'T' then decode_datetime(value) when 'L' then boolean(value) when 'M' then decode_memo(value) else encode_string(value.to_s).strip end end # Returns true if the column is a memo # # @return [Boolean] def memo? @memo ||= type == 'M' end # Schema definition # # @return [String] def schema_definition "\"#{underscored_name}\", #{schema_data_type}\n" end # Underscored name # # This is the column name converted to underscore format. # For example, MyColumn will be returned as my_column. # # @return [String] def underscored_name @underscored_name ||= begin name.gsub(/::/, '/'). gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). gsub(/([a-z\d])([A-Z])/,'\1_\2'). tr('-', '_'). downcase end end private def decode_date(value) #nodoc value.gsub!(' ', '0') value !~ /\S/ ? nil : Date.parse(value) rescue nil end def decode_datetime(value) #nodoc days, msecs = value.unpack('l2') secs = (msecs / 1000).to_i DateTime.jd(days, (secs/3600).to_i, (secs/60).to_i % 60, secs % 60) rescue nil end def decode_memo(value) #nodoc value && encode_string(value) end def unpack_number(value) #nodoc decimal.zero? ? value.to_i : value.to_f end def unpack_unsigned_long(value) #nodoc value.unpack('V')[0] end def boolean(value) #nodoc value.strip =~ /^(y|t)$/i ? true : false end def encode_string(value) #nodoc if @encoding && table.supports_encoding? if table.supports_string_encoding? value.force_encoding(@encoding).encode(*encoding_args) elsif table.supports_iconv? Iconv.conv('UTF-8', @encoding, value) end else value end end def encoding_args #nodoc [Encoding.default_external, {:undef => :replace, :invalid => :replace}] end def schema_data_type #nodoc case type when "N", "F" decimal > 0 ? ":float" : ":integer" when "I" ":integer" when "Y" ":decimal, :precision => 15, :scale => 4" when "D" ":date" when "T" ":datetime" when "L" ":boolean" when "M" ":text" when "B" if DBF::Table::FOXPRO_VERSIONS.keys.include?(@version) decimal > 0 ? ":float" : ":integer" else ":text" end else ":string, :limit => #{length}" end end def clean(value) #nodoc truncated_value = value.strip.partition("\x00").first truncated_value.gsub(/[^\x20-\x7E]/, '') end end end end