summaryrefslogtreecommitdiffstats
path: root/lib/cepis/csv.ex
diff options
context:
space:
mode:
Diffstat (limited to 'lib/cepis/csv.ex')
-rw-r--r--lib/cepis/csv.ex65
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