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.
Summary↑
add(eml, data, opts \\ []) | Adds content to matched elements |
compile!(eml, bindings \\ [], opts \\ []) | Same as |
compile(eml, bindings \\ [], opts \\ []) | Same as |
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(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 |
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 |
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 |
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 ↑
eml_node :: binary | Eml.Element.t | Eml.Parameter.t | Eml.Template.t
error :: {:error, term}
lang :: atom
path :: binary
funpackr_result :: binary | Eml.Parameter.t | Eml.Template.t | [binary | Eml.Parameter | Eml.Template.t]
Functions
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>"
Specs:
- compile(t, Eml.Template.bindings, Keyword.t) :: {:ok, Eml.Template.t} | error
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>"}
Specs:
- compile!(t, Eml.Template.bindings, Keyword.t) :: Eml.Template.t
Same as Eml.compile/3
, except that it raises an exception, instead of returning an
error tuple in case of an error.
Specs:
- element?(term) :: boolean
Checks if a term is a Eml.Element
struct.
Specs:
- empty?(term) :: boolean
Checks if a value is regarded as empty by Eml.
Specs:
- funpackr(t) :: funpackr_result
Extracts a value recursively from content or an element and flatten the results.
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
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"]
Specs:
Same as Eml.parse/2
, except that it raises an exception, instead of returning an
error tuple in case of an error.
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"}>]>]
Specs:
- render(t, Eml.Template.bindings, Keyword.t) :: {:ok, binary} | error
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 defaultEml.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 aretrue
(default) andfalse
.
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>"}
Specs:
- render!(t, Eml.Template.bindings, Keyword.t) :: binary
Same as Eml.render/3
, except that it raises an exception, instead of returning an
error tuple in case of an error.
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 "]>]
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"]>]>]
Specs:
- type(content) :: :content | :binary | :element | :template | :parameter | :undefined
Returns the type of content.
The types are :content
, :binary
, :element
, :template
, :parameter
, or :undefined
.
Specs:
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"
Specs:
- unpackr(t) :: unpackr_result
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"]
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>"
Macros
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
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()
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.
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>"