From 76c4d7fd14473b81f8085b423c38647f0192ded1 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 27 Feb 2019 05:15:08 +0700 Subject: [PATCH 1/3] Replace sheet.col_widths number field, with sheet.cols map field The cols field in sheet is a map, which can contain a width value as well as other values e.g. bg_color, numfmt etc. --- lib/elixlsx/sheet.ex | 29 ++++++++++++++++++++---- lib/elixlsx/xml_templates.ex | 43 +++++++++++++++++++++++------------- test/sheet_test.exs | 22 ++++++++++++++++++ test/xml_templates_test.exs | 26 ++++++++++++++++++++++ 4 files changed, 101 insertions(+), 19 deletions(-) create mode 100644 test/sheet_test.exs create mode 100644 test/xml_templates_test.exs diff --git a/lib/elixlsx/sheet.ex b/lib/elixlsx/sheet.ex index f0b265b..1a683ea 100644 --- a/lib/elixlsx/sheet.ex +++ b/lib/elixlsx/sheet.ex @@ -16,11 +16,11 @@ defmodule Elixlsx.Sheet do The property list describes formatting options for that cell. See Font.from_props/1 for a list of options. """ - defstruct name: "", rows: [], col_widths: %{}, row_heights: %{}, merge_cells: [], pane_freeze: nil, show_grid_lines: true + defstruct name: "", rows: [], cols: %{}, row_heights: %{}, merge_cells: [], pane_freeze: nil, show_grid_lines: true @type t :: %Sheet { name: String.t, rows: list(list(any())), - col_widths: %{pos_integer => number}, + cols: %{pos_integer => map}, row_heights: %{pos_integer => number}, merge_cells: [], pane_freeze: {number, number} | nil, @@ -121,14 +121,35 @@ defmodule Elixlsx.Sheet do end end + @spec set_col(Sheet.t, String.t, Keyword.t) :: Sheet.t + @doc ~S""" + Set various attributes for a given column. Column is indexed by + name ("A", ...) + """ + def set_col(sheet, column, [{k, v}]) do + index = Util.decode_col(column) + cols = Map.update(sheet.cols, index, %{k => v}, (&Map.put &1, k, v)) + Map.put(sheet, :cols, cols) + end + + def set_col(sheet, column, opts) do + index = Util.decode_col(column) + + cols = + Enum.reduce(opts, sheet.cols, fn {k, v}, acc -> + Map.update(acc, index, %{k => v}, &Map.put(&1, k, v)) + end) + + Map.put(sheet, :cols, cols) + end + @spec set_col_width(Sheet.t, String.t, number) :: Sheet.t @doc ~S""" Set the column width for a given column. Column is indexed by name ("A", ...) """ def set_col_width(sheet, column, width) do - update_in sheet.col_widths, - &(Map.put &1, Util.decode_col(column), width) + set_col(sheet, column, width: width) end @spec set_row_height(Sheet.t, number, number) :: Sheet.t diff --git a/lib/elixlsx/xml_templates.ex b/lib/elixlsx/xml_templates.ex index 79c59eb..0b65d88 100644 --- a/lib/elixlsx/xml_templates.ex +++ b/lib/elixlsx/xml_templates.ex @@ -272,20 +272,33 @@ defmodule Elixlsx.XMLTemplates do end end - defp make_col_width({k, v}) do - '' - end - - defp make_col_widths(sheet) do - if Kernel.map_size(sheet.col_widths) != 0 do - cols = Map.to_list(sheet.col_widths) - |> Enum.sort - |> Enum.map_join(&make_col_width/1) - - "#{cols}" - else - "" - end + defp make_col_range(key) do + key_str = Integer.to_string(key) + [" min=\"", key_str, "\" max=\"", key_str, "\""] + end + + defp make_col_width(%{width: width}) do + [" width=\"", Integer.to_string(width), "\" customWidth=\"1\""] + end + defp make_col_width(_), do: [] + + defp make_cols(%{cols: cols}) when cols == %{}, do: "" + defp make_cols(%{cols: cols}) do + [ + "", + Map.to_list(cols) + |> Enum.sort() + |> Enum.map(fn {key, props} -> [ + "" + ] + end), + "" + ] + # remove to output as iolist instead of string + |> Enum.join() end @spec make_sheet(Sheet.t, WorkbookCompInfo.t) :: String.t @@ -313,7 +326,7 @@ defmodule Elixlsx.XMLTemplates do """ - <> make_col_widths(sheet) <> + <> make_cols(sheet) <> """ """ diff --git a/test/sheet_test.exs b/test/sheet_test.exs new file mode 100644 index 0000000..51a1603 --- /dev/null +++ b/test/sheet_test.exs @@ -0,0 +1,22 @@ +defmodule Elixlsx.SheetTest do + use ExUnit.Case, async: false + use ExCheck + + alias Elixlsx.Sheet + + property :sheet_name do + for_all x in binary() do + Sheet.with_name(x).name == x + end + end + + property :sheet_cols do + for_all x in choose(65,90) do + sheet = + %Sheet{} + |> Sheet.set_col_width(<>, 10) + |> Sheet.set_col(<>, bg_color: "#FFFF00", numfmt: "mmm-yyyy") + sheet.cols[x - 64] == %{bg_color: "#FFFF00", numfmt: "mmm-yyyy", width: 10} + end + end +end diff --git a/test/xml_templates_test.exs b/test/xml_templates_test.exs new file mode 100644 index 0000000..b06b36c --- /dev/null +++ b/test/xml_templates_test.exs @@ -0,0 +1,26 @@ +defmodule Elixlsx.XMLTemplatesTest do + use ExUnit.Case + + alias Elixlsx.Compiler.WorkbookCompInfo + alias Elixlsx.Sheet + alias Elixlsx.XMLTemplates + + describe("make_sheet") do + setup do + [sheet: Sheet.with_name("Sheet1"), wci: %WorkbookCompInfo{}] + end + + test "with default values", %{sheet: sheet, wci: wci} do + assert XMLTemplates.make_sheet(sheet, wci) =~ "" + end + + test "with column widths set", %{sheet: sheet, wci: wci} do + xml = sheet + |> Sheet.set_col_width("A", 10) + |> Sheet.set_col_width("B", 12) + |> XMLTemplates.make_sheet(wci) + assert xml =~ "" + assert xml =~ "" + end + end +end From 6aa1cc43943ad8d1300f8c95b8d9054db0f96039 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 27 Feb 2019 06:12:44 +0700 Subject: [PATCH 2/3] Compiler registers col styles and xml_templates includes style id The compiler registers styles based on properties stored in sheet.cols. The registration method is the same as registering cell styles. The xml template generator looks up the style id and includes in the xml definition for a column. --- lib/elixlsx/compiler.ex | 19 ++++++++++++++++++- lib/elixlsx/xml_templates.ex | 14 +++++++++++--- test/sheet_test.exs | 4 ++-- test/xml_templates_test.exs | 17 ++++++++--------- 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/lib/elixlsx/compiler.ex b/lib/elixlsx/compiler.ex index 397059b..f523b47 100644 --- a/lib/elixlsx/compiler.ex +++ b/lib/elixlsx/compiler.ex @@ -52,6 +52,21 @@ defmodule Elixlsx.Compiler do end end + def compinfo_col_pass_style(wci, %{width: w} = props) when props == %{width: w}, do: wci + + def compinfo_col_pass_style wci, props do + update_in wci.cellstyledb, + &CellStyleDB.register_style(&1, + Elixlsx.Style.CellStyle.from_props(props)) + end + + @spec compinfo_from_cols(WorkbookCompInfo.t, list(list(any()))) :: WorkbookCompInfo.t + def compinfo_from_cols wci, cols do + Enum.sort(cols) + |> List.foldl(wci, fn ({_, props}, wci) -> + compinfo_col_pass_style(wci, props) + end) + end @spec compinfo_from_rows(WorkbookCompInfo.t, list(list(any()))) :: WorkbookCompInfo.t def compinfo_from_rows wci, rows do @@ -65,7 +80,9 @@ defmodule Elixlsx.Compiler do @spec compinfo_from_sheets(WorkbookCompInfo.t, list(Sheet.t)) :: WorkbookCompInfo.t def compinfo_from_sheets wci, sheets do List.foldl sheets, wci, fn (sheet, wci) -> - compinfo_from_rows wci, sheet.rows + wci + |> compinfo_from_rows(sheet.rows) + |> compinfo_from_cols(sheet.cols) end end diff --git a/lib/elixlsx/xml_templates.ex b/lib/elixlsx/xml_templates.ex index 0b65d88..1c4fcf8 100644 --- a/lib/elixlsx/xml_templates.ex +++ b/lib/elixlsx/xml_templates.ex @@ -277,13 +277,20 @@ defmodule Elixlsx.XMLTemplates do [" min=\"", key_str, "\" max=\"", key_str, "\""] end + defp make_col_style(props, _) when props == %{}, do: [] + defp make_col_style(%{width: w} = props, _) when props == %{width: w}, do: [] + defp make_col_style(props, wci) do + style_id = CellStyleDB.get_id(wci.cellstyledb, CellStyle.from_props(props)) + [" style=\"", Integer.to_string(style_id), "\""] + end + defp make_col_width(%{width: width}) do [" width=\"", Integer.to_string(width), "\" customWidth=\"1\""] end defp make_col_width(_), do: [] - defp make_cols(%{cols: cols}) when cols == %{}, do: "" - defp make_cols(%{cols: cols}) do + defp make_cols(%{cols: cols}, _) when cols == %{}, do: "" + defp make_cols(%{cols: cols}, wci) do [ "", Map.to_list(cols) @@ -292,6 +299,7 @@ defmodule Elixlsx.XMLTemplates do "" ] end), @@ -326,7 +334,7 @@ defmodule Elixlsx.XMLTemplates do """ - <> make_cols(sheet) <> + <> make_cols(sheet, wci) <> """ """ diff --git a/test/sheet_test.exs b/test/sheet_test.exs index 51a1603..010278f 100644 --- a/test/sheet_test.exs +++ b/test/sheet_test.exs @@ -15,8 +15,8 @@ defmodule Elixlsx.SheetTest do sheet = %Sheet{} |> Sheet.set_col_width(<>, 10) - |> Sheet.set_col(<>, bg_color: "#FFFF00", numfmt: "mmm-yyyy") - sheet.cols[x - 64] == %{bg_color: "#FFFF00", numfmt: "mmm-yyyy", width: 10} + |> Sheet.set_col(<>, bg_color: "#FFFF00", num_format: "mmm-yyyy") + sheet.cols[x - 64] == %{bg_color: "#FFFF00", num_format: "mmm-yyyy", width: 10} end end end diff --git a/test/xml_templates_test.exs b/test/xml_templates_test.exs index b06b36c..b123ea9 100644 --- a/test/xml_templates_test.exs +++ b/test/xml_templates_test.exs @@ -1,26 +1,25 @@ defmodule Elixlsx.XMLTemplatesTest do use ExUnit.Case - alias Elixlsx.Compiler.WorkbookCompInfo - alias Elixlsx.Sheet - alias Elixlsx.XMLTemplates + alias Elixlsx.{Compiler, Compiler.WorkbookCompInfo, Sheet, Workbook, XMLTemplates} describe("make_sheet") do setup do - [sheet: Sheet.with_name("Sheet1"), wci: %WorkbookCompInfo{}] + [sheet: Sheet.with_name("Sheet1"), wci: %WorkbookCompInfo{}, workbook: %Workbook{}] end test "with default values", %{sheet: sheet, wci: wci} do assert XMLTemplates.make_sheet(sheet, wci) =~ "" end - test "with column widths set", %{sheet: sheet, wci: wci} do - xml = sheet + test "with column attrs set", %{sheet: sheet, workbook: workbook} do + sheet = sheet |> Sheet.set_col_width("A", 10) - |> Sheet.set_col_width("B", 12) - |> XMLTemplates.make_sheet(wci) + |> Sheet.set_col("B", bg_color: "#FFFF00", num_format: "mmm-yyyy", width: 12) + wci = Compiler.make_workbook_comp_info(Workbook.insert_sheet(workbook, sheet)) + xml = XMLTemplates.make_sheet(sheet, wci) assert xml =~ "" - assert xml =~ "" + assert xml =~ "" end end end From 291d9e46597ec72d3b0a30a3c20f317c71e3bc56 Mon Sep 17 00:00:00 2001 From: Ryan Hart Date: Wed, 27 Feb 2019 12:54:20 +0700 Subject: [PATCH 3/3] accept width as float or integer --- lib/elixlsx/xml_templates.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/elixlsx/xml_templates.ex b/lib/elixlsx/xml_templates.ex index 1c4fcf8..7156566 100644 --- a/lib/elixlsx/xml_templates.ex +++ b/lib/elixlsx/xml_templates.ex @@ -284,9 +284,12 @@ defmodule Elixlsx.XMLTemplates do [" style=\"", Integer.to_string(style_id), "\""] end - defp make_col_width(%{width: width}) do + defp make_col_width(%{width: width}) when is_integer(width) do [" width=\"", Integer.to_string(width), "\" customWidth=\"1\""] end + defp make_col_width(%{width: width}) when is_float(width) do + [" width=\"", Float.to_string(width), "\" customWidth=\"1\""] + end defp make_col_width(_), do: [] defp make_cols(%{cols: cols}, _) when cols == %{}, do: ""