erlydtl_filters.erl 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261
  1. %%%-------------------------------------------------------------------
  2. %%% File: erlydtl_filters.erl
  3. %%% @author Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
  4. %%% @author Evan Miller <emmiller@gmail.com>
  5. %%% @copyright 2008 Roberto Saccon, Evan Miller
  6. %%% @doc
  7. %%% Template filters
  8. %%% @end
  9. %%%
  10. %%% The MIT License
  11. %%%
  12. %%% Copyright (c) 2007 Roberto Saccon, Evan Miller
  13. %%%
  14. %%% Permission is hereby granted, free of charge, to any person obtaining a copy
  15. %%% of this software and associated documentation files (the "Software"), to deal
  16. %%% in the Software without restriction, including without limitation the rights
  17. %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  18. %%% copies of the Software, and to permit persons to whom the Software is
  19. %%% furnished to do so, subject to the following conditions:
  20. %%%
  21. %%% The above copyright notice and this permission notice shall be included in
  22. %%% all copies or substantial portions of the Software.
  23. %%%
  24. %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  25. %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  26. %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  27. %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  28. %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  29. %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  30. %%% THE SOFTWARE.
  31. %%%
  32. %%% @since 2007-11-11 by Roberto Saccon, Evan Miller
  33. %%%-------------------------------------------------------------------
  34. -module(erlydtl_filters).
  35. -author('rsaccon@gmail.com').
  36. -author('emmiller@gmail.com').
  37. -author('drew dot gulino at google dot com').
  38. -define(TEST,"").
  39. %-define(NOTEST,1).
  40. -define(NODEBUG,1).
  41. -include_lib("eunit/include/eunit.hrl").
  42. -ifdef(TEST).
  43. -export([cast_to_float/1,cast_to_integer/1,stringformat_io/7,round/2,unjoin/2,addDefaultURI/1]).
  44. -endif.
  45. -export([add/2,
  46. addslashes/1,
  47. capfirst/1,
  48. center/2,
  49. cut/2,
  50. date/1,
  51. date/2,
  52. default/2,
  53. default_if_none/2,
  54. dictsort/2,
  55. dictsortreversed/2,
  56. divisibleby/2,
  57. %escape/, - implemented in erlydtl_compiler
  58. escapejs/1,
  59. filesizeformat/1,
  60. first/1,
  61. fix_ampersands/1,
  62. floatformat/2,
  63. force_escape/1,
  64. format_integer/1,
  65. format_number/1,
  66. get_digit/2,
  67. iriencode/1,
  68. join/2,
  69. last/1,
  70. length/1,
  71. length_is/2,
  72. linebreaks/1,
  73. linebreaksbr/1,
  74. linenumbers/1,
  75. ljust/2,
  76. lower/1,
  77. make_list/1,
  78. phone2numeric/1,
  79. pluralize/1,
  80. pluralize/2,
  81. pprint/1,
  82. random/1,
  83. random_num/1,
  84. random_range/1,
  85. removetags/2,
  86. rjust/2,
  87. %safe/, - implemented in erlydtl_compiler
  88. %safeseq/, - implemented in erlydtl_compiler
  89. slice/2,
  90. slugify/1,
  91. stringformat/2,
  92. striptags/1,
  93. time/1,
  94. time/2,
  95. timesince/1,
  96. timesince/2,
  97. timeuntil/1,
  98. timeuntil/2,
  99. title/1,
  100. truncatechars/2,
  101. truncatewords/2,
  102. truncatewords_html/2,
  103. unordered_list/1,
  104. upper/1,
  105. urlencode/1,
  106. urlize/1,
  107. urlize/2,
  108. urlizetrunc/2,
  109. wordcount/1,
  110. wordwrap/2,
  111. yesno/2]).
  112. -define(NO_ENCODE(C), ((C >= $a andalso C =< $z) orelse
  113. (C >= $A andalso C =< $Z) orelse
  114. (C >= $0 andalso C =< $9) orelse
  115. (C =:= $\. orelse C =:= $-
  116. orelse C =:= $~ orelse C =:= $_))).
  117. -define(NO_IRI_ENCODE(C), (?NO_ENCODE(C) orelse (
  118. C =:= $/ orelse
  119. C =:= $# orelse
  120. C =:= $[ orelse
  121. C =:= $] orelse
  122. C =:= $= orelse
  123. C =:= $: orelse
  124. C =:= $; orelse
  125. C =:= $$ orelse
  126. C =:= $& orelse
  127. C =:= $( orelse
  128. C =:= $) orelse
  129. C =:= $+ orelse
  130. C =:= $, orelse
  131. C =:= $! orelse
  132. C =:= $? orelse
  133. C =:= $* orelse
  134. C =:= $@ orelse
  135. C =:= $' orelse
  136. C =:= $~))).
  137. -define(KILOBYTE, 1024).
  138. -define(MEGABYTE, (1024 * ?KILOBYTE)).
  139. -define(GIGABYTE, (1024 * ?MEGABYTE)).
  140. -define(SECONDS_PER_MINUTE, 60).
  141. -define(SECONDS_PER_HOUR, (60 * ?SECONDS_PER_MINUTE)).
  142. -define(SECONDS_PER_DAY, (24 * ?SECONDS_PER_HOUR)).
  143. -define(SECONDS_PER_WEEK, (7 * ?SECONDS_PER_DAY)).
  144. -define(SECONDS_PER_MONTH, (30 * ?SECONDS_PER_DAY)).
  145. -define(SECONDS_PER_YEAR, (365 * ?SECONDS_PER_DAY)).
  146. %% @doc Adds to values
  147. add(LHS, RHS) when is_number(LHS), is_number(RHS) ->
  148. LHS + RHS;
  149. add(LHS, RHS) when is_binary(LHS) ->
  150. add(binary_to_list(LHS), RHS);
  151. add(LHS, RHS) when is_binary(RHS) ->
  152. add(LHS, binary_to_list(RHS));
  153. add(LHS, RHS) when is_list(LHS), is_list(RHS) ->
  154. case {to_numeric(LHS), to_numeric(RHS)} of
  155. {{number, LHSNum}, {number, RHSNum}} ->
  156. LHSNum + RHSNum;
  157. _ ->
  158. LHS ++ RHS
  159. end;
  160. add(LHS, RHS) when is_list(LHS), is_number(RHS) ->
  161. case to_numeric(LHS) of
  162. {number, LHSNum} ->
  163. LHSNum + RHS;
  164. _ ->
  165. LHS ++ to_string(RHS)
  166. end;
  167. add(LHS, RHS) when is_number(LHS), is_list(RHS) ->
  168. case to_numeric(RHS) of
  169. {number, RHSNum} ->
  170. LHS + RHSNum;
  171. _ ->
  172. to_string(LHS) ++ RHS
  173. end.
  174. to_string(Num) when is_integer(Num) ->
  175. integer_to_list(Num);
  176. to_string(Num) when is_float(Num) ->
  177. float_to_list(Num).
  178. to_numeric(List) ->
  179. try
  180. {number, list_to_integer(List)}
  181. catch
  182. error:badarg ->
  183. try
  184. {number, list_to_float(List)}
  185. catch
  186. error:badarg ->
  187. undefined
  188. end
  189. end.
  190. %% @doc Adds slashes before quotes.
  191. addslashes(Input) when is_binary(Input) ->
  192. addslashes(binary_to_list(Input));
  193. addslashes(Input) when is_list(Input) ->
  194. addslashes(Input, []).
  195. %% @doc Capitalizes the first character of the value.
  196. capfirst([H|T]) when H >= $a andalso H =< $z ->
  197. [H + $A - $a | T];
  198. capfirst(Other) when is_list(Other) ->
  199. Other;
  200. capfirst(<<Byte:8/integer, Binary/binary>>) when Byte >= $a andalso Byte =< $z ->
  201. [(Byte + $A - $a)|binary_to_list(Binary)];
  202. capfirst(Other) when is_binary(Other) ->
  203. Other.
  204. %% @doc Centers the value in a field of a given width.
  205. center(Input, Number) when is_binary(Input) ->
  206. list_to_binary(center(binary_to_list(Input), Number));
  207. center(Input, Number) when is_list(Input) ->
  208. string:centre(Input, Number).
  209. %% @doc Removes all values of arg from the given string.
  210. cut(Input, Arg) when is_binary(Arg) ->
  211. cut(Input, binary_to_list(Arg));
  212. cut(Input, Arg) when is_binary(Input) ->
  213. cut(binary_to_list(Input), Arg);
  214. cut(Input, [Char]) when is_list(Input) ->
  215. cut(Input, Char, []).
  216. %% @doc Formats a date according to the default format.
  217. date(Input) ->
  218. date(Input, "F j, Y").
  219. %% @doc Formats a date according to the given format.
  220. date(Input, FormatStr) when is_binary(Input) ->
  221. list_to_binary(date(binary_to_list(Input), FormatStr));
  222. date({{_,_,_} = Date,{_,_,_} = Time}, FormatStr) ->
  223. erlydtl_dateformat:format({Date, Time}, FormatStr);
  224. date({_,_,_} = Date, FormatStr) ->
  225. erlydtl_dateformat:format(Date, FormatStr);
  226. date(Input, _FormatStr) when is_list(Input) ->
  227. io:format("Unexpected date parameter : ~p~n", [Input]),
  228. "".
  229. %% @doc If value evaluates to `false', use given default. Otherwise, use the value.
  230. default(Input, Default) ->
  231. case erlydtl_runtime:is_false(Input) of
  232. true -> Default;
  233. false -> Input
  234. end.
  235. %% @doc If (and only if) value is `undefined', use given default. Otherwise, use the value.
  236. default_if_none(undefined, Default) ->
  237. Default;
  238. default_if_none(Input, _) ->
  239. Input.
  240. %% @doc Takes a list of dictionaries or proplists and returns that list sorted by the key given in the argument.
  241. dictsort(DictList, Key) when is_binary(Key) ->
  242. dictsort(DictList, [binary_to_atom(B,latin1) ||
  243. B <- binary:split(Key,<<".">>)]);
  244. dictsort(DictList, Key) ->
  245. case lists:all(
  246. fun(Dict) ->
  247. erlydtl_runtime:find_deep_value(Key, Dict) /= undefined
  248. end, DictList) of
  249. true ->
  250. lists:sort(
  251. fun(K1,K2) ->
  252. erlydtl_runtime:find_deep_value(Key,K1) =<
  253. erlydtl_runtime:find_deep_value(Key,K2)
  254. end, DictList);
  255. false -> error
  256. end.
  257. %% @doc Same as dictsort, but the list is reversed.
  258. dictsortreversed(DictList, Key) ->
  259. lists:reverse(dictsort(DictList, Key)).
  260. %% @doc Returns `true' if the value is divisible by the argument.
  261. divisibleby(Input, Divisor) when is_binary(Input) ->
  262. divisibleby(binary_to_list(Input), Divisor);
  263. divisibleby(Input, Divisor) when is_list(Input) ->
  264. divisibleby(list_to_integer(Input), Divisor);
  265. divisibleby(Input, Divisor) when is_binary(Divisor) ->
  266. divisibleby(Input, binary_to_list(Divisor));
  267. divisibleby(Input, Divisor) when is_list(Divisor) ->
  268. divisibleby(Input, list_to_integer(Divisor));
  269. divisibleby(Input, Divisor) when is_integer(Input), is_integer(Divisor) ->
  270. Input rem Divisor =:= 0.
  271. %% @doc Escapes characters for use in JavaScript strings.
  272. escapejs(Input) when is_binary(Input) ->
  273. escapejs(binary_to_list(Input));
  274. escapejs(Input) when is_list(Input) ->
  275. escapejs(Input, []).
  276. %% @doc Format the value like a human-readable file size.
  277. filesizeformat(Input) when is_binary(Input) ->
  278. filesizeformat(binary_to_list(Input));
  279. filesizeformat(Input) when is_list(Input) ->
  280. filesizeformat(list_to_integer(Input));
  281. filesizeformat(Bytes) when is_integer(Bytes), Bytes >= ?GIGABYTE->
  282. filesizeformat(Bytes / ?GIGABYTE, "GB");
  283. filesizeformat(Bytes) when is_integer(Bytes), Bytes >= ?MEGABYTE ->
  284. filesizeformat(Bytes / ?MEGABYTE, "MB");
  285. filesizeformat(Bytes) when is_integer(Bytes), Bytes >= ?KILOBYTE ->
  286. filesizeformat(Bytes / ?KILOBYTE, "KB");
  287. filesizeformat(Bytes) when is_integer(Bytes) ->
  288. integer_to_list(Bytes) ++ " bytes".
  289. %% @doc Returns the first item in a list.
  290. first([First|_Rest]) ->
  291. [First];
  292. first(<<First, _/binary>>) ->
  293. <<First>>.
  294. %% @doc Replaces ampersands with &amp; entities.
  295. fix_ampersands(Input) when is_binary(Input) ->
  296. fix_ampersands(Input, 0);
  297. fix_ampersands(Input) when is_list(Input) ->
  298. fix_ampersands(Input, []).
  299. %% @doc When used without an argument, rounds a floating-point number to one decimal place
  300. %% -- but only if there's a decimal part to be displayed
  301. floatformat(Number, Place) when is_binary(Number) ->
  302. floatformat(binary_to_list(Number), Place);
  303. floatformat(Number, Place) ->
  304. floatformat_io(Number, cast_to_integer(Place)).
  305. floatformat_io(Number, []) ->
  306. floatformat_io(Number, -1);
  307. floatformat_io(Number, Precision) when Precision > 0 ->
  308. Format = lists:flatten(io_lib:format("~~.~Bf",[Precision])),
  309. [Result] = io_lib:format(Format,[Number]),
  310. Result;
  311. floatformat_io(Number, Precision) when Precision < 0 ->
  312. Round = erlang:round(Number),
  313. RoundPrecision = round(Number, -Precision),
  314. case RoundPrecision == Round of
  315. true ->
  316. %Format = lists:flatten(io_lib:format("~~~BB",[-Precision])),
  317. [Result] = io_lib:format("~B",[Round]);
  318. false ->
  319. Format = lists:flatten(io_lib:format("~~.~Bf",[-Precision])),
  320. [Result] = io_lib:format(Format,[RoundPrecision])
  321. end,
  322. Result.
  323. round(Number, Precision) ->
  324. P = math:pow(10, Precision),
  325. round(Number * P) / P.
  326. %% @doc Applies HTML escaping to a string.
  327. force_escape(Input) when is_list(Input) ->
  328. escape(Input, []);
  329. force_escape(Input) when is_binary(Input) ->
  330. escape(Input, 0);
  331. force_escape(Input) ->
  332. Input.
  333. format_integer(Input) when is_integer(Input) ->
  334. integer_to_list(Input);
  335. format_integer(Input) ->
  336. Input.
  337. format_number(Input) when is_integer(Input) ->
  338. integer_to_list(Input);
  339. format_number(Input) when is_float(Input) ->
  340. io_lib:format("~p", [Input]);
  341. format_number(Input) when is_function(Input, 0) ->
  342. format_number(Input());
  343. format_number(Input) ->
  344. Input.
  345. %% @doc Given a whole number, returns the requested digit, where 1 is the right-most digit.
  346. get_digit(Input, Digit) when is_binary(Input) ->
  347. get_digit(binary_to_list(Input), Digit);
  348. get_digit(Input, Digit) when is_integer(Input) ->
  349. get_digit(integer_to_list(Input), Digit);
  350. get_digit(Input, Digit) when is_binary(Digit) ->
  351. get_digit(Input, binary_to_list(Digit));
  352. get_digit(Input, Digit) when is_list(Digit) ->
  353. get_digit(Input, list_to_integer(Digit));
  354. get_digit(Input, Digit) when Digit > erlang:length(Input) ->
  355. 0;
  356. get_digit(Input, Digit) when Digit > 0 ->
  357. lists:nth(Digit, lists:reverse(Input)) - $0;
  358. get_digit(Input, _) ->
  359. Input.
  360. iriencode(Input) ->
  361. iriencode(unicode:characters_to_list(Input), []).
  362. %% @doc Joins a list with a given separator.
  363. join(Input, Separator) when is_list(Input) ->
  364. join_io(Input, Separator).
  365. %% @doc Returns the last item in a list.
  366. last(Input) when is_binary(Input) ->
  367. case size(Input) of
  368. 0 -> Input;
  369. N ->
  370. Offset = N - 1,
  371. <<_:Offset/binary, Byte/binary>> = Input,
  372. Byte
  373. end;
  374. last(Input) when is_list(Input) ->
  375. [lists:last(Input)].
  376. %% @doc Returns the length of the value.
  377. length(Input) when is_list(Input) ->
  378. integer_to_list(erlang:length(Input));
  379. length(Input) when is_binary(Input) ->
  380. integer_to_list(size(Input)).
  381. %% @doc Returns True iff the value's length is the argument.
  382. length_is(Input, Number) when is_list(Input), is_integer(Number) ->
  383. length_is(Input, integer_to_list(Number));
  384. length_is(Input, Number) when is_list(Input), is_list(Number) ->
  385. ?MODULE:length(Input) =:= Number.
  386. %% @doc Replaces line breaks in plain text with appropriate HTML
  387. linebreaks(Input) when is_binary(Input) ->
  388. linebreaks(binary_to_list(Input),[]);
  389. linebreaks(Input) ->
  390. linebreaks(Input,[]).
  391. linebreaks([],Acc) ->
  392. "<p>" ++ lists:reverse(Acc) ++ "</p>";
  393. linebreaks([$\n|T], ">p<"++_ = Acc) ->
  394. linebreaks(T, Acc);
  395. linebreaks([$\r|T], ">p<"++_ = Acc) ->
  396. linebreaks(T, Acc);
  397. linebreaks([$\n, $\n|T],Acc) ->
  398. linebreaks(T, lists:reverse("</p><p>", Acc));
  399. linebreaks([$\r, $\n, $\r, $\n|T],Acc) ->
  400. linebreaks(T, lists:reverse("</p><p>", Acc));
  401. linebreaks([$\r, $\n|T], Acc) ->
  402. linebreaks(T, lists:reverse("<br />", Acc));
  403. linebreaks([$\n|T], Acc) ->
  404. linebreaks(T, lists:reverse("<br />", Acc));
  405. linebreaks([C|T], Acc) ->
  406. linebreaks(T, [C|Acc]).
  407. %% @doc Converts all newlines to HTML line breaks.
  408. linebreaksbr(Input) when is_binary(Input) ->
  409. linebreaksbr(Input, 0);
  410. linebreaksbr(Input) ->
  411. linebreaksbr(Input, []).
  412. %% @doc Displays text with line numbers.
  413. linenumbers(Input) when is_binary(Input) ->
  414. linenumbers(binary_to_list(Input));
  415. linenumbers(Input) when is_list(Input) ->
  416. linenumbers_io(Input, [], 1).
  417. linenumbers_io([], Acc, _) ->
  418. lists:reverse(Acc);
  419. linenumbers_io(Input, [], LineNumber) ->
  420. linenumbers_io(Input, lists:reverse(integer_to_list(LineNumber)++". "), LineNumber + 1);
  421. linenumbers_io("\n"++Rest, Acc, LineNumber) ->
  422. linenumbers_io(Rest, lists:reverse("\n" ++ integer_to_list(LineNumber) ++ ". ", Acc), LineNumber + 1);
  423. linenumbers_io([H|T], Acc, LineNumber) ->
  424. linenumbers_io(T, [H|Acc], LineNumber).
  425. %% @doc Left-aligns the value in a field of a given width.
  426. ljust(Input, Number) when is_binary(Input) ->
  427. list_to_binary(ljust(binary_to_list(Input), Number));
  428. ljust(Input, Number) when is_list(Input) ->
  429. string:left(Input, Number).
  430. %% @doc Converts a string into all lowercase.
  431. lower(Input) when is_binary(Input) ->
  432. lower(Input, 0);
  433. lower(Input) ->
  434. string:to_lower(Input).
  435. %% @doc Returns the value turned into a list. For an integer, it's a list of digits.
  436. %% For a string, it's a list of characters.
  437. %% Added this for DTL compatibility, but since strings are lists in Erlang, no need for this.
  438. make_list(Input) when is_binary(Input) ->
  439. make_list(binary_to_list(Input));
  440. make_list(Input) ->
  441. unjoin(Input,"").
  442. %% @doc Converts a phone number (possibly containing letters) to its numerical equivalent.
  443. phone2numeric(Input) when is_binary(Input) ->
  444. phone2numeric(binary_to_list(Input));
  445. phone2numeric(Input) when is_list(Input) ->
  446. phone2numeric(Input, []).
  447. %% @doc Returns a plural suffix if the value is not 1. By default, this suffix is 's'.
  448. pluralize(Number, Suffix) when is_binary(Suffix) ->
  449. pluralize_io(Number, binary_to_list(Suffix) );
  450. pluralize(Number, Suffix) when is_list(Suffix) ->
  451. pluralize_io(Number, Suffix).
  452. pluralize(Number) ->
  453. pluralize(Number, "s").
  454. pluralize_io(Number, Suffix) ->
  455. case lists:member($, , Suffix) of
  456. true ->
  457. [Singular, Plural] = string:tokens(Suffix,","),
  458. case Number of
  459. 0 -> Plural;
  460. 1 -> Singular;
  461. _ -> Plural
  462. end;
  463. false ->
  464. case Number of
  465. 0 -> Suffix;
  466. 1 -> [];
  467. _ -> Suffix
  468. end
  469. end.
  470. %% @doc "pretty print" arbitrary data structures. Used for debugging.
  471. pprint(Input) ->
  472. io_lib:format("~p",[Input]).
  473. %% @doc Returns a random item from the given list.
  474. random(Input) when is_list(Input) ->
  475. lists:nth(random:uniform(erlang:length(Input)), Input);
  476. random(_) ->
  477. "".
  478. random_num(Value) ->
  479. {A1,A2,A3} = now(),
  480. random:seed(A1, A2, A3),
  481. Rand = random:uniform(Value),
  482. Rand.
  483. %% random tags to be used when using erlydtl in testing
  484. random_range(Range) ->
  485. [Start, End] = string:tokens(Range,","),
  486. %?debugFmt("Start, End: ~p,~p~n",[Start,End]),
  487. random_range(cast_to_integer(Start),cast_to_integer(End)).
  488. random_range(Start, End) when End >= Start ->
  489. %?debugFmt("Input, Start, End: ~p,~p,~p~n",[Input,Start,End]),
  490. Range = End - Start,
  491. Rand = random:uniform(Range),
  492. Num = Rand + Start,
  493. lists:flatten(io_lib:format("~B",[Num])).
  494. removetags(Input, Tags) when is_binary(Input) ->
  495. removetags(binary_to_list(Input), Tags);
  496. removetags(Input, Tags) when is_binary(Tags) ->
  497. removetags(Input, binary_to_list(Tags));
  498. removetags(Input, Tags) ->
  499. TagList = string:tokens(Tags," "),
  500. TagListString = string:join(TagList,"|"),
  501. Regex = lists:flatten(io_lib:format("</?(~s)( |\n)?>",[TagListString])),
  502. Result = re:replace(Input,Regex,"", [global,{return,list}]),
  503. Result.
  504. %% @doc Right-aligns the value in a field of a given width.
  505. rjust(Input, Number) when is_binary(Input) ->
  506. list_to_binary(rjust(binary_to_list(Input), Number));
  507. rjust(Input, Number) ->
  508. string:right(Input, Number).
  509. %% @doc Returns a slice of the list.
  510. slice(Input, Index) when is_binary(Input) ->
  511. erlydtl_slice:slice(binary_to_list(Input), Index);
  512. slice(Input, Index) when is_list(Input) ->
  513. erlydtl_slice:slice(Input, Index).
  514. %% regex " ^([#0-\s+].)([0-9\*]+)(\.[0-9]+)([diouxXeEfFgGcrs]) " matches ALL of "-10.0f"
  515. %% ([#0-\s+]?)([0-9\*]+)?(\.?)([0-9]?)([diouxXeEfFgGcrs])
  516. %% @doc Returns a formatted string
  517. stringformat(Input, Conversion) when is_binary(Input) ->
  518. stringformat(binary_to_list(Input), Conversion);
  519. stringformat(Input, Conversion) when is_binary(Conversion) ->
  520. stringformat(Input, binary_to_list(Conversion));
  521. stringformat(Input, Conversion) ->
  522. ParsedConversion = re:replace(Conversion, "([\-#\+ ]?)([0-9\*]+)?(\.?)([0-9]?)([diouxXeEfFgGcrs])", "\\1 ,\\2 ,\\3 ,\\4 ,\\5 ", [{return,list}]),
  523. ?debugFmt("ParsedConversion: ~p~n", [ParsedConversion]),
  524. ParsedConversion1 = lists:map(fun(X) -> string:strip(X) end, string:tokens(ParsedConversion, ",")),
  525. [ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType] = ParsedConversion1,
  526. ?debugFmt("ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType: ~p, ~p, ~p, ~p, ~p ~n", [ConversionFlag, cast_to_integer(MinFieldWidth), Precision, cast_to_integer(PrecisionLength), ConversionType]),
  527. [String] = stringformat_io(Input, Conversion, ConversionFlag, cast_to_integer(MinFieldWidth), Precision, cast_to_integer(PrecisionLength), ConversionType),
  528. lists:flatten(String).
  529. %% @doc
  530. %% A conversion specifier contains two or more characters and has the following components, which must occur in this order:
  531. %%
  532. %% 1. The "%" character, which marks the start of the specifier.
  533. %% 2. Mapping key (optional), consisting of a parenthesised sequence of characters (for example, (somename)).
  534. %% 3. Conversion flags (optional), which affect the result of some conversion types.
  535. %% 4. Minimum field width (optional). If specified as an "*" (asterisk), the actual width is read from the next element of the tuple in values, and the object to convert comes after the minimum field width and optional precision.
  536. %% 5. Precision (optional), given as a "." (dot) followed by the precision. If specified as "*" (an asterisk), the actual width is read from the next element of the tuple in values, and the value to convert comes after the precision.
  537. %% 6. Length modifier (optional).
  538. %% 7. Conversion type.
  539. stringformat_io(Input, _Conversion, _ConversionFlag, [],
  540. _Precision, _PrecisionLength, "s") when is_list(Input) ->
  541. Format = lists:flatten(io_lib:format("~~s", [])),
  542. io_lib:format(Format, [Input]);
  543. stringformat_io(Input, _Conversion, ConversionFlag, MinFieldWidth,
  544. _Precision, _PrecisionLength, "s") when is_list(Input) ->
  545. %Conversion = [ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType],
  546. InputLength = erlang:length(Input),
  547. case erlang:abs(MinFieldWidth) < InputLength of
  548. true ->
  549. MFW = InputLength;
  550. false ->
  551. MFW = MinFieldWidth
  552. end,
  553. Format = lists:flatten(io_lib:format("~~~s~ps", [ConversionFlag,MFW])),
  554. io_lib:format(Format, [Input]);
  555. stringformat_io(Input, _Conversion, _ConversionFlag, MinFieldWidth,
  556. Precision, PrecisionLength, "f") when Precision == ".", MinFieldWidth == 0 ->
  557. Conversion1 = lists:concat(["","",Precision,PrecisionLength,"f"]),
  558. stringformat_io(Input, Conversion1, [], [], Precision, PrecisionLength, "f");
  559. stringformat_io(Input, Conversion, ConversionFlag, MinFieldWidth,
  560. Precision, "", "f") when Precision == "." ->
  561. Format = re:replace(Conversion, "f", "d", [{return, list}] ),
  562. stringformat_io(Input, Format, ConversionFlag, MinFieldWidth,
  563. Precision, "", "d");
  564. stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth,
  565. _Precision, _PrecisionLength, "f")->
  566. %Conversion = [ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType],
  567. Format = "~" ++ Conversion,
  568. io_lib:format(Format, [cast_to_float(Input)]);
  569. stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth,
  570. [], [], "d")->
  571. %?debugMsg("plain d"),
  572. %Conversion = [ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType],
  573. Format = "~" ++ re:replace(Conversion, "d", "B", [{return, list}] ),
  574. io_lib:format(Format, [cast_to_integer(Input)]);
  575. stringformat_io(Input, _Conversion, "-", MinFieldWidth,
  576. _Precision, PrecisionLength, "d") when MinFieldWidth > 0 ->
  577. %Format = "~" ++ re:replace(Conversion, "d", "B", [{return, list}] ),
  578. DecimalFormat = "~" ++ integer_to_list(PrecisionLength) ++ "..0B",
  579. Decimal = lists:flatten( io_lib:format(DecimalFormat, [cast_to_integer(Input)]) ),
  580. SpaceFormat = "~" ++ integer_to_list(MinFieldWidth - erlang:length(Decimal)) ++ "s",
  581. Spaces = io_lib:format(SpaceFormat,[""]),
  582. ?debugFmt("Spaces: |~s|", [Spaces]),
  583. ?debugFmt("Decimal: ~s", [Decimal]),
  584. [lists:flatten(Decimal ++ Spaces)];
  585. stringformat_io(Input, _Conversion, _ConversionFlag, MinFieldWidth,
  586. _Precision, PrecisionLength, "d") when MinFieldWidth > 0 ->
  587. %Format = "~" ++ re:replace(Conversion, "d", "B", [{return, list}] ),
  588. DecimalFormat = "~" ++ integer_to_list(PrecisionLength) ++ "..0B",
  589. Decimal = lists:flatten( io_lib:format(DecimalFormat, [cast_to_integer(Input)]) ),
  590. SpaceFormat = "~" ++ integer_to_list(MinFieldWidth - erlang:length(Decimal)) ++ "s",
  591. Spaces = io_lib:format(SpaceFormat,[""]),
  592. ?debugFmt("Spaces: |~s|", [Spaces]),
  593. ?debugFmt("Decimal: ~s", [Decimal]),
  594. [lists:flatten(Spaces ++ Decimal)];
  595. stringformat_io(Input, _Conversion, _ConversionFlag, _MinFieldWidth,
  596. _Precision, PrecisionLength, "d") ->
  597. %Conversion = [ConversionFlag, MinFieldWidth, Precision, PrecisionLength, ConversionType],
  598. %Format = "~" ++ PrecisionLength ++ "..0" ++ re:replace(Conversion, "d", "B", [{return, list}] ),
  599. %?debugFmt("precision d, Conversion: ~p~n", [Conversion]),
  600. Format = lists:flatten("~" ++ io_lib:format("~B..0B",[PrecisionLength])),
  601. ?debugFmt("Format: ~p~n",[Format]),
  602. io_lib:format(Format, [cast_to_integer(Input)]);
  603. stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth,
  604. _Precision, _PrecisionLength, "i")->
  605. Format = "~" ++ re:replace(Conversion, "i", "B", [{return, list}] ),
  606. io_lib:format(Format, [cast_to_integer(Input)]);
  607. stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth,
  608. _Precision, _PrecisionLength, "X")->
  609. Format = "~" ++ re:replace(Conversion, "X", ".16B", [{return, list}] ),
  610. io_lib:format(Format, [cast_to_integer(Input)]);
  611. stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth,
  612. _Precision, _PrecisionLength, "x")->
  613. Format = "~" ++ re:replace(Conversion, "x", ".16b", [{return, list}] ),
  614. io_lib:format(Format, [cast_to_integer(Input)]);
  615. stringformat_io(Input, Conversion, _ConversionFlag, _MinFieldWidth,
  616. _Precision, _PrecisionLength, "o")->
  617. Format = "~" ++ re:replace(Conversion, "o", ".8b", [{return, list}] ),
  618. io_lib:format(Format, [cast_to_integer(Input)]);
  619. stringformat_io(Input, _Conversion, _ConversionFlag, _MinFieldWidth,
  620. Precision, PrecisionLength, "e") when is_integer(PrecisionLength), PrecisionLength >= 2->
  621. ?debugFmt("PrecisionLength ~p~n", [PrecisionLength]),
  622. Conversion1 = lists:concat(["","",Precision,PrecisionLength + 1,"e"]),
  623. Format = "~" ++ Conversion1,
  624. io_lib:format(Format, [cast_to_float(Input)]);
  625. stringformat_io(Input, Conversion, ConversionFlag, MinFieldWidth,
  626. "", [], "e")->
  627. Format = "~" ++ re:replace(Conversion, "e", ".6e", [{return, list}] ),
  628. Raw = lists:flatten(stringformat_io(Input, Format, ConversionFlag, MinFieldWidth,
  629. ".", 6, "e")
  630. ),
  631. %io:format("Raw: ~p~n", [Raw]),
  632. Elocate = string:rstr(Raw,"e+"),
  633. %io:format("Elocate: ~p~n", [Elocate]),
  634. String = [string:substr(Raw,1,Elocate-1) ++ "e+"
  635. ++ io_lib:format("~2..0B",[list_to_integer(string:substr(Raw,Elocate+2))])], %works till +99, then outputs "**"
  636. %io:format("String: ~p~n", [String]),
  637. String;
  638. stringformat_io(Input, Conversion, ConversionFlag, MinFieldWidth,
  639. Precision, PrecisionLength, "E")->
  640. Format = re:replace(Conversion, "E", "e", [{return, list}] ),
  641. [Str] = stringformat_io(Input, Format, ConversionFlag, MinFieldWidth,
  642. Precision, PrecisionLength, "e"),
  643. [string:to_upper(Str)].
  644. %% @doc Strips all [X]HTML tags.
  645. striptags(Input) when is_binary(Input) ->
  646. striptags(binary_to_list(Input));
  647. striptags(Input) ->
  648. Regex = "(<[^>]+>)",
  649. Result = re:replace(Input,Regex,"", [global,{return,list}]),
  650. Result.
  651. cast_to_float([]) ->
  652. [];
  653. cast_to_float(Input) when is_float(Input) ->
  654. Input;
  655. cast_to_float(Input) when is_integer(Input) ->
  656. Input + 0.0;
  657. cast_to_float(Input) ->
  658. try list_to_float(Input)
  659. catch
  660. error:_Reason ->
  661. list_to_integer(Input) + 0.0
  662. end.
  663. cast_to_integer([]) ->
  664. [];
  665. cast_to_integer(Input) when is_integer(Input) ->
  666. Input;
  667. cast_to_integer(Input) when is_float(Input) ->
  668. erlang:round(Input);
  669. cast_to_integer(Input) when is_binary(Input) ->
  670. cast_to_integer(binary_to_list(Input));
  671. cast_to_integer(Input) when is_list(Input)->
  672. case lists:member($., Input) of
  673. true ->
  674. erlang:round(erlang:list_to_float(Input));
  675. false ->
  676. erlang:list_to_integer(Input)
  677. end.
  678. %% @doc Converts to lowercase, removes non-word characters (alphanumerics and underscores) and converts spaces to hyphens.
  679. slugify(Input) when is_binary(Input) ->
  680. slugify(binary_to_list(Input));
  681. slugify(Input) when is_list(Input) ->
  682. slugify(Input, []).
  683. %% @doc Formats a time according to the given format.
  684. time(Input) ->
  685. date(Input, "f a").
  686. time(Input, FormatStr) ->
  687. date(Input, FormatStr).
  688. timesince(Date) ->
  689. timesince(Date, calendar:local_time()).
  690. %%algorithm taken from django code
  691. timesince(Date,Comparison) ->
  692. Since = calendar:datetime_to_gregorian_seconds(Comparison) - calendar:datetime_to_gregorian_seconds(Date),
  693. timesince0(Since, [], 0).
  694. timesince0(_, Acc, 2) ->
  695. string:join(lists:reverse(Acc), ", ");
  696. timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_YEAR ->
  697. Years = Seconds div ?SECONDS_PER_YEAR,
  698. timesince0(Seconds rem ?SECONDS_PER_YEAR, [io_lib:format("~B ~s~s", [Years, "year", pluralize(Years)])|Acc], Terms+1);
  699. timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_MONTH ->
  700. Months = Seconds div ?SECONDS_PER_MONTH,
  701. timesince0(Seconds rem ?SECONDS_PER_MONTH, [io_lib:format("~B ~s~s", [Months, "month", pluralize(Months)])|Acc], Terms+1);
  702. timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_WEEK ->
  703. Weeks = Seconds div ?SECONDS_PER_WEEK,
  704. timesince0(Seconds rem ?SECONDS_PER_WEEK, [io_lib:format("~B ~s~s", [Weeks, "week", pluralize(Weeks)])|Acc], Terms+1);
  705. timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_DAY ->
  706. Days = Seconds div ?SECONDS_PER_DAY,
  707. timesince0(Seconds rem ?SECONDS_PER_DAY, [io_lib:format("~B ~s~s", [Days, "day", pluralize(Days)])|Acc], Terms+1);
  708. timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_HOUR ->
  709. Hours = Seconds div ?SECONDS_PER_HOUR,
  710. timesince0(Seconds rem ?SECONDS_PER_HOUR, [io_lib:format("~B ~s~s", [Hours, "hour", pluralize(Hours)])|Acc], Terms+1);
  711. timesince0(Seconds, Acc, Terms) when Seconds >= ?SECONDS_PER_MINUTE ->
  712. Minutes = Seconds div ?SECONDS_PER_MINUTE,
  713. timesince0(Seconds rem ?SECONDS_PER_MINUTE,[io_lib:format("~B ~s~s", [Minutes, "minute", pluralize(Minutes)])|Acc], Terms+1);
  714. timesince0(Seconds, Acc, Terms) when Seconds >= 1 ->
  715. timesince0(0, [io_lib:format("~B ~s~s", [Seconds, "second", pluralize(Seconds)])|Acc], Terms+1);
  716. timesince0(Seconds, [], 0) when Seconds =< 0 ->
  717. timesince0(0, ["0 minutes"], 1);
  718. timesince0(0, Acc, Terms) ->
  719. timesince0(0, Acc, Terms+1).
  720. timeuntil(Date) ->
  721. timesince(calendar:local_time(),Date).
  722. timeuntil(Date,Comparison) ->
  723. timesince(Comparison,Date).
  724. %% @doc Converts a string into titlecase.
  725. title(Input) when is_binary(Input) ->
  726. title(binary_to_list(Input));
  727. title(Input) when is_list(Input) ->
  728. title(Input, []).
  729. %% @doc Truncates a string after a certain number of characters.
  730. truncatechars(_Input, Max) when Max =< 0 ->
  731. "";
  732. truncatechars(Input, Max) when is_binary(Input) ->
  733. list_to_binary(truncatechars(binary_to_list(Input), Max));
  734. truncatechars(Input, Max) ->
  735. truncatechars(Input, Max, []).
  736. %% @doc Truncates a string after a certain number of words.
  737. truncatewords(_Input, Max) when Max =< 0 ->
  738. "";
  739. truncatewords(Input, Max) when is_binary(Input) ->
  740. list_to_binary(truncatewords(binary_to_list(Input), Max));
  741. truncatewords(Input, Max) ->
  742. truncatewords(Input, Max, []).
  743. %% @doc Similar to truncatewords, except that it is aware of HTML tags.
  744. truncatewords_html(_Input, Max) when Max =< 0 ->
  745. "";
  746. truncatewords_html(Input, Max) when is_binary(Input) ->
  747. truncatewords_html(binary_to_list(Input), Max);
  748. truncatewords_html(Input, Max) ->
  749. truncatewords_html(Input, Max, [], [], text).
  750. %% @doc Recursively takes a self-nested list and returns an HTML unordered list -- WITHOUT opening and closing `<ul>' tags.
  751. unordered_list(List) ->
  752. String = lists:flatten(unordered_list(List, [])),
  753. string:substr(String, 5, erlang:length(String) - 9).
  754. unordered_list([], Acc) ->
  755. ["<ul>", lists:reverse(Acc), "</ul>"];
  756. unordered_list([First|_] = List, []) when is_integer(First) ->
  757. "<li>"++List;
  758. unordered_list([First|Rest], Acc) when is_list(First), Rest == [] ->
  759. unordered_list(Rest, ["</li>"] ++ [unordered_list(First, []) | Acc ]) ;
  760. unordered_list([First|Rest], Acc) when is_list(First), is_integer(hd(hd(Rest))) ->
  761. unordered_list(Rest, [unordered_list(First, []) ++ "</li>" |Acc]);
  762. unordered_list([First|Rest], Acc) when is_list(First) ->
  763. unordered_list(Rest, [unordered_list(First, [])|Acc]).
  764. %% @doc Converts a string into all uppercase.
  765. upper(Input) when is_binary(Input) ->
  766. list_to_binary(upper(binary_to_list(Input)));
  767. upper(Input) ->
  768. string:to_upper(Input).
  769. %% @doc Escapes a value for use in a URL.
  770. urlencode(Input) when is_binary(Input) ->
  771. urlencode(Input, 0);
  772. urlencode(Input) when is_list(Input) ->
  773. urlencode(Input, []).
  774. %% @doc Returns the number of words.
  775. wordcount(Input) when is_binary(Input) ->
  776. wordcount(binary_to_list(Input));
  777. wordcount(Input) when is_list(Input) ->
  778. wordcount(Input, 0).
  779. %% @doc Wraps words at specified line length, uses `<BR/>' html tag to delimit lines
  780. wordwrap(Input, Number) when is_binary(Input) ->
  781. wordwrap(binary_to_list(Input), Number);
  782. wordwrap(Input, Number) when is_list(Input) ->
  783. wordwrap(Input, [], [], 0, Number).
  784. %% @doc Given a string mapping values for true, false and (optionally) undefined, returns one of those strings according to the value.
  785. yesno(Bool, Choices) when is_binary(Bool) ->
  786. yesno_io(binary_to_list(Bool), Choices);
  787. yesno(Bool, Choices) when is_binary(Choices) ->
  788. yesno_io(Bool, binary_to_list(Choices));
  789. yesno(Bool, Choices) when is_list(Choices) ->
  790. yesno_io(Bool, Choices).
  791. % internal
  792. addslashes([], Acc) ->
  793. lists:reverse(Acc);
  794. addslashes([H|T], Acc) when H =:= $"; H =:= $' ->
  795. addslashes(T, [H, $\\|Acc]);
  796. addslashes([H|T], Acc) ->
  797. addslashes(T, [H|Acc]).
  798. cut([], _, Acc) ->
  799. lists:reverse(Acc);
  800. cut([H|T], H, Acc) ->
  801. cut(T, H, Acc);
  802. cut([H|T], Char, Acc) ->
  803. cut(T, Char, [H|Acc]).
  804. escape(Binary, Index) when is_binary(Binary) ->
  805. case Binary of
  806. <<Pre:Index/binary, $<, Post/binary>> ->
  807. process_binary_match(Pre, <<"&lt;">>, size(Post), escape(Post, 0));
  808. <<Pre:Index/binary, $>, Post/binary>> ->
  809. process_binary_match(Pre, <<"&gt;">>, size(Post), escape(Post, 0));
  810. <<Pre:Index/binary, $&, Post/binary>> ->
  811. process_binary_match(Pre, <<"&amp;">>, size(Post), escape(Post, 0));
  812. <<Pre:Index/binary, 34, Post/binary>> ->
  813. process_binary_match(Pre, <<"&quot;">>, size(Post), escape(Post, 0));
  814. <<Pre:Index/binary, 39, Post/binary>> ->
  815. process_binary_match(Pre, <<"&#039;">>, size(Post), escape(Post, 0));
  816. <<_:Index/binary, _, _/binary>> ->
  817. escape(Binary, Index + 1);
  818. Binary ->
  819. Binary
  820. end;
  821. escape([], Acc) ->
  822. lists:reverse(Acc);
  823. escape("<" ++ Rest, Acc) ->
  824. escape(Rest, lists:reverse("&lt;", Acc));
  825. escape(">" ++ Rest, Acc) ->
  826. escape(Rest, lists:reverse("&gt;", Acc));
  827. escape("&" ++ Rest, Acc) ->
  828. escape(Rest, lists:reverse("&amp;", Acc));
  829. escape("\"" ++ Rest, Acc) ->
  830. escape(Rest, lists:reverse("&quot;", Acc));
  831. escape("'" ++ Rest, Acc) ->
  832. escape(Rest, lists:reverse("&#039;", Acc));
  833. escape([C | Rest], Acc) ->
  834. escape(Rest, [C | Acc]).
  835. escapejs([], Acc) ->
  836. lists:reverse(Acc);
  837. escapejs([C | Rest], Acc) when C < 32; C =:= $"; C =:= $'; C =:= $\\; C =:= $<;
  838. C =:= $>; C =:= $&; C =:= $=; C =:= $-; C =:= $;;
  839. C =:= 8232; C =:= 8233 -> % just following django here...
  840. escapejs(Rest, lists:reverse(lists:flatten(io_lib:format("\\u~4.16.0B", [C])), Acc));
  841. escapejs([C | Rest], Acc) ->
  842. escapejs(Rest, [C | Acc]).
  843. filesizeformat(Bytes, UnitStr) ->
  844. lists:flatten(io_lib:format("~.1f ~s", [Bytes, UnitStr])).
  845. fix_ampersands(Input, Index) when is_binary(Input) ->
  846. case Input of
  847. <<Pre:Index/binary, $&, Post/binary>> ->
  848. process_binary_match(Pre, <<"&amp;">>, size(Post), Post);
  849. <<_:Index/binary, _/binary>> ->
  850. fix_ampersands(Input, Index + 1);
  851. _ ->
  852. Input
  853. end;
  854. fix_ampersands([], Acc) ->
  855. lists:reverse(Acc);
  856. fix_ampersands("&" ++ Rest, Acc) ->
  857. fix_ampersands(Rest, lists:reverse("&amp;", Acc));
  858. fix_ampersands([C | Rest], Acc) ->
  859. fix_ampersands(Rest, [C | Acc]).
  860. iriencode([], Acc) ->
  861. lists:reverse(Acc);
  862. iriencode([C | Rest], Acc) when ?NO_IRI_ENCODE(C) ->
  863. iriencode(Rest, [C | Acc]);
  864. iriencode([$\s | Rest], Acc) ->
  865. iriencode(Rest, [$+ | Acc]);
  866. iriencode([C | Rest], Acc) ->
  867. <<Hi:4, Lo:4>> = <<C>>,
  868. iriencode(Rest, [hexdigit(Lo), hexdigit(Hi), $\% | Acc]).
  869. join_io([], _Sep) -> [];
  870. join_io([_] = X, _Sep) -> X;
  871. join_io([X|T], Sep) -> [X,Sep] ++ join_io(T, Sep).
  872. linebreaksbr(Input, Index) when is_binary(Input) ->
  873. Break = <<"<br />">>,
  874. case Input of
  875. <<Pre:Index/binary, $\r, $\n, Post/binary>> ->
  876. process_binary_match(Pre, Break, size(Post), linebreaksbr(Post, 0));
  877. <<Pre:Index/binary, $\n, Post/binary>> ->
  878. process_binary_match(Pre, Break, size(Post), linebreaksbr(Post, 0));
  879. <<_:Index/binary, _/binary>> ->
  880. linebreaksbr(Input, Index + 1);
  881. _ ->
  882. Input
  883. end;
  884. linebreaksbr([], Acc) ->
  885. lists:reverse(Acc);
  886. linebreaksbr("\r\n" ++ Rest, Acc) ->
  887. linebreaksbr(Rest, lists:reverse("<br />", Acc));
  888. linebreaksbr("\n" ++ Rest, Acc) ->
  889. linebreaksbr(Rest, lists:reverse("<br />", Acc));
  890. linebreaksbr([C | Rest], Acc) ->
  891. linebreaksbr(Rest, [C | Acc]).
  892. lower(Input, Index) ->
  893. case Input of
  894. <<Pre:Index/binary, Byte, Post/binary>> when Byte >= $A andalso Byte =< $Z ->
  895. process_binary_match(Pre, <<(Byte - $A + $a)>>, size(Post), lower(Post, 0));
  896. <<_:Index/binary, _/binary>> ->
  897. lower(Input, Index + 1);
  898. _ ->
  899. Input
  900. end.
  901. phone2numeric([], Acc) ->
  902. lists:reverse(Acc);
  903. phone2numeric([H|T], Acc) when H >= $a, H =< $c; H >= $A, H =< $C ->
  904. phone2numeric(T, [$2|Acc]);
  905. phone2numeric([H|T], Acc) when H >= $d, H =< $f; H >= $D, H =< $F ->
  906. phone2numeric(T, [$3|Acc]);
  907. phone2numeric([H|T], Acc) when H >= $g, H =< $i; H >= $G, H =< $I ->
  908. phone2numeric(T, [$4|Acc]);
  909. phone2numeric([H|T], Acc) when H >= $j, H =< $l; H >= $J, H =< $L ->
  910. phone2numeric(T, [$5|Acc]);
  911. phone2numeric([H|T], Acc) when H >= $m, H =< $o; H >= $M, H =< $O ->
  912. phone2numeric(T, [$6|Acc]);
  913. phone2numeric([H|T], Acc) when H >= $p, H =< $s; H >= $P, H =< $S ->
  914. phone2numeric(T, [$7|Acc]);
  915. phone2numeric([H|T], Acc) when H >= $t, H =< $v; H >= $T, H =< $V ->
  916. phone2numeric(T, [$8|Acc]);
  917. phone2numeric([H|T], Acc) when H >= $w, H =< $z; H >= $W, H =< $Z ->
  918. phone2numeric(T, [$9|Acc]);
  919. phone2numeric([H|T], Acc) ->
  920. phone2numeric(T, [H|Acc]).
  921. slugify([], Acc) ->
  922. lists:reverse(Acc);
  923. slugify([H|T], Acc) when H >= $A, H =< $Z ->
  924. slugify(T, [H-$A+$a|Acc]);
  925. slugify([$\ |T], Acc) ->
  926. slugify(T, [$-|Acc]);
  927. slugify([H|T], Acc) when H >= $a, H =< $z; H >= $0, H =< $9; H =:= $_ ->
  928. slugify(T, [H|Acc]);
  929. slugify([_|T], Acc) ->
  930. slugify(T, Acc).
  931. title([], Acc) ->
  932. lists:reverse(Acc);
  933. title([Char | Rest], [] = Acc) when Char >= $a, Char =< $z ->
  934. title(Rest, [Char + ($A - $a) | Acc]);
  935. title([Char | Rest], [$\ |_] = Acc) when Char >= $a, Char =< $z ->
  936. title(Rest, [Char + ($A - $a) | Acc]);
  937. title([Char | Rest], Acc) ->
  938. title(Rest, [Char | Acc]).
  939. truncatechars([], _CharsLeft, Acc) ->
  940. lists:reverse(Acc);
  941. truncatechars(_Input, 0, Acc) ->
  942. lists:reverse("..." ++ Acc);
  943. truncatechars([C|Rest], CharsLeft, Acc) ->
  944. truncatechars(Rest, CharsLeft - 1, [C|Acc]).
  945. truncatewords(Value, _WordsLeft, _Acc) when is_atom(Value) ->
  946. Value;
  947. truncatewords([], _WordsLeft, Acc) ->
  948. lists:reverse(Acc);
  949. truncatewords(_Input, 0, Acc) ->
  950. lists:reverse("..." ++ Acc);
  951. truncatewords([C1, C2|Rest], WordsLeft, Acc) when C1 =/= $\ andalso C2 =:= $\ ->
  952. truncatewords([C2|Rest], WordsLeft - 1, [C1|Acc]);
  953. truncatewords([C1|Rest], WordsLeft, Acc) ->
  954. truncatewords(Rest, WordsLeft, [C1|Acc]).
  955. truncatewords_html([], _WordsLeft, Acc, [], _) ->
  956. lists:reverse(Acc);
  957. truncatewords_html(_Input, 0, Acc, [], _) ->
  958. lists:reverse(Acc);
  959. truncatewords_html(Input, 0, Acc, [Tag|RestOfTags], done) ->
  960. truncatewords_html(Input, 0, ">"++Tag++"/<" ++ Acc, RestOfTags, done);
  961. truncatewords_html(Input, 0, Acc, [Tag|RestOfTags], _) ->
  962. truncatewords_html(Input, 0, "...>"++Tag++"/<" ++ Acc, RestOfTags, done);
  963. truncatewords_html([], WordsLeft, Acc, [Tag|RestOfTags], _) ->
  964. truncatewords_html([], WordsLeft, ">"++Tag++"/<" ++ Acc, RestOfTags, text);
  965. truncatewords_html([C|Rest], WordsLeft, Acc, Tags, text) when C =:= $< ->
  966. truncatewords_html(Rest, WordsLeft, [C|Acc], [""|Tags], tag);
  967. truncatewords_html([C1, C2|Rest], WordsLeft, Acc, Tags, text) when C1 =/= $\ , C2 =:= $\ ; C1 =/= $\ , C2 =:= $< ->
  968. truncatewords_html([C2|Rest], WordsLeft - 1, [C1|Acc], Tags, text);
  969. truncatewords_html([C|Rest], WordsLeft, Acc, Tags, text) ->
  970. truncatewords_html(Rest, WordsLeft, [C|Acc], Tags, text);
  971. truncatewords_html([C|Rest], WordsLeft, Acc, [""|Tags], tag) when C =:= $/ ->
  972. truncatewords_html(Rest, WordsLeft, [C|Acc], Tags, close_tag);
  973. truncatewords_html([C|Rest], WordsLeft, Acc, [Tag|RestOfTags], tag) when C >= $a, C =< $z; C >= $A, C =< $Z ->
  974. truncatewords_html(Rest, WordsLeft, [C|Acc], [[C|Tag]|RestOfTags], tag);
  975. truncatewords_html([C|Rest], WordsLeft, Acc, Tags, tag) when C =:= $> ->
  976. truncatewords_html(Rest, WordsLeft, [C|Acc], Tags, text);
  977. truncatewords_html([C|Rest], WordsLeft, Acc, Tags, tag) ->
  978. truncatewords_html(Rest, WordsLeft, [C|Acc], Tags, attrs);
  979. truncatewords_html([C|Rest], WordsLeft, Acc, Tags, attrs) when C =:= $> ->
  980. truncatewords_html(Rest, WordsLeft, [C|Acc], Tags, text);
  981. truncatewords_html([C|Rest], WordsLeft, Acc, [_Tag|RestOfTags], close_tag) when C =:= $> ->
  982. truncatewords_html(Rest, WordsLeft, [C|Acc], RestOfTags, text).
  983. wordcount([], Count) ->
  984. Count;
  985. wordcount([C1], Count) when C1 =/= $\ ->
  986. Count+1;
  987. wordcount([C1, C2|Rest], Count) when C1 =/= $\ andalso C2 =:= $\ ->
  988. wordcount([C2|Rest], Count + 1);
  989. wordcount([_|Rest], Count) ->
  990. wordcount(Rest, Count).
  991. % No more input, we're done
  992. wordwrap([], Acc, WordAcc, _LineLength, _WrapAt) ->
  993. lists:reverse(WordAcc ++ Acc);
  994. % Premature newline
  995. wordwrap([$\n | Rest], Acc, WordAcc, _LineLength, WrapAt) ->
  996. wordwrap(Rest, [$\n | WordAcc ++ Acc], [], 0, WrapAt);
  997. % Hit the wrap length at a space character. Add a newline
  998. wordwrap([$\ | Rest], Acc, WordAcc, WrapAt, WrapAt) ->
  999. wordwrap(Rest, [$\n | WordAcc ++ Acc], [], 0, WrapAt);
  1000. % Hit a space character before the wrap length. Keep going
  1001. wordwrap([$\ | Rest], Acc, WordAcc, LineLength, WrapAt) ->
  1002. wordwrap(Rest, [$\ | WordAcc ++ Acc], [], LineLength + 1 + erlang:length(WordAcc), WrapAt);
  1003. % Overflowed the current line while building a word
  1004. wordwrap([C | Rest], Acc, WordAcc, 0, WrapAt) when erlang:length(WordAcc) > WrapAt ->
  1005. wordwrap(Rest, Acc, [C | WordAcc], 0, WrapAt);
  1006. wordwrap([C | Rest], Acc, WordAcc, LineLength, WrapAt) when erlang:length(WordAcc) + LineLength > WrapAt ->
  1007. wordwrap(Rest, [$\n | Acc], [C | WordAcc], 0, WrapAt);
  1008. % Just building a word...
  1009. wordwrap([C | Rest], Acc, WordAcc, LineLength, WrapAt) ->
  1010. wordwrap(Rest, Acc, [C | WordAcc], LineLength, WrapAt).
  1011. % Taken from quote_plus of mochiweb_util
  1012. urlencode(Input, Index) when is_binary(Input) ->
  1013. case Input of
  1014. <<_:Index/binary, Byte, _/binary>> when ?NO_ENCODE(Byte) ->
  1015. urlencode(Input, Index + 1);
  1016. <<Pre:Index/binary, $\s, Post/binary>> ->
  1017. process_binary_match(Pre, <<"+">>, size(Post), urlencode(Post, 0));
  1018. <<Pre:Index/binary, Hi:4, Lo:4, Post/binary>> ->
  1019. HiDigit = hexdigit(Hi),
  1020. LoDigit = hexdigit(Lo),
  1021. Code = <<$\%, HiDigit, LoDigit>>,
  1022. process_binary_match(Pre, Code, size(Post), urlencode(Post, 0));
  1023. Input ->
  1024. Input
  1025. end;
  1026. urlencode([], Acc) ->
  1027. lists:reverse(Acc);
  1028. urlencode([C | Rest], Acc) when ?NO_ENCODE(C) ->
  1029. urlencode(Rest, [C | Acc]);
  1030. urlencode([$\s | Rest], Acc) ->
  1031. urlencode(Rest, [$+ | Acc]);
  1032. urlencode([C | Rest], Acc) ->
  1033. <<Hi:4, Lo:4>> = <<C>>,
  1034. urlencode(Rest, [hexdigit(Lo), hexdigit(Hi), $\% | Acc]).
  1035. %% @doc Converts URLs in text into clickable links.
  1036. %%TODO: Autoescape not yet implemented
  1037. urlize(Input) when is_binary(Input) ->
  1038. urlize(binary_to_list(Input),0);
  1039. urlize(Input) ->
  1040. urlize(Input,0).
  1041. urlize(Input, Trunc) when is_binary(Input) ->
  1042. urlize(binary_to_list(Input),Trunc);
  1043. urlize(Input, Trunc) ->
  1044. {ok,RE} = re:compile("(([[:alpha:]]+://|www\.)[^<>[:space:]]+[[:alnum:]/])"),
  1045. RegexResult = re:run(Input,RE,[global]),
  1046. case RegexResult of
  1047. {match, Matches} ->
  1048. Indexes = lists:map(fun(Match) -> lists:nth(2,Match) end, Matches),
  1049. Domains = lists:map(fun({Start, Length}) -> lists:sublist(Input, Start+1, Length) end, Indexes),
  1050. URIDomains = lists:map(fun(Domain) -> addDefaultURI(Domain) end, Domains),
  1051. case Trunc == 0 of
  1052. true ->
  1053. DomainsTrunc = Domains;
  1054. false ->
  1055. DomainsTrunc = lists:map(fun(Domain) -> string:concat( string:substr(Domain,1,Trunc-3), "...") end, Domains)
  1056. end,
  1057. ReplaceList = lists:zip(URIDomains,DomainsTrunc),
  1058. ReplaceStrings = lists:map(fun({URIDomain,Domain}) -> lists:flatten(io_lib:format("<a href=\"~s\" rel=\"nofollow\">~s</a>",[URIDomain,Domain])) end, ReplaceList),
  1059. Template = re:replace(Input,"(([[:alpha:]]+://|www\.)[^<>[:space:]]+[[:alnum:]/])", "~s", [global,{return,list}]),
  1060. Result = lists:flatten(io_lib:format(Template,ReplaceStrings)),
  1061. Result;
  1062. nomatch ->
  1063. Input
  1064. end.
  1065. %% @doc Converts URLs into clickable links just like urlize, but truncates URLs longer than the given character limit.
  1066. urlizetrunc(Input, Trunc) ->
  1067. urlize(Input, Trunc).
  1068. addDefaultURI(Domain) ->
  1069. case string:str(Domain,"://") of
  1070. 0 ->
  1071. Domain1 = string:concat("http://",Domain);
  1072. _ ->
  1073. Domain1 = Domain
  1074. end,
  1075. Domain1.
  1076. hexdigit(C) when C < 10 -> $0 + C;
  1077. hexdigit(C) when C < 16 -> $A + (C - 10).
  1078. process_binary_match(Pre, Insertion, SizePost, Post) ->
  1079. case {size(Pre), SizePost} of
  1080. {0, 0} -> Insertion;
  1081. {0, _} -> [Insertion, Post];
  1082. {_, 0} -> [Pre, Insertion];
  1083. _ -> [Pre, Insertion, Post]
  1084. end.
  1085. yesno_io(Bool, Choices) ->
  1086. %% io:format("Bool, Choices: ~p, ~p ~n",[Bool, Choices]),
  1087. Terms = string:tokens(Choices, ","),
  1088. case Bool of
  1089. true ->
  1090. lists:nth(1, Terms);
  1091. false ->
  1092. lists:nth(2, Terms);
  1093. undefined when erlang:length(Terms) == 2 -> % (converts undefined to false if no mapping for undefined is given)
  1094. lists:nth(2, Terms);
  1095. undefined when erlang:length(Terms) == 3 ->
  1096. lists:nth(3, Terms);
  1097. _ ->
  1098. error
  1099. end.
  1100. %% unjoin == split in other languages; inverse of join
  1101. %%FROM: http://www.erlang.org/pipermail/erlang-questions/2008-October/038896.html
  1102. unjoin(String, []) ->
  1103. unjoin0(String);
  1104. unjoin(String, [Sep]) when is_integer(Sep) ->
  1105. unjoin1(String, Sep);
  1106. unjoin(String, [C1,C2|L]) when is_integer(C1), is_integer(C2) ->
  1107. unjoin2(String, C1, C2, L).
  1108. %% Split a string at "", which is deemed to occur _between_
  1109. %% adjacent characters, but queerly, not at the beginning
  1110. %% or the end.
  1111. unjoin0([C|Cs]) ->
  1112. [[C] | unjoin0(Cs)];
  1113. unjoin0([]) ->
  1114. [].
  1115. %% Split a string at a single character separator.
  1116. unjoin1(String, Sep) ->
  1117. unjoin1_loop(String, Sep, "").
  1118. unjoin1_loop([Sep|String], Sep, Rev) ->
  1119. [lists:reverse(Rev) | unjoin1(String, Sep)];
  1120. unjoin1_loop([Chr|String], Sep, Rev) ->
  1121. unjoin1_loop(String, Sep, [Chr|Rev]);
  1122. unjoin1_loop([], _, Rev) ->
  1123. [lists:reverse(Rev)].
  1124. %% Split a string at a multi-character separator
  1125. %% [C1,C2|L]. These components are split out for
  1126. %% a fast match.
  1127. unjoin2(String, C1, C2, L) ->
  1128. unjoin2_loop(String, C1, C2, L, "").
  1129. unjoin2_loop([C1|S = [C2|String]], C1, C2, L, Rev) ->
  1130. case unjoin_prefix(L, String)
  1131. of no -> unjoin2_loop(S, C1, C2, L, [C1|Rev])
  1132. ; Rest -> [lists:reverse(Rev) | unjoin2(Rest, C1, C2, L)]
  1133. end;
  1134. unjoin2_loop([Chr|String], C1, C2, L, Rev) ->
  1135. unjoin2_loop(String, C1, C2, L, [Chr|Rev]);
  1136. unjoin2_loop([], _, _, _, Rev) ->
  1137. [lists:reverse(Rev)].
  1138. unjoin_prefix([C|L], [C|S]) -> unjoin_prefix(L, S);
  1139. unjoin_prefix([], S) -> S;
  1140. unjoin_prefix(_, _) -> no.