aboutsummaryrefslogtreecommitdiff
path: root/src/er_xdg_pipe_menu_renderer.erl
blob: fafed45c14bfe69790f793c8fb338d69331a5136 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
%%%-------------------------------------------------------------------
%% @doc Renders parsed desktop entries as an Openbox pipe menu.
%%
%% Given a list of `#desktop_entry{}' records, produces the XML that
%% Openbox expects on stdout from a pipe menu: one `<item>' per
%% visible entry (entries with `hidden = true' are dropped), sorted
%% case-insensitively by name, each wrapping an `Execute' action.
%% @end
%%%-------------------------------------------------------------------

-module(er_xdg_pipe_menu_renderer).

-include("er_xdg_pipe_menu.hrl").

-export([render/1]).

-spec render([#desktop_entry{}]) -> binary().
render(Entries) ->
    Visible = [E || E <- Entries, not E#desktop_entry.hidden],
    Sorted = lists:sort(fun by_name/2, Visible),
    iolist_to_binary([
        <<"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n">>,
        <<"<openbox_pipe_menu>\n">>,
        [render_item(E) || E <- Sorted],
        <<"</openbox_pipe_menu>\n">>
    ]).

by_name(#desktop_entry{name = A}, #desktop_entry{name = B}) ->
    string:lowercase(A) =< string:lowercase(B).

render_item(#desktop_entry{name = Name, exec = Exec, icon = Icon}) ->
    [
        <<"  <item label=\"">>,
        escape_attr(Name),
        <<"\"">>,
        icon_attr(Icon),
        <<">\n">>,
        <<"    <action name=\"Execute\">\n">>,
        <<"      <command>">>,
        escape_text(Exec),
        <<"</command>\n">>,
        <<"    </action>\n">>,
        <<"  </item>\n">>
    ].

icon_attr(undefined) -> <<>>;
icon_attr(Icon) -> [<<" icon=\"">>, escape_attr(Icon), <<"\"">>].

%% Attribute values additionally need '"' escaped; text content doesn't.
escape_attr(Bin) -> escape(Bin, [{<<"\"">>, <<"&quot;">>}]).
escape_text(Bin) -> escape(Bin, []).

escape(Bin, Extra) ->
    lists:foldl(
        fun({Pat, Repl}, Acc) -> binary:replace(Acc, Pat, Repl, [global]) end,
        Bin,
        [{<<"&">>, <<"&amp;">>}, {<<"<">>, <<"&lt;">>}, {<<">">>, <<"&gt;">>} | Extra]
    ).