-module(ephp_string). -author('manuel@altenwald.com'). -include("ephp.hrl"). -export([to_lower/1, to_upper/1, capitalize/2, escape/2, expand_mask/1, trim/1, trim/2, rtrim/2, ltrim/2, lpad/3, rpad/3, pad/3, join/2, vsn_cmp/2, spaces/1, repeat/2, reverse/1, hex2bin/1, ihex2bin/1, bin2hex/1, ibin2hex/1]). -spec to_lower(integer() | binary() | undefined) -> binary() | undefined. to_lower(undefined) -> undefined; to_lower(Text) -> unistring:to_lower(Text). -spec to_upper(integer() | binary()) -> binary(); (undefined) -> undefined. to_upper(undefined) -> undefined; to_upper(Text) -> unistring:to_upper(Text). -spec capitalize(binary(), [byte()]) -> binary(). capitalize(<<>>, _Sep) -> <<>>; capitalize(<>, Sep) -> Text = <<(unistring:to_upper(<>))/binary, Rest/binary>>, capitalize(Text, <<>>, Sep). capitalize(<<>>, Result, _Sep) -> Result; capitalize(<>, Result, Sep) -> case lists:member(A, Sep) of true when Rest =/= <<>> -> <> = Rest, case lists:member(B, Sep) of true -> capitalize(Rest, <>, Sep); false -> Cap = unistring:to_upper(<>), NewResult = <>, capitalize(NewRest, NewResult, Sep) end; _ -> capitalize(Rest, <>, Sep) end. -spec escape(mixed(), non_neg_integer()) -> binary(). escape(Bin, Escape) when is_binary(Bin) -> Bin2 = lists:foldl(fun (E, Acc) when E =:= Escape -> <>; (C, Acc) -> <> end, <<>>, binary_to_list(Bin)), <<"'", Bin2/binary, "'">>; escape(MemRef, Escape) when ?IS_MEM(MemRef) -> escape(ephp_mem:get(MemRef), Escape); escape(Mixed, _Escape) -> ephp_data:to_bin(Mixed). -ifdef(NATIVE_STR_REPLACE). replace(M, Subject, Seq) -> string:replace(M, Subject, Seq). -else. replace(M, Subject, Seq) -> binary:replace(M, Subject, Seq). -endif. -spec expand_mask(binary()) -> binary(). expand_mask(Mask) -> Opts = [{capture, all, binary}, global], case re:run(Mask, "(.)\\.\\.(.)", Opts) of {match, Matches} -> lists:foldl(fun([Subject, <>, <>], M) -> Seq = list_to_binary(lists:seq(Init, End)), iolist_to_binary(replace(M, Subject, Seq)) end, Mask, Matches); nomatch -> Mask end. -spec pad(binary(), integer(), binary() | undefined) -> binary(); (undefined, integer(), binary() | undefined) -> undefined. pad(undefined, _Size, _PadStr) -> undefined; pad(Bin, _Size, undefined) -> Bin; pad(Bin, Size, _PadStr) when byte_size(Bin) >= Size -> Bin; pad(Bin, Size, PadStr) -> MissingLen = Size - byte_size(Bin), SizeLeft = ephp_data:flooring(MissingLen / 2), NewBin = rpad(Bin, Size - SizeLeft, PadStr), lpad(NewBin, Size, PadStr). -spec lpad(binary(), integer(), binary() | undefined) -> binary(); (undefined, integer(), binary() | undefined) -> undefined. lpad(undefined, _PadLen, _PadStr) -> undefined; lpad(Input, _PadLen, undefined) -> Input; lpad(Input, PadLen, _PadStr) when byte_size(Input) >= PadLen -> Input; lpad(Input, PadLen, PadStr) -> MissingLen = PadLen - byte_size(Input), Times = ephp_data:ceiling(MissingLen / byte_size(PadStr)), <> = repeat(Times, PadStr), <>. -spec rpad(binary(), integer(), binary() | undefined) -> binary(); (undefined, integer(), binary() | undefined) -> undefined. rpad(undefined, _PadLen, _PadStr) -> undefined; rpad(Input, _PadLen, undefined) -> Input; rpad(Input, PadLen, _PadStr) when byte_size(Input) >= PadLen -> Input; rpad(Input, PadLen, PadStr) -> MissingLen = PadLen - byte_size(Input), Times = ephp_data:ceiling(MissingLen / byte_size(PadStr)), <> = repeat(Times, PadStr), <>. -spec trim(binary()) -> binary(); (undefined) -> undefined. trim(undefined) -> undefined; trim(Text) -> re:replace(Text, "^\\s+|\\s+$", "", [{return, binary}, global]). -spec trim(binary(), [byte()]) -> binary(). trim(Text, Chars) -> rtrim(ltrim(Text, Chars), Chars). -spec rtrim(binary(), [byte()]) -> binary(). rtrim(<<>>, _Chars) -> <<>>; rtrim(<>, Chars) -> case lists:member(A, Chars) of true -> <<>>; false -> <> end; rtrim(Text, Chars) -> Size = byte_size(Text) - 1, <> = Text, case lists:member(A, Chars) of true -> rtrim(Rest, Chars); false -> Text end. -spec ltrim(binary(), [byte()]) -> binary(). ltrim(<<>>, _Chars) -> <<>>; ltrim(<>, Chars) -> case lists:member(A, Chars) of true -> <<>>; false -> <> end; ltrim(<> = Text, Chars) -> case lists:member(A, Chars) of true -> ltrim(Rest, Chars); false -> Text end. -spec join([binary()], binary()) -> binary(). join([], _Sep) -> <<>>; join([Part], _Sep) -> Part; join([Head | Tail], Sep) -> lists:foldl(fun(Value, Acc) -> <> end, Head, Tail). -spec vsn_cmp(binary(), binary()) -> integer(); ([binary()], [binary()]) -> integer(). vsn_cmp(Vsn1, Vsn2) when is_binary(Vsn1) andalso is_binary(Vsn2) -> Vsn01 = hd(binary:split(Vsn1, <<"-">>)), Vsn1parts = binary:split(Vsn01, <<".">>, [global]), Vsn02 = hd(binary:split(Vsn2, <<"-">>)), Vsn2parts = binary:split(Vsn02, <<".">>, [global]), vsn_cmp([binary_to_integer(B) || B <- Vsn1parts], [binary_to_integer(B) || B <- Vsn2parts]); vsn_cmp([], []) -> 0; vsn_cmp([_ | _], []) -> 1; vsn_cmp([], [_ | _]) -> -1; vsn_cmp([A | _], [B | _]) when A > B -> 1; vsn_cmp([A | _], [B | _]) when A < B -> -1; vsn_cmp([A | ARest], [A | BRest]) -> vsn_cmp(ARest, BRest). -spec spaces(pos_integer()) -> binary(). %% @doc generate as many spaces as the number passed as param. spaces(Num) -> repeat(Num, 32). -spec repeat(pos_integer(), byte() | binary()) -> binary(). %% @doc repeat the byte passed as param as many times as the number passed as param. repeat(Num, Byte) when is_integer(Byte) -> repeat(Num, Byte, <<>>); repeat(Num, Bin) when is_binary(Bin) -> repeat(Num, Bin, <<>>). -spec repeat(pos_integer(), byte() | binary(), binary()) -> binary(). %% @doc repeat internal function. %% @see repeat/2 %% @private repeat(0, _ByteOrBin, Binary) -> Binary; repeat(N, Byte, Binary) when is_integer(Byte) -> repeat(N - 1, Byte, <>); repeat(N, Bin, Binary) when is_binary(Bin) -> repeat(N - 1, Bin, <>). -spec reverse(binary()) -> binary(). %% @doc get the reverse of a string passed as param. %% Reference: [https://stackoverflow.com/a/43310493] %% @end reverse(Str) -> Size = erlang:bit_size(Str), <> = Str, <>. -spec ihex2bin(binary()) -> binary(). %% @doc transform a hexadecimal string in (big-endian) ihex2bin(<>) -> <<(binary_to_integer(<>, 16)), (ihex2bin(Rest))/binary>>; ihex2bin(<>) -> <<(binary_to_integer(<>, 16))>>; ihex2bin(<>) -> <<(binary_to_integer(<<$0:8, A:8>>, 16))>>; ihex2bin(<<>>) -> <<>>. -spec hex2bin(binary()) -> binary(). %% @doc transform a hexadecimal string in (little-endian) hex2bin(<>) -> <<(binary_to_integer(<>, 16)), (hex2bin(Rest))/binary>>; hex2bin(<>) -> <<(binary_to_integer(<>, 16))>>; hex2bin(<>) -> <<(binary_to_integer(<>, 16))>>; hex2bin(<<>>) -> <<>>. -spec bin2hex(binary()) -> binary(). %% @doc transform a binary string in its hexadecimal representation. bin2hex(Bin) when is_binary(Bin) -> to_lower(<< <<(integer_to_binary(X, 16))/binary>> || <> <= Bin >>). -spec ibin2hex(binary()) -> binary(). %% @doc transform a binary string in its hexadecimal representation. ibin2hex(Bin) when is_binary(Bin) -> to_lower(<< <<(integer_to_binary(Y, 16))/binary, (integer_to_binary(X, 16))/binary>> || <> <= Bin >>).