diff --git a/src/jsx_encoder.erl b/src/jsx_encoder.erl index d39d49a..12b1c67 100644 --- a/src/jsx_encoder.erl +++ b/src/jsx_encoder.erl @@ -23,7 +23,7 @@ -module(jsx_encoder). --export([encoder/3, encode/1, encode/2, unzip/1]). +-export([encoder/3, encode/1, encode/2]). -spec encoder(Handler::module(), State::any(), Config::list()) -> jsx:encoder(). @@ -44,11 +44,10 @@ encode(Term, EntryPoint) -> encode_(Term, EntryPoint). -endif. -ifdef(maps_support). -encode(Map, _EntryPoint) when is_map(Map), map_size(Map) < 1 -> [start_object, end_object]; +encode(Map, _EntryPoint) when is_map(Map), map_size(Map) < 1 -> + [start_object, end_object]; encode(Term, EntryPoint) when is_map(Term) -> - lists:flatten( - [start_object] ++ [ EntryPoint:encode(T, EntryPoint) || T <- unpack(Term) ] ++ [end_object] - ); + [start_object] ++ unpack(Term, EntryPoint); encode(Term, EntryPoint) -> encode_(Term, EntryPoint). -endif. @@ -56,28 +55,29 @@ encode_([], _EntryPoint) -> [start_array, end_array]; encode_([{}], _EntryPoint) -> [start_object, end_object]; encode_([{_, _}|_] = Term, EntryPoint) -> - lists:flatten( - [start_object] ++ [ EntryPoint:encode(T, EntryPoint) || T <- unzip(Term) ] ++ [end_object] - ); + [start_object] ++ unzip(Term, EntryPoint); encode_(Term, EntryPoint) when is_list(Term) -> - lists:flatten( - [start_array] ++ [ EntryPoint:encode(T, EntryPoint) || T <- Term ] ++ [end_array] - ); + [start_array] ++ unhitch(Term, EntryPoint); encode_(Else, _EntryPoint) -> [Else]. -unzip(List) -> unzip(List, []). +unzip([{K, V}|Rest], EntryPoint) when is_integer(K); is_binary(K); is_atom(K) -> + [K] ++ EntryPoint:encode(V, EntryPoint) ++ unzip(Rest, EntryPoint); +unzip([], _) -> [end_object]. + + +unhitch([V|Rest], EntryPoint) -> + EntryPoint:encode(V, EntryPoint) ++ unhitch(Rest, EntryPoint); +unhitch([], _) -> [end_array]. -unzip([], Acc) -> lists:reverse(Acc); -unzip([{K, V}|Rest], Acc) when is_binary(K); is_atom(K); is_integer(K) -> unzip(Rest, [V, K] ++ Acc). -ifdef(maps_support). -unpack(Map) -> unpack(maps:keys(Map), Map, []). +unpack(Map, EntryPoint) -> unpack(Map, maps:keys(Map), EntryPoint). -unpack([], _, Acc) -> lists:reverse(Acc); -unpack([K|Rest], Map, Acc) when is_binary(K); is_atom(K); is_integer(K) -> - unpack(Rest, Map, [maps:get(K, Map), K] ++ Acc). +unpack(Map, [K|Rest], EntryPoint) when is_integer(K); is_binary(K); is_atom(K) -> + [K] ++ EntryPoint:encode(maps:get(K, Map), EntryPoint) ++ unpack(Map, Rest, EntryPoint); +unpack(_, [], _) -> [end_object]. -endif. diff --git a/src/jsx_parser.erl b/src/jsx_parser.erl index 4fea49d..7a8f230 100644 --- a/src/jsx_parser.erl +++ b/src/jsx_parser.erl @@ -87,36 +87,26 @@ incomplete(State, Handler, Stack, Config=#config{incomplete_handler=F}) -> handle_event(Event, {Handler, State}, _Config) -> {Handler, Handler:handle_event(Event, State)}. -value([start_object|Tokens], Handler, Stack, Config) -> - object(Tokens, handle_event(start_object, Handler, Config), [object|Stack], Config); -value([start_array|Tokens], Handler, Stack, Config) -> - array(Tokens, handle_event(start_array, Handler, Config), [array|Stack], Config); -value([{literal, Literal}|Tokens], Handler, Stack, Config) when Literal == true; Literal == false; Literal == null -> - maybe_done(Tokens, handle_event({literal, Literal}, Handler, Config), Stack, Config); -value([Literal|Tokens], Handler, Stack, Config) when Literal == true; Literal == false; Literal == null -> - value([{literal, Literal}] ++ Tokens, Handler, Stack, Config); -value([{integer, Number}|Tokens], Handler, Stack, Config) when is_integer(Number) -> - maybe_done(Tokens, handle_event({integer, Number}, Handler, Config), Stack, Config); -value([{float, Number}|Tokens], Handler, Stack, Config) when is_float(Number) -> - maybe_done(Tokens, handle_event({float, Number}, Handler, Config), Stack, Config); -value([{number, Number}|Tokens], Handler, Stack, Config) when is_integer(Number) -> - value([{integer, Number}] ++ Tokens, Handler, Stack, Config); -value([{number, Number}|Tokens], Handler, Stack, Config) when is_float(Number) -> - value([{float, Number}] ++ Tokens, Handler, Stack, Config); -value([Number|Tokens], Handler, Stack, Config) when is_integer(Number) -> - value([{integer, Number}] ++ Tokens, Handler, Stack, Config); -value([Number|Tokens], Handler, Stack, Config) when is_float(Number) -> - value([{float, Number}] ++ Tokens, Handler, Stack, Config); -value([{string, String}|Tokens], Handler, Stack, Config) when is_binary(String) -> +value([String|Tokens], Handler, Stack, Config) when is_binary(String) -> try clean_string(String, Config) of Clean -> maybe_done(Tokens, handle_event({string, Clean}, Handler, Config), Stack, Config) catch error:badarg -> ?error(value, [{string, String}|Tokens], Handler, Stack, Config) end; -value([String|Tokens], Handler, Stack, Config) when is_binary(String) -> - value([{string, String}] ++ Tokens, Handler, Stack, Config); -value([String|Tokens], Handler, Stack, Config) when is_atom(String) -> - value([{string, atom_to_binary(String, utf8)}] ++ Tokens, Handler, Stack, Config); +value([true|Tokens], Handler, Stack, Config) -> + maybe_done(Tokens, handle_event({literal, true}, Handler, Config), Stack, Config); +value([false|Tokens], Handler, Stack, Config) -> + maybe_done(Tokens, handle_event({literal, false}, Handler, Config), Stack, Config); +value([null|Tokens], Handler, Stack, Config) -> + maybe_done(Tokens, handle_event({literal, null}, Handler, Config), Stack, Config); +value([start_object|Tokens], Handler, Stack, Config) -> + object(Tokens, handle_event(start_object, Handler, Config), [object|Stack], Config); +value([start_array|Tokens], Handler, Stack, Config) -> + array(Tokens, handle_event(start_array, Handler, Config), [array|Stack], Config); +value([Number|Tokens], Handler, Stack, Config) when is_integer(Number) -> + maybe_done(Tokens, handle_event({integer, Number}, Handler, Config), Stack, Config); +value([Number|Tokens], Handler, Stack, Config) when is_float(Number) -> + maybe_done(Tokens, handle_event({float, Number}, Handler, Config), Stack, Config); value([{raw, Raw}|Tokens], Handler, Stack, Config) when is_binary(Raw) -> value((jsx:decoder(?MODULE, [], []))(Raw) ++ Tokens, Handler, Stack, Config); value([{{Year, Month, Day}, {Hour, Min, Sec}}|Tokens], Handler, Stack, Config) @@ -129,6 +119,10 @@ when is_integer(Year), is_integer(Month), is_integer(Day), is_integer(Hour), is_ Stack, Config ); +value([{_, Value}|Tokens], Handler, Stack, Config) -> + value([Value] ++ Tokens, Handler, Stack, Config); +value([String|Tokens], Handler, Stack, Config) when is_atom(String) -> + value([{string, atom_to_binary(String, utf8)}] ++ Tokens, Handler, Stack, Config); value([], Handler, Stack, Config) -> incomplete(value, Handler, Stack, Config); value(BadTokens, Handler, Stack, Config) when is_list(BadTokens) -> @@ -203,35 +197,35 @@ clean_string(Bin, Config) -> clean(Bin, [], Config). clean(<<>>, Acc, _) -> iolist_to_binary(Acc); clean(<>, Acc, Config) when X < 16#20 -> - maybe_replace(X, Rest, Acc, Config); + clean(Rest, [Acc, maybe_replace(X, Config)], Config); clean(<<34, Rest/binary>>, Acc, Config) -> - maybe_replace(34, Rest, Acc, Config); + clean(Rest, [Acc, maybe_replace(34, Config)], Config); clean(<<47, Rest/binary>>, Acc, Config) -> - maybe_replace(47, Rest, Acc, Config); + clean(Rest, [Acc, maybe_replace(47, Config)], Config); clean(<<92, Rest/binary>>, Acc, Config) -> - maybe_replace(92, Rest, Acc, Config); + clean(Rest, [Acc, maybe_replace(92, Config)], Config); clean(<>, Acc, Config=#config{uescape=true}) when X >= 16#80 -> - maybe_replace(X, Rest, Acc, Config); + clean(Rest, [Acc, maybe_replace(X, Config)], Config); clean(<>, Acc, Config) when X == 16#2028; X == 16#2029 -> - maybe_replace(X, Rest, Acc, Config); + clean(Rest, [Acc, maybe_replace(X, Config)], Config); clean(<<_/utf8, _/binary>> = Bin, Acc, Config) -> Size = count(Bin, 0, Config), <> = Bin, clean(Rest, [Acc, Clean], Config); %% surrogates clean(<<237, X, _, Rest/binary>>, Acc, Config) when X >= 160 -> - maybe_replace(surrogate, Rest, Acc, Config); + clean(Rest, [Acc, maybe_replace(surrogate, Config)], Config); %% overlong encodings and missing continuations of a 2 byte sequence clean(<>, Acc, Config) when X >= 192, X =< 223 -> - maybe_replace(badutf, strip_continuations(Rest, 1), Acc, Config); + clean(strip_continuations(Rest, 1), [Acc, maybe_replace(badutf, Config)], Config); %% overlong encodings and missing continuations of a 3 byte sequence clean(<>, Acc, Config) when X >= 224, X =< 239 -> - maybe_replace(badutf, strip_continuations(Rest, 2), Acc, Config); + clean(strip_continuations(Rest, 2), [Acc, maybe_replace(badutf, Config)], Config); %% overlong encodings and missing continuations of a 4 byte sequence clean(<>, Acc, Config) when X >= 240, X =< 247 -> - maybe_replace(badutf, strip_continuations(Rest, 3), Acc, Config); + clean(strip_continuations(Rest, 3), [Acc, maybe_replace(badutf, Config)], Config); clean(<<_, Rest/binary>>, Acc, Config) -> - maybe_replace(badutf, Rest, Acc, Config). + clean(Rest, [Acc, maybe_replace(badutf, Config)], Config). count(<<>>, N, _) -> N; @@ -474,43 +468,43 @@ strip_continuations(<>, N) when X >= 128, X =< 191 -> strip_continuations(Bin, _) -> Bin. -maybe_replace($\b, Rest, Acc, Config=#config{escaped_strings=true}) -> - clean(Rest, [Acc, $\\, $b], Config); -maybe_replace($\t, Rest, Acc, Config=#config{escaped_strings=true}) -> - clean(Rest, [Acc, $\\, $t], Config); -maybe_replace($\n, Rest, Acc, Config=#config{escaped_strings=true}) -> - clean(Rest, [Acc, $\\, $n], Config); -maybe_replace($\f, Rest, Acc, Config=#config{escaped_strings=true}) -> - clean(Rest, [Acc, $\\, $f], Config); -maybe_replace($\r, Rest, Acc, Config=#config{escaped_strings=true}) -> - clean(Rest, [Acc, $\\, $r], Config); -maybe_replace($\", Rest, Acc, Config=#config{escaped_strings=true}) -> - clean(Rest, [Acc, $\\, $\"], Config); -maybe_replace($/, Rest, Acc, Config=#config{escaped_strings=true}) -> +maybe_replace($\b, #config{escaped_strings=true}) -> + [$\\, $b]; +maybe_replace($\t, #config{escaped_strings=true}) -> + [$\\, $t]; +maybe_replace($\n, #config{escaped_strings=true}) -> + [$\\, $n]; +maybe_replace($\f, #config{escaped_strings=true}) -> + [$\\, $f]; +maybe_replace($\r, #config{escaped_strings=true}) -> + [$\\, $r]; +maybe_replace($\", #config{escaped_strings=true}) -> + [$\\, $\"]; +maybe_replace($/, Config=#config{escaped_strings=true}) -> case Config#config.escaped_forward_slashes of - true -> clean(Rest, [Acc, $\\, $/], Config); - false -> clean(Rest, [Acc, $/], Config) + true -> [$\\, $/]; + false -> [$/] end; -maybe_replace($\\, Rest, Acc, Config=#config{escaped_strings=true}) -> - clean(Rest, [Acc, $\\, $\\], Config); -maybe_replace(X, Rest, Acc, Config=#config{escaped_strings=true}) when X < 32 -> - clean(Rest, [Acc, json_escape_sequence(X)], Config); +maybe_replace($\\, #config{escaped_strings=true}) -> + [$\\, $\\]; +maybe_replace(X, #config{escaped_strings=true}) when X < 32 -> + json_escape_sequence(X); %% escaped even if no other escaping was requested! -maybe_replace(X, Rest, Acc, Config=#config{uescape=true}) when X >= 16#80 -> - clean(Rest, [Acc, json_escape_sequence(X)], Config); -maybe_replace(X, Rest, Acc, Config=#config{escaped_strings=true}) when X == 16#2028; X == 16#2029 -> +maybe_replace(X, #config{uescape=true}) when X >= 16#80 -> + json_escape_sequence(X); +maybe_replace(X, Config=#config{escaped_strings=true}) when X == 16#2028; X == 16#2029 -> case Config#config.unescaped_jsonp of - true -> clean(Rest, [Acc, <>], Config); - false -> clean(Rest, [Acc, json_escape_sequence(X)], Config) + true -> [<>]; + false -> json_escape_sequence(X) end; -maybe_replace(Atom, _, _, #config{strict_utf8=true}) when is_atom(Atom) -> +maybe_replace(Atom, #config{strict_utf8=true}) when is_atom(Atom) -> erlang:error(badarg); -maybe_replace(surrogate, Rest, Acc, Config) -> - clean(Rest, [Acc, <<16#fffd/utf8>>], Config); -maybe_replace(badutf, Rest, Acc, Config) -> - clean(Rest, [Acc, <<16#fffd/utf8>>], Config); -maybe_replace(X, Rest, Acc, Config) -> - clean(Rest, [Acc, <>], Config). +maybe_replace(surrogate, _Config) -> + [<<16#fffd/utf8>>]; +maybe_replace(badutf, _Config) -> + [<<16#fffd/utf8>>]; +maybe_replace(X, _Config) -> + [<>]. %% convert a codepoint to it's \uXXXX equiv.