defmodule ISO20022 do
@moduledoc """
ISO 20022 message parsing for Elixir.
Currently supported message types:
- `ISO20022.Camt053` — Bank to Customer Statement (camt.053.001.02 through .014)
## Example
{:ok, doc} = ISO20022.Camt053.parse(xml_string)
[statement | _] = doc.statements
IO.inspect(statement.account.iban)
"""
@doc """
Dispatches parsing to the correct module based on the XML namespace found in the
document root. Returns an error tuple for unsupported message types.
Currently only camt.053 is supported. More message types will be added in future releases.
"""
@spec parse(binary()) ::
{:ok, ISO20022.Camt053.Document.t()}
| {:error, term()}
def parse(xml) when is_binary(xml) do
case detect_message_type(xml) do
{:ok, :camt_053} -> ISO20022.Camt053.parse(xml)
{:ok, other} -> {:error, {:unsupported_message_type, other}}
{:error, _} = err -> err
end
end
defp detect_message_type(xml) do
case Saxy.SimpleForm.parse_string(xml, []) do
{:ok, {_tag, attrs, _children}} ->
ns = find_namespace(attrs)
classify_namespace(ns)
{:error, reason} ->
{:error, {:parse_error, reason}}
end
end
defp find_namespace(attrs) do
Enum.find_value(attrs, fn
{"xmlns", val} -> val
_ -> nil
end)
end
defp classify_namespace(nil), do: {:error, :no_namespace}
defp classify_namespace(ns) do
cond do
String.contains?(ns, "camt.053") -> {:ok, :camt_053}
String.contains?(ns, "camt.052") -> {:ok, :camt_052}
String.contains?(ns, "camt.054") -> {:ok, :camt_054}
String.contains?(ns, "pain.001") -> {:ok, :pain_001}
String.contains?(ns, "pacs.008") -> {:ok, :pacs_008}
true -> {:ok, {:unknown, ns}}
end
end
end