lib/excalt/vcard/addressbook_handler.ex

defmodule Excalt.Vcard.AddressbookHandler do
  @moduledoc """
  Handler for parsing the addressbooks from the server.
  """

  @behaviour Saxy.Handler

  # when we start document parsing the state is an empty list
  # in this case it will be populated at the end with addressbooks
  def handle_event(:start_document, _prolog, addressbooks) do
    {:ok, addressbooks}
  end

  def handle_event(:end_document, _, {_current_xml_tag, addressbooks}) do
    {:ok, addressbooks}
  end

  # from the xml response we need to get
  # href -> as url for the addressbook, the addressbook is missing
  # if the <d:status> el  of the response is 404 not found
  # displayname -> addressbook name on server | check if exists
  # addressbook-description -> short addressbook description | check if exists
  # supported-address-data -> can be a list of all supported server side vcards, if exists then we should also collect address-data-type in a list
  #
  #
  #

  def handle_event(:start_element, {xml_element, attributes}, {_current_xml_el, addressbooks}) do
    # String.match?(tag_name, ~r/response$/)
    addressbooks =
      if String.match?(xml_element, ~r/response/) do
        # by returning this, we init an empty addressbook struct so we can
        # populate it with related fields
        [%Excalt.Vcard.Addressbook{} | addressbooks]
      else
        addressbooks
      end

    # Extract content types and version
    # Who knows we might need it later on.
    addressbooks =
      if String.match?(xml_element, ~r/address-data-type/) do
        [current_addressbook | addressbooks] = addressbooks
        [content_type, version] = attributes
        {"content-type", content_type} = content_type
        {"version", version} = version

        current_addressbook =
          append_content_type_and_version(current_addressbook, content_type, version)

        [current_addressbook | addressbooks]
      else
        addressbooks
      end

    {:ok, {xml_element, addressbooks}}
  end

  def handle_event(:end_element, _, addressbooks) do
    {:ok, addressbooks}
  end

  # Do things within the element <el> content </el>
  def handle_event(:characters, content, {current_tag, addressbooks}) do
    # With each element we need to take care to pass already all
    # addressbooks or it will be overwritten
    # take care
    content = String.trim(content)

    addressbooks = update_current_addressbook(current_tag, ~r/href/, :url, addressbooks, content)

    addressbooks =
      update_current_addressbook(current_tag, ~r/displayname/, :name, addressbooks, content)

    addressbooks =
      update_current_addressbook(
        current_tag,
        ~r/addressbook-descriptio/,
        :description,
        addressbooks,
        content
      )

    # return all new and updated addressbooks
    #
    {:ok, {current_tag, addressbooks}}
  end

  @doc """
  Updates current addressbooks based on the if xml_element exists.
  """
  @spec update_current_addressbook(boolean(), atom(), List.t(), String.t()) ::
          [] | [%Excalt.Vcard.Addressbook{}]
  def update_current_addressbook(current_tag, regex, field, addressbooks, content) do
    String.match?(current_tag, regex) |> update_current_addressbook(field, addressbooks, content)
  end

  def update_current_addressbook(false, _field, addressbooks, _content), do: addressbooks

  def update_current_addressbook(true, _field, addressbooks, content) when length(content) < 0,
    do: addressbooks

  def update_current_addressbook(true, field, [current_addressbook | addressbooks], content) do
    updated_addressbook = Map.put(current_addressbook, field, content)
    [updated_addressbook | addressbooks]
  end

  def append_content_type_and_version(
        %Excalt.Vcard.Addressbook{content_types: content_types, versions: versions} =
          current_addressbook,
        content_type,
        version
      ) do
    %{
      current_addressbook
      | content_types: [content_type | content_types],
        versions: [version | versions]
    }
  end
end