Skip to content

Instantly share code, notes, and snippets.

@JoeZ99
Last active May 8, 2022 23:18
Show Gist options
  • Save JoeZ99/fe979b9e781e5633e8917f737a25671a to your computer and use it in GitHub Desktop.
Save JoeZ99/fe979b9e781e5633e8917f737a25671a to your computer and use it in GitHub Desktop.
An elixir module sax parser
defmodule XmlEventHandler do
@moduledoc """
Module to parse xml with lists of items based on events
"""
@behaviour Saxy.Handler
@type element :: %{
name: binary,
attributes: [{binary, binary}],
childs: [element()],
text: binary
}
@impl true
def handle_event(:start_document, _prolog),
do: {:ok, %{items: [], current_element: nil, stack: nil}}
def handle_event(:start_element, {"item", _attributes}, %{stack: nil} = state), do:
{:ok,
%{
state
| current_element: %{name: "item", text: "", attributes: [], childs: []},
stack: ["item"]
}}
def handle_event(
:start_element,
{name, attributes},
%{current_element: element, stack: stack} = state
)
when is_list(stack),
do:
{:ok,
%{
state
| current_element: add_child(element, {name, attributes}, stack),
stack: [name | stack]
}}
def handle_event(:end_element, "item", %{items: items, current_element: element} = state),
do: {:ok, %{state | items: [element | items], stack: nil}}
def handle_event(:end_element, name, %{stack: [name | stack]} = state),
do: {:ok, %{state | stack: stack}}
def handle_event(:characters, chars, %{stack: stack, current_element: element} = state)
when is_list(stack) do
case String.trim(chars) do
"" ->
{:ok, state}
trimmed_chars ->
{:ok, %{state | current_element: add_text(element, trimmed_chars, stack)}}
end
end
def handle_event(:cdata, cdata, state), do: handle_event(:characters, cdata, state)
def handle_event(_, _, state), do: {:ok, state}
@spec add_child(element(), {binary, [{binary, binary}]}, [binary]) :: element()
defp add_child(%{name: name, childs: childs} = element, {child_name, attributes}, [name]),
do: Map.put(element, :childs, [build_element(child_name, attributes) | childs])
defp add_child(%{childs: [last_child | childs]} = element, {name, attributes}, stack) do
{_, new_stack} = List.pop_at(stack, -1)
updated_child = add_child(last_child, {name, attributes}, new_stack)
Map.put(element, :childs, [updated_child | childs])
end
@spec add_text(element(), binary, [binary]) :: element()
defp add_text(%{name: name, text: ""} = element, added_text, [name]),
do: Map.put(element, :text, added_text)
defp add_text(%{name: name, text: text} = element, added_text, [name]),
do: Map.put(element, :text, "#{text} #{added_text}")
defp add_text(%{childs: [last_child | childs]} = element, added_text, stack) do
{_, new_stack} = List.pop_at(stack, -1)
updated_child = add_text(last_child, added_text, new_stack)
Map.put(element, :childs, [updated_child | childs])
end
@spec build_element(binary, [{binary, binary}]) :: element()
defp build_element(name, attributes),
do: %{name: name, attributes: attributes, childs: [], text: ""}
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment