From f8997562556bb6d1e32c2431b399e48ce9e33adb Mon Sep 17 00:00:00 2001 From: Milan Vit Date: Tue, 24 Oct 2023 14:55:06 +0900 Subject: [PATCH 1/8] Add 55 date style to recognized styles list Ref: https://hexdocs.pm/xlsxir/number_styles.html#content Ref: https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.numberingformat?view=openxml-2.8.1 --- lib/xlsxir/parse_style.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/xlsxir/parse_style.ex b/lib/xlsxir/parse_style.ex index d095d85..fac80a7 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: [] From 32f0491eba02f5ae496b71dc00c50bcbe13b000a Mon Sep 17 00:00:00 2001 From: Milan Vit Date: Wed, 25 Oct 2023 12:51:01 +0900 Subject: [PATCH 2/8] Store worksheet info in ETS by r:id rather than sheetId --- lib/xlsxir/parse_workbook.ex | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 From f4352180fb4b99fe8db79d887d4a050ec3ace299 Mon Sep 17 00:00:00 2001 From: Milan Vit Date: Thu, 26 Oct 2023 10:33:34 +0900 Subject: [PATCH 3/8] Add test for parsing sheet names --- mix.lock | 12 ++++++------ test/test_data/sheet_names.xlsx | Bin 0 -> 14356 bytes test/xlsxir_test.exs | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 test/test_data/sheet_names.xlsx 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/test_data/sheet_names.xlsx b/test/test_data/sheet_names.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..7bb9309072f8ebd65e2e44e8c1928d3ecb7c3b1d GIT binary patch literal 14356 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⪚lTfDvpqMW(PD` zm%S3ew+=#FKPm$U=I?^jewAe!w}Xiol@yi8TOpnFnrViCjmn0Gn&Xv4^KWk@EOYQh zIveUK&J#@eu#4?Fgz1GMaj`TwG1Fi@Id+XrBG_5E7gE?}LFe2^i&QiMRk#3t8cBcT zOwR5X$P{!(Ix`@jCn zA0yO{7e5|``?Z)Kl>GmudGjzD_gLleMAR=8AJ7cmZ&V)UqaLd~9+3H^Qjhvum4|Ve z$10DfC4Q+Kg1QR7`795!6OUCMx6Xg5P-6a8<)M-OSmkjW^_PkPX!7tkpXH&c`dH<0 zOYN6R8t!jZem2-1%RKI{{F3>M{~+_Y+wxf8ab@V2fDYM%z^^*d Keyword.keys() |> Enum.all?(&(&1 == :ok)) + + sheet_names = + res + |> Keyword.values() + |> Enum.map(fn ets_id -> get_multi_info(ets_id, :name) end) + + assert Enum.all?(sheet_names, &String.starts_with?(&1, "List")) + refute Enum.any?(sheet_names, &(&1 == nil)) + end + def error_cell_path(), do: "./test/test_data/error-date.xlsx" test "error cells can be parsed properly1" do From bb65104c0dfc368dd96debfcf0dc096a3e9d01f5 Mon Sep 17 00:00:00 2001 From: Milan Vit Date: Thu, 26 Oct 2023 10:34:57 +0900 Subject: [PATCH 4/8] Change order of assertions for better error messages --- test/xlsxir_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/xlsxir_test.exs b/test/xlsxir_test.exs index 3825c29..786a48d 100644 --- a/test/xlsxir_test.exs +++ b/test/xlsxir_test.exs @@ -91,8 +91,8 @@ defmodule XlsxirTest do |> Keyword.values() |> Enum.map(fn ets_id -> get_multi_info(ets_id, :name) end) - assert Enum.all?(sheet_names, &String.starts_with?(&1, "List")) 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" From c682551064fcb1933aaf163d677191efbdecd32d Mon Sep 17 00:00:00 2001 From: Adam Millerchip Date: Wed, 14 Aug 2024 16:47:51 +0900 Subject: [PATCH 5/8] Add formatter config and run mix format --- .formatter.exs | 3 + lib/xlsxir.ex | 1 - lib/xlsxir/convert_date.ex | 46 +++--- lib/xlsxir/convert_datetime.ex | 15 +- lib/xlsxir/parse_string.ex | 25 ++-- lib/xlsxir/parse_style.ex | 20 +-- lib/xlsxir/parse_worksheet.ex | 39 ++--- lib/xlsxir/stream_worksheet.ex | 2 +- lib/xlsxir/unzip.ex | 88 +++++++----- lib/xlsxir/xlsx_file.ex | 8 +- lib/xlsxir/xml_file.ex | 2 +- mix.exs | 31 ++-- test/convert_date_test.exs | 256 ++++++++++++++++++++++----------- test/convert_time_test.exs | 17 +-- test/doc_test.exs | 2 +- test/sax_parser_test.exs | 2 +- test/stream_test.exs | 8 +- test/xml_file_test.exs | 12 +- 18 files changed, 349 insertions(+), 228 deletions(-) create mode 100644 .formatter.exs 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/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..b4f4de4 100644 --- a/lib/xlsxir/convert_date.ex +++ b/lib/xlsxir/convert_date.ex @@ -18,20 +18,27 @@ defmodule Xlsxir.ConvertDate do {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..21e8838 100644 --- a/lib/xlsxir/convert_datetime.ex +++ b/lib/xlsxir/convert_datetime.ex @@ -17,15 +17,17 @@ defmodule Xlsxir.ConvertDateTime do iex> Xlsxir.ConvertDateTime.from_charlist('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 fac80a7..1f034c7 100644 --- a/lib/xlsxir/parse_style.ex +++ b/lib/xlsxir/parse_style.ex @@ -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_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..c86a9ed 100644 --- a/lib/xlsxir/unzip.ex +++ b/lib/xlsxir/unzip.ex @@ -1,5 +1,4 @@ defmodule Xlsxir.Unzip do - alias Xlsxir.XmlFile @moduledoc """ @@ -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 @@ -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/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/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 From 3cacd9dfa96b95a8767a8bff7b7fe33c9cd72bc4 Mon Sep 17 00:00:00 2001 From: Adam Millerchip Date: Wed, 14 Aug 2024 16:52:25 +0900 Subject: [PATCH 6/8] Replace '' with ~c'' in doctests --- lib/xlsxir/convert_date.ex | 2 +- lib/xlsxir/convert_datetime.ex | 2 +- lib/xlsxir/unzip.ex | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/xlsxir/convert_date.ex b/lib/xlsxir/convert_date.ex index b4f4de4..d31e7a4 100644 --- a/lib/xlsxir/convert_date.ex +++ b/lib/xlsxir/convert_date.ex @@ -14,7 +14,7 @@ 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 diff --git a/lib/xlsxir/convert_datetime.ex b/lib/xlsxir/convert_datetime.ex index 21e8838..c2208b6 100644 --- a/lib/xlsxir/convert_datetime.ex +++ b/lib/xlsxir/convert_datetime.ex @@ -14,7 +14,7 @@ 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(~c"0"), do: {0, 0, 0} diff --git a/lib/xlsxir/unzip.ex b/lib/xlsxir/unzip.ex index c86a9ed..4a46c6a 100644 --- a/lib/xlsxir/unzip.ex +++ b/lib/xlsxir/unzip.ex @@ -20,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) @@ -140,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/"}) From 48c2eb8f441036345554b81bcac5801ec135e81c Mon Sep 17 00:00:00 2001 From: Adam Millerchip Date: Wed, 14 Aug 2024 16:52:58 +0900 Subject: [PATCH 7/8] Replace use Mix.Config with import Config --- config/config.exs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) 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 From dc0a3278b9949d6cbb5f87bf051d22dc3f44150d Mon Sep 17 00:00:00 2001 From: Adam Millerchip Date: Wed, 14 Aug 2024 18:21:15 +0900 Subject: [PATCH 8/8] Add CI (#3) --- .github/workflows/tests.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/tests.yml 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