lib/parser/helpers.ex

defmodule CWMP.Protocol.Parser.ElemState do
  defstruct path: [],
            handler: nil,
            acc: nil
end

defmodule CWMP.Protocol.Parser.State do
  defstruct stack: [],
            last_text: "",
            curstate: nil,
            last_acc: nil
end

defmodule CWMP.Protocol.ParserHelpers do
  alias CWMP.Protocol.Parser.ElemState
  alias CWMP.Protocol.Parser.State

  defmacro __using__(_opts) do
    quote do
      import CWMP.Protocol.ParserHelpers
      @before_compile CWMP.Protocol.ParserHelpers
    end
  end

  defmacro __before_compile__(_opts) do
    # All unhandled start and end elements are simply ignored.
    quote do
      def start_element(state, _path, _attribs, _uri) do
        state
      end

      def end_element(state, _path) do
        state
      end
    end
  end

  # Pushes a new state onto the handler stack.
  def push_handler(state, handler) do
    newelem = %ElemState{handler: handler, acc: handler.initial_acc}
    %State{curstate: newelem, stack: [state.curstate | state.stack]}
  end

  # Applies a function to the current state accumulator.
  def update_acc(state, fun) do
    %State{state | curstate: %ElemState{state.curstate | acc: fun.(state.curstate.acc)}}
  end

  @doc """

  Ensures that the passed value is an integer and that it satisfies
  the restraints layed out by the anon function passed.

  """
  def integerValue(i, fun \\ fn x -> x end)

  def integerValue(i, fun) when is_integer(i) do
    if fun.(i) do
      i
    else
      raise "Integer does not validate"
    end
  end

  def integerValue(i, fun) do
    case Integer.parse(i) do
      {v, ""} ->
        if fun.(v) do
          v
        else
          raise "Integer does not validate"
        end

      _ ->
        raise "Integer value does not parse"
    end
  end

  def required_datetimeStructure(timestring) do
    case DateTime.from_iso8601(timestring) do
      {:ok, dt, utc_offset} ->
        %DateTime{dt | utc_offset: utc_offset}

      {:error, :missing_offset} ->
        # assume datetime to be a relative time since boot
        datetimeStructure(timestring <> "Z")

      {:error, reason} ->
        # the unparsable datetime must fail here
        raise "timestring '#{timestring}' is unparseable: #{inspect(reason)}"
        :error
    end
  end

  def datetimeStructure(timestring) do
    case DateTime.from_iso8601(timestring) do
      {:ok, dt, utc_offset} ->
        %DateTime{dt | utc_offset: utc_offset}

      {:error, :missing_offset} ->
        # assume datetime to be a relative time since boot
        datetimeStructure(timestring <> "Z")

      {:error, _reason} ->
        # the unparsable datetime must return the Unknown datetime here
        datetimeStructure("0001-01-01T00:00:00Z")
    end
  end

  def boolValue(s) do
    case s do
      "0" -> false
      _ -> true
    end
  end

  @doc """
    Returns nil or a "x-y" string extrapolated from the uri
    if it matches the pattern of a cwmp version.
  """
  def parse_cwmp_version(uri) do
    case Regex.run(~r/.*dslforum-org:cwmp-(\d-\d)$/, uri) do
      [_, ver] -> ver
      _ -> nil
    end
  end
end