Packages
earmark
1.4.45
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).
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/earmark_parser/ast_renderer.ex
defmodule Earmark.Parser.AstRenderer do
alias Earmark.Parser.Block
alias Earmark.Parser.Context
alias Earmark.Parser.Options
import Context, only: [clear_value: 1, modify_value: 2, prepend: 2, prepend: 3]
import Earmark.Parser.Ast.Emitter
import Earmark.Parser.Ast.Inline, only: [convert: 3]
import Earmark.Parser.Helpers.AstHelpers
import Earmark.Parser.Ast.Renderer.{HtmlRenderer, FootnoteRenderer, TableRenderer}
@moduledoc false
def render(blocks, context = %Context{options: %Options{}}, loose? \\ true) do
_render(blocks, context, loose?)
end
defp _render(blocks, context, loose?)
defp _render([], context, _loose?), do: context
defp _render([block | blocks], context, loose?) do
context1 = render_block(block, clear_value(context), loose?)
_render(blocks, prepend(context1, context), loose?)
end
defp render_block(block, context, loose?)
#############
# Paragraph #
#############
defp render_block(%Block.Para{lnb: lnb, lines: lines, attrs: attrs} = para, context, _loose?) do
context1 = convert(lines, lnb, context)
value = context1.value |> Enum.reverse()
ast =
emit("p", value, attrs)
|> annotate(para)
prepend(context, ast, context1)
end
########
# Html #
########
defp render_block(%Block.Html{annotation: annotation, html: html}, context, _loose?) do
render_html_block(html, context, annotation)
end
defp render_block(%Block.HtmlOneline{annotation: annotation, html: html}, context, _loose?) do
render_html_oneline(html, context, annotation)
end
defp render_block(%Block.HtmlComment{lines: lines}, context, _loose?) do
lines1 = lines |> Enum.map(&render_html_comment_line/1)
prepend(context, emit(:comment, lines1, [], %{comment: true}))
end
#########
# Ruler #
#########
defp render_block(%Block.Ruler{type: "-", attrs: attrs}, context, _loose?) do
prepend(context, emit("hr", [], merge_attrs(attrs, %{"class" => "thin"})))
end
defp render_block(%Block.Ruler{type: "_", attrs: attrs}, context, _loose?) do
prepend(context, emit("hr", [], merge_attrs(attrs, %{"class" => "medium"})))
end
defp render_block(%Block.Ruler{type: "*", attrs: attrs}, context, _loose?) do
prepend(context, emit("hr", [], merge_attrs(attrs, %{"class" => "thick"})))
end
###########
# Heading #
###########
defp render_block(
%Block.Heading{lnb: lnb, level: level, content: content, attrs: attrs},
context,
_loose?
) do
context1 = convert(content, lnb, clear_value(context))
modify_value(
context1,
fn _ ->
[
emit(
"h#{level}",
context1.value |> Enum.reverse(),
attrs)
]
end
)
end
##############
# Blockquote #
##############
defp render_block(%Block.BlockQuote{blocks: blocks, attrs: attrs}, context, _loose?) do
context1 = render(blocks, clear_value(context))
modify_value(context1, fn ast ->
[emit("blockquote", ast, attrs)]
end)
end
#########
# Table #
#########
defp render_block(
%Block.Table{lnb: lnb, header: header, rows: rows, alignments: aligns, attrs: attrs},
context,
_loose?
) do
{rows_ast, context1} = render_rows(rows, lnb, aligns, context)
{rows_ast1, context2} =
if header do
{header_ast, context3} = render_header(header, lnb, aligns, context1)
{[header_ast | rows_ast], context3}
else
{rows_ast, context1}
end
prepend(
clear_value(context2),
emit("table", rows_ast1, attrs)
)
end
########
# Code #
########
defp render_block(
%Block.Code{language: language, attrs: attrs} = block,
context = %Context{options: options},
_loose?
) do
classes =
if language && language != "",
do: [code_classes(language, options.code_class_prefix)],
else: []
lines = render_code(block)
prepend(
context,
emit("pre", emit("code", lines, classes), attrs)
)
end
#########
# Lists #
#########
@start_rgx ~r{\A\d+}
defp render_block(
%Block.List{type: type, bullet: bullet, blocks: items, attrs: attrs},
context,
_loose?
) do
context1 = render(items, clear_value(context))
start_map =
case bullet && Regex.run(@start_rgx, bullet) do
nil -> %{}
["1"] -> %{}
[start1] -> %{start: _normalize_start(start1)}
end
prepend(
context,
emit(to_string(type), context1.value, merge_attrs(attrs, start_map)),
context1
)
end
# format a spaced list item
defp render_block(
%Block.ListItem{blocks: blocks, attrs: attrs, loose?: loose?},
context,
_loose?
) do
context1 = render(blocks, clear_value(context), loose?)
prepend(
context,
emit("li", context1.value, attrs),
context1
)
end
########
# Text #
########
defp render_block(%Block.Text{line: line, lnb: lnb}, context, loose?) do
context1 = convert(line, lnb, clear_value(context))
ast = context1.value |> Enum.reverse()
if loose? do
modify_value(context1, fn _ -> [emit("p", ast)] end)
else
modify_value(context1, fn _ -> ast end)
end
end
##################
# Footnote Block #
##################
@empty_set MapSet.new([])
defp render_block(%Block.FnList{}=fn_list, context, _loose?) do
if MapSet.equal?(context.referenced_footnote_ids, @empty_set) do
context
else
render_defined_fns(fn_list, context)
end
end
#######################################
# Isolated IALs are rendered as paras #
#######################################
defp render_block(%Block.Ial{verbatim: verbatim}, context, _loose?) do
prepend(context, emit("p", "{:#{verbatim}}"))
end
####################
# IDDef is ignored #
####################
defp render_block(%Block.IdDef{}, context, _loose?), do: context
# Helpers
# -------
# Seems to be dead code but as GFM list handling is broken maybe we have a bug
# that does not call this correctly, anyhow AST triplets do not exits anymore
# so this code would break if called
# defp _fix_text_lines(ast, loose?)
# defp _fix_text_lines(ast, false), do: Enum.map(ast, &_fix_tight_text_line/1)
# defp _fix_text_lines(ast, true), do: Enum.map(ast, &_fix_loose_text_line/1)
# defp _fix_loose_text_line(node)
# defp _fix_loose_text_line({:text, _, lines}), do: emit("p", lines)
# defp _fix_loose_text_line(node), do: node
# defp _fix_tight_text_line(node)
# defp _fix_tight_text_line({:text, _, lines}), do: lines
# defp _fix_tight_text_line(node), do: node
# INLINE CANDIDATE
defp _normalize_start(start) do
case String.trim_leading(start, "0") do
"" -> "0"
start1 -> start1
end
end
end
# SPDX-License-Identifier: Apache-2.0