A Simple POET State Machine Accepting SAX Events to Build Plain Old Erlang Term

Per previous blogs:

I wrote a simple xml state machine that receives SAX events to build xmerl compitable XML tree.

This time, it's a simple POET (Plain Old Erlang Term) state machine, which receives SAX events to build the data in form of List and Tuple.

%%% A state machine which receives sax events and builds a Plain Old Erlang Term


-module(poet_sm).

-export([state/2]).

-export([test/0
        ]).

-record(poetsmState, {
    name = undefined,
    attributes = [],
    content = [],
    parents = []
}).


receive_events(Events) -> receive_events(Events, undefined).

receive_events([], _States) -> {ok, [], []};
receive_events([Event|T], States) ->
    case state(Event, States) of 
        {ok, TopObject} -> 
            {ok, TopObject, T};
        {error, Reason} -> 
            {error, Reason};
        States1 -> 
            receive_events(T, States1)    
    end.

state({startDocument}, _StateStack) ->
    State = #poetsmState{},
    [State];
state({endDocument}, StateStack) ->
    %io:fwrite(user, "endDocument, states: ~p~n", [StateStack]),
    case StateStack of
        {ok, TopObject} -> {ok, TopObject};
        _ -> {error, io:fwrite(user, "Bad object match, StateStack is: ~n~p~n", [StateStack])}
    end;
state({startElement, _Uri, LocalName, _QName, Attrs}, StateStack) ->
    %io:fwrite(user, "startElement~n", []),
    %% pop current State
    [State|_StatesPrev] = StateStack,
    #poetsmState{parents=Parents} = State,
    {_Pos, Attributes1} = lists:foldl(
        fun ({Name, Value}, {Pos, AccAttrs}) ->
                Pos1 = Pos + 1,
                Attr = {atom_to_list(Name), to_poet_value(Value)}, 
                {Pos1, [Attr|AccAttrs]}
        end, {0, []}, Attrs),
    Parents1 = [{LocalName, 0}|Parents],
    %% push new state of Attributes, Content and Parents to StateStack
    NewState = #poetsmState{name = LocalName,
                            attributes = Attributes1,
                            content = [],
                            parents = Parents1},
    [NewState|StateStack];
state({endElement, _Uri, LocalName, _QName}, StateStack) ->
    %% pop current State
    [State|StatesPrev] = StateStack,
    #poetsmState{name=Name,
                 attributes=Attributes,
                 content=Content,
                 parents=Parents} = State,
    %io:fwrite(user, "Element end with Name: ~p~n", [Name]),
    if  LocalName == undefined -> %% don't care 
            undefined; 
        LocalName /= Name -> 
            throw(lists:flatten(io_lib:format(
                "Element name match error: ~p should be ~p~n", [LocalName, Name])));
        true -> undefined
    end,
    %% composite a new object
    [_|_ParentsPrev] = Parents,
    Object = 
        if  Attributes == [] ->
                {Name, lists:reverse(Content)};
            true ->
                {Name, lists:reverse(Attributes), lists:reverse(Content)}
        end,
    %io:fwrite(user, "object: ~p~n", [Object]),
    %% put Object to parent's content and return new state stack
    case StatesPrev of
        [_ParentState|[]] -> %% reached the top now, return final result
            {ok, Object};
        [ParentState|Other] ->
            #poetsmState{content=ParentContent} = ParentState,
            ParentContent1 = [Object|ParentContent],
            %% update parent state and backward to it:
            ParentState1 = ParentState#poetsmState{content = ParentContent1},
            %io:fwrite(user, "endElement, state: ~p~n", [State1]),
            [ParentState1|Other]
        end;
state({characters, Characters}, StateStack) ->
    %% pop current State
    [State|StatesPrev] = StateStack,
    #poetsmState{name=Name,
                 content=Content,
                 parents=Parents} = State,
    [{Parent, Pos}|ParentsPrev] = Parents,
    Pos1 = Pos + 1,
    Value = to_poet_value(Characters), %parents = [{Parent, Pos1}|ParentsPrev]},
    Content1 = [Value|Content],
    Parents1 = [{Parent, Pos1}|ParentsPrev],
    UpdatedState = State#poetsmState{content = Content1,
                                     parents = Parents1},
    [UpdatedState|StatesPrev].

to_poet_value(Name) when is_atom(Name) ->
    to_poet_value(atom_to_list(Name));
to_poet_value(Chars) when is_list(Chars) ->
    %% it's string, should convert to binary, since list in poet means array 
    list_to_binary(Chars); 
to_poet_value(Value) ->
    Value.

test() ->
    Events = [
        {startDocument},
        {startElement, [], feed, [], [{link, "http://lightpole.net"}, {author, "Caoyuan"}]},
        {characters, "feed text"},
        {startElement, [], entry, [], [{tag, "Erlang, Function"}]},
        {characters, "Entry1's text"},
        {endElement, [], entry, []},
        {startElement, [], entry, [], []},
        {characters, "Entry2's text"},
        {endElement, [], entry, []},
        {endElement, [], feed, []},
        {endDocument}
    ],

    %% Streaming:
    {ok, Poet1, _Rest} = receive_events(Events),
    io:fwrite(user, "Streaming Result: ~n~p~n", [Poet1]).

The result will be something like:

{feed,[{"link",<<"http://lightpole.net">>},{"author",<<"Caoyuan">>}],
      [<<"feed text">>,
       {entry,[{"tag",<<"Erlang, Function">>}],[<<"Entry1's text">>]},
       {entry,[<<"Entry2's text">>]}]}

The previous iCal and JSON examples can be parsed to POET by modifing the front-end parser a bit.

你可能感兴趣的:(json,xml,.net,erlang,REST)