diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..3d8ce11 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,3 @@ +[ + inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..fa52eb3 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,30 @@ +name: Run Tests +on: + push: + +jobs: + run_tests: + runs-on: ubuntu-latest + env: + MIX_ENV: test + steps: + - uses: actions/checkout@v4 + + - name: Install Erlang & Elixir + uses: erlef/setup-beam@v1 + id: setup-beam + with: + otp-version: '26.2' + elixir-version: '1.17.2' + + - name: Install mix dependencies + run: mix deps.get + + - name: Check warnings + run: mix compile --warnings-as-errors + + - name: Check formatting + run: mix format --check-formatted + + - name: Run tests + run: mix test diff --git a/config/config.exs b/config/config.exs index b1a43bf..becde76 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,7 +1 @@ -# This file is responsible for configuring your application -# and its dependencies with the aid of the Mix.Config module. -use Mix.Config - -# when extracting to file, files will be extracted -# in a sub directory in the `:extract_base_dir` directory. -# config :xlsxir, extract_base_dir: "temp" +import Config diff --git a/lib/xlsxir.ex b/lib/xlsxir.ex index 03fa492..1c0a170 100644 --- a/lib/xlsxir.ex +++ b/lib/xlsxir.ex @@ -4,7 +4,6 @@ defmodule Xlsxir do use Application def start(_type, _args) do - children = [ %{id: Xlsxir.StateManager, start: {Xlsxir.StateManager, :start_link, []}, type: :worker} ] diff --git a/lib/xlsxir/convert_date.ex b/lib/xlsxir/convert_date.ex index 1210b87..d31e7a4 100644 --- a/lib/xlsxir/convert_date.ex +++ b/lib/xlsxir/convert_date.ex @@ -14,24 +14,31 @@ defmodule Xlsxir.ConvertDate do ## Example - iex> Xlsxir.ConvertDate.from_serial('27514') + iex> Xlsxir.ConvertDate.from_serial(~c'27514') {1975, 4, 30} """ def from_serial(serial) do - f_serial = serial - |> convert_char_number - |> is_float - |> case do - false -> List.to_integer(serial) - true -> serial - |> List.to_float() - |> Float.floor - |> round - end + f_serial = + serial + |> convert_char_number + |> is_float + |> case do + false -> + List.to_integer(serial) + + true -> + serial + |> List.to_float() + |> Float.floor() + |> round + end # Convert to gregorian days and get date from that - gregorian = f_serial - 2 + # adjust two days for first and last day since base year - date_to_days({1900, 1, 1}) # Add days in base year 1900 + # adjust two days for first and last day since base year + # Add days in base year 1900 + gregorian = + f_serial - 2 + + date_to_days({1900, 1, 1}) gregorian |> days_to_date @@ -50,11 +57,14 @@ defmodule Xlsxir.ConvertDate do str |> String.match?(~r/[.eE]/) |> case do - false -> List.to_integer(number) - true -> case Float.parse(str) do - {f, _} -> f - _ -> raise "Invalid Float" - end - end + false -> + List.to_integer(number) + + true -> + case Float.parse(str) do + {f, _} -> f + _ -> raise "Invalid Float" + end + end end end diff --git a/lib/xlsxir/convert_datetime.ex b/lib/xlsxir/convert_datetime.ex index bc0a3b7..c2208b6 100644 --- a/lib/xlsxir/convert_datetime.ex +++ b/lib/xlsxir/convert_datetime.ex @@ -14,18 +14,20 @@ defmodule Xlsxir.ConvertDateTime do ## Example - iex> Xlsxir.ConvertDateTime.from_charlist('41261.6013888889') + iex> Xlsxir.ConvertDateTime.from_charlist(~c'41261.6013888889') ~N[2012-12-18 14:26:00] """ - def from_charlist('0'), do: {0, 0, 0} + def from_charlist(~c"0"), do: {0, 0, 0} + def from_charlist(charlist) do charlist - |> List.to_float + |> List.to_float() |> from_float end def from_float(n) when is_float(n) do - n = if n > 59, do: n - 1, else: n # Lotus bug + # Lotus bug + n = if n > 59, do: n - 1, else: n convert_from_serial(n) end @@ -36,6 +38,7 @@ defmodule Xlsxir.ConvertDateTime do {hours, minutes, seconds} end + defp convert_from_serial(n) when is_float(n) do {whole_days, fractional_day} = split_float(n) {hours, minutes, seconds} = convert_from_serial(fractional_day) @@ -48,9 +51,11 @@ defmodule Xlsxir.ConvertDateTime do end defp split_float(f) do - whole = f - |> Float.floor + whole = + f + |> Float.floor() |> round + {whole, f - whole} end end diff --git a/lib/xlsxir/parse_string.ex b/lib/xlsxir/parse_string.ex index b8e20d2..2e312ce 100644 --- a/lib/xlsxir/parse_string.ex +++ b/lib/xlsxir/parse_string.ex @@ -22,22 +22,27 @@ defmodule Xlsxir.ParseString do %__MODULE__{tid: GenServer.call(Xlsxir.StateManager, :new_table)} end - def sax_event_handler({:startElement,_,'si',_,_}, %__MODULE__{tid: tid, index: index}), do: %__MODULE__{tid: tid, index: index} + def sax_event_handler({:startElement, _, ~c"si", _, _}, %__MODULE__{tid: tid, index: index}), + do: %__MODULE__{tid: tid, index: index} - def sax_event_handler({:startElement,_,'family',_,_}, state) do + def sax_event_handler({:startElement, _, ~c"family", _, _}, state) do %{state | family: true} end - def sax_event_handler({:characters, value}, - %__MODULE__{family_string: fam_str} = state) do - value = value |> to_string - %{state | family_string: fam_str <> value} + def sax_event_handler( + {:characters, value}, + %__MODULE__{family_string: fam_str} = state + ) do + value = value |> to_string + %{state | family_string: fam_str <> value} end - def sax_event_handler({:endElement,_,'si',_}, - %__MODULE__{family_string: fam_str, tid: tid, index: index} = state) do - :ets.insert(tid, {index, fam_str}) - %{state | index: index + 1} + def sax_event_handler( + {:endElement, _, ~c"si", _}, + %__MODULE__{family_string: fam_str, tid: tid, index: index} = state + ) do + :ets.insert(tid, {index, fam_str}) + %{state | index: index + 1} end def sax_event_handler(_, state), do: state diff --git a/lib/xlsxir/parse_style.ex b/lib/xlsxir/parse_style.ex index d095d85..1f034c7 100644 --- a/lib/xlsxir/parse_style.ex +++ b/lib/xlsxir/parse_style.ex @@ -33,7 +33,7 @@ defmodule Xlsxir.ParseStyle do 69, 70 ] - @date [14, 15, 16, 17, 18, 19, 20, 21, 22, 27, 30, 36, 45, 46, 47, 50, 57] + @date [14, 15, 16, 17, 18, 19, 20, 21, 22, 27, 30, 36, 45, 46, 47, 50, 55, 57] defstruct custom_style: %{}, cellxfs: false, index: 0, tid: nil, num_fmt_ids: [] @@ -54,23 +54,23 @@ defmodule Xlsxir.ParseStyle do %__MODULE__{tid: GenServer.call(Xlsxir.StateManager, :new_table)} end - def sax_event_handler({:startElement, _, 'cellXfs', _, _}, state) do + def sax_event_handler({:startElement, _, ~c"cellXfs", _, _}, state) do %{state | cellxfs: true} end - def sax_event_handler({:endElement, _, 'cellXfs', _}, state) do + def sax_event_handler({:endElement, _, ~c"cellXfs", _}, state) do %{state | cellxfs: false} end def sax_event_handler( - {:startElement, _, 'xf', _, xml_attr}, + {:startElement, _, ~c"xf", _, xml_attr}, %__MODULE__{num_fmt_ids: num_fmt_ids} = state ) do if state.cellxfs do xml_attr |> Enum.filter(fn attr -> case attr do - {:attribute, 'numFmtId', _, _, _} -> true + {:attribute, ~c"numFmtId", _, _, _} -> true _ -> false end end) @@ -79,7 +79,7 @@ defmodule Xlsxir.ParseStyle do %{state | num_fmt_ids: num_fmt_ids ++ [id]} _ -> - %{state | num_fmt_ids: num_fmt_ids ++ ['0']} + %{state | num_fmt_ids: num_fmt_ids ++ [~c"0"]} end else state @@ -87,14 +87,14 @@ defmodule Xlsxir.ParseStyle do end def sax_event_handler( - {:startElement, _, 'numFmt', _, xml_attr}, + {:startElement, _, ~c"numFmt", _, xml_attr}, %__MODULE__{custom_style: custom_style} = state ) do temp = Enum.reduce(xml_attr, %{}, fn attr, acc -> case attr do - {:attribute, 'numFmtId', _, _, id} -> Map.put(acc, :id, id) - {:attribute, 'formatCode', _, _, cd} -> Map.put(acc, :cd, cd) + {:attribute, ~c"numFmtId", _, _, id} -> Map.put(acc, :id, id) + {:attribute, ~c"formatCode", _, _, cd} -> Map.put(acc, :cd, cd) _ -> nil end end) @@ -112,7 +112,7 @@ defmodule Xlsxir.ParseStyle do Enum.reduce(num_fmt_ids, 0, fn style_type, acc -> case List.to_integer(style_type) do i when i in @num -> :ets.insert(tid, {index + acc, nil}) - i when i in @date -> :ets.insert(tid, {index + acc, 'd'}) + i when i in @date -> :ets.insert(tid, {index + acc, ~c"d"}) _ -> add_custom_style(tid, style_type, custom_type, index + acc) end @@ -129,7 +129,7 @@ defmodule Xlsxir.ParseStyle do |> Enum.reduce(%{}, fn {k, v}, acc -> cond do String.match?(to_string(v), ~r/\bred\b/i) -> Map.put_new(acc, k, nil) - String.match?(to_string(v), ~r/[dhmsy]/i) -> Map.put_new(acc, k, 'd') + String.match?(to_string(v), ~r/[dhmsy]/i) -> Map.put_new(acc, k, ~c"d") true -> Map.put_new(acc, k, nil) end end) diff --git a/lib/xlsxir/parse_workbook.ex b/lib/xlsxir/parse_workbook.ex index c620350..ec6f0ed 100644 --- a/lib/xlsxir/parse_workbook.ex +++ b/lib/xlsxir/parse_workbook.ex @@ -12,18 +12,18 @@ defmodule Xlsxir.ParseWorkbook do %__MODULE__{tid: GenServer.call(Xlsxir.StateManager, :new_table)} end - def sax_event_handler({:startElement, _, 'sheet', _, xml_attrs}, state) do + def sax_event_handler({:startElement, _, ~c"sheet", _, xml_attrs}, state) do sheet = Enum.reduce(xml_attrs, %{name: nil, sheet_id: nil, rid: nil}, fn attr, sheet -> case attr do - {:attribute, 'name', _, _, name} -> + {:attribute, ~c"name", _, _, name} -> %{sheet | name: name |> to_string} - {:attribute, 'sheetId', _, _, sheet_id} -> + {:attribute, ~c"sheetId", _, _, sheet_id} -> {sheet_id, _} = sheet_id |> to_string |> Integer.parse() %{sheet | sheet_id: sheet_id} - {:attribute, 'id', _, _, rid} -> + {:attribute, ~c"id", _, _, rid} -> "rId" <> rid = rid |> to_string {rid, _} = Integer.parse(rid) %{sheet | rid: rid} @@ -37,8 +37,8 @@ defmodule Xlsxir.ParseWorkbook do end def sax_event_handler(:endDocument, %__MODULE__{tid: tid} = state) do - Enum.map(state.sheets, fn %{sheet_id: sheet_id, name: name} -> - :ets.insert(tid, {sheet_id, name}) + Enum.map(state.sheets, fn %{rid: rid, name: name} -> + :ets.insert(tid, {rid, name}) end) state diff --git a/lib/xlsxir/parse_worksheet.ex b/lib/xlsxir/parse_worksheet.ex index 4ce84b8..5527e55 100644 --- a/lib/xlsxir/parse_worksheet.ex +++ b/lib/xlsxir/parse_worksheet.ex @@ -55,7 +55,7 @@ defmodule Xlsxir.ParseWorksheet do end def sax_event_handler( - {:startElement, _, 'row', _, _}, + {:startElement, _, ~c"row", _, _}, %__MODULE__{tid: tid, max_rows: max_rows}, _excel, _ @@ -63,11 +63,11 @@ defmodule Xlsxir.ParseWorksheet do %__MODULE__{tid: tid, max_rows: max_rows} end - def sax_event_handler({:startElement, _, 'c', _, xml_attr}, state, %{styles: styles_tid}, _) do + def sax_event_handler({:startElement, _, ~c"c", _, xml_attr}, state, %{styles: styles_tid}, _) do a = Enum.reduce(xml_attr, %{}, fn attr, acc -> case attr do - {:attribute, 's', _, _, style} -> + {:attribute, ~c"s", _, _, style} -> Map.put(acc, "s", find_styles(styles_tid, List.to_integer(style))) {:attribute, key, _, _, ref} -> @@ -80,19 +80,20 @@ defmodule Xlsxir.ParseWorksheet do %{state | cell_ref: cell_ref, num_style: num_style, data_type: data_type} end - def sax_event_handler({:startElement, _, 'f', _, _}, state, _, _) do + def sax_event_handler({:startElement, _, ~c"f", _, _}, state, _, _) do %{state | value_type: :formula} end - def sax_event_handler({:startElement, _, el, _, _}, state, _, _) when el in ['v', 't'] do + def sax_event_handler({:startElement, _, el, _, _}, state, _, _) when el in [~c"v", ~c"t"] do %{state | value_type: :value} end - def sax_event_handler({:endElement, _, el, _, _}, state, _, _) when el in ['f', 'v', 't'] do + def sax_event_handler({:endElement, _, el, _, _}, state, _, _) + when el in [~c"f", ~c"v", ~c"t"] do %{state | value_type: nil} end - def sax_event_handler({:startElement, _, 'is', _, _}, state, _, _), + def sax_event_handler({:startElement, _, ~c"is", _, _}, state, _, _), do: %{state | value_type: :value} def sax_event_handler({:characters, value}, state, _, _) do @@ -103,7 +104,7 @@ defmodule Xlsxir.ParseWorksheet do end end - def sax_event_handler({:endElement, _, 'c', _}, %__MODULE__{row: row} = state, excel, _) do + def sax_event_handler({:endElement, _, ~c"c", _}, %__MODULE__{row: row} = state, excel, _) do cell_value = format_cell_value(excel, [state.data_type, state.num_style, state.value]) new_cell = [to_string(state.cell_ref), cell_value] @@ -118,7 +119,7 @@ defmodule Xlsxir.ParseWorksheet do end def sax_event_handler( - {:endElement, _, 'row', _}, + {:endElement, _, ~c"row", _}, %__MODULE__{tid: tid, max_rows: max_rows} = state, _excel, _ @@ -180,7 +181,7 @@ defmodule Xlsxir.ParseWorksheet do acc + char - 65 + 1 end) - "#{column_from_index(col_index + 1, '')}#{line}" + "#{column_from_index(col_index + 1, ~c"")}#{line}" end def fill_empty_cells(from, from, _line, cells), do: Enum.reverse(cells) @@ -202,22 +203,22 @@ defmodule Xlsxir.ParseWorksheet do # Empty cell with assigned attribute [_, _, ""] -> nil # Type error - ['e', _, e] -> List.to_string(e) + [~c"e", _, e] -> List.to_string(e) # Type string - ['s', _, i] -> find_string(strings_tid, List.to_integer(i)) + [~c"s", _, i] -> find_string(strings_tid, List.to_integer(i)) # Type number [nil, nil, n] -> convert_char_number(n) - ['n', nil, n] -> convert_char_number(n) + [~c"n", nil, n] -> convert_char_number(n) # ISO 8601 type date - [nil, 'd', d] -> convert_date_or_time(d) - ['n', 'd', d] -> convert_date_or_time(d) - ['d', 'd', d] -> convert_iso_date(d) + [nil, ~c"d", d] -> convert_date_or_time(d) + [~c"n", ~c"d", d] -> convert_date_or_time(d) + [~c"d", ~c"d", d] -> convert_iso_date(d) # Type formula w/ string - ['str', _, s] -> List.to_string(s) + [~c"str", _, s] -> List.to_string(s) # Type boolean - ['b', _, s] -> s == '1' + [~c"b", _, s] -> s == ~c"1" # Type string - ['inlineStr', _, s] -> List.to_string(s) + [~c"inlineStr", _, s] -> List.to_string(s) # Unmapped type _ -> raise "Unmapped attribute #{Enum.at(list, 0)}. Unable to process" end diff --git a/lib/xlsxir/stream_worksheet.ex b/lib/xlsxir/stream_worksheet.ex index 15a3603..e86b277 100644 --- a/lib/xlsxir/stream_worksheet.ex +++ b/lib/xlsxir/stream_worksheet.ex @@ -34,7 +34,7 @@ defmodule Xlsxir.StreamWorksheet do %ParseWorksheet{} end - def sax_event_handler({:endElement, _, 'row', _}, state, _excel) do + def sax_event_handler({:endElement, _, ~c"row", _}, state, _excel) do unless Enum.empty?(state.row) do value = state.row |> Enum.reverse() diff --git a/lib/xlsxir/unzip.ex b/lib/xlsxir/unzip.ex index 6d4fb40..4a46c6a 100644 --- a/lib/xlsxir/unzip.ex +++ b/lib/xlsxir/unzip.ex @@ -1,5 +1,4 @@ defmodule Xlsxir.Unzip do - alias Xlsxir.XmlFile @moduledoc """ @@ -21,11 +20,11 @@ defmodule Xlsxir.Unzip do iex> path = "./test/test_data/test.xlsx" iex> Xlsxir.Unzip.validate_path_and_index(path, 0) - {:ok, './test/test_data/test.xlsx'} + {:ok, ~c'./test/test_data/test.xlsx'} iex> path = "./test/test_data/test.validfilebutnotxlsx" iex> Xlsxir.Unzip.validate_path_and_index(path, 0) - {:ok, './test/test_data/test.validfilebutnotxlsx'} + {:ok, ~c'./test/test_data/test.validfilebutnotxlsx'} iex> path = "./test/test_data/test.xlsx" iex> Xlsxir.Unzip.validate_path_and_index(path, 100) @@ -39,7 +38,7 @@ defmodule Xlsxir.Unzip do path = String.to_charlist(path) case valid_extract_request?(path, index) do - :ok -> {:ok, path} + :ok -> {:ok, path} {:error, reason} -> {:error, reason} end end @@ -68,47 +67,57 @@ defmodule Xlsxir.Unzip do def validate_path_all_indexes(path) do path = String.to_charlist(path) + case :zip.list_dir(path) do - {:ok, file_list} -> - indexes = file_list - |> Enum.filter(fn (file) -> - case file do - {:zip_file, filename, _, _, _, _} -> - filename |> to_string |> String.starts_with?("xl/worksheets/sheet") - _ -> - nil - end - end) - |> Enum.map(fn ({:zip_file, filename, _, _, _, _}) -> - index = filename - |> to_string - |> String.replace_prefix("xl/worksheets/sheet", "") - |> String.replace_suffix(".xml", "") - |> String.to_integer - index - 1 - end) - |> Enum.sort + {:ok, file_list} -> + indexes = + file_list + |> Enum.filter(fn file -> + case file do + {:zip_file, filename, _, _, _, _} -> + filename |> to_string |> String.starts_with?("xl/worksheets/sheet") + + _ -> + nil + end + end) + |> Enum.map(fn {:zip_file, filename, _, _, _, _} -> + index = + filename + |> to_string + |> String.replace_prefix("xl/worksheets/sheet", "") + |> String.replace_suffix(".xml", "") + |> String.to_integer() + + index - 1 + end) + |> Enum.sort() + {:ok, indexes} - {:error, _reason} -> {:error, @filetype_error} + + {:error, _reason} -> + {:error, @filetype_error} end end defp valid_extract_request?(path, index) do case :zip.list_dir(path) do - {:ok, file_list} -> search_file_list(file_list, index) + {:ok, file_list} -> search_file_list(file_list, index) {:error, _reason} -> {:error, @filetype_error} end end defp search_file_list(file_list, index) do - sheet = 'xl/worksheets/sheet#{index + 1}.xml' - results = file_list - |> Enum.map(fn file -> - case file do - {:zip_file, ^sheet, _, _, _, _} -> :ok - _ -> nil - end - end) + sheet = ~c"xl/worksheets/sheet#{index + 1}.xml" + + results = + file_list + |> Enum.map(fn file -> + case file do + {:zip_file, ^sheet, _, _, _, _} -> :ok + _ -> nil + end + end) if Enum.member?(results, :ok) do :ok @@ -131,7 +140,7 @@ defmodule Xlsxir.Unzip do An example file named `test.zip` located in './test_data/test' containing a single file named `test.txt`: iex> path = "./test/test_data/test.zip" - iex> file_list = ['test.txt'] + iex> file_list = [~c'test.txt'] iex> Xlsxir.Unzip.extract_xml(file_list, path, :memory) {:ok, [%Xlsxir.XmlFile{content: "test_successful", name: "test.txt", path: nil}]} iex> Xlsxir.Unzip.extract_xml(file_list, path, {:file, "temp/"}) @@ -144,14 +153,17 @@ defmodule Xlsxir.Unzip do |> to_charlist |> extract_from_zip(file_list, to) |> case do - {:error, reason} -> {:error, reason} - {:ok, []} -> {:error, @xml_not_found_error} - {:ok, files_list} -> {:ok, build_xml_files(files_list)} - end + {:error, reason} -> {:error, reason} + {:ok, []} -> {:error, @xml_not_found_error} + {:ok, files_list} -> {:ok, build_xml_files(files_list)} + end end - defp extract_from_zip(path, file_list, :memory), do: :zip.extract(path, [{:file_list, file_list}, :memory]) - defp extract_from_zip(path, file_list, {:file, dest_path}), do: :zip.extract(path, [{:file_list, file_list}, {:cwd, dest_path}]) + defp extract_from_zip(path, file_list, :memory), + do: :zip.extract(path, [{:file_list, file_list}, :memory]) + + defp extract_from_zip(path, file_list, {:file, dest_path}), + do: :zip.extract(path, [{:file_list, file_list}, {:cwd, dest_path}]) defp build_xml_files(files_list) do files_list @@ -165,6 +177,6 @@ defmodule Xlsxir.Unzip do # When extracting to temp file defp build_xml_file(file_path) do - %XmlFile{name: Path.basename(file_path), path: to_string(file_path)} + %XmlFile{name: Path.basename(file_path), path: to_string(file_path)} end end diff --git a/lib/xlsxir/xlsx_file.ex b/lib/xlsxir/xlsx_file.ex index ac2f9a4..863dde0 100644 --- a/lib/xlsxir/xlsx_file.ex +++ b/lib/xlsxir/xlsx_file.ex @@ -115,7 +115,7 @@ defmodule Xlsxir.XlsxFile do defp fill_empty_cells_at_end(tid, end_column, index) when is_integer(index) do build_and_replace(tid, end_column, index) - nex_index= :ets.next(tid, index) + nex_index = :ets.next(tid, index) fill_empty_cells_at_end(tid, end_column, nex_index) end @@ -133,7 +133,7 @@ defmodule Xlsxir.XlsxFile do empty_cells = Xlsxir.ParseWorksheet.fill_empty_cells(from, to, index, []) new_cells = cells ++ empty_cells - true = :ets.insert(tid, {index, new_cells}) + true = :ets.insert(tid, {index, new_cells}) end @doc """ @@ -256,8 +256,8 @@ defmodule Xlsxir.XlsxFile do defp zip_paths_list(worksheet_indexes) do worksheet_indexes - |> Enum.map(fn worksheet_index -> 'xl/worksheets/sheet#{worksheet_index + 1}.xml' end) - |> Enum.concat(['xl/styles.xml', 'xl/sharedStrings.xml', 'xl/workbook.xml']) + |> Enum.map(fn worksheet_index -> ~c"xl/worksheets/sheet#{worksheet_index + 1}.xml" end) + |> Enum.concat([~c"xl/styles.xml", ~c"xl/sharedStrings.xml", ~c"xl/workbook.xml"]) end defp parse_styles_to_ets(%__MODULE__{styles_xml_file: nil} = xlsx_file), do: xlsx_file diff --git a/lib/xlsxir/xml_file.ex b/lib/xlsxir/xml_file.ex index 45bdb28..2ad31d3 100644 --- a/lib/xlsxir/xml_file.ex +++ b/lib/xlsxir/xml_file.ex @@ -5,7 +5,7 @@ defmodule Xlsxir.XmlFile do (located in the `path` field) """ - defstruct [name: nil, path: nil, content: nil] + defstruct name: nil, path: nil, content: nil @doc """ Open an XmlFile diff --git a/mix.exs b/mix.exs index dbac462..b2e364c 100644 --- a/mix.exs +++ b/mix.exs @@ -3,17 +3,17 @@ defmodule Xlsxir.Mixfile do def project do [ - app: :xlsxir, - version: "1.6.4", - name: "Xlsxir", - source_url: "https://github.com/jsonkenl/xlsxir", - elixir: "~> 1.4", - build_embedded: Mix.env == :prod, - start_permanent: Mix.env == :prod, - description: description(), - package: package(), - deps: deps(), - docs: [main: "overview", extras: ["CHANGELOG.md", "NUMBER_STYLES.md", "OVERVIEW.md"]] + app: :xlsxir, + version: "1.6.4", + name: "Xlsxir", + source_url: "https://github.com/jsonkenl/xlsxir", + elixir: "~> 1.4", + build_embedded: Mix.env() == :prod, + start_permanent: Mix.env() == :prod, + description: description(), + package: package(), + deps: deps(), + docs: [main: "overview", extras: ["CHANGELOG.md", "NUMBER_STYLES.md", "OVERVIEW.md"]] ] end @@ -27,7 +27,7 @@ defmodule Xlsxir.Mixfile do defp deps do [ {:ex_doc, "~> 0.19", only: :dev, runtime: false}, - #{:earmark, github: "pragdave/earmark", override: true, only: :dev}, + # {:earmark, github: "pragdave/earmark", override: true, only: :dev}, {:erlsom, "~> 1.5"} ] end @@ -43,10 +43,9 @@ defmodule Xlsxir.Mixfile do maintainers: ["Jason Kennell"], licenses: ["MIT License"], links: %{ - "Github" => "https://github.com/jsonkenl/xlsxir", - "Change Log" => "https://hexdocs.pm/xlsxir/changelog.html" - } + "Github" => "https://github.com/jsonkenl/xlsxir", + "Change Log" => "https://hexdocs.pm/xlsxir/changelog.html" + } ] end - end diff --git a/mix.lock b/mix.lock index 435e078..5c8fcd9 100644 --- a/mix.lock +++ b/mix.lock @@ -1,8 +1,8 @@ %{ - "earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm"}, - "erlsom": {:hex, :erlsom, "1.5.0", "c5a5cdd0ee0e8dca62bcc4b13ff08da24fdefc16ccd8b25282a2fda2ba1be24a", [:rebar3], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.19.3", "3c7b0f02851f5fc13b040e8e925051452e41248f685e40250d7e40b07b9f8c10", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, - "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, + "earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm", "000aaeff08919e95e7aea13e4af7b2b9734577b3e6a7c50ee31ee88cab6ec4fb"}, + "erlsom": {:hex, :erlsom, "1.5.0", "c5a5cdd0ee0e8dca62bcc4b13ff08da24fdefc16ccd8b25282a2fda2ba1be24a", [:rebar3], [], "hexpm", "55a9dbf9cfa77fcfc108bd8e2c4f9f784dea228a8f4b06ea10b684944946955a"}, + "ex_doc": {:hex, :ex_doc, "0.19.3", "3c7b0f02851f5fc13b040e8e925051452e41248f685e40250d7e40b07b9f8c10", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0e11d67e662142fc3945b0ee410c73c8c956717fbeae4ad954b418747c734973"}, + "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5fbc8e549aa9afeea2847c0769e3970537ed302f93a23ac612602e805d9d1e7f"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "adf0218695e22caeda2820eaba703fa46c91820d53813a2223413da3ef4ba515"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm", "5c040b8469c1ff1b10093d3186e2e10dbe483cd73d79ec017993fb3985b8a9b3"}, } diff --git a/test/convert_date_test.exs b/test/convert_date_test.exs index 942a3b2..5f532e3 100644 --- a/test/convert_date_test.exs +++ b/test/convert_date_test.exs @@ -4,22 +4,36 @@ defmodule ConvertDateTest do import Xlsxir.ConvertDate - def test_one_data(), do: ['42005', '42036', '42064', '42095', '42125', '42156', '42186', '42217', '42248', '42278', '42309', '42339'] + def test_one_data(), + do: [ + ~c"42005", + ~c"42036", + ~c"42064", + ~c"42095", + ~c"42125", + ~c"42156", + ~c"42186", + ~c"42217", + ~c"42248", + ~c"42278", + ~c"42309", + ~c"42339" + ] - def test_one_results() do + def test_one_results() do [ - {2015,1,1}, - {2015,2,1}, - {2015,3,1}, - {2015,4,1}, - {2015,5,1}, - {2015,6,1}, - {2015,7,1}, - {2015,8,1}, - {2015,9,1}, - {2015,10,1}, - {2015,11,1}, - {2015,12,1} + {2015, 1, 1}, + {2015, 2, 1}, + {2015, 3, 1}, + {2015, 4, 1}, + {2015, 5, 1}, + {2015, 6, 1}, + {2015, 7, 1}, + {2015, 8, 1}, + {2015, 9, 1}, + {2015, 10, 1}, + {2015, 11, 1}, + {2015, 12, 1} ] end @@ -27,23 +41,38 @@ defmodule ConvertDateTest do assert Enum.map(test_one_data(), &from_serial/1) == test_one_results() end - def test_two_data(), do: ['42035', '42063', '42094', '42124', '42155', '42185', '42216', '42247', '42277', '42308', '42338', '42369', '44530'] + def test_two_data(), + do: [ + ~c"42035", + ~c"42063", + ~c"42094", + ~c"42124", + ~c"42155", + ~c"42185", + ~c"42216", + ~c"42247", + ~c"42277", + ~c"42308", + ~c"42338", + ~c"42369", + ~c"44530" + ] - def test_two_results() do + def test_two_results() do [ - {2015,1,31}, - {2015,2,28}, - {2015,3,31}, - {2015,4,30}, - {2015,5,31}, - {2015,6,30}, - {2015,7,31}, - {2015,8,31}, - {2015,9,30}, - {2015,10,31}, - {2015,11,30}, - {2015,12,31}, - {2021,11,30} + {2015, 1, 31}, + {2015, 2, 28}, + {2015, 3, 31}, + {2015, 4, 30}, + {2015, 5, 31}, + {2015, 6, 30}, + {2015, 7, 31}, + {2015, 8, 31}, + {2015, 9, 30}, + {2015, 10, 31}, + {2015, 11, 30}, + {2015, 12, 31}, + {2021, 11, 30} ] end @@ -51,22 +80,36 @@ defmodule ConvertDateTest do assert Enum.map(test_two_data(), &from_serial/1) == test_two_results() end - def test_three_data(), do: ['42019', '42050', '42078', '42109', '42139', '42170', '42200', '42231', '42262', '42292', '42323', '42353'] + def test_three_data(), + do: [ + ~c"42019", + ~c"42050", + ~c"42078", + ~c"42109", + ~c"42139", + ~c"42170", + ~c"42200", + ~c"42231", + ~c"42262", + ~c"42292", + ~c"42323", + ~c"42353" + ] - def test_three_results() do + def test_three_results() do [ - {2015,1,15}, - {2015,2,15}, - {2015,3,15}, - {2015,4,15}, - {2015,5,15}, - {2015,6,15}, - {2015,7,15}, - {2015,8,15}, - {2015,9,15}, - {2015,10,15}, - {2015,11,15}, - {2015,12,15} + {2015, 1, 15}, + {2015, 2, 15}, + {2015, 3, 15}, + {2015, 4, 15}, + {2015, 5, 15}, + {2015, 6, 15}, + {2015, 7, 15}, + {2015, 8, 15}, + {2015, 9, 15}, + {2015, 10, 15}, + {2015, 11, 15}, + {2015, 12, 15} ] end @@ -74,22 +117,36 @@ defmodule ConvertDateTest do assert Enum.map(test_three_data(), &from_serial/1) == test_three_results() end - def test_four_data(), do: ['42370', '42401', '42430', '42461', '42491', '42522', '42552', '42583', '42614', '42644', '42675', '42705'] + def test_four_data(), + do: [ + ~c"42370", + ~c"42401", + ~c"42430", + ~c"42461", + ~c"42491", + ~c"42522", + ~c"42552", + ~c"42583", + ~c"42614", + ~c"42644", + ~c"42675", + ~c"42705" + ] - def test_four_results() do + def test_four_results() do [ - {2016,1,1}, - {2016,2,1}, - {2016,3,1}, - {2016,4,1}, - {2016,5,1}, - {2016,6,1}, - {2016,7,1}, - {2016,8,1}, - {2016,9,1}, - {2016,10,1}, - {2016,11,1}, - {2016,12,1} + {2016, 1, 1}, + {2016, 2, 1}, + {2016, 3, 1}, + {2016, 4, 1}, + {2016, 5, 1}, + {2016, 6, 1}, + {2016, 7, 1}, + {2016, 8, 1}, + {2016, 9, 1}, + {2016, 10, 1}, + {2016, 11, 1}, + {2016, 12, 1} ] end @@ -97,22 +154,36 @@ defmodule ConvertDateTest do assert Enum.map(test_four_data(), &from_serial/1) == test_four_results() end - def test_five_data(), do: ['42400', '42429', '42460', '42490', '42521', '42551', '42582', '42613', '42643', '42674', '42704', '42735'] + def test_five_data(), + do: [ + ~c"42400", + ~c"42429", + ~c"42460", + ~c"42490", + ~c"42521", + ~c"42551", + ~c"42582", + ~c"42613", + ~c"42643", + ~c"42674", + ~c"42704", + ~c"42735" + ] - def test_five_results() do + def test_five_results() do [ - {2016,1,31}, - {2016,2,29}, - {2016,3,31}, - {2016,4,30}, - {2016,5,31}, - {2016,6,30}, - {2016,7,31}, - {2016,8,31}, - {2016,9,30}, - {2016,10,31}, - {2016,11,30}, - {2016,12,31} + {2016, 1, 31}, + {2016, 2, 29}, + {2016, 3, 31}, + {2016, 4, 30}, + {2016, 5, 31}, + {2016, 6, 30}, + {2016, 7, 31}, + {2016, 8, 31}, + {2016, 9, 30}, + {2016, 10, 31}, + {2016, 11, 30}, + {2016, 12, 31} ] end @@ -120,27 +191,40 @@ defmodule ConvertDateTest do assert Enum.map(test_five_data(), &from_serial/1) == test_five_results() end - def test_six_data(), do: ['42384', '42415', '42444', '42475', '42505', '42536', '42566', '42597', '42628', '42658', '42689', '42719'] + def test_six_data(), + do: [ + ~c"42384", + ~c"42415", + ~c"42444", + ~c"42475", + ~c"42505", + ~c"42536", + ~c"42566", + ~c"42597", + ~c"42628", + ~c"42658", + ~c"42689", + ~c"42719" + ] - def test_six_results() do + def test_six_results() do [ - {2016,1,15}, - {2016,2,15}, - {2016,3,15}, - {2016,4,15}, - {2016,5,15}, - {2016,6,15}, - {2016,7,15}, - {2016,8,15}, - {2016,9,15}, - {2016,10,15}, - {2016,11,15}, - {2016,12,15} + {2016, 1, 15}, + {2016, 2, 15}, + {2016, 3, 15}, + {2016, 4, 15}, + {2016, 5, 15}, + {2016, 6, 15}, + {2016, 7, 15}, + {2016, 8, 15}, + {2016, 9, 15}, + {2016, 10, 15}, + {2016, 11, 15}, + {2016, 12, 15} ] end test "middle of every month in leap year (2016)" do assert Enum.map(test_six_data(), &from_serial/1) == test_six_results() end - end diff --git a/test/convert_time_test.exs b/test/convert_time_test.exs index 1bd3843..883f9fb 100644 --- a/test/convert_time_test.exs +++ b/test/convert_time_test.exs @@ -4,13 +4,14 @@ defmodule ConvertTimeTest do import Xlsxir.ConvertDateTime - @test_data %{'0.0' => {0, 0 , 0}, - '0.25' => {6, 0, 0}, - '0.5' => {12, 0, 0}, - '0.29166666666666669' => {7, 0, 0}, - '0.64583333333333337' => {15, 30, 0}, - '0.754'=> {18, 5, 45}} - + @test_data %{ + ~c"0.0" => {0, 0, 0}, + ~c"0.25" => {6, 0, 0}, + ~c"0.5" => {12, 0, 0}, + ~c"0.29166666666666669" => {7, 0, 0}, + ~c"0.64583333333333337" => {15, 30, 0}, + ~c"0.754" => {18, 5, 45} + } test "converts fractions to the appropriate numbers" do for {input, expected} <- @test_data do @@ -19,6 +20,6 @@ defmodule ConvertTimeTest do end test "accepts a single 0 as a valid float value" do - assert from_charlist('0') == {0, 0, 0} + assert from_charlist(~c"0") == {0, 0, 0} end end diff --git a/test/doc_test.exs b/test/doc_test.exs index fd6e87b..2489069 100644 --- a/test/doc_test.exs +++ b/test/doc_test.exs @@ -5,4 +5,4 @@ defmodule DocTest do doctest Xlsxir.ConvertDate doctest Xlsxir.SaxParser doctest Xlsxir.XlsxFile -end \ No newline at end of file +end diff --git a/test/sax_parser_test.exs b/test/sax_parser_test.exs index 64ce90c..35b6bfb 100644 --- a/test/sax_parser_test.exs +++ b/test/sax_parser_test.exs @@ -14,7 +14,7 @@ defmodule SaxParserTest do defp find_string(tid, index) do :ets.lookup(tid, index) - |> List.first + |> List.first() |> elem(1) end end diff --git a/test/stream_test.exs b/test/stream_test.exs index 7c57abe..3c33b4d 100644 --- a/test/stream_test.exs +++ b/test/stream_test.exs @@ -8,17 +8,17 @@ defmodule StreamTest do test "produces a stream" do s = stream_list(path(), 8) assert %Stream{} = s - assert 51 == s |> Enum.map(&(&1)) |> length + assert 51 == s |> Enum.map(& &1) |> length end test "stream can run multiple times" do s = stream_list(path(), 8) assert %Stream{} = s # First run should proceed normally - assert {:ok, _} = Task.yield( Task.async( fn() -> s |> Stream.run() end ), 2000) + assert {:ok, _} = Task.yield(Task.async(fn -> s |> Stream.run() end), 2000) # second run will hang on missing fs resources (before fix) and hang (default 60s) - assert {:ok, _} = Task.yield( Task.async( fn() -> s |> Stream.run() end ), 2000) + assert {:ok, _} = Task.yield(Task.async(fn -> s |> Stream.run() end), 2000) # third run because reasons - assert {:ok, _} = Task.yield( Task.async( fn() -> s |> Stream.run() end ), 2000) + assert {:ok, _} = Task.yield(Task.async(fn -> s |> Stream.run() end), 2000) end end diff --git a/test/test_data/sheet_names.xlsx b/test/test_data/sheet_names.xlsx new file mode 100644 index 0000000..7bb9309 Binary files /dev/null and b/test/test_data/sheet_names.xlsx differ diff --git a/test/xlsxir_test.exs b/test/xlsxir_test.exs index 3090e76..786a48d 100644 --- a/test/xlsxir_test.exs +++ b/test/xlsxir_test.exs @@ -81,6 +81,20 @@ defmodule XlsxirTest do assert [["string one", "string two", 10, 20, {2016, 1, 1}]] == get_list(tid) end + test "able to parse sheet names correctly" do + res = multi_extract("./test/test_data/sheet_names.xlsx") + assert length(res) == 10 + assert res |> Keyword.keys() |> Enum.all?(&(&1 == :ok)) + + sheet_names = + res + |> Keyword.values() + |> Enum.map(fn ets_id -> get_multi_info(ets_id, :name) end) + + refute Enum.any?(sheet_names, &(&1 == nil)) + assert Enum.all?(sheet_names, &String.starts_with?(&1, "List")) + end + def error_cell_path(), do: "./test/test_data/error-date.xlsx" test "error cells can be parsed properly1" do diff --git a/test/xml_file_test.exs b/test/xml_file_test.exs index c66bbff..08b4419 100644 --- a/test/xml_file_test.exs +++ b/test/xml_file_test.exs @@ -4,15 +4,18 @@ defmodule XmlFileTest do def no_shared_path(), do: "./test/test_data/noShared.xlsx" test "open memory XmlFile" do - assert {:ok, _file_pid} = Xlsxir.XmlFile.open(%Xlsxir.XmlFile{content: File.read!("./test/test_data/test/xl/styles.xml")}) + assert {:ok, _file_pid} = + Xlsxir.XmlFile.open(%Xlsxir.XmlFile{ + content: File.read!("./test/test_data/test/xl/styles.xml") + }) end test "open filepath XmlFile" do - assert {:ok, _file_pid} = Xlsxir.XmlFile.open(%Xlsxir.XmlFile{path: "./test/test_data/test/xl/styles.xml"}) + assert {:ok, _file_pid} = + Xlsxir.XmlFile.open(%Xlsxir.XmlFile{path: "./test/test_data/test/xl/styles.xml"}) end test "parses xlsx without sharedStings and styles" do - # here is a spec which sayeth there shalt always be shared strings: # # https://msdn.microsoft.com/en-us/library/office/gg278314.aspx @@ -26,7 +29,6 @@ defmodule XmlFileTest do s = Xlsxir.stream_list(no_shared_path(), 0) assert %Stream{} = s - assert 3 == s |> Enum.map(&(&1)) |> length + assert 3 == s |> Enum.map(& &1) |> length end - end