Current section
Files
Jump to
Current section
Files
lib/elixir_xml_to_map.ex
defmodule XmlToMap do
@moduledoc """
Simple convenience module for getting a map out of an XML string.
"""
alias XmlToMap.{NaiveMap, NestedMap}
@doc """
`naive_map/1` utility is inspired by `Rails Hash.from_xml()` but is
"naive" in that it is convenient (requires no setup) but carries the same
drawbacks.
For example no validation over what should be a collection. If and only if
nodes are repeated at the same level will they become a list. If a node has
attributes we'll prepend a "-" in front of them and merge them into the map
and take the node value and nest that inside "#content" key.
"""
@defaults %{namespace_match_fn: &__MODULE__.default_namespace_match_fn/0}
def naive_map(xml, opts \\ []) do
params = Enum.into(opts, @defaults)
tree = get_generic_data_structure(xml, params)
NaiveMap.parse(tree)
end
def nested_map(xml, opts \\ []) do
params = Enum.into(opts, @defaults)
{purge_empty, params} = Map.pop(params, :purge_empty, false)
tree = get_generic_data_structure(xml, params)
NestedMap.parse(tree, purge_empty)
end
# Default function to handle namespace in xml
# This fuction invokes an anonymous function that has this parameters: Name, Namespace, Prefix.
# Usually, the namespace appears at the top of xml file, e.g `xmlns:g="http://base.google.com/ns/1.0"`,
# where `g` is the Prefix and `"http://base.google.com/ns/1.0"` the Namespace,
# the Name is the key tag in xml.
# It should return a term. It is called for each tag and attribute name.
# The result will be used in the output.
# When a namespace and prefix is found, it'll return a term like "#{prefix}:#{name}",
# otherwise it will return only the name.
# The default behavior in :erlsom.simple_form/2 is Name if Namespace == undefined, and a string {Namespace}Name otherwise.
def default_namespace_match_fn do
fn name, namespace, prefix ->
cond do
namespace != [] && prefix != [] -> "#{prefix}:#{name}"
true -> name
end
end
end
defp get_generic_data_structure(xml, %{namespace_match_fn: namespace_match_fn}) do
{:ok, element, _tail} = :erlsom.simple_form(xml, [{:nameFun, namespace_match_fn.()}])
element
end
# erlsom simple_form returns a kind of tree:
# Result = {ok, Element, Tail},
# where Element = {Tag, Attributes, Content},
# Tag is a string
# Attributes = [{AttributeName, Value}],
# Content is a list of Elements and/or strings.
defp get_generic_data_structure(xml, _opts) do
{:ok, element, _tail} = :erlsom.simple_form(xml)
element
end
end