Xml in Elixir

Here’s the translation of the XML example from Go to Elixir:

defmodule Plant do
  defstruct [:id, :name, :origin]

  def new(id, name, origin \\ []) do
    %Plant{id: id, name: name, origin: origin}
  end

  defimpl String.Chars, for: Plant do
    def to_string(plant) do
      "Plant id=#{plant.id}, name=#{plant.name}, origin=#{inspect(plant.origin)}"
    end
  end
end

defmodule XMLExample do
  def run do
    coffee = Plant.new(27, "Coffee", ["Ethiopia", "Brazil"])

    # Emit XML representing our plant
    {:ok, xml} = XmlBuilder.document("plant", %{id: coffee.id}, [
      XmlBuilder.element("name", coffee.name),
      coffee.origin |> Enum.map(&XmlBuilder.element("origin", &1))
    ])
    |> XmlBuilder.generate(format: :indent)

    IO.puts(xml)

    # To add a generic XML header to the output, prepend it explicitly
    IO.puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n#{xml}")

    # Use XmlToMap to parse XML into a data structure
    {:ok, parsed} = XmlToMap.naive_map(xml)
    plant = %Plant{
      id: String.to_integer(parsed["plant"]["id"]),
      name: parsed["plant"]["name"],
      origin: List.wrap(parsed["plant"]["origin"])
    }
    IO.puts(plant)

    tomato = Plant.new(81, "Tomato", ["Mexico", "California"])

    # The nesting structure is represented using nested maps
    nesting = %{
      nesting: %{
        parent: %{
          child: %{
            plant: [
              %{id: coffee.id, name: coffee.name, origin: coffee.origin},
              %{id: tomato.id, name: tomato.name, origin: tomato.origin}
            ]
          }
        }
      }
    }

    {:ok, nested_xml} = XmlBuilder.document(nesting)
    |> XmlBuilder.generate(format: :indent)

    IO.puts(nested_xml)
  end
end

XMLExample.run()

This Elixir code demonstrates XML processing similar to the original Go example. Here are some key points:

  1. We define a Plant struct to represent our data structure.

  2. Instead of using struct tags for XML mapping, we manually construct the XML structure using the XmlBuilder library.

  3. For XML parsing, we use the XmlToMap library to convert XML to Elixir maps.

  4. The String.Chars protocol is implemented for Plant to provide a string representation.

  5. Nesting is represented using nested Elixir maps, which XmlBuilder can convert to nested XML elements.

  6. Error handling is done using Elixir’s pattern matching on tuples (e.g., {:ok, result}).

To run this example, you would need to add the following dependencies to your mix.exs file:

defp deps do
  [
    {:xml_builder, "~> 2.1"},
    {:xml_to_map, "~> 0.1.1"}
  ]
end

And then run mix deps.get to install these dependencies.

This example showcases Elixir’s approach to working with XML, which differs from Go’s in its use of external libraries and functional programming paradigms.