Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us
Notesclub

XML Schemas

livebooks/xml_schema.livemd

XML Schemas

Installing Deps.

To demonstrate this we will use SweetXML to query the XML.

Mix.install([:data_schema, :sweet_xml])

Transforming XML

Let’s imagine that we have some XML that we wish to turn into a struct. What would it require to enable that? First a new Xpath data accessor:

defmodule XpathAccessor do
  @behaviour DataSchema.DataAccessBehaviour
  import SweetXml, only: [sigil_x: 2]

  @impl true
  def field(data, path) do
    SweetXml.xpath(data, ~x"#{path}"s)
  end

  @impl true
  def list_of(data, path) do
    SweetXml.xpath(data, ~x"#{path}"l)
  end

  @impl true
  def has_one(data, path) do
    SweetXml.xpath(data, ~x"#{path}")
  end

  @impl true
  def has_many(data, path) do
    SweetXml.xpath(data, ~x"#{path}"l)
  end
end

As we can see our accessor uses the library Sweet XML to access the XML. That means if we wanted to change the library later we would only need to alter this one module for all of our schemas to benefit from the change.

Our source data looks like this:

source_data = """

  This is a blog post
  
    This is a comment
    This is another comment
  
  
    This is a draft blog post
  

"""

Let’s define our schemas like so:

defmodule DraftPost do
  import DataSchema, only: [data_schema: 1]

  @data_accessor XpathAccessor
  data_schema(field: {:content, "./Content/text()", fn value -> {:ok, value} end})
end

defmodule Comment do
  import DataSchema, only: [data_schema: 1]

  @data_accessor XpathAccessor
  data_schema(field: {:text, "./text()", &{:ok, to_string(&1)}})
end

defmodule BlogPost do
  import DataSchema, only: [data_schema: 1]

  @data_accessor XpathAccessor
  @datetime_fields [
    field: {:date, "/Blog/@date", &Date.from_iso8601/1},
    field: {:time, "/Blog/@time", &Time.from_iso8601/1}
  ]
  data_schema(
    field: {:content, "/Blog/Content/text()", &{:ok, to_string(&1)}},
    has_many: {:comments, "//Comment", Comment},
    has_one: {:draft, "/Blog/Draft", DraftPost},
    aggregate: {:post_datetime, @datetime_fields, &NaiveDateTime.new(&1.date, &1.time)}
  )
end

And now we can transform:

source_data = """

  This is a blog post
  
    This is a comment
    This is another comment
  
  
    This is a draft blog post
  

"""

DataSchema.to_struct(source_data, BlogPost)