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