%%%------------------------------------------------------------------- %%% @author Gordon Guthrie %%% @copyright (C) 2009, Gordon Guthrie %%% @doc, %%% %%% @end %%% Created : 10 Sep 2009 by gordonguthrie@backawinner.gg %%%------------------------------------------------------------------- -module(erlmd). -include_lib("erlmd/include/types.hrl"). -export([conv/1, conv/2, conv_utf8/1, conv_file/2, conv_ast/1, conv_html/1, default_opts/0]). -import(lists, [flatten/1, reverse/1]). -define(SPACE, 32). -define(TAB, 9). -define(LF, 10). -define(CR, 13). -define(NBSP, 160). -define(AMP, $&, $a, $m, $p, $;). -define(COPY, $&, $c, $o, $p, $y, $;). %%% the lexer first lexes the input %%% make_lines does 2 passes: %%% * it chops the lexed strings into lines which it represents as a %%% list of lists %%% * it then types the lines into the following: %%% * normal lines %%% * reference style links %%% * reference style images %%% * special line types %%% - blank %%% - SETEXT header lines %%% - ATX header lines %%% - blockquote %%% - unordered lists %%% - ordered lists %%% - code blocks %%% - horizontal rules %%% the parser then does its magic interpolating the references as appropriate %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %%% Public API %%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% @doc Default options for conversion -spec default_opts() -> map(). default_opts() -> #{format => html}. %% @doc Convert Markdown using default options (HTML output) -spec conv(string()) -> string(). conv(Input) -> conv(Input, default_opts()). %% @doc Convert Markdown with options %% Options: %% #{format => html} - Convert to HTML (default) %% #{format => ast} - Return AST structure -spec conv(string() | document(), map()) -> string() | document(). conv(Input, #{format := ast}) -> conv_ast(Input); conv(Input, #{format := html}) -> conv_html(Input). %% @doc Convert Markdown string to AST -spec conv_ast(string()) -> document(). conv_ast(String) when is_list(String) -> Lex = lex(String), UntypedLines = make_lines(Lex), {TypedLines, Refs} = type_lines(UntypedLines), erlmd_ast:build(TypedLines, Refs). %% @doc Convert to HTML (accepts AST or Markdown string) -spec conv_html(document() | string()) -> string(). conv_html(AST) when is_record(AST, document) -> erlmd_html:render(AST); conv_html(String) when is_list(String) -> conv_html(conv_ast(String)). %% @doc Convert UTF-8 encoded Markdown to HTML -spec conv_utf8(list()) -> list(). conv_utf8(Utf8) -> Str = xmerl_ucs:from_utf8(Utf8), Res = conv(Str), % Uses default HTML output xmerl_ucs:to_utf8(Res). %% @doc Convert Markdown file to HTML file conv_file(FileIn, FileOut) -> case file:open(FileIn, [read]) of {ok, Device} -> Input = get_all_lines(Device,[]), Output = conv(Input), % Uses default HTML output write(FileOut, Output); _ -> error end. get_all_lines(Device, Accum) -> case io:get_line(Device,"") of eof -> file:close(Device), Accum; Line -> get_all_lines(Device,Accum ++ Line) end. write(File, Text) -> _Return=filelib:ensure_dir(File), case file:open(File, [write]) of {ok, Id} -> io:fwrite(Id, "~s~n", [Text]), file:close(Id); _ -> error end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %%% Make the lines from the raw tokens %%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% make_lines(Tokens) -> ml1(Tokens, [], []). ml1([], [], A2) -> reverse(A2); ml1([], A1, A2) -> ml1([], [], [reverse(A1) | A2]); ml1([{{lf, _}, _} = H | T], A1, A2) -> ml1(T, [], [ml2(H, A1) | A2]); ml1([H | T], A1, A2) -> ml1(T, [H | A1], A2). ml2(H, List) -> reverse([H | List]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% %%% Process the lines and give each line a type. The valid types are: %%% * normal line %%% * reference style links %%% * reference style images %%% * special line types %%% - blank %%% - SETEXT header lines %%% - ATX header lines %%% - unordered lists (including code blocks) %%% - ordered lists (including code blocks) %%% - blockquotes %%% - code blocks %%% - horizontal rules %%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% type_lines(Lines) -> {Refs, TypedLines} = t_l1(Lines, [], []), % io:format("TypedLines before stripping ~p~n", [TypedLines]), {strip_lines(TypedLines), Refs}. t_l1([], A1, A2) -> {A1, reverse(A2)}; %% this clause extracts URL and Image refs %% (it is the only one that uses A1 and A2... %% inlines can have up to 3 spaces before it t_l1([[{{ws, sp}, _}, {{inline, open}, _} | T1] = H | T2], A1, A2) -> t_inline(H, T1, T2, A1, A2); t_l1([[{{ws, tab}, _}, {{inline, open}, _} | _] = H | T2], A1, A2) -> t_l1(T2, A1, [type_ws(H) | A2]); t_l1([[{{ws, comp}, W}, {{inline, open}, _} | T1] = H | T2], A1, A2) -> case gt(W, 4) of {true, _R} -> t_l1(T2, A1, [type_ws(H) | A2]); false -> t_inline(H, T1, T2, A1, A2) end; t_l1([[{{inline, open}, _} | T1] = H | T2], A1, A2) -> t_inline(H, T1, T2, A1, A2); %% types setext lines t_l1([[{{md, eq}, _} | _T] = H | T], A1, A2) -> t_l1(T, A1, [type_setext_h1(H) | A2]); %% NOTE 1: generates a ul as the default not a normal line %% NOTE 2: depending on the context this might generate an

header %% or an
%% NOTE 3: space - is typed to a bullet down in