Current section

Files

Jump to
mobile_number_format lib compiler.ex
Raw

lib/compiler.ex

defmodule MobileNumberFormat.Compiler do
@moduledoc false
require MobileNumberFormat.SaxParser
alias MobileNumberFormat.SaxParser
def compile() do
{:ok, {_, territories}} =
Path.join(:code.priv_dir(:mobile_number_format), "PhoneNumberMetadata.xml")
|> File.stream!()
|> Stream.filter(&!Regex.match?(~r/^\s*<--(.*?)-->\s*$/, &1))
|> Stream.filter(&!Regex.match?(~r/^\s*$/, &1))
|> Saxy.parse_stream(SaxParser, {[], []})
territories =
territories
|> Enum.filter(&(&1.regex))
|> Enum.sort_by(fn territory -> territory.country_calling_code end)
contents = [
quote do
def formatting_data_per_territory() do
unquote(Macro.escape(territories))
end
def valid_number?(%{} = data) do
match?({:ok, _}, parse(data))
end
def valid_country_calling_code?(country_calling_code) do
fetch_by_country_calling_code(country_calling_code) != :error
end
def valid_iso_country_code?(iso_country_code) do
fetch_by_iso_country_code(iso_country_code) != :error
end
def parse(%{} = data) do
international_number_arg = Map.get(data, :international_number, nil)
national_number_arg = Map.get(data, :national_number, nil)
country_calling_code_arg = Map.get(data, :country_calling_code, nil)
iso_country_code_arg = Map.get(data, :iso_country_code, nil)
cond do
iso_country_code_arg || country_calling_code_arg ->
fetch_by_iso_country_code_or_country_calling_code(iso_country_code_arg, country_calling_code_arg)
|> case do
{:ok, formatting_rules} ->
formatting_rules_list = List.wrap(formatting_rules)
cond do
national_number_arg ->
do_parse(formatting_rules_list, national_number_arg, :national)
international_number_arg ->
do_parse(formatting_rules_list, international_number_arg, :international)
end
error ->
error
end
international_number_arg ->
do_parse(nil, international_number_arg, :international)
true ->
:insufficient_data
end
end
defp do_parse(formatting_rules_list, national_number_arg, :national) do
national_number_arg = national_number_arg |> to_string()
cleaned_national_number = national_number_arg |> String.replace(~r/[^\d]/, "")
Enum.find_value(formatting_rules_list, fn formatting_rules ->
%{
country_calling_code: country_calling_code,
iso_country_code: iso_country_code,
national_prefix: national_prefix
} = formatting_rules
if match_national_number?(formatting_rules, cleaned_national_number) do
%{"trimmed_national_number_input" => trimmed_national_number_input} =
Regex.named_captures(~r/(?<trimmed_national_number_input>\d.*\d)/, national_number_arg)
{:ok, %{
iso_country_code: iso_country_code,
country_calling_code: country_calling_code,
national_number: cleaned_national_number,
trimmed_national_number_input: trimmed_national_number_input
}}
else
cleaned_national_number_without_prefix = cleaned_national_number |> String.replace_prefix(national_prefix, "")
if(
cleaned_national_number != cleaned_national_number_without_prefix
&& match_national_number?(formatting_rules, cleaned_national_number_without_prefix)
) do
national_number_without_prefix_arg =
national_prefix
|> String.graphemes()
|> Enum.reduce(national_number_arg, fn digit, national_number_arg ->
String.replace(national_number_arg, digit, "", global: false)
end)
%{"trimmed_national_number_input" => trimmed_national_number_input} =
Regex.named_captures(~r/(?<trimmed_national_number_input>\d.*\d)/, national_number_without_prefix_arg)
{:ok, %{
iso_country_code: iso_country_code,
country_calling_code: country_calling_code,
national_number: cleaned_national_number_without_prefix,
trimmed_national_number_input: trimmed_national_number_input
}}
end
end
end)
|> case do
nil ->
:invalid_number
result ->
result
end
end
defp do_parse(nil, international_number_arg, :international) do
cleaned_international_number = international_number_arg |> to_string() |> String.replace(~r/[^\d]/, "")
unquote(Macro.escape(territories))
|> Enum.filter(&String.starts_with?(cleaned_international_number, &1.country_calling_code))
|> do_parse(international_number_arg, :international)
end
defp do_parse(formatting_rules_list, international_number_arg, :international) do
international_number_arg = international_number_arg |> to_string()
Enum.find_value(formatting_rules_list, fn formatting_rules ->
%{
country_calling_code: country_calling_code,
iso_country_code: iso_country_code,
national_prefix: national_prefix
} = formatting_rules
national_number_arg =
country_calling_code
|> String.graphemes()
|> Enum.reduce(international_number_arg, fn digit, international_number_arg ->
String.replace(international_number_arg, digit, "", global: false)
end)
case Regex.named_captures(~r/(?<national_prefix>\d+?)\s*\)(?<national_number>.*)$/, national_number_arg) do
nil ->
%{national_number_arg: national_number_arg}
%{"national_number" => matched_national_number, "national_prefix" => matched_national_prefix} ->
matched_national_prefix = matched_national_prefix |> String.replace(~r/[^\d]/, "")
if national_prefix == matched_national_prefix do
%{national_number_arg: matched_national_number}
end
end
|> case do
%{national_number_arg: national_number_arg} ->
cleaned_national_number = national_number_arg |> String.replace(~r/[^\d]/, "")
if match_national_number?(formatting_rules, cleaned_national_number) do
%{"trimmed_national_number_input" => trimmed_national_number_input} =
Regex.named_captures(~r/(?<trimmed_national_number_input>\d.*\d)/, national_number_arg)
{:ok, %{
iso_country_code: iso_country_code,
country_calling_code: country_calling_code,
national_number: cleaned_national_number,
trimmed_national_number_input: trimmed_national_number_input
}}
end
nil ->
nil
end
end)
|> case do
nil ->
:invalid_number
result ->
result
end
end
defp match_national_number?(%{regex: regex, possible_lengths: possible_lengths}, national_number) do
String.length(national_number) in possible_lengths && Regex.match?(regex, national_number)
end
defp fetch_by_iso_country_code(iso_country_code) do
unquote(Macro.escape(territories))
|> Enum.find(&(&1.iso_country_code == iso_country_code))
|> case do
nil ->
:error
formatting_rules ->
{:ok, formatting_rules}
end
end
defp fetch_by_country_calling_code(country_calling_code) do
country_calling_code = clean_country_calling_code(country_calling_code)
unquote(Macro.escape(territories))
|> Enum.filter(&(&1.country_calling_code == country_calling_code))
|> case do
[] ->
:error
[formatting_rules] ->
{:ok, formatting_rules}
formatting_rules_list ->
{:ok, formatting_rules_list}
end
end
defp fetch_by_iso_country_code_or_country_calling_code(iso_country_code_arg, country_calling_code_arg) do
cond do
iso_country_code_arg && country_calling_code_arg ->
case fetch_by_iso_country_code(iso_country_code_arg) do
:error ->
:invalid_iso_country_code
{:ok, %{country_calling_code: country_calling_code}} ->
if clean_country_calling_code(country_calling_code_arg) != country_calling_code do
:invalid_country_calling_code
else
fetch_by_iso_country_code_or_country_calling_code(iso_country_code_arg, nil)
end
end
iso_country_code_arg ->
case fetch_by_iso_country_code(iso_country_code_arg) do
:error ->
:invalid_iso_country_code
{:ok, formatting_rules} ->
{:ok, formatting_rules}
end
country_calling_code_arg ->
case fetch_by_country_calling_code(country_calling_code_arg) do
:error ->
:invalid_country_calling_code
{:ok, formatting_rules} ->
{:ok, formatting_rules}
end
end
end
defp clean_country_calling_code(country_calling_code) do
country_calling_code = country_calling_code |> to_string()
country_calling_code
|> String.pad_leading(String.length(country_calling_code) + 1, "+")
|> String.replace_prefix("+", "")
|> Integer.parse()
|> case do
{number, ""} ->
number |> to_string()
:error ->
:error
end
end
end
]
module = :"Elixir.MobileNumberFormat"
Module.create(module, contents, Macro.Env.location(__ENV__))
:code.purge(module)
end
end