defmodule Cldr.Validity.T do @moduledoc false alias Cldr.Validity.U @field_mapping %{ "language" => :language, "m0" => :m0, "s0" => :s0, "d0" => :d0, "i0" => :i0, "k0" => :k0, "t0" => :t0, "h0" => :h0, "x0" => :x0 } @fields Map.values(@field_mapping) @inverse_field_mapping Enum.map(@field_mapping, fn {k, v} -> {v, k} end) |> Map.new() @validity_data Cldr.Config.validity(:t) @dont_process_keys ["language"] @valid_keys Map.keys(@validity_data) ++ @dont_process_keys @process_keys @valid_keys -- @dont_process_keys def fields do @fields end def field_mapping do @field_mapping end @doc """ Decodes and validates that a given value is valid for a given key. Returns both the canonical key and the canonical value or an error. """ def decode("language" = key, {:ok, language_tag}) do {:ok, {map(key), language_tag}} end def decode("language", {:error, error_language_tag}) do {:error, error_language_tag} end def decode("x0" = key, private_use) do {:ok, {map(key), private_use}} end def decode(key, value) do with {:ok, value} <- valid(key, value) do {:ok, {map(key), atomize(value)}} else {:error, _value} -> {:error, U.invalid_value_error(key, value)} end end @doc """ Encodes a key and value into the form required for a string version of a language tag. """ def encode(key, value) do unmapped_key = unmap(key) {unmapped_key, encode_key(unmapped_key, value)} end # Encode key functions take the form that is # in the language tag locale struct and encodes it # back to what is required in a textual form of # the locale. for {key, values} <- @validity_data, key in @process_keys do inverted_values = Enum.map(values, &String.to_atom/1) defp encode_key(unquote(key), value) when value in unquote(inverted_values) do Atom.to_string(value) end end defp encode_key(key, values) when is_list(values) do values |> Enum.map(&encode_key(key, &1)) |> Enum.join("-") end defp encode_key(_key, {year}) do to_string(year) end defp encode_key(_key, {year, month}) do to_string(year) <> pad(month) <> to_string(month) end defp encode_key(_key, {year, month, day}) do to_string(year) <> pad(month) <> to_string(month) <> pad(day) <> to_string(day) end defp encode_key("language", nil) do nil end defp encode_key("language", language) do String.downcase(language.canonical_locale_name) end defp encode_key("x0", private_use) when is_list(private_use) do Enum.join(private_use, "-") end defp encode_key("x0", private_use) do private_use end # Check that the value provided # is acceptable for the given key. for {key, values} <- @validity_data, key in @process_keys do defp valid(unquote(key), value) when value in unquote(values) do {:ok, String.to_atom(value)} end end defp valid(key, values) when is_list(values) do case valid_list(key, values) do {:ok, values} -> # Dates sort to the end Enum.sort(values, fn <>, <> when digit_1 >= ?0 and digit_1 <= ?9 and digit_2 >= ?0 and digit_2 <= ?9 -> digit_1 < digit_2 <>, _date_2 when digit_1 >= ?0 and digit_1 <= ?9 -> false _date_1, <> when digit_1 >= ?0 and digit_1 <= ?9 -> true value_1, value_2 -> value_1 < value_2 end) |> wrap(:ok) other -> other end end defp valid(key, value) when key in @valid_keys do case Integer.parse(value) do {_integer, ""} -> {:ok, make_date_tuple(value)} _other -> {:error, U.invalid_value_error(key, value)} end end defp valid("language", language) do language end defp valid("x0", private_use) do private_use end defp valid(key, _value) do {:error, U.invalid_key_error(key)} end def valid_list(key, values) do Enum.reduce_while(values, {:ok, []}, fn value, {:ok, acc} -> # if we push a date tuple on a prior round then the # date tuple isn't in last place which it is required to # be so we return an error if length(acc) > 0 && is_tuple(hd(acc)) do {:halt, {:error, invalid_date_order(key, hd(acc))}} else case valid(key, value) do {:ok, value} -> {:cont, {:ok, [value | acc]}} {:error, _value} -> {:halt, {:error, U.invalid_value_error(key, value)}} end end end) end def wrap(term, atom) do {atom, term} end def pad(number) when number < 10, do: " " def pad(_number), do: "" defp make_date_tuple(<>) do {String.to_integer(year), String.to_integer(month), String.to_integer(day)} end defp make_date_tuple(<>) do {String.to_integer(year), String.to_integer(month)} end defp make_date_tuple(<>) do {String.to_integer(year)} end defp map(key) do Map.fetch!(@field_mapping, key) end defp unmap(key) do Map.fetch!(@inverse_field_mapping, key) end defp atomize(value) when is_atom(value) do value end defp atomize(value) when is_binary(value) do String.to_atom(value) end defp atomize(other) do other end defp invalid_date_order(key, value) do encoded = encode_key(key, value) {Cldr.LanguageTag.ParseError, "The date #{inspect(encoded)} must be the last value in a subtag"} end end