Eml

Eml makes markup a first class citizen in Elixir. It provides a flexible and modular toolkit for generating, parsing and manipulating markup. It’s main focus is html, but other markup languages could be implemented as well.

To start off:

This piece of code

use Eml.Language.HTML

name = "Vincent"
age  = 36

div class: "person" do
  div do
    span "name: "
    span name
  end
  div do
    span "age: "
    span age
  end
end |> Eml.render

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 cover most of Eml’s public API.

Source

Summary

__using__()

Import defeml, defhtml and precompile macro’s

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

Adds content to matched elements

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

Same as Eml.render/3 except that it returns a template that might conatain unbound parameters

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

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)

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 \\ [])

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

select(eml, opts \\ [])

Selects content from arbritary eml

to_content(data, acc \\ [], insert_at \\ :begin)

Converts data to eml content by using the Eml.Data protocol

transform(eml, fun)

Recursively transforms eml content

type(node)

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

content :: [t]

transformable :: t | [t]

lang :: module

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 elements are selected that satisfy all conditions.

Examples:

iex> e = div do
...>   span [id: "inner1", class: "inner"], "hello "
...>   span [id: "inner2", class: "inner"], "world"
...> 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, 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 returns a template that might conatain unbound parameters.

In case of error, raises an Eml.CompileError exception.

Examples:

iex> t = Eml.compile(body(h1([id: "main-title"], :the_title)))
#Template<[:the_title]>
iex> t.chunks
["<body><h1 id='main-title'>", #param:the_title, "</h1></body>"]
iex> Eml.render(t, the_title: "The Title")
"<body><h1 id='main-title'>The Title</h1></body>"
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 = div do
...>   span [id: "inner1", class: "inner"], "hello "
...>   span [id: "inner2", class: "inner"], "world"
...> 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.

In case of error, raises an Eml.ParseError exception.

Examples:

iex> Eml.parse("<body><h1 id='main-title'>The title</h1></body>")
[#body<[#h1<%{id: "main-title"} ["The title"]>]>]
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 = div do
...>   span [id: "inner1", class: "inner"], "hello "
...>   span [id: "inner2", class: "inner"], "world"
...> 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.

In case of error, raises an Eml.CompileError exception.

Examples:

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

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

iex> Eml.render(p "Tom & Jerry")
"<p>Tom & Jerry</p>"
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 elements are selected that satisfy all conditions.

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

Examples:

iex> e = div do
...>   span [id: "inner1", class: "inner"], "hello "
...>   span [id: "inner2", class: "inner"], "world"
...> 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
to_content(data, acc \\ [], insert_at \\ :begin)

Specs:

Converts data to eml content by using the Eml.Data protocol.

It also concatenates binaries and flatten lists to ensure the result is valid content.

Example

iex> Eml.to_content(["1", 2, [3], " ", ["miles"]])
["123 miles"]

You can also use this function to add data to existing content:

iex> Eml.to_content(42, [" is the number"], :begin)
["42 is the number"]

iex> Eml.to_content(42, ["the number is "], :end)
["the number is 42"]
Source
transform(eml, fun)

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.to_content/3 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.

Examples:

iex> e = div do
...>   span [id: "inner1", class: "inner"], "hello "
...>   span [id: "inner2", class: "inner"], "world"
...> 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(node)

Specs:

  • type(t) :: :string | :element | :template | :parameter | :undefined

Returns the type of content.

The types are :string, :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> Eml.unpack ["42"]
"42"

iex> Eml.unpack 42
42

iex> Eml.unpack(div "hallo")
"hallo"

iex> Eml.unpack Eml.unpack(div(span("hallo")))
"hallo"
Source
unpackr(element)

Specs:

Extracts a value recursively from content or an element

Examples

iex> Eml.unpackr div(span(42))
"42"

iex> Eml.unpackr 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.to_content/3 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 elements are selected that satisfy all conditions.

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

Examples:

iex> e = div do
...>   span [id: "inner1", class: "inner"], "hello "
...>   span [id: "inner2", class: "inner"], "world"
...> 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

__using__()

Import defeml, defhtml and precompile macro’s.

Invoking it translates to:

import Eml, only: [defeml: 2, defhtml: 2, precompile: 1, precompile: 2]
Source
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 
  use Eml.Language.HTML
  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
  use Eml.Language.HTML
  div(content) |> Eml.render
end
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