Module: SmarterCSV::HashTransformations
- Included in:
- Reader
- Defined in:
- lib/smarter_csv/hash_transformations.rb
Constant Summary collapse
- NUMERIC_REGEX =
Frozen regex constants for performance (avoid recompilation on every value)
/\A[+-]?\d+(?:\.\d+)?\z/.freeze
- ZERO_REGEX =
/\A[+-]?0+(?:\.0+)?\z/.freeze
- ZERO_BYTE =
First-byte values that can begin a numeric literal — used to skip the numeric regexes for values that obviously aren’t numbers (e.g. city names).
'0'.ord
- NINE_BYTE =
48
'9'.ord
- PLUS_BYTE =
57
'+'.ord
- MINUS_BYTE =
43
'-'.ord
Instance Method Summary collapse
Instance Method Details
#hash_transformations(hash, options) ⇒ Object
45
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/smarter_csv/hash_transformations.rb', line 18 def hash_transformations(hash, ) # Modify hash in-place for performance (avoids allocating a second hash per row) # Remove nil/empty keys hash.delete(nil) hash.delete('') hash.delete(:"") remove_empty_values = [:remove_empty_values] == true remove_zero_values = [:remove_zero_values] nil_values_matching = [:nil_values_matching] convert_to_numeric = [:convert_values_to_numeric] value_converters = [:value_converters] # Early return if no transformations needed return hash unless remove_empty_values || remove_zero_values || nil_values_matching || convert_to_numeric || value_converters # {only:}/{except:} limits on numeric conversion apply only when the option is a Hash; # in the common case (true/false) skip the per-key check entirely. numeric_has_limits = convert_to_numeric.is_a?(Hash) rails = has_rails keys_to_delete = nil # lazily allocated only if something is actually removed hash.each do |k, v| # Nil-ify values matching the pattern (keeps the key; remove_empty_values handles deletion) if nil_values_matching str_val = v.is_a?(String) ? v : (v.is_a?(Numeric) ? v.to_s : nil) if str_val && nil_values_matching.match?(str_val) hash[k] = nil v = nil # fall through: remove_empty_values will delete the key if true end end # Check if this key/value should be removed # Note: numeric values (Integer/Float) are never blank, so skip the blank check for them if remove_empty_values && !v.is_a?(Numeric) && (rails ? v.blank? : blank?(v)) (keys_to_delete ||= []) << k next end # Handle both string zeros ("0", "0.0") and numeric zeros (already converted by C) if remove_zero_values && ((v.is_a?(String) && ZERO_REGEX.match?(v)) || (v.is_a?(Numeric) && v == 0)) (keys_to_delete ||= []) << k next end # Convert to numeric if requested if convert_to_numeric && v.is_a?(String) && (!numeric_has_limits || !limit_execution_for_only_or_except(, :convert_values_to_numeric, k)) # Fast-reject: the string is already stripped and NUMERIC_REGEX is \A-anchored on a digit or sign, # so a value whose first byte isn't a digit, '+', or '-' cannot be numeric — skip the regex entirely. first_byte = v.getbyte(0) if first_byte && ((first_byte >= ZERO_BYTE && first_byte <= NINE_BYTE) || first_byte == MINUS_BYTE || first_byte == PLUS_BYTE) if NUMERIC_REGEX.match?(v) hash[k] = v.include?('.') ? v.to_f : v.to_i end end end # Apply value converters if value_converters converter = value_converters[k] hash[k] = converter.respond_to?(:convert) ? converter.convert(hash[k]) : converter.call(hash[k]) if converter end end # Delete marked keys keys_to_delete&.each { |key| hash.delete(key) } hash end |