diff --git a/example.exs b/example.exs index c53cde1..25aa1a7 100755 --- a/example.exs +++ b/example.exs @@ -136,6 +136,16 @@ sheet6 = # nest further |> Sheet.group_cols("C", "D") +# Sheets can be protected. Cells are locked by default but can be set to be unlocked +# Here, A1 is editable while the sheet is protected, but B1 is not +sheet7 = + %Sheet{ + name: "Protected", + protected: true + } + |> Sheet.set_cell("A1", "Can be edited", locked: false) + |> Sheet.set_cell("B1", "Cannot be edited") + Workbook.append_sheet(workbook, sheet4) |> Workbook.append_sheet(sheet5) |> Workbook.append_sheet(sheet6) diff --git a/lib/elixlsx/sheet.ex b/lib/elixlsx/sheet.ex index a426991..b2a5595 100644 --- a/lib/elixlsx/sheet.ex +++ b/lib/elixlsx/sheet.ex @@ -25,7 +25,8 @@ defmodule Elixlsx.Sheet do group_rows: [], merge_cells: [], pane_freeze: nil, - show_grid_lines: true + show_grid_lines: true, + protected: false @type t :: %Sheet{ name: String.t(), @@ -36,7 +37,8 @@ defmodule Elixlsx.Sheet do group_rows: list(rowcol_group), merge_cells: [{String.t(), String.t()}], pane_freeze: {number, number} | nil, - show_grid_lines: boolean() + show_grid_lines: boolean(), + protected: boolean() } @type rowcol_group :: Range.t() | {Range.t(), opts :: keyword} diff --git a/lib/elixlsx/style/cell_style.ex b/lib/elixlsx/style/cell_style.ex index 88a6b99..d184b45 100644 --- a/lib/elixlsx/style/cell_style.ex +++ b/lib/elixlsx/style/cell_style.ex @@ -4,14 +4,16 @@ defmodule Elixlsx.Style.CellStyle do alias Elixlsx.Style.Font alias Elixlsx.Style.Fill alias Elixlsx.Style.BorderStyle + alias Elixlsx.Style.Protection - defstruct font: nil, fill: nil, numfmt: nil, border: nil + defstruct font: nil, fill: nil, numfmt: nil, border: nil, protection: nil @type t :: %CellStyle{ font: Font.t(), fill: Fill.t(), numfmt: NumFmt.t(), - border: BorderStyle.t() + border: BorderStyle.t(), + protection: Protection.t() } def from_props(props) do @@ -19,8 +21,9 @@ defmodule Elixlsx.Style.CellStyle do fill = Fill.from_props(props) numfmt = NumFmt.from_props(props) border = BorderStyle.from_props(props[:border]) + protection = Protection.from_props(props) - %CellStyle{font: font, fill: fill, numfmt: numfmt, border: border} + %CellStyle{font: font, fill: fill, numfmt: numfmt, border: border, protection: protection} end def is_date?(cellstyle) do diff --git a/lib/elixlsx/style/protection.ex b/lib/elixlsx/style/protection.ex new file mode 100644 index 0000000..5e42d35 --- /dev/null +++ b/lib/elixlsx/style/protection.ex @@ -0,0 +1,25 @@ +defmodule Elixlsx.Style.Protection do + @moduledoc ~S""" + Protection properties. + + Supported formatting properties are: + + - locked: boolean + """ + alias __MODULE__ + + defstruct locked: nil + + @type t :: %Protection{ + locked: boolean + } + + @doc ~S""" + Create a Protection object from a property list. + """ + def from_props(props) do + ft = %Protection{locked: props[:locked]} + + if ft == %Protection{}, do: nil, else: ft + end +end diff --git a/lib/elixlsx/xml_templates.ex b/lib/elixlsx/xml_templates.ex index 7421e2e..46c2210 100644 --- a/lib/elixlsx/xml_templates.ex +++ b/lib/elixlsx/xml_templates.ex @@ -12,6 +12,7 @@ defmodule Elixlsx.XMLTemplates do alias Elixlsx.Style.Font alias Elixlsx.Style.Fill alias Elixlsx.Style.BorderStyle + alias Elixlsx.Style.Protection alias Elixlsx.Sheet # TODO: the xml_text_exape functions belong into Elixlsx.Util, @@ -291,6 +292,12 @@ defmodule Elixlsx.XMLTemplates do """ end + defp xl_sheet_protection(%Sheet{protected: true}) do + "" + end + + defp xl_sheet_protection(_), do: "" + defp xl_sheet_rows(data, row_heights, grouping_info, wci) do rows = Enum.zip(data, 1..length(data)) @@ -459,6 +466,7 @@ defmodule Elixlsx.XMLTemplates do """ <> xl_merge_cells(sheet.merge_cells) <> + xl_sheet_protection(sheet) <> """ @@ -583,15 +591,25 @@ defmodule Elixlsx.XMLTemplates do alignment -> {"applyAlignment=\"1\"", alignment} end - end + end + + {apply_protection, protection_tag} = + case style.protection do + nil -> + {"", ""} + + protection -> + {"applyProtection=\"1\"", ""} + end """ + xfId="0" #{apply_alignment} #{apply_protection}> #{wrap_text_tag} + #{protection_tag} """ end @@ -646,6 +664,11 @@ defmodule Elixlsx.XMLTemplates do end end + @spec protection_attrs(Protection.t()) :: String.t() + defp protection_attrs(%Protection{locked: true}), do: "locked=\"1\" " + defp protection_attrs(%Protection{locked: false}), do: "locked=\"0\" " + defp protection_attrs(_), do: "" + # Returns the inner content of the block. @spec make_cellxfs(list(CellStyle.t()), WorkbookCompInfo.t()) :: String.t() defp make_cellxfs(ordered_style_list, wci) do