Eml

Eml stands for Elixir Markup Language. It provides a flexible and modular toolkit for generating, parsing and manipulating markup, written in the Elixir programming language. It’s main focus is html, but other markup languages could be implemented as well.

To start off:

This piece of code

Eml.render! eml do
  name = "Vincent"
  age  = 36

  div class: "person" do
    div [], [ span([], "name: "), span([], name) ]
    div [], [ span([], "age: "), span([], age) ]
  end
end

produces

<div class='person'>
  <div>
    <span>name: </span>
    <span>Vincent</span>
  </div>
  <div>
    <span>age: </span>
    <span>36</span>
  </div>
</div>

The functions and macro’s in the Eml module are the bread and butter of the Eml library.

Source

Summary

add(eml, data, opts \\ [])

Adds content to matched elements

compile!(eml, bindings \\ [], opts \\ [])

Same as Eml.compile/3, except that it raises an exception, instead of returning an error tuple in case of an error

compile(eml, bindings \\ [], opts \\ [])

Same as Eml.render/3 except that it doesn’t return an error when not all parameters are bound and always returns a template

defeml(call, do_block)

Define a function that produces eml

defhtml(call, do_block)

Define a function that produces html

element?(element)

Checks if a term is a Eml.Element struct

eml(opts, block \\ [])

Define eml content

empty?(element)

Checks if a value is regarded as empty by Eml

funpackr(eml)

Extracts a value recursively from content or an element and flatten the results

member?(eml, opts)

Returns true if there’s at least one node matches the provided options, returns false otherwise

parse!(data, lang \\ Eml.Language.Html)

Same as Eml.parse/2, except that it raises an exception, instead of returning an error tuple in case of an error

parse(data, lang \\ Eml.Language.Html)

Parses data and converts it to eml

precompile(name \\ nil, opts)

Define a function that compiles eml to a template during compile time

remove(eml, opts \\ [])

Removes matched nodes from the eml tree

render!(eml, bindings \\ [], opts \\ [])

Same as Eml.render/3, except that it raises an exception, instead of returning an error tuple in case of an error

render(eml, bindings \\ [], opts \\ [])

Renders eml content to the specified language, which is html by default

select(eml, opts \\ [])

Selects content from arbritary eml

transform(eml, fun, lang \\ Eml.Language.Native)

Recursively transforms eml content

type(content)

Returns the type of content

unpack(element)

Extracts a value from content (which is always a list) or an element

unpackr(element)

Extracts a value recursively from content or an element

update(eml, fun, opts \\ [])

Updates matched nodes

Types

error :: {:error, term}

lang :: atom

path :: binary

funpackr_result :: binary | Eml.Parameter.t | Eml.Template.t | [binary | Eml.Parameter | Eml.Template.t]

Functions

add(eml, data, opts \\ [])

Specs:

Adds content to matched elements.

It traverses and returns the complete eml tree. Nodes are matched depending on the provided options.

Those options can be:

  • :tag - match element by tag (atom)
  • :id - match element by id (binary)
  • :class - match element by class (binary)
  • :at - add new content at begin or end of existing

content, default is :end (:begin | :end)

When :tag, :id, or :class are combined, only element is selected that satisfies all conditions.

Examples:

