diff options
Diffstat (limited to 'lib/cepis/csv.ex')
| -rw-r--r-- | lib/cepis/csv.ex | 65 |
1 files changed, 65 insertions, 0 deletions
diff --git a/lib/cepis/csv.ex b/lib/cepis/csv.ex new file mode 100644 index 0000000..58b554f --- /dev/null +++ b/lib/cepis/csv.ex @@ -0,0 +1,65 @@ +defmodule Cepis.CSV do + @moduledoc false + + @spec parse_with_header(binary()) :: [%{optional(String.t()) => String.t()}] + def parse_with_header(text) do + case parse(text) do + [] -> [] + [header | rows] -> Enum.map(rows, &zip(header, &1)) + end + end + + @spec parse(binary()) :: [[String.t()]] + def parse(text) do + text + |> strip_bom() + |> outside([], [], []) + |> Enum.reject(&(&1 == [""])) + end + + defp zip(header, row) do + values = List.to_tuple(row) + width = tuple_size(values) + + header + |> Enum.with_index() + |> Map.new(fn {key, index} -> + {key, if(index < width, do: elem(values, index), else: "")} + end) + end + + defp strip_bom(<<0xEF, 0xBB, 0xBF, rest::binary>>), do: rest + defp strip_bom(text), do: text + + defp outside(<<>>, field, row, rows), do: emit(field, row, rows) + + defp outside(<<?", rest::binary>>, [], row, rows), do: inside(rest, [], row, rows) + + defp outside(<<?,, rest::binary>>, field, row, rows), + do: outside(rest, [], [flush(field) | row], rows) + + defp outside(<<?\r, ?\n, rest::binary>>, field, row, rows), + do: outside(rest, [], [], [Enum.reverse([flush(field) | row]) | rows]) + + defp outside(<<?\n, rest::binary>>, field, row, rows), + do: outside(rest, [], [], [Enum.reverse([flush(field) | row]) | rows]) + + defp outside(<<char, rest::binary>>, field, row, rows), + do: outside(rest, [field, char], row, rows) + + defp inside(<<>>, field, row, rows), do: emit(field, row, rows) + + defp inside(<<?", ?", rest::binary>>, field, row, rows), + do: inside(rest, [field, ?"], row, rows) + + defp inside(<<?", rest::binary>>, field, row, rows), do: outside(rest, field, row, rows) + + defp inside(<<char, rest::binary>>, field, row, rows), + do: inside(rest, [field, char], row, rows) + + defp emit(field, row, rows) do + Enum.reverse([Enum.reverse([flush(field) | row]) | rows]) + end + + defp flush(field), do: IO.iodata_to_binary(field) +end |