Skip to content

Instantly share code, notes, and snippets.

@vysakh0
Last active April 3, 2018 14:57
Show Gist options
  • Save vysakh0/e6aacc9c87e006144d96 to your computer and use it in GitHub Desktop.
Save vysakh0/e6aacc9c87e006144d96 to your computer and use it in GitHub Desktop.
Parse xml to elixir maps including the attributes
defmodule Parser do
require Record
Record.defrecord :xmlElement, Record.extract(:xmlElement, from_lib: "xmerl/include/xmerl.hrl")
Record.defrecord :xmlText, Record.extract(:xmlText, from_lib: "xmerl/include/xmerl.hrl")
Record.defrecord :xmlAttribute, Record.extract(:xmlAttribute, from_lib: "xmerl/include/xmerl.hrl")
def run(xml) do
run(xml, '//response')
end
def run(xml, path) do
{doc, _} = xml |> :binary.bin_to_list |> :xmerl_scan.string
:xmerl_xpath.string(path, doc)
|> parse(%{}, [])
end
defp parse([], result, _), do: result
defp parse([node | tail], result, path) when Record.is_record(node, :xmlElement) do
content = xmlElement(node, :content)
name = xmlElement(node, :name)
attributes = xmlElement(node, :attributes)
new_path = path ++ [name]
result = put_result(result, content, "", new_path)
result = parse(content, result, new_path)
result = parse(attributes, result, new_path)
parse(tail, result, path)
end
defp parse([node | tail], result, path) when Record.is_record(node, :xmlAttribute)do
name = xmlAttribute(node, :name)
val = xmlAttribute(node, :value) |> to_string
parent_path = List.delete_at(path, -1)
if parent_path !== [], do: children = get_in(result, parent_path)
if is_list(children) do
{[new], new_children}= Enum.partition(children, fn(x) -> Map.has_key?(x, List.last(path)) end)
value = new_children ++ [Map.put(new, name, val)]
result = put_in(result, parent_path, value)
else
new_path = path ++ [name]
result = put_in(result, new_path, val)
end
parse(tail, result, path)
end
defp parse([content | tail], result, path) when Record.is_record(content, :xmlText)do
{name, _} = xmlText(content, :parents) |> hd
value = xmlText(content, :value) |> to_string
parent_path = List.delete_at(path, -1)
children = get_in(result, parent_path)
if is_list(children) do
new = Map.put(%{}, name, value)
value = children ++ [new]
result = put_in(result, parent_path, value)
else
result = put_in(result, path, value)
end
parse(tail, result, path)
end
defp put_result(result, contents, _attrs, path) when length(contents) > 1 do
put_in(result, path, [])
end
defp put_result(result, [content], _attr, path) when Record.is_record(content, :xmlElement) do
put_in(result, path, %{})
end
defp put_result(result, _, _, _), do: result
end
defmodule ParserTest do
use ExUnit.Case
@xml """
<result>
<event id="2">
<title>My event</title>
<artist id="4">
<name>Michael Jackson</name>
</artist>
<artist id="5">
<name>Jackson</name>
</artist>
</event>
<event id="3">
<title>My event 2</title>
<artist id="1">
<name>Rolling Stones</name>
</artist>
</event>
<abcd><title>sdf</title></abcd>
<lo id="23"><title>sdf</title></lo>
<xyz><title>sdf</title><desc>hi</desc></xyz>
<lmn><title>sdf</title><desc value="hi">hi</desc></lmn>
</result>
"""
test "xml parser " do
assert Parser.run(@xml, '//abcd') == %{abcd: %{title: "sdf"}}
assert Parser.run(@xml, '//xyz') == %{xyz: [%{title: "sdf"}, %{desc: "hi"}]}
assert Parser.run(@xml, '//lo') == %{lo: %{title: "sdf", id: "23"}}
assert Parser.run(@xml, '//lmn') == %{lmn: [%{title: "sdf"}, %{desc: "hi", value: "hi"}]}
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment