Current section

Files

Jump to
afterbuy lib xml decoder.ex
Raw

lib/xml/decoder.ex

defmodule Afterbuy.XML.Decoder do
@moduledoc """
Module to recursively traverse the output of erlsom.simple_form
and produce a map.
erlsom uses character lists so this library converts them to
Elixir binary string.
"""
defp parse([value]) do
case is_tuple(value) do
true -> parse(value)
false -> to_string(value) |> String.trim()
end
end
defp parse({name, attr, content}) do
parsed_content = parse(content)
case is_map(parsed_content) do
true ->
%{to_string(name) => parsed_content |> Map.merge(attr_map(attr))}
false ->
%{to_string(name) => parsed_content}
end
end
defp parse(list) when is_list(list) do
parsed_list = Enum.map(list, &{to_string(elem(&1, 0)), parse(&1)})
Enum.reduce(parsed_list, %{}, fn {k, v}, acc ->
case Map.get(acc, k) do
nil -> Map.put_new(acc, k, v[k])
[h | t] -> Map.put(acc, k, [h | t] ++ [v[k]])
prev -> Map.put(acc, k, [prev] ++ [v[k]])
end
end)
end
defp attr_map(list) do
list |> Enum.map(fn {k, v} -> {to_string(k), to_string(v)} end) |> Map.new()
end
@doc """
decode(xml) utility is inspired by Rails Hash.from_xml()
but is "naive" in that it is convenient (requires no setup)
but carries the same drawbacks. Use with caution.
XML and Maps are non-isomorphic.
Attributes on some nodes don't carry over if the node has just
one text value (like a leaf). Naive map has no validation over
what should be a collection. If and only if nodes are repeated
at the same level will they beome a list.
"""
def decode!(xml) do
# can't handle xmlns
xml = String.replace(xml, ~r/(\sxmlns="\S+")|(xmlns:ns2="\S+")/, "")
{:ok, tuples, _} = :erlsom.simple_form(xml)
parse(tuples)
end
end