Packages
earmark
0.1.12
1.5.0-pre1
retired
1.5.0-pre
retired
1.4.49
retired
1.4.48
retired
1.4.47
retired
1.4.46
retired
1.4.45
retired
1.4.44
retired
1.4.43
retired
1.4.42
retired
1.4.41
retired
1.4.40
retired
1.4.39
retired
1.4.38
retired
1.4.37
retired
1.4.36
retired
1.4.35
retired
1.4.34
retired
1.4.33
retired
1.4.32
retired
1.4.31
retired
1.4.30
retired
1.4.29
retired
1.4.28
retired
1.4.27
retired
1.4.26
retired
1.4.25
retired
1.4.24
retired
1.4.23
retired
1.4.22
retired
1.4.21
retired
1.4.20
retired
1.4.19
retired
1.4.18
retired
1.4.17
retired
1.4.16
retired
1.4.16-pre2
retired
1.4.16-pre1
retired
1.4.16-pre
retired
1.4.15
retired
1.4.14
retired
1.4.13
retired
1.4.12
retired
1.4.10
retired
1.4.9
retired
1.4.8
retired
1.4.7
retired
1.4.6
retired
1.4.5
retired
1.4.4
retired
1.4.3
retired
1.4.2
retired
1.4.1
retired
1.4.0
retired
1.3.6
retired
1.3.5
retired
1.3.4
retired
1.3.3
retired
1.3.2
retired
1.3.1
retired
1.3.0
retired
1.2.6
retired
1.2.5
retired
1.2.4
retired
1.2.3
retired
1.2.2
retired
1.2.1
retired
1.2.0
retired
1.1.1
retired
1.1.0
retired
1.0.3
retired
1.0.2
retired
1.0.1
retired
1.0.0
retired
0.2.1
retired
0.2.0
retired
0.1.19
retired
0.1.18
retired
0.1.17
retired
0.1.16
retired
0.1.15
retired
0.1.14
retired
0.1.13
retired
0.1.12
retired
0.1.10
retired
0.1.9
retired
0.1.8
retired
0.1.7
retired
0.1.6
retired
0.1.5
retired
0.1.4
retired
0.1.3
retired
0.1.2
retired
0.1.1
retired
0.1.0
retired
Earmark is a pure-Elixir Markdown converter. It is intended to be used as a library (just call Earmark.as_html), but can also be used as a command-line tool (run mix escript.build first). Output generation is pluggable.
Retired package: Deprecated - Earmark is no longer maintained. Migrate to a replacement, for example MDEx (https://hex.pm/packages/mdex).
Current section
Files
Jump to
Current section
Files
lib/earmark/html_renderer.ex
defmodule Earmark.HtmlRenderer do
defmodule EarmarkError do
defexception [:message]
def exception(msg), do: %__MODULE__{message: msg}
end
alias Earmark.Block
import Earmark.Inline, only: [ convert: 2 ]
import Earmark.Helpers, only: [ escape: 1, behead: 2 ]
def render(blocks, context, map_func) do
map_func.(blocks, &(render_block(&1, context, map_func)))
|> IO.iodata_to_binary
end
#############
# Paragraph #
#############
def render_block(%Block.Para{lines: lines, attrs: attrs}, context, _mf) do
lines = convert(lines, context)
add_attrs("<p>#{lines}</p>\n", attrs)
end
########
# Html #
########
def render_block(%Block.Html{html: html}, _context, _mf) do
Enum.intersperse(html, ?\n)
end
def render_block(%Block.HtmlOther{html: html}, _context, _mf) do
Enum.intersperse(html, ?\n)
end
#########
# Ruler #
#########
def render_block(%Block.Ruler{type: "-", attrs: attrs}, _context, _mf) do
add_attrs("<hr/>\n", attrs, [{"class", ["thin"]}])
end
def render_block(%Block.Ruler{type: "_", attrs: attrs}, _context, _mf) do
add_attrs("<hr/>\n", attrs, [{"class", ["medium"]}])
end
def render_block(%Block.Ruler{type: "*", attrs: attrs}, _context, _mf) do
add_attrs("<hr/>\n", attrs, [{"class", ["thick"]}])
end
###########
# Heading #
###########
def render_block(%Block.Heading{level: level, content: content, attrs: attrs}, _context, _mf) do
html = "<h#{level}>#{content}</h#{level}>\n"
add_attrs(html, attrs)
end
##############
# Blockquote #
##############
def render_block(%Block.BlockQuote{blocks: blocks, attrs: attrs}, context, mf) do
body = render(blocks, context, mf)
html = "<blockquote>#{body}</blockquote>\n"
add_attrs(html, attrs)
end
#########
# Table #
#########
def render_block(%Block.Table{header: header, rows: rows, alignments: aligns, attrs: attrs}, context, _mf) do
cols = for align <- aligns, do: "<col align=\"#{align}\">\n"
html = [ add_attrs("<table>\n", attrs), "<colgroup>\n", cols, "</colgroup>\n" ]
if header do
html = [ html, "<thead>\n",
add_table_rows(context, [header], "th"),
"</thead>\n" ]
end
html = [ html, add_table_rows(context, rows, "td"), "</table>\n" ]
html
end
########
# Code #
########
def render_block(%Block.Code{lines: lines, language: language, attrs: attrs}, _context, _mf) do
class = if language, do: ~s{ class="#{language}"}, else: ""
tag = ~s[<pre><code#{class}>]
lines = lines |> Enum.map(&(escape(&1))) |> Enum.join("\n") |> String.strip
html = ~s[#{tag}#{lines}</code></pre>\n]
add_attrs(html, attrs)
end
#########
# Lists #
#########
def render_block(%Block.List{type: type, blocks: items, attrs: attrs}, context, mf) do
content = render(items, context, mf)
html = "<#{type}>\n#{content}</#{type}>\n"
add_attrs(html, attrs)
end
# format a single paragraph list item, and remove the para tags
def render_block(%Block.ListItem{blocks: blocks, spaced: false, attrs: attrs}, context, mf)
when length(blocks) == 1 do
content = render(blocks, context, mf)
content = Regex.replace(~r{</?p>}, content, "")
html = "<li>#{content}</li>\n"
add_attrs(html, attrs)
end
# format a spaced list item
def render_block(%Block.ListItem{blocks: blocks, attrs: attrs}, context, mf) do
content = render(blocks, context, mf)
html = "<li>#{content}</li>\n"
add_attrs(html, attrs)
end
##################
# Footnote Block #
##################
def render_block(%Block.FnList{blocks: footnotes}, context, mf) do
items = Enum.map(footnotes, fn(note) ->
[last_block | blocks] = Enum.reverse(note.blocks)
[last_line | lines] = Enum.reverse(last_block.lines)
last_line = ~s[#{last_line} <a href="#fnref:#{note.number}" title="return to article" class="reversefootnote">↩</a>]
last_block = put_in(last_block.lines, Enum.reverse([last_line | lines]))
blocks = Enum.reverse([last_block | blocks])
%Block.ListItem{attrs: "#fn:#{note.number}", type: :ol, blocks: blocks}
end)
html = render_block(%Block.List{type: :ol, blocks: items}, context, mf)
Enum.join([~s[<div class="footnotes">], "<hr>", html, "</div>"], "\n")
end
####################
# IDDef is ignored #
####################
def render_block(%Block.IdDef{}, _context, _mf) do
""
end
#####################################
# And here are the inline renderers #
#####################################
def br, do: "<br/>"
def codespan(text), do: ~s[<code class="inline">#{text}</code>]
def em(text), do: "<em>#{text}</em>"
def strong(text), do: "<strong>#{text}</strong>"
def link(url, text), do: ~s[<a href="#{url}">#{text}</a>]
def link(url, text, nil), do: ~s[<a href="#{url}">#{text}</a>]
def link(url, text, title), do: ~s[<a href="#{url}" title="#{title}">#{text}</a>]
def image(path, alt, nil) do
~s[<img src="#{path}" alt="#{alt}"/>]
end
def image(path, alt, title) do
~s[<img src="#{path}" alt="#{alt}" title="#{title}"/>]
end
def footnote_link(ref, backref, number), do: ~s[<a href="##{ref}" id="#{backref}" class="footnote" title="see footnote">#{number}</a>]
# Table rows
def add_table_rows(context, rows, tag) do
for row <- rows, do: "<tr>\n#{add_tds(context, row, tag)}\n</tr>\n"
end
def add_tds(context, row, tag) do
for col <- row, do: "<#{tag}>#{convert(col, context)}</#{tag}>"
end
##############################################
# add attributes to the outer tag in a block #
##############################################
def add_attrs(text, attrs_as_string, default_attrs \\ [])
def add_attrs(text, nil, []), do: text
def add_attrs(text, nil, default), do: add_attrs(text, "", default)
def add_attrs(text, attrs, default) do
default
|> Enum.into(HashDict.new)
|> expand(attrs)
|> attrs_to_string
|> add_to(text)
end
def expand(dict, attrs) do
cond do
Regex.match?(~r{^\s*$}, attrs) -> dict
match = Regex.run(~r{^\.(\S+)\s*}, attrs) ->
[ leader, class ] = match
Dict.update(dict, "class", [ class ], &[ class | &1])
|> expand(behead(attrs, leader))
match = Regex.run(~r{^\#(\S+)\s*}, attrs) ->
[ leader, id ] = match
Dict.update(dict, "id", [ id ], &[ id | &1])
|> expand(behead(attrs, leader))
match = Regex.run(~r{^(\S+)=\'([^\']*)'\s*}, attrs) -> #'
[ leader, name, value ] = match
Dict.update(dict, name, [ value ], &[ value | &1])
|> expand(behead(attrs, leader))
match = Regex.run(~r{^(\S+)=\"([^\"]*)"\s*}, attrs) -> #"
[ leader, name, value ] = match
Dict.update(dict, name, [ value ], &[ value | &1])
|> expand(behead(attrs, leader))
match = Regex.run(~r{^(\S+)=(\S+)\s*}, attrs) ->
[ leader, name, value ] = match
Dict.update(dict, name, [ value ], &[ value | &1])
|> expand(behead(attrs, leader))
:otherwise ->
raise EarmarkError, "Invalid Markdown attributes: {#{attrs}}"
end
end
def attrs_to_string(attrs) do
(for { name, value } <- attrs, do: ~s/#{name}="#{Enum.join(value, " ")}"/)
|> Enum.join(" ")
end
def add_to(attrs, text) do
Regex.replace(~r/>/, text, " #{attrs}>", global: false)
end
end