Module: Sentiero::Web::CsvWriter

Defined in:
lib/sentiero/web/csv_writer.rb

Overview

Minimal RFC 4180 CSV serializer with spreadsheet-formula-injection guarding: cells starting with a formula trigger are prefixed with a single quote so the spreadsheet treats them as text rather than executing them.

Constant Summary collapse

FORMULA_TRIGGERS =

Tab/CR included: some spreadsheets strip them before re-evaluating the cell.

["=", "+", "-", "@", "\t", "\r"].freeze

Class Method Summary collapse

Class Method Details

.format_cell(cell) ⇒ Object



22
23
24
# File 'lib/sentiero/web/csv_writer.rb', line 22

def format_cell(cell)
  quote(guard_injection(stringify(cell)))
end

.format_row(row) ⇒ Object



18
19
20
# File 'lib/sentiero/web/csv_writer.rb', line 18

def format_row(row)
  row.map { |cell| format_cell(cell) }.join(",")
end

.generate(headers, rows) ⇒ Object



14
15
16
# File 'lib/sentiero/web/csv_writer.rb', line 14

def generate(headers, rows)
  ([headers] + rows).map { |row| format_row(row) }.join("\r\n") + "\r\n"
end

.guard_injection(value) ⇒ Object



35
36
37
# File 'lib/sentiero/web/csv_writer.rb', line 35

def guard_injection(value)
  value.start_with?(*FORMULA_TRIGGERS) ? "'#{value}" : value
end

.quote(value) ⇒ Object



39
40
41
42
# File 'lib/sentiero/web/csv_writer.rb', line 39

def quote(value)
  return value unless value.match?(/[",\r\n]/)
  %("#{value.gsub('"', '""')}")
end

.stringify(cell) ⇒ Object



26
27
28
29
30
31
32
33
# File 'lib/sentiero/web/csv_writer.rb', line 26

def stringify(cell)
  case cell
  when nil then ""
  when true then "true"
  when false then "false"
  else cell.to_s
  end
end