Packages
earmark
1.2.4
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/helpers/link_parser.ex
defmodule Earmark.Helpers.LinkParser do
import Earmark.Helpers.LeexHelpers, only: [tokenize: 2]
import Earmark.Helpers.YeccHelpers, only: [parse!: 2]
import Earmark.Helpers.StringHelpers, only: [behead: 2]
# Hopfully this will go away in v1.3
# **********************************
#
# Right now it needs to parse the url part of strings according to the following grammar
#
# url -> ( inner_url )
# url -> ( inner_url title )
#
# inner_url -> ( inner_url )
# inner_url -> [ inner_url ]
# inner_url -> url_char*
#
# url_char -> . - quote - ( - ) - [ - ]
#
# title -> quote .* quote ;; not LALR-k here
#
# quote -> "
# quote -> ' ;; yep allowing '...." for now
#
# non_quote -> . - quote
@doc false
def parse_link( src, lnb ) do
with {link_text, parsed_text} <- parse!(src, lexer: :link_text_lexer, parser: :link_text_parser),
beheaded <- behead(src, to_string(parsed_text)),
tokens <- tokenize(beheaded, with: :link_text_lexer) do
p_url(tokens, lnb) |> make_result(to_string(link_text), to_string(parsed_text))
end
end
defp p_url([{:open_paren, _}|ts], lnb), do: url(ts, {[], [], nil}, [:close_paren], lnb)
defp p_url(_, _), do: nil
# push one level
defp url([{:open_paren, text}|ts], result, needed, lnb), do: url(ts, add(result, text), [:close_paren|needed], lnb)
# pop last level
defp url([{:close_paren, _}|_], result, [:close_paren], _lnb), do: result
# pop inner level
defp url([{:close_paren, text}|ts], result, [:close_paren|needed], lnb), do: url(ts, add(result, text), needed, lnb)
# A quote on level 0 -> bailing out if there is a matching quote
defp url(ts_all = [{:open_title, text}|ts], result, [:close_paren], lnb) do
case bail_out_to_title(ts_all, result, lnb) do
nil -> url(ts, add(result, text), [:close_paren], lnb)
res -> res
end
end
# All these are just added to the url
defp url([{:open_bracket, text}|ts], result, needed, lnb), do: url(ts, add(result, text), needed, lnb)
defp url([{:close_bracket, text}|ts], result, needed, lnb), do: url(ts, add(result, text), needed, lnb)
defp url([{:any_quote, text}|ts], result, needed, lnb), do: url(ts, add(result, text), needed, lnb)
defp url([{:verbatim, text}|ts], result, needed, lnb), do: url(ts, add(result, text), needed, lnb)
defp url([{:escaped, text}|ts], result, needed, lnb), do: url(ts, add(result, text), needed, lnb)
# That is not good, actually this is not a legal url part of a link
defp url(_, _, _, _), do: nil
defp bail_out_to_title(ts, result, lnb) do
with remaining_text <- ts |> Enum.map(&text_of_token/1) |> Enum.join("") do
case title(remaining_text, lnb) do
nil -> nil
{title_text, inner_title, messages} -> add_title( result, {title_text, inner_title, messages} )
end
end
end
defp text_of_token(token)
defp text_of_token({:escaped, text}), do: "\\#{text}"
defp text_of_token({_, text}), do: text
# sic!!! Greedy and not context aware, matching '..." and "...' for backward comp
@title_end_rgx ~r{\s+['"](.*)['"](?=\))}
defp title(remaining_text, lnb) do
case Regex.run(@title_end_rgx, remaining_text) do
nil -> nil
[parsed, inner] -> {parsed, inner, deprecations(parsed, lnb)}
end
end
defp deprecations(string, lnb) do
with stripped <- String.trim(string),
opening <- String.first(stripped),
closing <- String.last(stripped), do: _deprecations(opening, closing, lnb)
end
defp _deprecations(opening, closing, _lnb) when opening == closing, do: []
defp _deprecations(_opening, _closing, lnb) do
[ {:warning, lnb, "deprecated, mismatching quotes will not be parsed as matching in v1.3"} ]
end
defp make_result(nil, _, _), do: nil
defp make_result({parsed, url, title}, text, img), do: make_result({parsed, url, title, []}, text, img)
defp make_result({parsed, url, title, messages}, link_text, "!" <> _) do
{ "})", link_text, list_to_text(url), title, messages }
end
defp make_result({parsed, url, title, messages}, link_text, _) do
{ "[#{link_text}](#{list_to_text(parsed)})", link_text, list_to_text(url), title, messages }
end
defp add({parsed_text, url_text, nil}, text), do: {[text|parsed_text], [text|url_text], nil}
defp add_title({parsed_text, url_text, _}, {parsed,inner,messages}), do: {[parsed|parsed_text], url_text, inner, messages}
defp list_to_text(lst), do: lst |> Enum.reverse() |> Enum.join("")
end