abstracted internal state of the `jsx_to_term' handler
This commit is contained in:
parent
1909687aff
commit
62914f422a
1 changed files with 105 additions and 22 deletions
|
@ -25,6 +25,7 @@
|
|||
|
||||
-export([to_term/2]).
|
||||
-export([init/1, handle_event/2]).
|
||||
-export([start_object/1, start_array/1, finish/1, insert/2, insert/3]).
|
||||
|
||||
|
||||
-record(config, {
|
||||
|
@ -70,33 +71,20 @@ parse_config([], Config) ->
|
|||
Config.
|
||||
|
||||
|
||||
init(Config) -> {[[]], parse_config(Config)}.
|
||||
init(Config) -> {[], parse_config(Config)}.
|
||||
|
||||
|
||||
handle_event(end_json, {[[Terms]], _Config}) -> Terms;
|
||||
handle_event(end_json, {Term, _Config}) -> Term;
|
||||
|
||||
handle_event(start_object, {Terms, Config}) -> {[[]|Terms], Config};
|
||||
handle_event(end_object, {[[], {key, Key}, Last|Terms], Config}) ->
|
||||
{[[{Key, [{}]}] ++ Last] ++ Terms, Config};
|
||||
handle_event(end_object, {[Object, {key, Key}, Last|Terms], Config}) ->
|
||||
{[[{Key, lists:reverse(Object)}] ++ Last] ++ Terms, Config};
|
||||
handle_event(end_object, {[[], Last|Terms], Config}) ->
|
||||
{[[[{}]] ++ Last] ++ Terms, Config};
|
||||
handle_event(end_object, {[Object, Last|Terms], Config}) ->
|
||||
{[[lists:reverse(Object)] ++ Last] ++ Terms, Config};
|
||||
handle_event(start_object, {Stack, Config}) -> {start_object(Stack), Config};
|
||||
handle_event(end_object, {Stack, Config}) -> {finish(Stack), Config};
|
||||
|
||||
handle_event(start_array, {Terms, Config}) -> {[[]|Terms], Config};
|
||||
handle_event(end_array, {[List, {key, Key}, Last|Terms], Config}) ->
|
||||
{[[{Key, lists:reverse(List)}] ++ Last] ++ Terms, Config};
|
||||
handle_event(end_array, {[List, Last|Terms], Config}) ->
|
||||
{[[lists:reverse(List)] ++ Last] ++ Terms, Config};
|
||||
handle_event(start_array, {Stack, Config}) -> {start_array(Stack), Config};
|
||||
handle_event(end_array, {Stack, Config}) -> {finish(Stack), Config};
|
||||
|
||||
handle_event({key, Key}, {Terms, Config}) -> {[{key, format_key(Key, Config)}] ++ Terms, Config};
|
||||
handle_event({key, Key}, {Stack, Config}) -> {insert(format_key(Key, Config), Stack), Config};
|
||||
|
||||
handle_event({_, Event}, {[{key, Key}, Last|Terms], Config}) ->
|
||||
{[[{Key, Event}] ++ Last] ++ Terms, Config};
|
||||
handle_event({_, Event}, {[Last|Terms], Config}) ->
|
||||
{[[Event] ++ Last] ++ Terms, Config}.
|
||||
handle_event({_, Event}, {Stack, Config}) -> {insert(Event, Stack), Config}.
|
||||
|
||||
|
||||
format_key(Key, Config) ->
|
||||
|
@ -113,6 +101,44 @@ format_key(Key, Config) ->
|
|||
end.
|
||||
|
||||
|
||||
%% internal state is a stack of in progress objects/arrays
|
||||
%% `[Current, Parent, Grandparent,...OriginalAncestor]`
|
||||
%% an object has the representation on the stack of
|
||||
%% `{object, [{NthKey, NthValue}, {NMinus1Key, NthMinus1Value},...{FirstKey, FirstValue}]}`
|
||||
%% of if there's a key with a yet to be matched value
|
||||
%% `{object, Key, [{NthKey, NthValue},...]}`
|
||||
%% an array looks like
|
||||
%% `{array, [NthValue, NthMinus1Value,...FirstValue]}`
|
||||
|
||||
%% allocate a new object on top of the stack
|
||||
start_object(Stack) -> [{object, []}] ++ Stack.
|
||||
|
||||
%% allocate a new array on top of the stack
|
||||
start_array(Stack) -> [{array, []}] ++ Stack.
|
||||
|
||||
%% finish an object or array and insert it into the parent object if it exists
|
||||
finish([{object, []}]) -> [{}];
|
||||
finish([{object, []}|Rest]) -> insert([{}], Rest);
|
||||
finish([{object, Pairs}]) -> lists:reverse(Pairs);
|
||||
finish([{object, Pairs}|Rest]) -> insert(lists:reverse(Pairs), Rest);
|
||||
finish([{array, Values}]) -> lists:reverse(Values);
|
||||
finish([{array, Values}|Rest]) -> insert(lists:reverse(Values), Rest);
|
||||
finish(_) -> erlang:error(badarg).
|
||||
|
||||
%% insert a value when there's no parent object or array
|
||||
insert(Value, []) -> Value;
|
||||
%% insert a key or value into an object or array, autodetects the 'right' thing
|
||||
insert(Key, [{object, Pairs}|Rest]) -> [{object, Key, Pairs}] ++ Rest;
|
||||
insert(Value, [{object, Key, Pairs}|Rest]) -> [{object, [{Key, Value}] ++ Pairs}] ++ Rest;
|
||||
insert(Value, [{array, Values}|Rest]) -> [{array, [Value] ++ Values}] ++ Rest;
|
||||
insert(_, _) -> erlang:error(badarg).
|
||||
|
||||
%% insert a key/value pair into an object
|
||||
insert(Key, Value, [{object, Pairs}|Rest]) -> [{object, [{Key, Value}] ++ Pairs}] ++ Rest;
|
||||
insert(_, _, _) -> erlang:error(badarg).
|
||||
|
||||
|
||||
|
||||
|
||||
%% eunit tests
|
||||
|
||||
|
@ -158,13 +184,70 @@ format_key_test_() ->
|
|||
].
|
||||
|
||||
|
||||
rep_manipulation_test_() ->
|
||||
[
|
||||
{"allocate a new object on an empty stack", ?_assertEqual(
|
||||
[{object, []}],
|
||||
start_object([])
|
||||
)},
|
||||
{"allocate a new object on a stack", ?_assertEqual(
|
||||
[{object, []}, {object, []}],
|
||||
start_object([{object, []}])
|
||||
)},
|
||||
{"allocate a new array on an empty stack", ?_assertEqual(
|
||||
[{array, []}],
|
||||
start_array([])
|
||||
)},
|
||||
{"allocate a new array on a stack", ?_assertEqual(
|
||||
[{array, []}, {object, []}],
|
||||
start_array([{object, []}])
|
||||
)},
|
||||
{"insert a key into an object", ?_assertEqual(
|
||||
[{object, key, []}, junk],
|
||||
insert(key, [{object, []}, junk])
|
||||
)},
|
||||
{"insert a value into an object", ?_assertEqual(
|
||||
[{object, [{key, value}]}, junk],
|
||||
insert(value, [{object, key, []}, junk])
|
||||
)},
|
||||
{"insert a value into an array", ?_assertEqual(
|
||||
[{array, [value]}, junk],
|
||||
insert(value, [{array, []}, junk])
|
||||
)},
|
||||
{"insert a key/value pair into an object", ?_assertEqual(
|
||||
[{object, [{key, value}, {x, y}]}, junk],
|
||||
insert(key, value, [{object, [{x, y}]}, junk])
|
||||
)},
|
||||
{"finish an object with no ancestor", ?_assertEqual(
|
||||
[{a, b}, {x, y}],
|
||||
finish([{object, [{x, y}, {a, b}]}])
|
||||
)},
|
||||
{"finish an empty object", ?_assertEqual(
|
||||
[{}],
|
||||
finish([{object, []}])
|
||||
)},
|
||||
{"finish an object with an ancestor", ?_assertEqual(
|
||||
[{object, [{key, [{a, b}, {x, y}]}, {foo, bar}]}],
|
||||
finish([{object, [{x, y}, {a, b}]}, {object, key, [{foo, bar}]}])
|
||||
)},
|
||||
{"finish an array with no ancestor", ?_assertEqual(
|
||||
[a, b, c],
|
||||
finish([{array, [c, b, a]}])
|
||||
)},
|
||||
{"finish an array with an ancestor", ?_assertEqual(
|
||||
[{array, [[a, b, c], d, e, f]}],
|
||||
finish([{array, [c, b, a]}, {array, [d, e, f]}])
|
||||
)}
|
||||
].
|
||||
|
||||
|
||||
handle_event_test_() ->
|
||||
Data = jsx:test_cases(),
|
||||
[
|
||||
{
|
||||
Title, ?_assertEqual(
|
||||
Term,
|
||||
lists:foldl(fun handle_event/2, {[[]], #config{}}, Events ++ [end_json])
|
||||
lists:foldl(fun handle_event/2, {[], #config{}}, Events ++ [end_json])
|
||||
)
|
||||
} || {Title, _, Term, Events} <- Data
|
||||
].
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue