defmodule CWMP.Protocol.Parser do
@moduledoc """
Parsing of CWMP XML messages, and conversion to Elixir data structures.
"""
alias CWMP.Protocol.Parser.ElemState
alias CWMP.Protocol.Parser.State
alias CWMP.Protocol.Parser.ParseError
@doc """
Parses a CWMP XML envelope and converts it to an Elixir data structure.
"""
def parse!(source) do
try do
{:ok, %State{curstate: %ElemState{acc: acc}}, _} =
:erlsom.parse_sax(source, initial_state(), &parse_step/2)
acc
catch
{:error, err} when is_list(err) -> raise ParseError, message: "#{err}"
{:error, err} -> raise ParseError, message: "#{inspect(err)}"
end
end
@doc """
Same as parse!, but non-throwing.
"""
def parse(source) do
try do
{:ok, parse!(source)}
rescue
err -> {:error, err}
end
end
def initial_elem_state(handler) do
%ElemState{handler: handler, acc: handler.initial_acc}
end
defp initial_state do
%State{
curstate: initial_elem_state(Elixir.CWMP.Protocol.Parser.Envelope),
stack: [%ElemState{}]
}
end
defp parse_step({:characters, chars}, state) do
%State{state | last_text: String.trim("#{chars}")}
end
defp parse_step({:startElement, uri, name, _prefix, attribs}, state) do
newpath = [name | state.curstate.path]
newinner = %ElemState{state.curstate | path: newpath}
new_state = %State{state | curstate: newinner}
new_state = state.curstate.handler.start_element(new_state, newpath, attribs, to_string(uri))
%State{new_state | last_text: ""}
end
defp parse_step({:endElement, _uri, _name, _prefix}, state) do
state =
case state.curstate.path do
# Empty, so pop off the handler stack
[] ->
case state.stack do
[newcur | rest] ->
%State{state | curstate: newcur, stack: rest, last_acc: state.curstate.acc}
_ ->
raise "Imbalanced tags"
end
_ ->
state
end
curpath = state.curstate.path
state = %State{state | curstate: %ElemState{state.curstate | path: Enum.drop(curpath, 1)}}
state =
case state.curstate.handler do
nil -> %State{state | curstate: %ElemState{state.curstate | acc: state.last_acc}}
handler -> handler.end_element(state, curpath)
end
%State{state | last_text: ""}
end
defp parse_step(_, state) do
state
end
end