defmodule RecycledCloud.OpenNebula.Schema do @moduledoc """ Helpers used to process OpenNebula's XSD schema. ,. /,,;';;. ,;;;.. ,,;. ' .','' `::;:' ``;;;;' `..' ` ,,/' ,,// Here be dragons! We import the records from Erlang header files (.hrl) generated by erlsom as macros in the Records module. The only way I found to programatically call a macro (apply/3 only works with functions) is by playing around with the AST to template said call... I'm not fond of this as it makes things more complex than I would like them to be, but likely the easiest way to user erlsom's data binder from elixir (and it's not *that* bad). The ideal would be to generate elixir structs from source XSD files, but it would take much more work. Heavily inspired from Gary Poster's: http://codesinger.blogspot.com/2015/12/elixir-erlang-records-and-erlsom-xml.html I stole the dragon art from ASCII.co.uk. """ require Logger alias RecycledCloud.OpenNebula.Schema alias RecycledCloud.OpenNebula.Schema.Records @opennebula_release "5.12" ## # Data-binding: access and extract XSD models. defp get_raw(type, object) do priv_dir = :code.priv_dir(:recycledcloud) |> to_string {data_dir, extension} = case type do :xsd -> {"xsd-src", ".xsd"} :hrl -> {"xsd-records", ".hrl"} end raw_path = [priv_dir, "opennebula", @opennebula_release, data_dir, object <> extension] raw_path |> Path.join() end def get_xsd_for(object), do: get_raw(:xsd, object) def get_hrl_for(object), do: get_raw(:hrl, object) def generate_model_for(object) do {:ok, model} = object |> get_xsd_for() |> :erlsom.compile_xsd_file() model end ## # Data-binding: transform erlsom records to Elixir maps. def scan(raw, object_type) do {:ok, data, _rest} = :erlsom.scan(raw, Records.get_model_for(object_type)) data end defp transform_value({k, l}) when is_list(l) do {k, Enum.map(l, fn v -> map_record(v) end)} end defp transform_value({k, v}), do: {k, map_record(v)} # Here be dragons! See moduledoc. def map_record(data) when is_tuple(data) do schema = elem(data, 0) if schema in Keyword.keys(Schema.Records.__info__(:macros)) do call = quote do require RecycledCloud.OpenNebula.Schema.Records RecycledCloud.OpenNebula.Schema.Records."XSD"(:data) end replace = fn :XSD, {model, data} -> {model, {model, data}} :data, {model, data} -> {data, {model, data}} node, stubs -> {node, stubs} end {{inserted, _raw}, []} = call |> Macro.prewalk({schema, Macro.escape(data)}, replace) |> Code.eval_quoted() Enum.into(inserted, Map.new, &transform_value/1) else Logger.debug("Ignoring unknown OpenNebula Model #{schema}.") data end end def map_record(data = [first | _rest]) when is_integer(first), do: List.to_string(data) def map_record(:undefined), do: nil def map_record(data), do: data end