lib/image/xmp.ex

defmodule Image.Xmp do
  import SweetXml

  def extract_xmp(xmp) do
    SweetXml.xpath(xmp, ~x"//x:xmpmeta",
      artist: ~x".//dc:creator/rdf:Seq/rdf:li/text()"s |> transform_by(&maybe_nil/1),
      keywords: ~x".//dc:subject/rdf:Bag/rdf:li/text()"ls,
      created_at: ~x".//@xmp:CreateDate"s |> transform_by(&to_date_time/1),
      rating: ~x".//@xmp:Rating"s |> transform_by(&to_integer/1),
      copyright: [
        ~x".//dc:rights/rdf:Alt"l,
        text: ~x"./rdf:li/text()"ls,
        language: ~x"./rdf:li/@xml:lang"ls
      ],
      title: [
        ~x".//dc:title/rdf:Alt"l,
        text: ~x"./rdf:li/text()"ls,
        language: ~x"./rdf:li/@xml:lang"ls
      ],
      description: [
        ~x".//dc:description/rdf:Alt"l,
        text: ~x"./rdf:li/text()"ls,
        language: ~x"./rdf:li/@xml:lang"ls
      ],
      location: [
        ~x".//rdf:Description",
        city: ~x"./@photoshop:City"s |> transform_by(&maybe_nil/1),
        state: ~x"./@photoshop:State"s |> transform_by(&maybe_nil/1),
        territory: ~x"./@photoshop:Country"s |> transform_by(&maybe_nil/1),
        territory_code: ~x"./@Iptc4xmpCore:CountryCode"s |> transform_by(&maybe_nil/1)
      ]
    )
    |> zip(:description)
    |> zip(:title)
    |> zip(:copyright)
  end

  defp zip(map, key) do
    case Map.fetch(map, key) do
      {:ok, value} ->
        zipped = zip(value)
        Map.put(map, key, zipped)

      :error ->
        map
    end
  end

  defp zip([%{language: languages, text: texts}]) do
    languages
    |> Enum.zip(texts)
    |> Map.new()
  end

  defp zip([]) do
    nil
  end

  defp to_date_time("") do
    nil
  end

  defp to_date_time(string) do
    case NaiveDateTime.from_iso8601(string) do
      {:ok, date_time} -> date_time
      _other -> string
    end
  end

  defp to_integer(string) do
    case Integer.parse(string) do
      {integer, ""} -> integer
      _other -> nil
    end
  end

  defp maybe_nil(""), do: nil
  defp maybe_nil(other), do: other
end