Packages

An XML SAX parser and encoder in Elixir.

Current section

Files

Jump to
sax lib saxy builder.ex
Raw

lib/saxy/builder.ex

defprotocol Sax.Builder do
@moduledoc """
Protocol for building XML content.
## Deriving
This helps to generate XML content simple form in trivial cases.
There are a few required options:
* `name` - tag name of generated XML element.
* `attributes` - fields to be encoded as attributes.
* `children` - fields to be encoded as element children.
## Examples
defmodule Person do
@derive {Sax.Builder, name: "person", attributes: [:gender], children: [:name]}
defstruct [:name, :gender]
end
iex> person = %Person{name: "Alice", gender: "female"}
iex> Sax.Builder.build(person)
{"person", [{"gender", "female"}], [{:characters, "Alice"}]}
Custom implementation could be done by implementing protocol:
defmodule User do
defstruct [:username, :name]
end
defimpl Sax.Builder do
import Sax.XML
def build(user) do
element(
"Person",
[{"userName", user.username}],
[element("Name", [], user.name)]
)
end
end
iex> user = %User{name: "Alice", username: "alice99"}
iex> Sax.Builder.build(user)
{"Person", [{"userName", "alice99"}], [{"Name", [], [characters: "Alice"]}]}
"""
@doc """
Builds `content` to XML content in simple form.
"""
@spec build(content :: term()) :: Sax.XML.content()
def build(content)
end
defimpl Sax.Builder, for: Any do
defmacro __deriving__(module, _struct, options) do
name = Keyword.fetch!(options, :name)
attribute_fields = Keyword.get(options, :attributes, [])
children_fields = Keyword.get(options, :children, [])
quote do
defimpl Sax.Builder, for: unquote(module) do
def build(struct) do
import Sax.XML
attributes =
struct
|> Map.take(unquote(attribute_fields))
|> Enum.to_list()
children =
struct
|> Map.take(unquote(children_fields))
|> Map.values()
element(unquote(name), attributes, children)
end
end
end
end
def build(%_{} = struct) do
raise Protocol.UndefinedError,
protocol: @protocol,
value: struct,
description: """
Sax.Builder.Content doesn't know how to build this struct.
You can derive the implementation by specifying in the module.
@derive {
Sax.Builder.Content,
[name: "person",
attributes: [:gender, :telephone],
children: [:name]]
}
defstruct ...
"""
end
end
defimpl Sax.Builder, for: Tuple do
import Sax.XML, only: [characters: 1]
def build({type, _} = form)
when type in [:characters, :comment, :cdata, :reference],
do: form
def build({_name, _attributes, _content} = form), do: form
def build(other) do
raise Protocol.UndefinedError,
protocol: @protocol,
value: other,
description: "cannot build content with tuple"
end
end
defimpl Sax.Builder, for: BitString do
import Sax.XML, only: [characters: 1]
def build(binary) when is_binary(binary) do
characters(binary)
end
def build(bitstring) do
raise Protocol.UndefinedError,
protocol: @protocol,
value: bitstring,
description: "cannot build content with a bitstring"
end
end
defimpl Sax.Builder, for: Atom do
import Sax.XML, only: [characters: 1]
def build(nil), do: characters("")
def build(value) do
value
|> Atom.to_string()
|> characters()
end
end
defimpl Sax.Builder, for: Integer do
import Sax.XML, only: [characters: 1]
def build(value) do
value
|> Integer.to_string()
|> characters()
end
end
defimpl Sax.Builder, for: Float do
import Sax.XML, only: [characters: 1]
def build(value) do
value
|> Float.to_string()
|> characters()
end
end
defimpl Sax.Builder, for: NaiveDateTime do
import Sax.XML, only: [characters: 1]
def build(value) do
value
|> NaiveDateTime.to_iso8601()
|> characters()
end
end
defimpl Sax.Builder, for: DateTime do
import Sax.XML, only: [characters: 1]
def build(value) do
value
|> DateTime.to_iso8601()
|> characters()
end
end
defimpl Sax.Builder, for: Date do
import Sax.XML, only: [characters: 1]
def build(value) do
value
|> Date.to_iso8601()
|> characters()
end
end
defimpl Sax.Builder, for: Time do
import Sax.XML, only: [characters: 1]
def build(value) do
value
|> Time.to_iso8601()
|> characters()
end
end