Compare commits
16 Commits
master
...
dev/html-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2202b8ba27 | ||
|
|
8496866b95 | ||
|
|
6d6f648ec6 | ||
|
|
e22bb03cb4 | ||
|
|
551cf8d616 | ||
|
|
910fe1689e | ||
|
|
25f12f4d2c | ||
|
|
b3cab0abc4 | ||
|
|
846000c1e7 | ||
|
|
448cf8f8d5 | ||
|
|
96bf70c47c | ||
|
|
90c66b5993 | ||
|
|
5efa674d40 | ||
|
|
bf03faf454 | ||
|
|
4ad8931d77 | ||
|
|
3b359dd9af |
13
examples/htmx/card.erml
Normal file
13
examples/htmx/card.erml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{'div', #{class => "card"}, [
|
||||||
|
{'div', #{class => "card-images"}, [
|
||||||
|
{img, #{src => {card_image_url}, class => "img-responsive"}}
|
||||||
|
]},
|
||||||
|
{'div', #{class => "card-header"}, [
|
||||||
|
{'div', #{class => "card-title h5"}, {card_title}},
|
||||||
|
{'div', #{class => "card-subtitle text-gray"}, {card_subtitle}}
|
||||||
|
]},
|
||||||
|
{'div', #{class => "card-body"}, {card_body}},
|
||||||
|
{'div', #{class => "card-footer"}, [
|
||||||
|
{button, #{class => "btn btn-primary"}, {card_button}}
|
||||||
|
]}
|
||||||
|
]}.
|
||||||
99
examples/htmx/index.erml
Normal file
99
examples/htmx/index.erml
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
{html, [
|
||||||
|
{head, [
|
||||||
|
{title, [<<"This is an example">>]},
|
||||||
|
{link, #{rel => "stylesheet", href => "https://unpkg.com/spectre.css/dist/spectre.min.css"}, []},
|
||||||
|
{link, #{rel => "stylesheet", href => "https://unpkg.com/spectre.css/dist/spectre-exp.min.css"}, []},
|
||||||
|
{link, #{rel => "stylesheet", href => "https://unpkg.com/spectre.css/dist/spectre-icons.min.css"}, []},
|
||||||
|
{script, #{src => "https://unpkg.com/htmx.org@1.9.9"}, []}
|
||||||
|
]},
|
||||||
|
{body, [
|
||||||
|
{'header', #{class => "navbar"}, [
|
||||||
|
{section, #{class => "navbar-section"}, [
|
||||||
|
{a, #{href => "#", class => "navbar-brand mr-2"}, <<"Gabarit HTML">>},
|
||||||
|
{a, #{href => "#", class => "btn btn-link"}, <<"Introduction">>},
|
||||||
|
{a, #{href => "#", class => "btn btn-link"}, <<"Docs">>}
|
||||||
|
]},
|
||||||
|
{section, #{class => "navbar-section"}, [
|
||||||
|
{'div', #{class => "input-group input-inline"}, [
|
||||||
|
{input, #{class => "form-input", type => "text", placeholder => "search"}, []},
|
||||||
|
{button, #{class => "btn btn-primary input-group-btn"}, <<"search">>}
|
||||||
|
]}
|
||||||
|
]}
|
||||||
|
]},
|
||||||
|
{'div', #{class => "container"}, [
|
||||||
|
{'div', #{class => "columns"}, [
|
||||||
|
|
||||||
|
% card 1
|
||||||
|
{'div', #{class => "card"}, [
|
||||||
|
{'div', #{class => "card-images"}, [
|
||||||
|
{img, #{src => "https://picsum.photos/id/1/300/200", class => "img-responsive"}}
|
||||||
|
]},
|
||||||
|
{'div', #{class => "card-header"}, [
|
||||||
|
{'div', #{class => "card-title h5"}, [<<"card 1">>]},
|
||||||
|
{'div', #{class => "card-subtitle text-gray"}, [<<"This is card subtitle">>]}
|
||||||
|
]},
|
||||||
|
{'div', #{class => "card-body"}, [<<"This is the body!">>]},
|
||||||
|
{'div', #{class => "card-footer"}, [
|
||||||
|
{button, #{class => "btn btn-primary"}, <<"refresh">>}
|
||||||
|
]}
|
||||||
|
]},
|
||||||
|
% card 2
|
||||||
|
{'div', #{class => "card"}, [
|
||||||
|
{'div', #{class => "card-images"}, [
|
||||||
|
{img, #{src => "https://picsum.photos/id/2/300/200", class => "img-responsive"}}
|
||||||
|
]},
|
||||||
|
{'div', #{class => "card-header"}, [
|
||||||
|
{'div', #{class => "card-title h5"}, [<<"test">>]},
|
||||||
|
{'div', #{class => "card-subtitle text-gray"}, [<<"test">>]}
|
||||||
|
]},
|
||||||
|
{'div', #{class => "card-body"}, [<<"test">>]},
|
||||||
|
{'div', #{class => "card-footer"}, [
|
||||||
|
{button, #{class => "btn btn-primary"}, <<"refresh">>}
|
||||||
|
]}
|
||||||
|
]},
|
||||||
|
% card 3
|
||||||
|
{'div', #{class => "card"}, [
|
||||||
|
{'div', #{class => "card-images"}, [
|
||||||
|
{img, #{src => "https://picsum.photos/id/3/300/200", class => "img-responsive"}}
|
||||||
|
]},
|
||||||
|
{'div', #{class => "card-header"}, [
|
||||||
|
{'div', #{class => "card-title h5"}, [<<"test">>]},
|
||||||
|
{'div', #{class => "card-subtitle text-gray"}, [<<"test">>]}
|
||||||
|
]},
|
||||||
|
{'div', #{class => "card-body"}, [<<"test">>]},
|
||||||
|
{'div', #{class => "card-footer"}, [
|
||||||
|
{button, #{class => "btn btn-primary"}, <<"refresh">>}
|
||||||
|
]}
|
||||||
|
]},
|
||||||
|
% card 4
|
||||||
|
{'div', #{class => "card"}, [
|
||||||
|
{'div', #{class => "card-images"}, [
|
||||||
|
{img, #{src => "https://picsum.photos/id/4/300/200", class => "img-responsive"}}
|
||||||
|
]},
|
||||||
|
{'div', #{class => "card-header"}, [
|
||||||
|
{'div', #{class => "card-title h5"}, [<<"test">>]},
|
||||||
|
{'div', #{class => "card-subtitle text-gray"}, [<<"test">>]}
|
||||||
|
]},
|
||||||
|
{'div', #{class => "card-body"}, [<<"test">>]},
|
||||||
|
{'div', #{class => "card-footer"}, [
|
||||||
|
{button, #{class => "btn btn-primary"}, <<"refresh">>}
|
||||||
|
]}
|
||||||
|
]},
|
||||||
|
% card 5
|
||||||
|
{'div', #{class => "card"}, [
|
||||||
|
{'div', #{class => "card-images"}, [
|
||||||
|
{img, #{src => "https://picsum.photos/id/5/300/200", class => "img-responsive"}}
|
||||||
|
]},
|
||||||
|
{'div', #{class => "card-header"}, [
|
||||||
|
{'div', #{class => "card-title h5"}, [<<"test">>]},
|
||||||
|
{'div', #{class => "card-subtitle text-gray"}, [<<"test">>]}
|
||||||
|
]},
|
||||||
|
{'div', #{class => "card-body"}, [<<"test">>]},
|
||||||
|
{'div', #{class => "card-footer"}, [
|
||||||
|
{button, #{class => "btn btn-primary"}, <<"refresh">>}
|
||||||
|
]}
|
||||||
|
]}
|
||||||
|
]}
|
||||||
|
]}
|
||||||
|
]}
|
||||||
|
]}.
|
||||||
10
priv/Makefile
Normal file
10
priv/Makefile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
######################################################################
|
||||||
|
#
|
||||||
|
######################################################################
|
||||||
|
SOURCE ?= https://raw.githubusercontent.com/w3c/xml-entities/gh-pages/unicode.xml
|
||||||
|
|
||||||
|
unicode.xml:
|
||||||
|
curl -so $@ $(SOURCE)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm unicode.xml
|
||||||
13
priv/example.erml
Normal file
13
priv/example.erml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{html, [
|
||||||
|
{head, [
|
||||||
|
{apply, {module, function, args}},
|
||||||
|
{apply, {function, args},
|
||||||
|
{apply, fun() -> {ok, <<>>} end},
|
||||||
|
{apply, fun(Opts) -> {ok, <<>>} end},
|
||||||
|
% send
|
||||||
|
% {cast, {module, function, []}}
|
||||||
|
{script, #{ src => "https://unpkg.com/htmx.org@1.9.9"}, []}
|
||||||
|
]},
|
||||||
|
{body, [
|
||||||
|
]},
|
||||||
|
]}
|
||||||
323
src/gabarit_entities.erl
Normal file
323
src/gabarit_entities.erl
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% @author Mathieu Kerjouan
|
||||||
|
%%% @doc
|
||||||
|
%%%
|
||||||
|
%%% This module extract all entities from unicode.xml file and convert
|
||||||
|
%%% them into indexed maps. The first one generated creates an index
|
||||||
|
%%% of ascii/utf8 chars, the second one is doing the opposite, by
|
||||||
|
%%% indexing directly the entity. That's quite dirty, but it works for
|
||||||
|
%%% now.
|
||||||
|
%%%
|
||||||
|
%%% The next step is to directly create a module template using
|
||||||
|
%%% `merl', where all entities/chars are being stored instead of
|
||||||
|
%%% starting a process.
|
||||||
|
%%%
|
||||||
|
%%% For now, everything is done inside a `gen_server':
|
||||||
|
%%%
|
||||||
|
%%% ```
|
||||||
|
%%% {ok, _} = gabarit_entities:start().
|
||||||
|
%%%
|
||||||
|
%%% {ok, #{ "8879-isolat1" => <<"é">>
|
||||||
|
%%% , "9573-2003-isolat1" => <<"é">>,
|
||||||
|
%%% , "xhtml1-lat1" => <<"é">>
|
||||||
|
%%% }
|
||||||
|
%%% } = gabarit_entities:char_to_entity(<<"é"/utf8>>).
|
||||||
|
%%%
|
||||||
|
%%% {ok,<<"é"/utf8>>}
|
||||||
|
%%% = gabarit_entities:entity_to_char(<<"é"/utf8>>).
|
||||||
|
%%%
|
||||||
|
%%% {ok,<<"à">>}
|
||||||
|
%%% = gabarit_entities:char_to_entity(<<"à"/utf8>>, "xhtml1-lat1").
|
||||||
|
%%% '''
|
||||||
|
%%%
|
||||||
|
%%% @end
|
||||||
|
%%%===================================================================
|
||||||
|
-module(gabarit_entities).
|
||||||
|
-behavior(gen_server).
|
||||||
|
-export([start/0, start_link/0]).
|
||||||
|
-export([entity_to_char/1, char_to_entity/1, char_to_entity/2]).
|
||||||
|
-export([get_entities/0, get_chars/0]).
|
||||||
|
-export([init/1]).
|
||||||
|
-export([handle_info/2, handle_cast/2, handle_call/3]).
|
||||||
|
-export([merl/0]).
|
||||||
|
-include_lib("xmerl/include/xmerl.hrl").
|
||||||
|
-record(?MODULE, { chars = #{} :: map()
|
||||||
|
, entities = #{} :: map()
|
||||||
|
, options = #{} :: map()
|
||||||
|
}).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% draft to generate a module.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
merl() ->
|
||||||
|
merl_entities().
|
||||||
|
|
||||||
|
merl_entities() ->
|
||||||
|
Entities = get_entities(),
|
||||||
|
Keys = maps:keys(Entities),
|
||||||
|
EntityToChar = merl_entities(Keys, Entities, [], length(Keys)+3),
|
||||||
|
[ {attribute,1,module,t}
|
||||||
|
, {attribute,2,export,[{entity_to_char,1}]}
|
||||||
|
, EntityToChar
|
||||||
|
].
|
||||||
|
|
||||||
|
merl_entities([], _, Buffer, _) ->
|
||||||
|
{function,3,entity_to_char,1, Buffer};
|
||||||
|
merl_entities([Key|Rest], Entities, Buffer, Counter) ->
|
||||||
|
#{ Key := Value} = Entities,
|
||||||
|
Pattern = {bin, Counter, [{bin_element, Counter, {string, Counter, binary_to_list(Key)}, default, default}]},
|
||||||
|
Return = {bin, Counter, [{bin_element, Counter, {string, Counter, binary_to_list(Value)}, default, default}]},
|
||||||
|
Clause = {clause,Counter,[Pattern, [], Return]},
|
||||||
|
merl_entities(Rest, Entities, [Clause|Buffer], Counter-1).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-spec start() -> {ok, pid()}.
|
||||||
|
|
||||||
|
start() ->
|
||||||
|
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-spec start_link() -> {ok, pid()}.
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-spec entity_to_char(Entity) -> Return when
|
||||||
|
Entity :: binary(),
|
||||||
|
Return :: {ok, binary()}
|
||||||
|
| {error, {binary(), not_found}}
|
||||||
|
| timeout.
|
||||||
|
|
||||||
|
entity_to_char(<<Entity/binary>>) ->
|
||||||
|
gen_server:call(?MODULE, {entity_to_char, Entity}, 1000).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-spec char_to_entity(Entity) -> Return when
|
||||||
|
Entity :: binary(),
|
||||||
|
Return :: {ok, map()}
|
||||||
|
| {error, {binary(), not_found}}
|
||||||
|
| timeout.
|
||||||
|
|
||||||
|
char_to_entity(<<Char/binary>>) ->
|
||||||
|
gen_server:call(?MODULE, {char_to_entity, Char}, 1000).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-spec char_to_entity(Entity, Mode) -> Return when
|
||||||
|
Entity :: binary(),
|
||||||
|
Mode :: string(),
|
||||||
|
Return :: {ok, binary()}
|
||||||
|
| {error, {binary(), not_found}}
|
||||||
|
| timeout.
|
||||||
|
|
||||||
|
char_to_entity(<<Char/binary>>, Mode) ->
|
||||||
|
gen_server:call(?MODULE, {char_to_entity, Char, Mode}, 1000).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-spec get_entities() -> map().
|
||||||
|
|
||||||
|
get_entities() ->
|
||||||
|
gen_server:call(?MODULE, entities, 1000).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-spec get_chars() -> map().
|
||||||
|
|
||||||
|
get_chars() ->
|
||||||
|
gen_server:call(?MODULE, chars, 1000).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
init(Args) ->
|
||||||
|
{ok, {Chars, Entities}} = parse(),
|
||||||
|
State = #?MODULE{ chars = Chars
|
||||||
|
, entities = Entities
|
||||||
|
, options = Args
|
||||||
|
},
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
handle_call({char_to_entity, Char, Mode}, _, #?MODULE{ chars = Chars } = State) ->
|
||||||
|
case maps:get(Char, Chars, undefined) of
|
||||||
|
undefined ->
|
||||||
|
{reply, {error, {Char, not_found}}, State};
|
||||||
|
#{ Mode := Entity } ->
|
||||||
|
{reply, {ok, Entity}, State};
|
||||||
|
_ ->
|
||||||
|
{reply, {error, {Char, not_found}}, State}
|
||||||
|
end;
|
||||||
|
handle_call({char_to_entity, Char}, _, #?MODULE{ chars = Chars, options = #{ default := Default }} = State) ->
|
||||||
|
case maps:get(Char, Chars, undefined) of
|
||||||
|
undefined ->
|
||||||
|
{reply, {error, {Char, not_found}}, State};
|
||||||
|
Entity ->
|
||||||
|
case Entity of
|
||||||
|
#{ Default := Result } ->
|
||||||
|
{reply, {ok, Result}, State};
|
||||||
|
_ ->
|
||||||
|
{reply, {error, {Char, not_found, Default}}, State}
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
handle_call({char_to_entity, Char}, _, #?MODULE{ chars = Chars } = State) ->
|
||||||
|
case maps:get(Char, Chars, undefined) of
|
||||||
|
undefined ->
|
||||||
|
{reply, {error, {Char, not_found}}, State};
|
||||||
|
Entity ->
|
||||||
|
{reply, {ok, Entity}, State}
|
||||||
|
end;
|
||||||
|
handle_call({entity_to_char, Entity}, _, #?MODULE{ entities = Entities} = State) ->
|
||||||
|
case maps:get(Entity, Entities, undefined) of
|
||||||
|
undefined ->
|
||||||
|
{reply, {error, {Entity, not_found}}, State};
|
||||||
|
Char ->
|
||||||
|
{reply, {ok, Char}, State}
|
||||||
|
end;
|
||||||
|
handle_call(entities, _, #?MODULE{ entities = Entities } = State) ->
|
||||||
|
{reply, Entities, State};
|
||||||
|
handle_call(chars, _, #?MODULE{ chars = Chars } = State) ->
|
||||||
|
{reply, Chars, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
handle_cast(_,State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
handle_info(_,State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
options() ->
|
||||||
|
[{event_fun, fun event/3}].
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
parse() ->
|
||||||
|
Priv = code:priv_dir(gabarit),
|
||||||
|
Filename = filename:join(Priv, "unicode.xml"),
|
||||||
|
{ok, Content} = file:read_file(Filename),
|
||||||
|
stream(binary_to_list(Content)).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-spec stream(XML) -> Return when
|
||||||
|
XML :: binary() | string(),
|
||||||
|
Return :: {ok, {CharMap, EntityMap}},
|
||||||
|
CharMap :: map(),
|
||||||
|
EntityMap :: map().
|
||||||
|
|
||||||
|
stream(Content) ->
|
||||||
|
{ok, {charlist, Buffer}, _} = xmerl_sax_parser:stream(Content, options()),
|
||||||
|
list_to_map(Buffer, #{}, #{}).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
list_to_map(#{}, ByChars, ByEntities) -> {ok, {ByChars, ByEntities}};
|
||||||
|
list_to_map([], ByChars, ByEntities) -> {ok, {ByChars, ByEntities}};
|
||||||
|
list_to_map([#{ binary := Binary, entity := Entities }|Rest], ByChars, ByEntities) ->
|
||||||
|
EntitiesMap = entities_to_map(Entities, #{}),
|
||||||
|
EntityChar = entity_to_char(Binary, Entities, #{}),
|
||||||
|
list_to_map(Rest, ByChars#{ Binary => EntitiesMap }, maps:merge(ByEntities, EntityChar));
|
||||||
|
list_to_map([_|Rest], ByChars, ByEntities) ->
|
||||||
|
list_to_map(Rest, ByChars, ByEntities).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
entities_to_map([], Buffer) -> Buffer;
|
||||||
|
entities_to_map([#{ id := Id, set := Set }|Rest], Buffer) ->
|
||||||
|
entities_to_map(Rest, Buffer#{ Set => Id }).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
entity_to_char(_Binary, [], Buffer) -> Buffer;
|
||||||
|
entity_to_char(Binary, [#{ id := Id }|Rest], Buffer) ->
|
||||||
|
entity_to_char(Binary, Rest, Buffer#{ Id => Binary }).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
event(startDocument, _Location, State) -> State;
|
||||||
|
event({startElement, [], "unicode", _, Attributes}, _Location, State) ->
|
||||||
|
case get_attribute(Attributes, "unicode") of
|
||||||
|
"15.1" -> State;
|
||||||
|
Version -> throw({error, Version})
|
||||||
|
end;
|
||||||
|
event({startElement, [], "entitygroups", _, _Attributes}, _Location, State) ->
|
||||||
|
State;
|
||||||
|
event({startElement, [], "charlist", _, _Attributes}, _Location, _State) ->
|
||||||
|
{charlist, #{}};
|
||||||
|
event({startElement, [], "character",_,Attributes}, _Location, {charlist, Buffer}) ->
|
||||||
|
Id = get_attribute(Attributes, "id"),
|
||||||
|
Dec = get_attribute(Attributes, "dec"),
|
||||||
|
Mode = get_attribute(Attributes, "mode"),
|
||||||
|
Type = get_attribute(Attributes, "type"),
|
||||||
|
case decimal_to_utf8(Dec) of
|
||||||
|
{ok, FromDec} ->
|
||||||
|
Char = #{ id => Id
|
||||||
|
, dec => Dec
|
||||||
|
, mode => Mode
|
||||||
|
, type => Type
|
||||||
|
, binary => FromDec
|
||||||
|
},
|
||||||
|
{charlist, [Char|Buffer]};
|
||||||
|
{error, _} ->
|
||||||
|
{charlist, Buffer}
|
||||||
|
end;
|
||||||
|
event({startElement, [], "entity", _, Attributes}, _Location, {charlist, [Char|Buffer]}) ->
|
||||||
|
EntityId = get_attribute(Attributes, "id"),
|
||||||
|
Set = get_attribute(Attributes, "set"),
|
||||||
|
BinaryEntity = list_to_binary(EntityId),
|
||||||
|
Entity = #{ id => <<"&", BinaryEntity/binary, ";">>, set => Set },
|
||||||
|
case Char of
|
||||||
|
#{ entity := List } ->
|
||||||
|
{charlist, [Char#{ entity => [Entity|List]}|Buffer]};
|
||||||
|
_ ->
|
||||||
|
{charlist, [Char#{ entity => [Entity]}|Buffer]}
|
||||||
|
end;
|
||||||
|
event(_Event, _Location, State) ->
|
||||||
|
State.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
decimal_to_utf8(Int) ->
|
||||||
|
try
|
||||||
|
Integer = list_to_integer(Int),
|
||||||
|
{ok, <<Integer/utf8>>}
|
||||||
|
catch
|
||||||
|
_:_ -> {error, Int}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
get_attribute(List, Key) -> get_attribute(List, Key, undefined).
|
||||||
|
get_attribute([], _, Default) -> Default;
|
||||||
|
get_attribute([{_,_,Key,Value}|_], Key, _Default) -> Value;
|
||||||
|
get_attribute([_Attribute|Rest], Key, Default) -> get_attribute(Rest, Key, Default).
|
||||||
104
src/gabarit_generator.erl
Normal file
104
src/gabarit_generator.erl
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% @doc draft.
|
||||||
|
%%% @end
|
||||||
|
%%%===================================================================
|
||||||
|
-module(gabarit_generator).
|
||||||
|
-export([compile/3, compile/4]).
|
||||||
|
-export([flatten/2]).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
compile(Module, Args, Data) ->
|
||||||
|
compile(Module, Args, Data, #{}).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
compile(Module, Args, Data, Opts) ->
|
||||||
|
init_loop(Module, Args, Data, Opts).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
init_loop(Module, Args, Data, Opts) ->
|
||||||
|
try Module:init(Args) of
|
||||||
|
{ok, State} ->
|
||||||
|
loop(Module, Data, Opts, State, [], [])
|
||||||
|
catch
|
||||||
|
E:R:S ->
|
||||||
|
{error, {E,R,S}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% custom function to flatten an improper list containing binaries and
|
||||||
|
%% other terms.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
flatten([], Buffer)
|
||||||
|
when is_binary(Buffer) -> Buffer;
|
||||||
|
flatten([], Buffer)
|
||||||
|
when is_list(Buffer) -> lists:reverse(Buffer);
|
||||||
|
flatten([H|T], Buffer)
|
||||||
|
when is_binary(H), is_binary(Buffer) ->
|
||||||
|
flatten(T, <<Buffer/binary, H/binary>>);
|
||||||
|
flatten([H|T], Buffer)
|
||||||
|
when is_binary(Buffer) ->
|
||||||
|
flatten(T, [H|[Buffer]]);
|
||||||
|
flatten([H|T], [Last|Rest])
|
||||||
|
when is_binary(Last), is_binary(H) ->
|
||||||
|
flatten(T, [<<Last/binary, H/binary>>|Rest]);
|
||||||
|
flatten([H|T], Buffer)
|
||||||
|
when is_list(Buffer) ->
|
||||||
|
flatten(T, [H|Buffer]).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
loop(Module, Data, Opts, State, LBuffer, RBuffer) ->
|
||||||
|
case elements(Module, Data, Opts, State, LBuffer, RBuffer) of
|
||||||
|
{ok, L, R, _State} ->
|
||||||
|
List = lists:flatten([L,R]),
|
||||||
|
Flatten = flatten(List, <<>>),
|
||||||
|
{ok, Flatten};
|
||||||
|
Elsewise ->
|
||||||
|
Elsewise
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
elements(Module, [], Opts, State, LB, RB) ->
|
||||||
|
{ok, LB, RB, State};
|
||||||
|
elements(Module, [Element|Elements], Opts, State, LB, RB) ->
|
||||||
|
case element(Module, Element, Opts, State) of
|
||||||
|
{ok, Content, NewState} ->
|
||||||
|
elements(Module, Elements, Opts, NewState, [LB, Content], RB);
|
||||||
|
{ok, Begin, End, NewState} ->
|
||||||
|
elements(Module, Elements, Opts, NewState, [LB, Begin], [End,RB]);
|
||||||
|
Elsewise ->
|
||||||
|
Elsewise
|
||||||
|
end;
|
||||||
|
elements(Module, Element, Opts, State, LB, RB) ->
|
||||||
|
case element(Module, Element, Opts, State) of
|
||||||
|
{ok, Content, NewState} ->
|
||||||
|
{ok, [LB, Content], RB, NewState};
|
||||||
|
{ok, Begin, End, NewState} ->
|
||||||
|
{ok, [LB, Begin], [End,RB], NewState};
|
||||||
|
Elsewise ->
|
||||||
|
Elsewise
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
element(Module, Element, Opts, State) ->
|
||||||
|
case Module:tag(Element, Opts, State) of
|
||||||
|
{ok, Content, NewState} ->
|
||||||
|
{ok, Content, NewState};
|
||||||
|
{ok, Begin, End, NewState} ->
|
||||||
|
{ok, Begin, End, NewState};
|
||||||
|
{ok, Begin, End, Inner, NewState} ->
|
||||||
|
elements(Module, Inner, Opts, NewState, [Begin], [End]);
|
||||||
|
Elsewise ->
|
||||||
|
Elsewise
|
||||||
|
end.
|
||||||
520
src/gabarit_html.erl
Normal file
520
src/gabarit_html.erl
Normal file
@@ -0,0 +1,520 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% @doc draft: Create html page using tuple, map and binaries.
|
||||||
|
%%%
|
||||||
|
%%% ```
|
||||||
|
%%% % create a simple page
|
||||||
|
%%% Title = {h1, <<"this is my title">>}.
|
||||||
|
%%% Paragraph1 = {p, <<"long time ago...">>}.
|
||||||
|
%%% Paragraph2 = {p, <<"that's all folks!">>}.
|
||||||
|
%%% Paragraph3 = fun(_Opts) -> {ok, {p, <<"end.">>}} end.
|
||||||
|
%%% Body = [Title, Paragraph1, Paragraph2, Paragraph3].
|
||||||
|
%%% gabarit_html:create({html, Body}).
|
||||||
|
%%% '''
|
||||||
|
%%%
|
||||||
|
%%% Will generate this page:
|
||||||
|
%%%
|
||||||
|
%%% ```
|
||||||
|
%%% <<"<html><h1>this is my title</h1>",
|
||||||
|
%%% "<p>long time ago...</p>",
|
||||||
|
%%% "<p>that's all folks!</p>",
|
||||||
|
%%% "<p>end.</p>",
|
||||||
|
%%% "</html>">>
|
||||||
|
%%% '''
|
||||||
|
%%%
|
||||||
|
%%% @todo convert this code as behavior.
|
||||||
|
%%% @todo when reading a template, compile it as module.
|
||||||
|
%%% @todo when a template includes dynamic code, allow rendering it
|
||||||
|
%%% with custom option in the mode.
|
||||||
|
%%%
|
||||||
|
%%% @end
|
||||||
|
%%%===================================================================
|
||||||
|
-module(gabarit_html).
|
||||||
|
-export([open/1, open/2]).
|
||||||
|
-export([create/1, create/2]).
|
||||||
|
-export([table/2]).
|
||||||
|
-export([join/1, join/2]).
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-type tag() :: atom() | list() | binary().
|
||||||
|
-type attributes() :: #{}.
|
||||||
|
-type content() :: [].
|
||||||
|
-type options() :: #{}.
|
||||||
|
-type element() :: {tag(), attributes()}
|
||||||
|
| {tag(), attributes(), content()}
|
||||||
|
| {tag(), attributes(), content(), options()}
|
||||||
|
| {apply, {atom(), list()}}
|
||||||
|
| {apply, {atom(), atom(), list()}}
|
||||||
|
| {include, list() | binary()}
|
||||||
|
| binary().
|
||||||
|
-type elements() :: [element()].
|
||||||
|
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @doc A demo function to generate table.
|
||||||
|
%% @end
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
table([], _) ->
|
||||||
|
create({table, []});
|
||||||
|
table([Header|Rest], #{ header := true } = Opts) ->
|
||||||
|
Keys = maps:keys(Header),
|
||||||
|
Head = table_header(Keys, Header, Opts),
|
||||||
|
Body = table_rows(Rest, Keys, [], Opts),
|
||||||
|
{table, [Head, Body]};
|
||||||
|
table([Header|Rest] = Rows, Opts) ->
|
||||||
|
Keys = maps:keys(Header),
|
||||||
|
Body = table_rows(Rows, Keys, [], Opts),
|
||||||
|
{table, Body}.
|
||||||
|
|
||||||
|
table_header(Keys, Data, #{ extra := true }) ->
|
||||||
|
Th = [ {th, [{span, #{}, Key}
|
||||||
|
, <<" ">>
|
||||||
|
,{span, #{}, [<<"(">>, maps:get(Key, Data), <<")">>]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|| Key <- Keys ],
|
||||||
|
{thead, {tr, #{}, Th}};
|
||||||
|
table_header(Keys, _, Opts) ->
|
||||||
|
{thead, {tr, #{}, [ {th, #{}, Key} || Key <- Keys ]}}.
|
||||||
|
|
||||||
|
table_rows([], _, Buffer, _) -> {tbody, Buffer};
|
||||||
|
table_rows([Last], Keys, Buffer, #{ footer := true }) ->
|
||||||
|
Footer = {tfooter, {tr, [ {td, maps:get(Last, Key)} || Key <- Keys ]}},
|
||||||
|
Body = {tbody, lists:reverse(Buffer)},
|
||||||
|
[Body, Footer];
|
||||||
|
table_rows([Row|Rest], Keys, Buffer, Opts) ->
|
||||||
|
table_rows(Rest, Keys, [{tr, [ {td, [maps:get(Key, Row)]} || Key <- Keys ]}|Buffer], Opts).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @doc create a new HTML page from elements.
|
||||||
|
%% @end
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-spec create(Element) -> Return when
|
||||||
|
Element :: element() | elements(),
|
||||||
|
Return :: binary().
|
||||||
|
|
||||||
|
create(Element) ->
|
||||||
|
create(Element, #{}).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @doc create a new HTML page from elements.
|
||||||
|
%% @end
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-spec create(Element, Opts) -> Return when
|
||||||
|
Element :: element() | elements(),
|
||||||
|
Opts :: options(),
|
||||||
|
Return :: binary().
|
||||||
|
|
||||||
|
create(Element, Opts) ->
|
||||||
|
Full = tags(Element, Opts),
|
||||||
|
doctype(Full, Opts).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @doc create a new HTML page from elements.
|
||||||
|
%% @end
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
open(Path) ->
|
||||||
|
open(Path, #{}).
|
||||||
|
|
||||||
|
open(Path, Opts) ->
|
||||||
|
{ok, Data} = file:consult(Path),
|
||||||
|
create(Data, Opts).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
tags([], _) -> <<>>;
|
||||||
|
tags(Elements, Opts) ->
|
||||||
|
tags(Elements, <<>>, Opts).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
tags([], Buffer, _Opts) -> Buffer;
|
||||||
|
tags(Element, Buffer, Opts)
|
||||||
|
when is_tuple(Element) ->
|
||||||
|
tags([Element], Buffer, Opts);
|
||||||
|
tags([Element|Elements], Buffer, Opts)
|
||||||
|
when is_tuple(Element) ->
|
||||||
|
Tag = tag(Element, Opts),
|
||||||
|
tags(Elements, <<Buffer/binary, Tag/binary>>, Opts);
|
||||||
|
tags([Element|Elements], Buffer, Opts)
|
||||||
|
when is_list(Element) ->
|
||||||
|
Tags = tags(Element, Opts),
|
||||||
|
tags(Elements, <<Buffer/binary, Tags/binary>>, Opts);
|
||||||
|
tags([Element|Elements], Buffer, Opts) ->
|
||||||
|
Encoded = text(Element, Opts),
|
||||||
|
tags(Elements, <<Buffer/binary, Encoded/binary>>, Opts);
|
||||||
|
tags(Element, Buffer, Opts) ->
|
||||||
|
Encoded = text(Element, Opts),
|
||||||
|
<<Buffer/binary, Encoded/binary>>.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
doctype(Buffer, #{ doctype := true }) ->
|
||||||
|
<<"<!DOCTYPE html>", Buffer/binary>>;
|
||||||
|
doctype(Buffer, _Opts) ->
|
||||||
|
Buffer.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
text(Content, Opts)
|
||||||
|
when is_integer(Content) ->
|
||||||
|
text(integer_to_binary(Content), Opts);
|
||||||
|
text(Content, Opts)
|
||||||
|
when is_atom(Content) ->
|
||||||
|
text(atom_to_binary(Content), Opts);
|
||||||
|
text(Content, #{ html_entities := false })
|
||||||
|
when is_binary(Content) ->
|
||||||
|
Content;
|
||||||
|
text(Content, _Opts)
|
||||||
|
when is_binary(Content) ->
|
||||||
|
gabarit_html_entities:encode(Content).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
tag({include_raw, Path}, Opts) ->
|
||||||
|
case file:read_file(Path) of
|
||||||
|
{ok, Content} ->
|
||||||
|
gabarit_html_entities:encode(Content);
|
||||||
|
Elsewise ->
|
||||||
|
throw(Elsewise)
|
||||||
|
end;
|
||||||
|
tag({include_template, Path}, Opts) ->
|
||||||
|
case file:consult(Path) of
|
||||||
|
{ok, Content} ->
|
||||||
|
tags([Content], Opts);
|
||||||
|
Elsewise ->
|
||||||
|
throw(Elsewise)
|
||||||
|
end;
|
||||||
|
tag({include_template, Path, Variables}, Opts) ->
|
||||||
|
case file:consult(Path) of
|
||||||
|
{ok, Content} ->
|
||||||
|
tags([Content], #{ variables => Variables });
|
||||||
|
Elsewise ->
|
||||||
|
throw(Elsewise)
|
||||||
|
end;
|
||||||
|
% variable support
|
||||||
|
tag({Variable}, Opts) ->
|
||||||
|
Result = get_variable(Variable, Opts),
|
||||||
|
tags([Result], Opts);
|
||||||
|
tag({apply, {Function, Args}}, #{module := Module} = Opts)
|
||||||
|
when is_atom(Function), is_list(Args) ->
|
||||||
|
case apply(Module, Function, [Opts, Args]) of
|
||||||
|
{ok, Result} when is_binary(Result) ->
|
||||||
|
gabarit_html_entities:encode(Result);
|
||||||
|
{ok, Result} when is_list(Result) ->
|
||||||
|
tags(Result, Opts);
|
||||||
|
{ok, Result} when is_tuple(Result) ->
|
||||||
|
tag(Result, Opts)
|
||||||
|
end;
|
||||||
|
tag({apply, {Module, Function, Args}}, Opts)
|
||||||
|
when is_atom(Module), is_atom(Function), is_list(Args) ->
|
||||||
|
case apply(Module, Function, [Opts|Args]) of
|
||||||
|
{ok, Result} when is_binary(Result) ->
|
||||||
|
gabarit_html_entities:encode(Result);
|
||||||
|
{ok, Result} when is_list(Result) ->
|
||||||
|
tags(Result, Opts);
|
||||||
|
{ok, Result} when is_tuple(Result) ->
|
||||||
|
tag(Result, Opts)
|
||||||
|
end;
|
||||||
|
tag({apply, Fun}, Opts)
|
||||||
|
when is_function(Fun, 1) ->
|
||||||
|
case Fun(Opts) of
|
||||||
|
{ok, Result} when is_binary(Result) ->
|
||||||
|
gabarit_html_entities:encode(Result);
|
||||||
|
{ok, Result} when is_list(Result) ->
|
||||||
|
tags(Result, Opts);
|
||||||
|
{ok, Result} when is_tuple(Result) ->
|
||||||
|
tag(Result, Opts)
|
||||||
|
end;
|
||||||
|
tag({Element, Content}, Opts)
|
||||||
|
when is_binary(Content) ->
|
||||||
|
tag({Element, #{}, Content}, Opts);
|
||||||
|
% {html, []} should use default or empty attributes. The second
|
||||||
|
% element of the tuple is the inner content.
|
||||||
|
tag({Element, Content}, Opts)
|
||||||
|
when is_list(Content) ->
|
||||||
|
tag({Element, #{}, Content}, Opts);
|
||||||
|
tag({Element, Content}, Opts)
|
||||||
|
when is_tuple(Content) ->
|
||||||
|
tag({Element, #{}, Content}, Opts);
|
||||||
|
% {html, #{}} is a tag without content and using customer attributes.
|
||||||
|
tag({Element, Attributes}, Opts)
|
||||||
|
when is_map(Attributes) ->
|
||||||
|
tag({Element, Attributes, []}, Opts);
|
||||||
|
tag({Element, Attributes, _Content} = Tag, Opts) ->
|
||||||
|
tag1(Tag, <<>>, Opts);
|
||||||
|
tag(Element, Opts) when is_function(Element) ->
|
||||||
|
tag1(Element, <<>>, Opts).
|
||||||
|
|
||||||
|
tag_test() ->
|
||||||
|
% tag can be defined with a triplet
|
||||||
|
[?assertEqual(<<"<html></html>">>
|
||||||
|
,tag({html, #{}, []}, []))
|
||||||
|
|
||||||
|
% tag can be defined with a pair
|
||||||
|
,?assertEqual(<<"<html></html>">>
|
||||||
|
,tag({html, []}, []))
|
||||||
|
|
||||||
|
% tag can be defined with a pair
|
||||||
|
,?assertEqual(<<"<html>test</html>">>
|
||||||
|
,tag({html, [<<"test">>]}, []))
|
||||||
|
|
||||||
|
% tag can be defined with a pair
|
||||||
|
,?assertEqual(<<"<html>test</html>">>
|
||||||
|
,tag({html, <<"test">>}, []))
|
||||||
|
|
||||||
|
% tag's content can be a list containing binary
|
||||||
|
,?assertEqual(<<"<html>test</html>">>
|
||||||
|
,tag({html, #{}, [<<"test">>]}, []))
|
||||||
|
|
||||||
|
% tag's content can be a binary
|
||||||
|
,?assertEqual(<<"<html>test</html>">>
|
||||||
|
,tag({html, #{}, <<"test">>}, []))
|
||||||
|
|
||||||
|
% attribute's value can be an atom
|
||||||
|
,?assertEqual(<<"<html id=\"test\">test</html>">>
|
||||||
|
,tag({html, #{ id => test }, [<<"test">>]}, []))
|
||||||
|
|
||||||
|
% attribute's value can be a binary
|
||||||
|
,?assertEqual(<<"<html id=\"test\">test</html>">>
|
||||||
|
,tag({html, #{ id => <<"test">> }, [<<"test">>]}, []))
|
||||||
|
|
||||||
|
% attribute's value can be a string
|
||||||
|
,?assertEqual(<<"<html id=\"test\">test</html>">>
|
||||||
|
,tag({html, #{ id => "test" }, [<<"test">>]}, []))
|
||||||
|
|
||||||
|
% attribute's key can be a binary
|
||||||
|
,?assertEqual(<<"<html id=\"test\">test</html>">>
|
||||||
|
,tag({html, #{ <<"id">> => "test" }, [<<"test">>]}, []))
|
||||||
|
|
||||||
|
% attributes values must be encoded using html entities
|
||||||
|
,?assertEqual(<<"<html id=\"&test\">test</html>">>
|
||||||
|
,tag({html, #{ <<"id">> => "&test" }, [<<"test">>]}, []))
|
||||||
|
|
||||||
|
% empty element support
|
||||||
|
,?assertEqual(<<"<html id=\"test\" />">>
|
||||||
|
,tag({{empty, html}, #{ id => "test"}}, []))
|
||||||
|
].
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
% support for empty tag
|
||||||
|
tag1({{empty, Element}, Attributes, Content}, Buffer, Opts)
|
||||||
|
when is_atom(Element) ->
|
||||||
|
NewElement = atom_to_binary(Element),
|
||||||
|
tag1({{empty, NewElement}, Attributes, Content}, Buffer, Opts);
|
||||||
|
tag1({{empty, Element}, Attributes, Content}, Buffer, Opts)
|
||||||
|
when is_binary(Element), is_map(Attributes) ->
|
||||||
|
tag_empty({Element, Attributes, Content, #{}}, Buffer, Opts);
|
||||||
|
% support for non-empty tag
|
||||||
|
tag1({Element, Attributes, Content}, Buffer, Opts)
|
||||||
|
when is_atom(Element) ->
|
||||||
|
NewElement = atom_to_binary(Element),
|
||||||
|
tag1({NewElement, Attributes, Content}, Buffer, Opts);
|
||||||
|
tag1({Element, Attributes, Content}, Buffer, Opts)
|
||||||
|
when is_binary(Element), is_map(Attributes) ->
|
||||||
|
Item = {Element, Attributes, Content, #{}},
|
||||||
|
% @todo add suppot for pre and code tags.
|
||||||
|
case Element of
|
||||||
|
<<"base">> -> tag_without_content(Item, Buffer, Opts);
|
||||||
|
<<"br">> -> tag_without_content(Item, Buffer, Opts);
|
||||||
|
<<"img">> -> tag_without_content(Item, Buffer, Opts);
|
||||||
|
<<"input">> -> tag_without_content(Item, Buffer, Opts);
|
||||||
|
<<"link">> -> tag_without_content(Item, Buffer, Opts);
|
||||||
|
<<"meta">> -> tag_without_content(Item, Buffer, Opts);
|
||||||
|
<<"source">> -> tag_without_content(Item, Buffer, Opts);
|
||||||
|
_ -> tag_with_content(Item, Buffer, Opts)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
tag_with_content({Element, Attributes, Content, _LocalOpts}, _Buffer, Opts) ->
|
||||||
|
NewAttributes = attributes(Attributes, Opts),
|
||||||
|
StartElement = bracket(Element, NewAttributes),
|
||||||
|
NewContent = tags(Content, Opts),
|
||||||
|
EndElement = bracket_end(Element),
|
||||||
|
<<StartElement/binary, NewContent/binary, EndElement/binary>>.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
tag_without_content({Element, Attributes, _Content, _LocalOpts}, _Buffer, Opts) ->
|
||||||
|
NewAttributes = attributes(Attributes, Opts),
|
||||||
|
Item = bracket(Element, NewAttributes),
|
||||||
|
<<Item/binary>>.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
tag_empty({Element, Attributes, _Content, _LocalOpts}, _Buffer, Opts) ->
|
||||||
|
NewAttributes = attributes(Attributes, Opts),
|
||||||
|
Item = bracket_empty(Element, NewAttributes),
|
||||||
|
<<Item/binary>>.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
bracket_empty(Element, <<>>) ->
|
||||||
|
<<"<", Element/binary, " />">>;
|
||||||
|
bracket_empty(Element, Attributes) ->
|
||||||
|
<<"<", Element/binary, " ", Attributes/binary, " />">>.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
bracket(Element, <<>>) ->
|
||||||
|
<<"<", Element/binary, ">">>;
|
||||||
|
bracket(Element, Attributes) ->
|
||||||
|
<<"<", Element/binary, " ", Attributes/binary, ">">>.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
bracket_end(Element) ->
|
||||||
|
<<"</", Element/binary, ">">>.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @hidden
|
||||||
|
%% @doc
|
||||||
|
%% @end
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
attributes(Attributes, Opts)
|
||||||
|
when map_size(Attributes) =:= 0 ->
|
||||||
|
<<>>;
|
||||||
|
attributes(Attributes, Opts) ->
|
||||||
|
Keys = maps:keys(Attributes),
|
||||||
|
attributes(Attributes, Keys, [], Opts).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @hidden
|
||||||
|
%% @doc
|
||||||
|
%% @end
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
attributes(_Attributes, [], Buffer, Opts) ->
|
||||||
|
join(lists:reverse(Buffer), <<" ">>);
|
||||||
|
attributes(Attributes, [Key|Keys], Buffer, Opts) ->
|
||||||
|
Value = erlang:map_get(Key, Attributes),
|
||||||
|
Attribute = attribute(Key, Value, Opts),
|
||||||
|
attributes(Attributes, Keys, [Attribute|Buffer], Opts).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @hidden
|
||||||
|
%% @doc
|
||||||
|
%% @todo cleanup the mess for htmx
|
||||||
|
%% @end
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
attribute(Key, Value, Opts) ->
|
||||||
|
case key(Key, Opts) of
|
||||||
|
% css
|
||||||
|
{ok, <<"style", _binary>> = NewKey} ->
|
||||||
|
NewValue = value(Value, Opts),
|
||||||
|
Quoted = single_quote(NewValue),
|
||||||
|
<<NewKey/binary,"=", Quoted/binary>>;
|
||||||
|
% this is a javascript like attribute
|
||||||
|
{ok, <<"on", _binary>> = NewKey} ->
|
||||||
|
NewValue = value(Value, Opts),
|
||||||
|
Quoted = single_quote(NewValue),
|
||||||
|
<<NewKey/binary,"=", Quoted/binary>>;
|
||||||
|
% this is an htmx attribute, must be a valid json.
|
||||||
|
{ok, <<"hx-vals">> = NewKey} ->
|
||||||
|
NewValue = value(Value, Opts),
|
||||||
|
Quoted = single_quote(NewValue),
|
||||||
|
<<NewKey/binary,"=", Quoted/binary>>;
|
||||||
|
% standard htmx attributes
|
||||||
|
{ok, <<"hx-", _/binary>> = NewKey} ->
|
||||||
|
NewValue = value(Value, Opts),
|
||||||
|
Quoted = double_quote(NewValue),
|
||||||
|
<<NewKey/binary,"=", Quoted/binary>>;
|
||||||
|
% this is an htmx attribute
|
||||||
|
{ok, <<"htmx-", _/binary>> = NewKey} ->
|
||||||
|
NewValue = value(Value, Opts),
|
||||||
|
Quoted = double_quote(NewValue),
|
||||||
|
<<NewKey/binary,"=", Quoted/binary>>;
|
||||||
|
{ok, <<"href">> = NewKey} ->
|
||||||
|
NewValue = value(Value, Opts),
|
||||||
|
Quoted = double_quote(NewValue),
|
||||||
|
<<NewKey/binary,"=", Quoted/binary>>;
|
||||||
|
% javascript element
|
||||||
|
{ok, <<"src">> = NewKey} ->
|
||||||
|
NewValue = value(Value, Opts),
|
||||||
|
Quoted = single_quote(NewValue),
|
||||||
|
<<NewKey/binary,"=", Quoted/binary>>;
|
||||||
|
% that's another kind of key
|
||||||
|
{ok, NewKey} ->
|
||||||
|
NewValue = value(Value, Opts),
|
||||||
|
Encoded = gabarit_html_entities:encode(NewValue),
|
||||||
|
Quoted = double_quote(Encoded),
|
||||||
|
<<NewKey/binary, "=", Quoted/binary>>
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
join(Binaries) -> join(Binaries, <<" ">>).
|
||||||
|
|
||||||
|
join(Binaries, Sep) -> join(Binaries, Sep, <<>>).
|
||||||
|
join([Binary], _, Buffer) ->
|
||||||
|
<<Binary/binary, Buffer/binary>>;
|
||||||
|
join([Binary|Rest], Sep, Buffer) ->
|
||||||
|
join(Rest, Sep, <<Sep/binary, Binary/binary, Buffer/binary>>).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
get_variable(Variable, #{variables := Variables} = Opts) ->
|
||||||
|
case maps:get(Variable, Variables, '$undefined') of
|
||||||
|
'$undefined' ->
|
||||||
|
throw({error, {undefined, Variable}});
|
||||||
|
Content -> Content
|
||||||
|
end;
|
||||||
|
get_variable(Variable, Opts) ->
|
||||||
|
throw({error, {unset, variables}}).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
value({Variable}, Opts) ->
|
||||||
|
get_variable(Variable, Opts);
|
||||||
|
value(Value, _Opts) when is_list(Value) ->
|
||||||
|
list_to_binary(Value);
|
||||||
|
value(Value, _Opts) when is_atom(Value) ->
|
||||||
|
atom_to_binary(Value);
|
||||||
|
value(Value, _Opts) when is_integer(Value) ->
|
||||||
|
integer_to_binary(Value);
|
||||||
|
value(Value, _Opts) when is_binary(Value) ->
|
||||||
|
Value.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
key(Key, _Opts) when is_list(Key) ->
|
||||||
|
{ok, list_to_binary(Key)};
|
||||||
|
key(Key, _Opts) when is_atom(Key) ->
|
||||||
|
{ok, atom_to_binary(Key)};
|
||||||
|
key(Key, _Opts) when is_integer(Key) ->
|
||||||
|
{ok, integer_to_binary(Key)};
|
||||||
|
key(Key, _Opts) when is_binary(Key) ->
|
||||||
|
{ok, Key};
|
||||||
|
key(Key, Opts) ->
|
||||||
|
{error, Key}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
double_quote(Value) ->
|
||||||
|
<<$\", Value/binary, $\">>.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
single_quote(Value) ->
|
||||||
|
<<$\', Value/binary, $\'>>.
|
||||||
4342
src/gabarit_html_entities.erl
Normal file
4342
src/gabarit_html_entities.erl
Normal file
File diff suppressed because it is too large
Load Diff
489
src/gabarit_html_tag.erl
Normal file
489
src/gabarit_html_tag.erl
Normal file
@@ -0,0 +1,489 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% @doc draft module to convert gabarit html into behavior.
|
||||||
|
%%%
|
||||||
|
%%% The idea behind this module is to offer a comprehensive, flexible
|
||||||
|
%%% and easy interface to generate optimized HTML code. When
|
||||||
|
%%% evaluated, a template should produce two kind of data:
|
||||||
|
%%%
|
||||||
|
%%% 1. if the page is static, a `binary' term is produced containing
|
||||||
|
%%% the whole document
|
||||||
|
%%%
|
||||||
|
%%% 2. if the page is dynamic (including some function call), a list
|
||||||
|
%%% containing binary and function is then produced.
|
||||||
|
%%%
|
||||||
|
%%% == Requirement ==
|
||||||
|
%%%
|
||||||
|
%%% - Reusable HTML part
|
||||||
|
%%%
|
||||||
|
%%% - Optimized HTML
|
||||||
|
%%%
|
||||||
|
%%% - Flexible HTML generator (include, function, module call...)
|
||||||
|
%%%
|
||||||
|
%%% - Flexible HTML entities (standard, strict, custom...)
|
||||||
|
%%%
|
||||||
|
%%% - Compatible with spectre css by default (see:
|
||||||
|
%%% https://picturepan2.github.io/spectre/)
|
||||||
|
%%%
|
||||||
|
%%% - Compatible with htmx by default (see: https://htmx.org/)
|
||||||
|
%%%
|
||||||
|
%%% == More ==
|
||||||
|
%%%
|
||||||
|
%%% More tests will be required to evaluate the performance of the
|
||||||
|
%%% whole implementation, and if it's required to create dedicated
|
||||||
|
%%% module when compiling. Different models can be used:
|
||||||
|
%%%
|
||||||
|
%%% - Store templates into ETS (in memory)
|
||||||
|
%%%
|
||||||
|
%%% - Compile a module with common interface
|
||||||
|
%%%
|
||||||
|
%%% - Store static page in cache with versionning and dynamic one on
|
||||||
|
%%% another storage layer.
|
||||||
|
%%%
|
||||||
|
%%% == Notes ==
|
||||||
|
%%%
|
||||||
|
%%% ```
|
||||||
|
%%% {}: null
|
||||||
|
%%% {Variable}: variable
|
||||||
|
%%% {Tag, Content}: tag without attribute
|
||||||
|
%%% {Tag, Attribute, Content}: full tag
|
||||||
|
%%% {Tag, Attribute, Content, Opts}: full tag with custom options
|
||||||
|
%%% {apply, Module, Function, Args}: special tag
|
||||||
|
%%% {apply, Function}
|
||||||
|
%%% {apply, Function, Args}
|
||||||
|
%%% '''
|
||||||
|
%%%
|
||||||
|
%%% Static template produce (a binary):
|
||||||
|
%%%
|
||||||
|
%%% ```
|
||||||
|
%%% <<"<body><head>test</head></body>">>
|
||||||
|
%%% '''
|
||||||
|
%%%
|
||||||
|
%%% Dynamic template production (a list of binaries and tuples):
|
||||||
|
%%%
|
||||||
|
%%% ```
|
||||||
|
%%% [ <<"<body><head>"
|
||||||
|
%%% , {apply, Module, Function, Args}
|
||||||
|
%%% , "</head></body>">>
|
||||||
|
%%% ]
|
||||||
|
%%% '''
|
||||||
|
%%%
|
||||||
|
%%% ```
|
||||||
|
%%% 1: [{body, #{}, {head, #{}, []}}].
|
||||||
|
%%% 2: {body, #{}, {head, #{}, []}}.
|
||||||
|
%%% 3: ["<body>", {head, #{}, []} ,"</body>"]
|
||||||
|
%%% 4: ["<body>", "<head>", "</head>", "</body>"]
|
||||||
|
%%% 5: "<body><head></head></body>"
|
||||||
|
%%% '''
|
||||||
|
%%%
|
||||||
|
%%% @end
|
||||||
|
%%%===================================================================
|
||||||
|
-module(gabarit_html_tag).
|
||||||
|
-export([create/1, create/2]).
|
||||||
|
-export([init/1]).
|
||||||
|
-export([tag/3]).
|
||||||
|
-export([attribute/5]).
|
||||||
|
-export([content/3]).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% some type definition.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-type state() :: term().
|
||||||
|
-type data() :: binary() | function().
|
||||||
|
-type return_ok() :: {ok, data(), state()}.
|
||||||
|
-type return_stop() :: {stop, term(), state()}.
|
||||||
|
-type return() :: return_ok() | return_stop().
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
create(Data) -> create(Data, #{}).
|
||||||
|
create(Data, Opts) -> gabarit_generator:compile(?MODULE, Opts, Data).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-spec init(Args) -> Return when
|
||||||
|
Args :: term(),
|
||||||
|
Return :: term().
|
||||||
|
|
||||||
|
init(Args) ->
|
||||||
|
{ok, []}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @doc main function used to generate html using gabarit tags.
|
||||||
|
%% @end
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-spec tag(Tag, Opts, State) -> Return when
|
||||||
|
Tag :: term(),
|
||||||
|
Opts :: map(),
|
||||||
|
State :: term(),
|
||||||
|
Return :: return().
|
||||||
|
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
% add variable support (template only). A variable can be any Erlang
|
||||||
|
% term but MUST be present in variables key from Opts
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
tag({Variable} = Tag, Opts, State) ->
|
||||||
|
{stop, {todo, Tag, Opts}, State};
|
||||||
|
|
||||||
|
%--------------------------------------------------------------------
|
||||||
|
% explicit content with specific options
|
||||||
|
%--------------------------------------------------------------------
|
||||||
|
tag({content, Content} = Tag, Opts, State) ->
|
||||||
|
content(Content, Opts, State);
|
||||||
|
tag({content, Content, LocalOpts} = Tag, Opts, State) ->
|
||||||
|
content(Content, maps:merge(Opts, LocalOpts), State);
|
||||||
|
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
% add raw include support. A raw include can use default parameters
|
||||||
|
% from Opts but can also use local parameters, useful if someone needs
|
||||||
|
% to insert a script or stylecheat.
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
tag({include_raw, Path} = Tag, Opts, State) ->
|
||||||
|
{stop, {todo, Tag, Opts}, State};
|
||||||
|
tag({include_raw, Path, LocalOpts} = Tag, Opts, State) ->
|
||||||
|
{stop, {todo, Tag, Opts}, State};
|
||||||
|
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
% add template include support. A template is an erml file or a list
|
||||||
|
% of term containing gabarit html tags.
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
tag({include_template, Path} = Tag, Opts, State) ->
|
||||||
|
{stop, {todo, Tag, Opts}, State};
|
||||||
|
tag({include_template, Path, LocalOpts} = Tag, Opts, State) ->
|
||||||
|
{stop, {todo, Tag, Opts}, State};
|
||||||
|
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
% call a standard behaviors and integrate their answer in the
|
||||||
|
% template. this will create a dynamic template. This part of the code
|
||||||
|
% is managed by the generator or the final renderer.
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
% default call to gen_server
|
||||||
|
tag({call, Pid, Message}, Opts, State) ->
|
||||||
|
tag({gen_server, call, Pid, Message, 1000}, Opts, State);
|
||||||
|
tag({call, Pid, Message, Timeout}, Opts, State) ->
|
||||||
|
tag({gen_server, call, Pid, Message, Timeout}, Opts, State);
|
||||||
|
|
||||||
|
% gen_server support
|
||||||
|
tag({gen_server, call, Pid, Message}, Opts, State) ->
|
||||||
|
tag({gen_server, call, Pid, Message, 1000}, Opts, State);
|
||||||
|
tag({gen_server, call, Pid, Message, Timeout} = Call, Opts, State) ->
|
||||||
|
{ok, Call, State};
|
||||||
|
|
||||||
|
% statem support
|
||||||
|
tag({gen_statem, call, Pid, Message}, Opts, State) ->
|
||||||
|
tag({gen_statem, call, Pid, Message, 1000}, Opts, State);
|
||||||
|
tag({gen_statem, call, Pid, Message, Timeout} = Call, Opts, State) ->
|
||||||
|
{ok, Call, State};
|
||||||
|
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
% add MFA support. This part of the code add a dynamic layer to the
|
||||||
|
% page. Every time the page is called, MFA defined is called and then
|
||||||
|
% generate a new page.
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
tag({apply, Module, Function, Arguments}, Opts, State)
|
||||||
|
when is_atom(Module), is_atom(Function), is_list(Arguments) ->
|
||||||
|
try apply(Module, Function, Arguments) of
|
||||||
|
{ok, Result} when is_binary(Result) ->
|
||||||
|
{ok, Result, State}
|
||||||
|
catch
|
||||||
|
E:R:S -> {E,R,S}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
% add local function support (helper) to execute on the same module.
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
tag({apply, Function, Arguments}, Opts, State)
|
||||||
|
when is_atom(Function), is_list(Arguments) ->
|
||||||
|
try Function(Arguments) of
|
||||||
|
{ok, Result} when is_binary(Result) ->
|
||||||
|
{ok, Result, State}
|
||||||
|
catch
|
||||||
|
E:R:S -> {E,R,S}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
% add support for function generator (without argument). Same as MFA.
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
tag({apply, Function}, Opts, State)
|
||||||
|
when is_function(Function, 0) ->
|
||||||
|
try Function() of
|
||||||
|
{ok, Result} when is_binary(Result) ->
|
||||||
|
{ok, Result, State}
|
||||||
|
catch
|
||||||
|
E:R:S -> {E,R,S}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
% add support for function with opts. Same as MFA.
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
tag({apply, Function}, Opts, State)
|
||||||
|
when is_function(Function, 1) ->
|
||||||
|
try Function(Opts) of
|
||||||
|
{ok, Result} when is_binary(Result) ->
|
||||||
|
{ok, Result, State}
|
||||||
|
catch
|
||||||
|
E:R:S -> {E,R,S}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
% add support for empty tag. Empty tags are special tags without
|
||||||
|
% content.
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
tag({empty, Tag} = T, Opts, State) ->
|
||||||
|
{stop, {todo, T, Opts}, State};
|
||||||
|
tag({{empty, Tag} = T, Attributes}, Opts, State) ->
|
||||||
|
{stop, {todo, T, Opts}, State};
|
||||||
|
|
||||||
|
%--------------------------------------------------------------------
|
||||||
|
% some tags will behave differently:
|
||||||
|
% - base
|
||||||
|
% - br
|
||||||
|
% - img
|
||||||
|
% - input
|
||||||
|
% - link
|
||||||
|
% - meta
|
||||||
|
% - source
|
||||||
|
%--------------------------------------------------------------------
|
||||||
|
tag({<<"code">>, Attributes, [Integer|_] = Content} = Tag, Opts, State)
|
||||||
|
when is_list(Content), is_integer(Integer), Integer>0 ->
|
||||||
|
Result = list_to_binary(Content),
|
||||||
|
tag({<<"code">>, Attributes, Result}, Opts, State);
|
||||||
|
tag({<<"code">>, Attributes, [List|_] = Content} = Tag, Opts, State)
|
||||||
|
when is_list(Content), is_list(List) ->
|
||||||
|
Result = list_to_binary(string:join(Content, "\n")),
|
||||||
|
tag({<<"code">>, Attributes, Result}, Opts, State);
|
||||||
|
tag({<<"code">>, Attributes, [Binary|_] = Content} = Tag, Opts, State)
|
||||||
|
when is_list(Content), is_binary(Binary) ->
|
||||||
|
Result = join(Content, <<"\n">>),
|
||||||
|
tag({<<"code">>, Attributes, Result}, Opts, State);
|
||||||
|
tag({<<"code">>, Attributes, Content}, Opts, State)
|
||||||
|
when is_binary(Content) ->
|
||||||
|
Begin = bracket(<<"code">>, <<>>),
|
||||||
|
End = bracket_end(<<"code">>),
|
||||||
|
{ok, <<Begin/binary, Content/binary, End/binary>>, State};
|
||||||
|
|
||||||
|
tag({<<"pre">>, Attributes, [Integer|_] = Content} = Tag, Opts, State)
|
||||||
|
when is_list(Content), is_integer(Integer), Integer>0 ->
|
||||||
|
Result = list_to_binary(Content),
|
||||||
|
tag({<<"pre">>, Attributes, Result}, Opts, State);
|
||||||
|
tag({<<"pre">>, Attributes, [List|_] = Content} = Tag, Opts, State)
|
||||||
|
when is_list(Content), is_list(List) ->
|
||||||
|
Result = list_to_binary(string:join(Content, "\n")),
|
||||||
|
tag({<<"pre">>, Attributes, Result}, Opts, State);
|
||||||
|
tag({<<"pre">>, Attributes, [Binary|_] = Content} = Tag, Opts, State)
|
||||||
|
when is_list(Content), is_binary(Binary) ->
|
||||||
|
Result = join(Content, <<"\n">>),
|
||||||
|
tag({<<"pre">>, Attributes, Result}, Opts, State);
|
||||||
|
tag({<<"pre">>, Attributes, Content}, Opts, State)
|
||||||
|
when is_binary(Content) ->
|
||||||
|
Begin = bracket(<<"pre">>, <<>>),
|
||||||
|
End = bracket_end(<<"pre">>),
|
||||||
|
{ok, <<Begin/binary, Content/binary, End/binary>>, State};
|
||||||
|
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
% add support for regular tag
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
tag({Tag, Content}, Opts, State) ->
|
||||||
|
tag({Tag, #{}, Content}, Opts, State);
|
||||||
|
tag({Tag, Attributes, Content}, Opts, State)
|
||||||
|
when is_binary(Tag) ->
|
||||||
|
case attributes(Tag, Attributes, Opts, State) of
|
||||||
|
{ok, <<>>, NewState} ->
|
||||||
|
Begin = <<"<", Tag/binary,">">>,
|
||||||
|
End = <<"</",Tag/binary,">">>,
|
||||||
|
{ok, Begin, End, Content, State};
|
||||||
|
{ok, Serialized, NewState} ->
|
||||||
|
Begin = <<"<", Tag/binary," ", Serialized/binary, ">">>,
|
||||||
|
End = <<"</",Tag/binary,">">>,
|
||||||
|
{ok, Begin, End, Content, State}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
% add support when a list is present. If a list is present, we assume
|
||||||
|
% this is a list of tags and we should treat them one by one.
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
tag(Tags, Opts, State)
|
||||||
|
when is_list(Tags) ->
|
||||||
|
{stop, {todo, Tags, Opts}, State};
|
||||||
|
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
% All tags defined as atom, list or numbers are converted into binary
|
||||||
|
% by default. It offers a flexible way to define tags for the developer.
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
tag({Tag, Content}, Opts, State) ->
|
||||||
|
tag({Tag, #{}, Content}, Opts, State);
|
||||||
|
tag({Tag, Attributes, Content}, Opts, State)
|
||||||
|
when is_atom(Tag) ->
|
||||||
|
NewTag = atom_to_binary(Tag),
|
||||||
|
tag({NewTag, Attributes, Content}, Opts, State);
|
||||||
|
tag({Tag, Attributes, Content}, Opts, State)
|
||||||
|
when is_list(Tag) ->
|
||||||
|
NewTag = list_to_binary(Tag),
|
||||||
|
tag({NewTag, Attributes, Content}, Opts, State);
|
||||||
|
tag({Tag, Attributes, Content}, Opts, State)
|
||||||
|
when is_integer(Tag) ->
|
||||||
|
NewTag = integer_to_binary(Tag),
|
||||||
|
tag({NewTag, Attributes, Content}, Opts, State);
|
||||||
|
tag({Tag, Attributes, Content}, Opts, State)
|
||||||
|
when is_float(Tag) ->
|
||||||
|
NewTag = float_to_binary(Tag),
|
||||||
|
tag({NewTag, Attributes, Content}, Opts, State);
|
||||||
|
|
||||||
|
%--------------------------------------------------------------------
|
||||||
|
% we assume binary, numbers, and atoms are text. these tags must be
|
||||||
|
% protected and encoded with html entities.
|
||||||
|
%--------------------------------------------------------------------
|
||||||
|
tag(Text, Opts, State)
|
||||||
|
when is_binary(Text); is_atom(Text);
|
||||||
|
is_integer(Text); is_float(Text) ->
|
||||||
|
content(Text, Opts, State);
|
||||||
|
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
% If unsupported tags are present, we should stop.
|
||||||
|
%---------------------------------------------------------------------
|
||||||
|
tag(Unsupported, Opts, State) ->
|
||||||
|
{stop, {todo, Unsupported, Opts}, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
attributes(Tag, Attributes, Opts, State) ->
|
||||||
|
attributes(Tag, Attributes, maps:keys(Attributes), [], Opts, State).
|
||||||
|
|
||||||
|
attributes(Tag, Attributes, [], Buffer, Opts, State) ->
|
||||||
|
{ok, join(lists:reverse(Buffer)), State};
|
||||||
|
attributes(Tag, Attributes, [Key|Keys], Buffer, Opts, State) ->
|
||||||
|
Value = maps:get(Key, Attributes),
|
||||||
|
case attribute(Tag, Key, Value, Opts, State) of
|
||||||
|
{ok, K, V, NewState} ->
|
||||||
|
Pair = <<K/binary,"=",V/binary>>,
|
||||||
|
attributes(Tag, Attributes, Keys, [Pair|Buffer], Opts, NewState);
|
||||||
|
Elsewise ->
|
||||||
|
Elsewise
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @doc main function used to generate html using gabarit tags.
|
||||||
|
%% @end
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-spec attribute(Tag, Key, Value, Opts, State) -> Return when
|
||||||
|
Tag :: binary(),
|
||||||
|
Key :: term(),
|
||||||
|
Value :: term(),
|
||||||
|
Opts :: map(),
|
||||||
|
State :: term(),
|
||||||
|
Return :: return().
|
||||||
|
|
||||||
|
% convert keys to binary
|
||||||
|
attribute(Tag, Key, Value, Opts, State)
|
||||||
|
when is_atom(Key) ->
|
||||||
|
attribute(Tag, atom_to_binary(Key), Value, Opts, State);
|
||||||
|
attribute(Tag, Key, Value, Opts, State)
|
||||||
|
when is_list(Key) ->
|
||||||
|
attribute(Tag, list_to_binary(Key), Value, Opts, State);
|
||||||
|
|
||||||
|
% convert values to binary
|
||||||
|
attribute(Tag, Key, Value, Opts, State)
|
||||||
|
when is_atom(Value) ->
|
||||||
|
attribute(Tag, Key, atom_to_binary(Value), Opts, State);
|
||||||
|
attribute(Tag, Key, Value, Opts, State)
|
||||||
|
when is_list(Value) ->
|
||||||
|
attribute(Tag, Key, list_to_binary(Value), Opts, State);
|
||||||
|
|
||||||
|
% some example of specific attributes
|
||||||
|
attribute(<<"a">>, <<"href">> = Key, Value, Opts, State) ->
|
||||||
|
{ok, Key, Value, State};
|
||||||
|
attribute(<<"img">>, <<"src">> = Key, Value, Opts, State) ->
|
||||||
|
{ok, Key, Value, State};
|
||||||
|
attribute(_Tag, <<"style">>, Value, Opts, State) ->
|
||||||
|
{ok, <<"style">>, Value, State};
|
||||||
|
attribute(_Tag, <<"on", _/binary>> = Key, Value, Opts, State) ->
|
||||||
|
{ok, Key, Value, State};
|
||||||
|
attribute(_Tag, <<"hx-vals">> = Key, Value, Opts, State) ->
|
||||||
|
{ok, Key, Value, State};
|
||||||
|
attribute(_Tag, <<"hx-", _binary/binary>> = Key, Value, Opts, State) ->
|
||||||
|
{ok, Key, Value, State};
|
||||||
|
attribute(_Tag, <<"htmx-", _binary/binary>> = Key, Value, Opts, State) ->
|
||||||
|
{ok, Key, Value, State};
|
||||||
|
attribute(_Tag, Key, Value, Opts, State)
|
||||||
|
when is_binary(Key), is_binary(Value) ->
|
||||||
|
{ok, Key, quote(Value, $"), State};
|
||||||
|
|
||||||
|
% global attributes, by default, not supported
|
||||||
|
attribute(Tag, Key, Value, Opts, State) ->
|
||||||
|
{stop, {todo, Tag, Key, Value, Opts, State}, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% @doc main function to check content (inner text).
|
||||||
|
%% @end
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
-spec content(Content, Opts, State) -> Return when
|
||||||
|
Content :: term(),
|
||||||
|
Opts :: map(),
|
||||||
|
State :: term(),
|
||||||
|
Return :: return().
|
||||||
|
|
||||||
|
content(Content, Opts, State)
|
||||||
|
when is_atom(Content) ->
|
||||||
|
content(atom_to_binary(Content), Opts, State);
|
||||||
|
content(Content, Opts, State)
|
||||||
|
when is_integer(Content) ->
|
||||||
|
content(integer_to_binary(Content), Opts, State);
|
||||||
|
content(Content, Opts, State)
|
||||||
|
when is_float(Content) ->
|
||||||
|
content(float_to_binary(Content), Opts, State);
|
||||||
|
content(Content, Opts, State)
|
||||||
|
when is_list(Content) ->
|
||||||
|
content(list_to_binary(Content), Opts, State);
|
||||||
|
content(Content, #{ entities := false } = _Opts, State)
|
||||||
|
when is_binary(Content) ->
|
||||||
|
{ok, Content, State};
|
||||||
|
content(Content, Opts, State)
|
||||||
|
when is_binary(Content) ->
|
||||||
|
{ok, gabarit_html_entities:encode(Content), State};
|
||||||
|
content(Content, Opts, State) ->
|
||||||
|
{stop, {todo, content, Content, Opts, State}, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
join([]) -> <<>>;
|
||||||
|
join(Binaries) -> join(Binaries, <<" ">>).
|
||||||
|
|
||||||
|
join([], _) -> <<>>;
|
||||||
|
join(Binaries, Sep) -> join(Binaries, Sep, <<>>).
|
||||||
|
|
||||||
|
join([], _, Buffer) -> Buffer;
|
||||||
|
join([Binary], _, Buffer) ->
|
||||||
|
<<Binary/binary, Buffer/binary>>;
|
||||||
|
join([Binary|Rest], Sep, Buffer) ->
|
||||||
|
join(Rest, Sep, <<Sep/binary, Binary/binary, Buffer/binary>>).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
quote(Value, Char) ->
|
||||||
|
<<Char, Value/binary, Char>>.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
bracket_empty(Element, <<>>) ->
|
||||||
|
<<"<", Element/binary, " />">>;
|
||||||
|
bracket_empty(Element, Attributes) ->
|
||||||
|
<<"<", Element/binary, " ", Attributes/binary, " />">>.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
bracket(Element, <<>>) ->
|
||||||
|
<<"<", Element/binary, ">">>;
|
||||||
|
bracket(Element, Attributes) ->
|
||||||
|
<<"<", Element/binary, " ", Attributes/binary, ">">>.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%%
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
bracket_end(Element) ->
|
||||||
|
<<"</", Element/binary, ">">>.
|
||||||
17
test/gabarit_SUITE_data/html_001.erml
Normal file
17
test/gabarit_SUITE_data/html_001.erml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{html, [
|
||||||
|
{head, [
|
||||||
|
{apply, {module, function, args}},
|
||||||
|
{apply, {function, args}},
|
||||||
|
{apply, fun(_Opts) -> {ok, <<>>} end},
|
||||||
|
]},
|
||||||
|
{body, [
|
||||||
|
{'div', [
|
||||||
|
{h1, <<"title">>},
|
||||||
|
{p, <<"paragraph1">>},
|
||||||
|
{p, <<"paragraph2">>},
|
||||||
|
{p, [{span, <<"test">>},
|
||||||
|
<<"paragraph1">>]},
|
||||||
|
]},
|
||||||
|
{include, "/path/to/paragraph.erlm" }
|
||||||
|
]}
|
||||||
|
]}.
|
||||||
10
test/gabarit_html_SUITE_data/code_simple.erml
Normal file
10
test/gabarit_html_SUITE_data/code_simple.erml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% Simple code template.
|
||||||
|
%%%===================================================================
|
||||||
|
{code, [
|
||||||
|
<<"struct proc *p;">>,
|
||||||
|
<<"struct process *pr;">>,
|
||||||
|
<<"struct pdevinit *pdev;">>,
|
||||||
|
<<"extern struct pdevinit pdevinit[];">>,
|
||||||
|
<<"extern void disk_init(void);">>,
|
||||||
|
]}.
|
||||||
10
test/gabarit_html_SUITE_data/form_select.erml
Normal file
10
test/gabarit_html_SUITE_data/form_select.erml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% A form with select/option.
|
||||||
|
%%%===================================================================
|
||||||
|
{'div', #{class => "form-group"}, [
|
||||||
|
{select, #{class => "form-select"}, [
|
||||||
|
{option, [<<"Joe Armstrong">>]},
|
||||||
|
{option, [<<"Robert Virding">>]},
|
||||||
|
{option, [<<"Mike Williams">>]}
|
||||||
|
]}
|
||||||
|
]}.
|
||||||
13
test/gabarit_html_SUITE_data/form_simple.erml
Normal file
13
test/gabarit_html_SUITE_data/form_simple.erml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% A simple form.
|
||||||
|
%%%===================================================================
|
||||||
|
{'div', #{class => "form-group"}, [
|
||||||
|
{label, #{ class => "form-label"
|
||||||
|
, for => "input-example-1", ["Name"]
|
||||||
|
}, []},
|
||||||
|
{input, #{ class => "form-input"
|
||||||
|
, type => "text"
|
||||||
|
, id => "input-example-1"
|
||||||
|
, placeholder => "Name"
|
||||||
|
}, []}
|
||||||
|
]}.
|
||||||
9
test/gabarit_html_SUITE_data/hero_simple.erml
Normal file
9
test/gabarit_html_SUITE_data/hero_simple.erml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% A simple hero page.
|
||||||
|
%%%===================================================================
|
||||||
|
{'div', #{class => "hero bg-gray"}, [
|
||||||
|
{'div', #{class => "hero-body"}, [
|
||||||
|
{h1, <<"Hero title">>},
|
||||||
|
{p, <<"This is a hero example">>}
|
||||||
|
]}
|
||||||
|
]}.
|
||||||
9
test/gabarit_html_SUITE_data/list_simple.erml
Normal file
9
test/gabarit_html_SUITE_data/list_simple.erml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% A static list.
|
||||||
|
%%%===================================================================
|
||||||
|
{ul, [
|
||||||
|
{li, <<"In order to handle failure, you need two machines.">>},
|
||||||
|
{li, <<"Shared memory is evil.">>},
|
||||||
|
{li, <<"Erlang is like Meccano. Meccano is very good.">>},
|
||||||
|
{li, <<"Bad ideas in computer science and anywhere are sticky.">>}
|
||||||
|
]}.
|
||||||
4
test/gabarit_html_SUITE_data/list_template.erml
Normal file
4
test/gabarit_html_SUITE_data/list_template.erml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% A template list.
|
||||||
|
%%%===================================================================
|
||||||
|
{ul, {list}}.
|
||||||
19
test/gabarit_html_SUITE_data/page_complex.erml
Normal file
19
test/gabarit_html_SUITE_data/page_complex.erml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% A more complex page calling other tag.
|
||||||
|
%%%===================================================================
|
||||||
|
{html, [
|
||||||
|
{head, [
|
||||||
|
{title, <<"Lorem Ipsum">>}
|
||||||
|
]},
|
||||||
|
{body, [
|
||||||
|
% calling a module
|
||||||
|
{'div', #{id => "module"}, [
|
||||||
|
{apply, {gabarit_test_module, random_paragraph, []}}
|
||||||
|
]},
|
||||||
|
% include a raw text
|
||||||
|
% including a template with variable
|
||||||
|
{'div', #{id => "template"}, [
|
||||||
|
{include_template, "quote_template", #{quote => <<"test">>, author => <<"test">>}}
|
||||||
|
]}
|
||||||
|
]}
|
||||||
|
]}.
|
||||||
25
test/gabarit_html_SUITE_data/page_include.erml
Normal file
25
test/gabarit_html_SUITE_data/page_include.erml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% A more complex page calling other tag.
|
||||||
|
%%%===================================================================
|
||||||
|
{html, [
|
||||||
|
{head, [
|
||||||
|
{title, <<"Include test">>}
|
||||||
|
]},
|
||||||
|
{body, [
|
||||||
|
{'div', #{id => "ascii"}, [
|
||||||
|
{include_raw, "raw_ascii.txt"}
|
||||||
|
]},
|
||||||
|
{'div', #{id => "html"}, [
|
||||||
|
{include_raw, "raw_html.txt"}
|
||||||
|
]},
|
||||||
|
{'div', #{id => "chinese"}, [
|
||||||
|
{include_raw, "raw_chinese.txt"}
|
||||||
|
]},
|
||||||
|
{'div', #{id => "japanese"}, [
|
||||||
|
{include_raw, "raw_japanese.txt"}
|
||||||
|
]},
|
||||||
|
{'div', #{id => "russian"}, [
|
||||||
|
{include_raw, "raw_russian.txt"}
|
||||||
|
]}
|
||||||
|
]}
|
||||||
|
]}.
|
||||||
53
test/gabarit_html_SUITE_data/page_simple.erml
Normal file
53
test/gabarit_html_SUITE_data/page_simple.erml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% This is a simple template page containing Lorem Ipsum content.
|
||||||
|
%%%===================================================================
|
||||||
|
{html, [
|
||||||
|
{head, [
|
||||||
|
{title, <<"Lorem Ipsum">>}
|
||||||
|
]},
|
||||||
|
{body, [
|
||||||
|
{h1, <<"Lorem Ipsum">>},
|
||||||
|
{h2, <<"Neque porro quisquam est qui dolorem ipsum quia dolor sit"
|
||||||
|
"amet, consectetur, adipisci velit...">>},
|
||||||
|
{p, <<"Lorem ipsum dolor sit amet, consectetur adipiscing"
|
||||||
|
"elit. Aenean vel lobortis augue, sit amet mollis"
|
||||||
|
"tortor. Etiam ante tortor, rutrum sit amet dolor vel,"
|
||||||
|
"maximus commodo lacus. Donec eu ex sed diam ullamcorper"
|
||||||
|
"facilisis. Integer felis quam, rhoncus nec fermentum vel,"
|
||||||
|
"aliquam eu libero. Donec lectus dui, mollis vitae elit sit"
|
||||||
|
"amet, consectetur placerat nibh. Maecenas sit amet elit eu"
|
||||||
|
"libero auctor volutpat ut sit amet metus. Nunc consequat"
|
||||||
|
"facilisis dolor, non scelerisque diam luctus eu. Nunc"
|
||||||
|
"bibendum lectus non nisi rhoncus convallis. Proin"
|
||||||
|
"dignissim, nulla at mattis venenatis, quam massa ornare"
|
||||||
|
"massa, dictum eleifend nisi sapien at quam. Donec lacinia"
|
||||||
|
"dapibus augue vel consectetur. Nulla facilisi. Mauris non"
|
||||||
|
"blandit lectus, imperdiet sollicitudin quam. Fusce semper"
|
||||||
|
"nisi eu enim convallis ullamcorper. Aenean velit arcu,"
|
||||||
|
"dictum ut arcu vitae, rutrum condimentum metus. Ut nulla"
|
||||||
|
"ante, ultricies et urna eu, hendrerit ullamcorper"
|
||||||
|
"massa. Vestibulum vestibulum volutpat tempus. ">>},
|
||||||
|
{p, <<"Pellentesque interdum augue eu blandit"
|
||||||
|
"molestie. Pellentesque iaculis mauris eget odio sagittis, a"
|
||||||
|
"sagittis tortor molestie. Cras vestibulum leo vitae lorem"
|
||||||
|
"feugiat, ac pretium tellus dictum. Integer tristique erat"
|
||||||
|
"lectus, pellentesque mollis tortor tincidunt non. Sed et"
|
||||||
|
"diam et dui dapibus egestas. Phasellus eu elementum lectus,"
|
||||||
|
"ac pretium enim. Donec tempor, ipsum sit amet rhoncus"
|
||||||
|
"rutrum, ante nibh volutpat nibh, eu consectetur mauris"
|
||||||
|
"metus id tellus. Donec a mattis ex. Quisque aliquet est non"
|
||||||
|
"erat porttitor, eget ultrices erat dignissim. Vestibulum"
|
||||||
|
"ante ipsum primis in faucibus orci luctus et ultrices"
|
||||||
|
"posuere cubilia curae;">>},
|
||||||
|
{p, <<"Nunc sodales urna tortor. Vestibulum dui sem, venenatis non"
|
||||||
|
"tellus vel, tristique tempor mauris. Mauris consectetur"
|
||||||
|
"mauris a nisl suscipit pretium. Fusce diam odio,"
|
||||||
|
"sollicitudin ac diam et, auctor gravida tellus. Vivamus est"
|
||||||
|
"nisl, venenatis sit amet accumsan ac, sagittis id"
|
||||||
|
"risus. Sed nec eros purus. Vestibulum vulputate nulla ac"
|
||||||
|
"pellentesque tempus. Cras vehicula eros eu nulla mollis"
|
||||||
|
"ornare. Pellentesque et tellus viverra, egestas diam et,"
|
||||||
|
"iaculis dolor. Quisque facilisis turpis quis nibh bibendum"
|
||||||
|
"posuere.">>}
|
||||||
|
]}
|
||||||
|
]}.
|
||||||
7
test/gabarit_html_SUITE_data/quote_simple.erml
Normal file
7
test/gabarit_html_SUITE_data/quote_simple.erml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% A simple quote using blockquote and cite tags.
|
||||||
|
%%%===================================================================
|
||||||
|
{blockquote, [
|
||||||
|
{p, <<"Four languages to learn: C, Prolog, Erlang, Javascript.">>},
|
||||||
|
{cite, <<"Joe Armstrong">>}
|
||||||
|
]}.
|
||||||
7
test/gabarit_html_SUITE_data/quote_template.erml
Normal file
7
test/gabarit_html_SUITE_data/quote_template.erml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
%%%===================================================================
|
||||||
|
%%% A templating quote using blockquote and cite tags.
|
||||||
|
%%%===================================================================
|
||||||
|
{blockquote, [
|
||||||
|
{p, [{quote}]},
|
||||||
|
{cite, [{author}]}
|
||||||
|
]}.
|
||||||
49
test/gabarit_html_SUITE_data/raw_ascii.txt
Normal file
49
test/gabarit_html_SUITE_data/raw_ascii.txt
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
Neque porro quisquam est qui dolorem ipsum quia dolor sit amet,
|
||||||
|
consectetur, adipisci velit...
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce non
|
||||||
|
velit iaculis, congue risus non, posuere ante. Nunc gravida aliquet
|
||||||
|
pellentesque. Nullam euismod sed dui elementum efficitur. Pellentesque
|
||||||
|
elementum diam eget consequat rhoncus. Nunc nunc nisi, egestas sit
|
||||||
|
amet feugiat vel, tristique eget risus. Nunc eget dui et libero
|
||||||
|
placerat consectetur. Maecenas iaculis dui tincidunt mattis
|
||||||
|
congue. Duis odio ligula, eleifend et ullamcorper eu, consectetur ac
|
||||||
|
nisi. Pellentesque ut risus odio. Integer feugiat, tellus nec ultrices
|
||||||
|
sollicitudin, nisi ex ornare tortor, in venenatis nulla ipsum cursus
|
||||||
|
tortor.
|
||||||
|
|
||||||
|
Nunc molestie luctus convallis. Cras bibendum lorem convallis sapien
|
||||||
|
iaculis feugiat. Maecenas porta dui ac elementum bibendum. Maecenas
|
||||||
|
lobortis elit ex, vel auctor arcu ornare vitae. In hac habitasse
|
||||||
|
platea dictumst. Morbi vitae leo sit amet lacus auctor faucibus. Nam
|
||||||
|
bibendum ipsum turpis, sit amet posuere elit viverra eu. Maecenas
|
||||||
|
tempus lacinia lectus, eu ultrices orci maximus nec.
|
||||||
|
|
||||||
|
Vivamus dignissim feugiat diam. Nunc vulputate vestibulum
|
||||||
|
tempus. Donec semper id sapien in consectetur. Class aptent taciti
|
||||||
|
sociosqu ad litora torquent per conubia nostra, per inceptos
|
||||||
|
himenaeos. Nullam blandit tempus libero id lobortis. Etiam convallis
|
||||||
|
vehicula ligula at auctor. Nullam et porta turpis, a porttitor
|
||||||
|
odio. Aliquam imperdiet sit amet magna eu tempor. Nunc finibus porta
|
||||||
|
ante nec ornare.
|
||||||
|
|
||||||
|
Interdum et malesuada fames ac ante ipsum primis in
|
||||||
|
faucibus. Vestibulum ante ipsum primis in faucibus orci luctus et
|
||||||
|
ultrices posuere cubilia curae; Quisque sit amet ex urna. Pellentesque
|
||||||
|
habitant morbi tristique senectus et netus et malesuada fames ac
|
||||||
|
turpis egestas. Quisque quis massa vel ante vestibulum rutrum. Cras eu
|
||||||
|
sem facilisis dolor molestie ultrices. Donec luctus ante a lorem
|
||||||
|
gravida tincidunt. Duis scelerisque quis tortor non fringilla. Morbi
|
||||||
|
in efficitur nisl. Ut et felis at nunc tincidunt varius sit amet id
|
||||||
|
lectus. Praesent ut nulla sed tortor volutpat imperdiet. Duis euismod
|
||||||
|
velit et tincidunt suscipit. Nullam vitae tristique purus. Curabitur
|
||||||
|
ut mi ipsum. Phasellus ultricies risus non odio fermentum porttitor.
|
||||||
|
|
||||||
|
Pellentesque non augue dolor. Ut euismod odio sit amet sapien
|
||||||
|
fringilla sollicitudin. Vivamus vel erat quis eros tincidunt fringilla
|
||||||
|
ac et metus. Aliquam odio justo, gravida eget aliquet feugiat,
|
||||||
|
accumsan semper tortor. Donec pharetra massa erat, sed ullamcorper
|
||||||
|
augue ultricies eget. Nam sit amet metus hendrerit nunc posuere porta
|
||||||
|
non nec odio. Mauris pulvinar erat in massa dapibus mattis.
|
||||||
1
test/gabarit_html_SUITE_data/raw_chinese.txt
Normal file
1
test/gabarit_html_SUITE_data/raw_chinese.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
坏人活着是为了吃与喝,而好人却是为了活着才吃与喝。
|
||||||
1
test/gabarit_html_SUITE_data/raw_html.txt
Normal file
1
test/gabarit_html_SUITE_data/raw_html.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<p>this is a test.</p>
|
||||||
3
test/gabarit_html_SUITE_data/raw_japanese.txt
Normal file
3
test/gabarit_html_SUITE_data/raw_japanese.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
佛道をならふといふは 自己をならふ也 自己をならふといふは 自己をわする
|
||||||
|
るなり 自己をわするるといふは 萬法に證せらるるなり 萬法に證せらるると
|
||||||
|
いふは 自己の身心および他己の身心をして脱落せしむるなり
|
||||||
10
test/gabarit_html_SUITE_data/raw_russian.txt
Normal file
10
test/gabarit_html_SUITE_data/raw_russian.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Желать вам всякого добра — я желаю, о мужи афиняне, и люблю вас, а
|
||||||
|
слушаться буду скорее бога[комм. 1], чем вас, и, пока есть во мне
|
||||||
|
дыхание и способность, не перестану философствовать, уговаривать и
|
||||||
|
убеждать всякого из вас, кого только встречу, говоря то самое, что
|
||||||
|
обыкновенно говорю: о лучший из мужей, гражданин города Афин,
|
||||||
|
величайшего из городов и больше всех прославленного за мудрость и
|
||||||
|
силу, не стыдно ли тебе, что ты заботишься о деньгах, чтобы их у тебя
|
||||||
|
было как можно больше, о славе и о почестях, а о разумности, об истине
|
||||||
|
и о душе своей, чтобы она была как можно лучше, — не заботишься и не
|
||||||
|
помышляешь?
|
||||||
Reference in New Issue
Block a user