iex> e = eml do
...>   div do
...>     span [id: "inner1", class: "inner"], "hello "
...>     span [id: "inner2", class: "inner"], "world"
...>   end
...> end
[#div<[#span<%{id: "inner1", class: "inner"} ["hello "]>,
  #span<%{id: "inner2", class: "inner"} ["world"]>]>]
iex> Eml.add(e, "dear ", id: "inner1")
[#div<[#span<%{id: "inner1", class: "inner"} ["hello dear "]>,
  #span<%{id: "inner2", class: "inner"} ["world"]>]>]
iex> Eml.add(e, "__", class: "inner", at: :begin)
[#div<[#span<%{id: "inner1", class: "inner"} ["__hello "]>,
  #span<%{id: "inner2", class: "inner"} ["__world"]>]>]
iex> Eml.add(e, (eml do: span "!"), tag: :div) |> Eml.render!()
"<div><span id='inner1' class='inner'>hello </span><span id='inner2' class='inner'>world</span><span>!</span></div>"
Source
compile(eml, bindings \\ [], opts \\ [])

Specs:

Same as Eml.render/3 except that it doesn’t return an error when not all parameters are bound and always returns a template.

Examples:

iex> t = Eml.compile (eml do: body([], h1([id: "main-title"], :the_title)))
{ :ok, #Template<[:the_title]> }
iex> Eml.render(t, bindings: [the_title: "The Title"])
{:ok, "<body><h1 id='main-title'>The Title</h1></body>"}
Source
compile!(eml, bindings \\ [], opts \\ [])

Specs:

Same as Eml.compile/3, except that it raises an exception, instead of returning an error tuple in case of an error.

Source
element?(element)

Specs:

  • element?(term) :: boolean

Checks if a term is a Eml.Element struct.

Source
empty?(element)

Specs:

  • empty?(term) :: boolean

Checks if a value is regarded as empty by Eml.

Source
funpackr(eml)

Specs:

Extracts a value recursively from content or an element and flatten the results.

Source
member?(eml, opts)

Specs:

Returns true if there’s at least one node matches the provided options, returns false otherwise.

In other words, returns true when the same select query would return a non-empty list.

See select/3 for a description of the provided options.

Examples:

iex> e = eml do
...>   div do
...>     span [id: "inner1", class: "inner"], "hello "
...>     span [id: "inner2", class: "inner"], "world"
...>   end
...> end
[#div<[#span<%{id: "inner1", class: "inner"} ["hello "]>,
  #span<%{id: "inner2", class: "inner"} ["world"]>]>]
iex> Eml.member?(e, id: "inner1")
true
iex> Eml.member?(e, class: "inner", id: "test")
false
iex> Eml.member?(e, pat: ~r/h.*o/)
true
Source
parse(data, lang \\ Eml.Language.Html)

Specs:

Parses data and converts it to eml

How the data is interpreted depends on the lang argument. The default value is `Eml.Language.Html’, which means that strings are parsed as html. The other language that is supported by default is Eml.Language.Native, which is for example used when setting content in an Eml.Element node. Appart from the provided language, this function performs some conversions on its own. Mainly flattening of lists and concatenating binaries in a list.

Examples:

iex> Eml.parse("<body><h1 id='main-title'>The title</h1></body>")
[#body<[#h1<%{id: "main-title"} ["The title"]>]>]

iex> Eml.parse([1, 2, 3,[4, 5, "6"], " ", true, " ", [false]], Eml.Language.Native)
["123456 true false"]
Source
parse!(data, lang \\ Eml.Language.Html)

Specs:

Same as Eml.parse/2, except that it raises an exception, instead of returning an error tuple in case of an error.

Source
remove(eml, opts \\ [])

Specs:

Removes matched nodes from the eml tree.

See update/3 for a description of the provided options.

Examples:

iex> e = eml do
...>   div do
...>     span [id: "inner1", class: "inner"], "hello "
...>     span [id: "inner2", class: "inner"], "world"
...>   end
...> end
[#div<[#span<%{id: "inner1", class: "inner"} ["hello "]>,
  #span<%{id: "inner2", class: "inner"} ["world"]>]>]
iex> Eml.remove(e, tag: :div)
[]
iex> Eml.remove(e, id: "inner1")
[#div<[#span<%{id: "inner2", class: "inner"} ["world"]>]>]
iex> Eml.remove(e, pat: ~r/.*/)
[#div<[#span<%{id: "inner1", class: "inner"}>,
  #span<%{id: "inner2", class: "inner"}>]>]
Source
render(eml, bindings \\ [], opts \\ [])

Specs:

Renders eml content to the specified language, which is html by default.

When the provided eml contains a template, you can bind its parameters by providing a Keyword list as the second argument where the keys are the parameter id’s.

The accepted options are:

  • :lang - The language to render to, by default Eml.Language.Html
  • :quote - The type of quotes used for attribute values. Accepted values are :single (default) and :double.
  • :escape - Escape &, < and > in attribute values and content to HTML entities. Accepted values are true (default) and false.

Examples:

iex> Eml.render (eml do: body([], h1([id: "main-title"], "A title")))
{:ok, "<body><h1 id='main-title'>A title</h1></body>"}

iex> Eml.render (eml do: body([], h1([id: "main-title"], "A title"))), quote: :double
{:ok, "<body><h1 id="main-title">A title</h1></body>"}

iex> Eml.render (eml do: p([], "Tom & Jerry"))
{:ok, "<p>Tom & Jerry</p>"}
Source
render!(eml, bindings \\ [], opts \\ [])

Specs:

Same as Eml.render/3, except that it raises an exception, instead of returning an error tuple in case of an error.

Source
select(eml, opts \\ [])

Selects content from arbritary eml.

It will traverse the complete eml tree, so all nodes are evaluated. There is however currently no way to select templates or parameters.

Nodes are matched depending on the provided options.

Those options can be:

  • :tag - match element by tag (atom)
  • :id - match element by id (binary)
  • :class - match element by class (binary)
  • :pat - match binary content by regular expression (RegEx.t)
  • :parent - when set to true, selects the parent node of the matched node (boolean)

When :tag, :id, or :class are combined, only element is selected that satisfies all conditions.

When the :pat options is used, :tag, :id and :class will be ignored.

Examples:

iex> e = eml do
...>   div do
...>     span [id: "inner1", class: "inner"], "hello "
...>     span [id: "inner2", class: "inner"], "world"
...>   end
...> end
[#div<[#span<%{id: "inner1", class: "inner"} ["hello "]>,
  #span<%{id: "inner2", class: "inner"} ["world"]>]>]
iex> Eml.select(e, id: "inner1")
[#span<%{id: "inner1", class: "inner"} ["hello "]>]
iex> Eml.select(e, class: "inner")
[#span<%{id: "inner1", class: "inner"} ["hello "]>,
 #span<%{id: "inner2", class: "inner"} ["world"]>]
iex> Eml.select(e, class: "inner", id: "test")
[]
iex> Eml.select(e, pat: ~r/h.*o/)
["hello "]
iex> Eml.select(e, pat: ~r/H.*o/, parent: true)
[#span<%{id: "inner1", class: "inner"} ["hello "]>]
Source
transform(eml, fun, lang \\ Eml.Language.Native)

Specs:

Recursively transforms eml content.

This is the most low level operation provided by Eml for manipulating eml nodes. For example, update/3 and remove/2 are implemented by using this function.

It accepts any eml and traverses all nodes of the provided eml tree. The provided transform function will be evaluated for every node transform/3 encounters. Parent nodes will be transformed before their children. Child nodes of a parent will be evaluated before moving to the next sibling.

When the provided function returns nil, the node will be removed from the eml tree. Any other returned value will be evaluated by Eml.parse!/2 in order to guarantee valid eml.

Note that because parent nodes are evaluated before their children, no children will be evaluated if the parent is removed.

Accepts a lang as optional 3rd argument, in order to specify how transformed data should be interpreted, defaults to Eml.Language.Native

Examples:

iex> e = eml do
...>   div do
...>     span [id: "inner1", class: "inner"], "hello "
...>     span [id: "inner2", class: "inner"], "world"
...>   end
...> end
[#div<[#span<%{id: "inner1", class: "inner"} ["hello "]>,
  #span<%{id: "inner2", class: "inner"} ["world"]>]>]
iex> Eml.transform(e, fn x -> if Element.has?(x, tag: :span), do: "matched", else: x end)
[#div<["matched", "matched"]>]
iex> Eml.transform(e, fn x ->
...> IO.puts(inspect x)
...> x end)
#div<[#span<%{id: "inner1", class: "inner"} ["hello "]>, #span<%{id: "inner2", class: "inner"} ["world"]>]>
#span<%{id: "inner1", class: "inner"} ["hello "]>
"hello "
#span<%{id: "inner2", class: "inner"} ["world"]>
"world"
[#div<[#span<%{id: "inner1", class: "inner"} ["hello "]>,
  #span<%{id: "inner2", class: "inner"} ["world"]>]>]
Source
type(content)

Specs:

  • type(content) :: :content | :binary | :element | :template | :parameter | :undefined

Returns the type of content.

The types are :content, :binary, :element, :template, :parameter, or :undefined.

Source
unpack(element)

Specs:

  • unpack(t) :: t

Extracts a value from content (which is always a list) or an element

Examples

iex> unpack ["42"]
"42"

iex> unpack 42
42

iex> unpack (eml do: div([], "hallo"))
#div<["hallo"]>

iex> unpack unpack (eml do: div([], "hallo"))
"hallo"
Source
unpackr(element)

Specs:

Extracts a value recursively from content or an element

Examples

iex> Eml.unpackr eml do: div([], 42)
"42"

iex> Eml.unpackr eml do: div([], [span([], "Hallo"), span([], " world")])
["Hallo", " world"]
Source
update(eml, fun, opts \\ [])

Specs:

Updates matched nodes.

When nodes are matched, the provided function will be evaluated with the matched node as argument.

When the provided function returns nil, the node will be removed from the eml tree. Any other returned value will be evaluated by Eml.parse!/2 in order to guarantee valid eml.

Nodes are matched depending on the provided options.

Those options can be:

  • :tag - match element by tag (atom)
  • :id - match element by id (binary)
  • :class - match element by class (binary)
  • :pat - match binary content by regular expression (RegEx.t)
  • :parent - when set to true, selects the parent node of the matched node (boolean)

When :tag, :id, or :class are combined, only element is selected that satisfies all conditions.

When the :pat options is used, :tag, :id and :class will be ignored.

Examples:

iex> e = eml do
...>   div do
...>     span [id: "inner1", class: "inner"], "hello "
...>     span [id: "inner2", class: "inner"], "world"
...>   end
...> end
[#div<[#span<%{id: "inner1", class: "inner"} ["hello "]>,
  #span<%{id: "inner2", class: "inner"} ["world"]>]>]
iex> Eml.update(e, fn m -> Element.id(m, "outer") end, tag: :div)
[#div<%{id: "outer"}
 [#span<%{id: "inner1", class: "inner"} ["hello "]>,
  #span<%{id: "inner2", class: "inner"} ["world"]>]>]
iex> Eml.update(e, fn m -> Element.id(m, "outer") end, id: "inner2", parent: true)
[#div<%{id: "outer"}
 [#span<%{id: "inner1", class: "inner"} ["hello "]>,
  #span<%{id: "inner2", class: "inner"} ["world"]>]>]
iex> Eml.update(e, fn s -> String.upcase(s) end, pat: ~r/.+/) |> Eml.render!()
"<div><span id='inner1' class='inner'>HELLO </span><span id='inner2' class='inner'>WORLD</span></div>"
Source

Macros

defeml(call, do_block)

Define a function that produces eml.

This macro is provided both for convenience and to be able to show intention of code.

This:

defeml mydiv(content), do: div(%{}, content)

is effectively the same as:

def mydiv(content), do: eml do div(%{}, content) end
Source
defhtml(call, do_block)

Define a function that produces html.

This macro is provided both for convenience and to be able to show intention of code.

This:

defhtml mydiv(content), do: div(%{}, content)

is effectively the same as:

def mydiv(content), do: eml do div(%{}, content) end |> Eml.render()
Source
eml(opts, block \\ [])

Define eml content.

Just like in other Elixir blocks, evaluates all expressions and returns the last. Code inside an eml block is just regular Elixir code. The purpose of the eml/2 macro is to make it more convenient to render eml.

It does this by doing two things:

  • Provide a lexical scope where all element macro’s are imported to
  • Parse the last expression of the block in order to guarantee valid eml content

To illustrate, the expressions below all produce the same output:

  • eml do: div([], 42)
  • Eml.Element.new(:div, %{}, 42) |> Eml.parse!(Eml.Language.Native)
  • Eml.Element.Html.div([], 42) |> Eml.parse!(Eml.Language.Native)

Note that since the Elixir Kernel module by default imports the div/2 function in to the global namespace, this function is inside an eml block only available as Kernel.div/2.

Instead of defining a do block, you can also provide a path to a file with eml content. See Eml.precompile/2 for an example with an external file.

Source
precompile(name \\ nil, opts)

Define a function that compiles eml to a template during compile time.

The function that this macro defines accepts optionally a bindings object as argument for binding values to parameters. Note that because the code in the do block is evaluated at compile time, it’s not possible to call other functions from the same module.

Instead of defining a do block, you can also provide a path to a file with eml content.

When you ommit the name, this macro can also be used to precompile a block or file inside any function.

Example:

iex> File.write! "test.eml.exs", "div [id: "name"], :name"
iex> defmodule MyTemplates do
...>   use Eml
...>
...>   precompile test1 do
...>     prefix = "fruit"
...>     div do
...>       span [class: "prefix"], prefix
...>       span [class: "content"], :fruit
...>     end
...>   end
...>
...>   precompile from_file, file: "test.eml.exs"
...>
...>   defhtml test2 do
...>     precompiled = precompile do
...>       # Everything inside this block is evaluated at compile time
...>       p [], :fruit
...>     end
...>
...>     # the rest of the function is evaluated at runtime
...>     body do
...>       bind precompiled, fruit: "Strawberry"
...>     end
...>   end
...> end
iex> File.rm! "test.eml.exs"
iex> MyTemplates.test1
#Template<[:fruit]>
iex> MyTemplates.test fruit: "lemon"
"<div><span class='prefix'>fruit</span><span class='content'>lemon</span></div>"
iex> MyTemplates.from_file name: "Vincent"
"<div id='name'>Vincent</div>"
iex> MyTemplated.test2
"<body><p>Strawberry</p></body>"
Source