Module: SmarterCSV::HashTransformations

Included in:
Reader
Defined in:
lib/smarter_csv/hash_transformations.rb

Constant Summary collapse

FLOAT_REGEX =

Frozen regex constants for performance (avoid recompilation on every value)

/\A[+-]?\d+\.\d+\z/.freeze
INTEGER_REGEX =
/\A[+-]?\d+\z/.freeze
ZERO_REGEX =
/\A0+(?:\.0+)?\z/.freeze

Instance Method Summary collapse

Instance Method Details

#hash_transformations(hash, options) ⇒ Object



10
11
12
13
14
15
16
17
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
# File 'lib/smarter_csv/hash_transformations.rb', line 10

def hash_transformations(hash, options)
  # 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 = options[:remove_empty_values] == true
  remove_zero_values = options[:remove_zero_values]
  remove_values_matching = options[:remove_values_matching]
  convert_to_numeric = options[:convert_values_to_numeric]
  value_converters = options[:value_converters]

  # Early return if no transformations needed
  return hash unless remove_empty_values || remove_zero_values || remove_values_matching || convert_to_numeric || value_converters

  keys_to_delete = []

  hash.each do |k, v|
    # 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) && (has_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

    # Match against string values, or against the string representation of numeric values
    if remove_values_matching
      str_val = v.is_a?(String) ? v : (v.is_a?(Numeric) ? v.to_s : nil)
      if str_val && remove_values_matching.match?(str_val)
        keys_to_delete << k
        next
      end
    end

    # Convert to numeric if requested
    if convert_to_numeric && v.is_a?(String) && !limit_execution_for_only_or_except(options, :convert_values_to_numeric, k)
      if FLOAT_REGEX.match?(v)
        hash[k] = v.to_f
      elsif INTEGER_REGEX.match?(v)
        hash[k] = v.to_i
      end
    end

    # Apply value converters
    if value_converters
      converter = value_converters[k]
      hash[k] = converter.convert(hash[k]) if converter
    end
  end

  # Delete marked keys
  keys_to_delete.each { |k| hash.delete(k) }

  hash
end