Posts for the month of August 2007

From Rails to Erlyweb - Part II Manage Project - Reloaded

The migrating from Rails to Erlyweb of our project is going to be finished. I got more experience on how to deal with Erlyweb. First, the project management can be more straightforward. Here is it:

2. Manage project - Reloaded

Erlyweb provides erlyweb:compile(App, ..) to compile the source files under app directory. To start an app, you usually should erlydb:start(mysql, ....) and compile app files first. To make life easy, you can put some scripting like code under myproject\script directory. Here's my project source tree:

myproject
  + apps
  |   + myapp
  |       + ebin   
  |       + include
  |       + nbproject
  |       + src
  |           + components
  |           + lib
  |           + services
  |       + test
  |       + www
  + config
  |   * yaws.conf
  |   * erlyweb.conf
  + script
      + ebin
      + src
           * erlyweb_app.erl

Where, config/yaws.conf contains the yaws' configuration. Here's mine:

ebin_dir = D:/myapp/trunk/script/ebin

<server localhost>
	port = 8000
	listen = 0.0.0.0
	docroot = D:/myapp/trunk/apps/myapp/www
	appmods = </myapp, erlyweb>
        start_mod = erlyweb_app
	<opaque>
		appname = myapp
                environment = development
        </opaque>
</server>

You may have noticed, all beams under D:/myapp/trunk/script/ebin will be auto-loaded when yaws starts up. And you can prepare another yaws.conf for test or production environment by change the environment var in opaque

Now the config/erlyweb.conf:

{pa, ["script/ebin", 
      "apps/myapp/ebin", 
      "vendor/erlyweb/ebin", 
      "vendor/eunit/ebin"]}.

{i, ["vendor", 
     "apps/myapp/include",
     "/usr/local/lib/yaws"]}.

{production,  [{dbdriver, mysql},
               {database, "mydb_production"},
               {hostname, "localhost"}, 
               {username, "mememe"},
               {password, "pwpwpw"}]}.

{development, [{dbdriver, mysql},
               {database, "mydb_development"},
               {hostname, "localhost"}, 
               {username, "mememe"},
               {password, "pwpwpw"}]}.

{test,        [{dbdriver, mysql},
               {database, "mydb_test"},
               {hostname, "localhost"}, 
               {username, "mememe"},
               {password, "pwpwpw"}]}.

erlyweb_app.erl is the boot scripting code, which will be used to start db connection and compile the code. Currently I run these scripts manually. I'll talk later.

Notice: erlyweb 0.6.2 needed, which contains Haoboy's logfun patch.

%% @doc Main entrance to the entire erlyweb application.
-module(erlyweb_app).

-export([start/1]).

-export([get_conf/1,
         build/1,
         build_test/1, 
	 build_product/1,
	 environment/1,
	 decompile/2,
	 db_log/4,
	 db_dummy_log/4
        ]).

-include("yaws/include/yaws.hrl").
-include("yaws/include/yaws_api.hrl").

db_log(Module, Line, Level, FormatFun) ->
    mysql:log(Module, Line, Level, FormatFun).
db_dummy_log(_Mod, _Line, _Level, _FormatFun) ->
    empty.

%% @doc call back function when yaws start an app  
%% @see man yaws.conf
%%      start_mod = Module
%%          Defines  a  user  provided  callback  module.  At startup of the
%%          server, Module:start/1 will  be  called.   The  #sconf{}  record
%%          (defined  in  yaws.hrl) will be used as the input argument. This
%%          makes it possible for  a  user  application  to  syncronize  the
%%          startup  with  the  yaws  server as well as getting hold of user
%%          specific  configuration  data,  see  the  explanation  for   the
%%           context.
start(SConf) ->
    Opaque = SConf#sconf.opaque,
    AppName = proplists:get_value("appname", Opaque),
    Environment = list_to_atom(proplists:get_value("environment", Opaque)),
    {_I, Pa, Pz, Dbdriver, Database, Hostname, Username, Password} = get_conf(Environment),
    {ok, Cwd} = file:get_cwd(),
    error_logger:info_msg("CWD: ~s~n", [Cwd]),
    add_code_path(Pa, Pz),
    LogFun = 
        case Environment of
            undefined -> 
		fun erlyweb_app:db_log/4;
	    production ->
		fun erlyweb_app:db_dummy_log/4;
            development ->
                %code:add_pathz("../apps/ewp/src/test"),
		fun erlyweb_app:db_log/4;
	    test ->
		fun erlyweb_app:db_log/4
        end,
    error_logger:info_msg("Starting app <~s> as <~s> using database <~s>~n", 
        [AppName, Environment, Database]),    
    start_db(Dbdriver, Database, Hostname, Username, Password, LogFun).

add_code_path(Pa, Pz) ->
    AddedPa = [{Dir, code:add_patha(Dir)} || Dir <- Pa],
    AddedPz = [{Dir, code:add_pathz(Dir)} || Dir <- Pz],
    error_logger:info_msg("Add code patha: ~p~n", [AddedPa]), 
    error_logger:info_msg("Add code pathz: ~p~n", [AddedPz]).  

get_conf(Environment) when is_list(Environment) ->
    get_conf(list_to_atom(Environment));
get_conf(Environment) when is_atom(Environment) ->
    {ok, Confs} = file:consult("config/erlyweb.conf"),
    I = 
        case proplists:get_value(i, Confs) of
            undefined -> [];
            IX -> IX
        end,
    Pa = 
        case proplists:get_value(pa, Confs) of
            undefined -> [];
            PaX -> PaX
        end,
    Pz = 
        case proplists:get_value(pz, Confs) of
            undefined -> [];
            PzX -> PzX
        end,
    EnvConfs = proplists:get_value(Environment, Confs),
    Dbdriver = proplists:get_value(dbdriver, EnvConfs),
    Database = proplists:get_value(database, EnvConfs),
    Hostname = proplists:get_value(hostname, EnvConfs),
    Username = proplists:get_value(username, EnvConfs),
    Password = proplists:get_value(password, EnvConfs),
    {I, Pa, Pz, Dbdriver, Database, Hostname, Username, Password}.

start_db(Dbdriver, Database, Hostname, Username, Password, LogFun) ->
    erlydb:start(Dbdriver, [{database, Database},
			    {hostname, Hostname},
			    {username, Username},
			    {password, Password},
			    {logfun,   LogFun}]).

%% This is developer's entrance to the module.  
build(AppName) ->
    io:format("Building development version of ~s.~n", [AppName]),
    build(AppName, [debug_info], development).

build_test(AppName) ->
    io:format("Building test version of ~s.~n", [AppName]),
    build(AppName, [debug_info], test).

build_product(AppName) ->
    io:format("Building product version of ~s.~n", [AppName]),
    build(AppName, [no_debug_info], production).

build(AppName, Options, Environment) when is_atom(AppName) -> 
    build(atom_to_list(AppName), Options, Environment);
build(AppName, Options, Environment) when is_list(AppName) ->
    {I, Pa, Pz, Dbdriver, Database, Hostname, Username, Password} = get_conf(Environment),
    add_code_path(Pa, Pz),
    start_db(Dbdriver, Database, Hostname, Username, Password, fun erlyweb_app:db_log/4),
    compile(AppName, Options ++ [{auto_compile, false}], I, Dbdriver).
    
compile(AppName, Options, I, Dbdriver) ->
    erlyweb:compile("./apps/" ++ AppName, lists:foldl(
                        fun(Dir, Acc) ->
				[{i, filename:absname(Dir)} | Acc]
			end, [], I) ++
		        [{erlydb_driver, Dbdriver}] ++ Options).


decompile(AppName, Beam) when is_list(AppName) ->
    decompile(list_to_atom(AppName), Beam);
decompile(AppName, Beam) when is_atom(AppName) ->
    {BinFilename, SrcFilename} = 
        case AppName of 
            erlyweb -> 
	        {"./vendor/erlyweb/ebin/" ++ atom_to_list(Beam),
	         "./erlyweb_" ++ atom_to_list(Beam)};
	    _ ->
	        {"./apps/" ++ atom_to_list(AppName) ++ "/ebin/" ++ atom_to_list(Beam),
	         "./apps/" ++ atom_to_list(AppName) ++ "_" ++ atom_to_list(Beam)}
        end,
    decompile_beam(BinFilename, SrcFilename).

decompile_beam(BinFilename, SrcFilename) ->
    io:format("Beam file: ~s~n", [BinFilename]),
    io:format("Source file: ~s~n", [SrcFilename++".erl"]),
    {ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(BinFilename, [abstract_code]),
    %% do not with ".erl" ext?, otherwise will be compiled by erlyweb
    {ok, S} = file:open(SrcFilename ++ ".erl", write), 
    io:fwrite(S, "~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).

To build it,

> erlc -I /opt/local/lib/yaws/include erlyweb_app.erl -o ebin

The erlyweb_app.erl is almost escript ready, but I use it as module functions currently. It's pre-compiled and erlyweb_app.beam is placed under script/ebin

So, I start myapp by steps:

cd \myproject
yaws -sname myapp -i --conf config/yaws.conf --erlang "-smp auto"
1> erlyweb_app:build(myapp).

The erlyweb_app.erl is almost escript ready, but I use it as module functions currently. It's pre-compiled and erlyweb_app.beam is placed under script/ebin

After I made changes to myapp, I run above erlyweb_app:build(myapp). again, then everything is up to date.

And if you'd like to build it from another erl shell, try this:

erl -sname erlybird
(erlybird@myhost)1> rpc:call(myapp@myhost, erlyweb_app, build, [myapp])

Yes, next version of ErlyBird will support building erlyweb apps remotely in ErlyBird's Erlang shell.

recbird - An Erlang Dynamic Record Inferring Parse Transform

You should have read Yariv's recless blog, a nice blog talking about how to make record accessing simple.

Recless is a static type inferring record parse transform, that means, as described in Yariv's blog:

one main restriction in Recless’s type inference algorithm: function parameters must indicate their record types for type inference to work on them. For instance, this won’t work:
get_name(Person) -> Person.name.
Instead, you must write this:
get_name(Person = #person{}) -> Person.name.

How about a dynamic record inferring solution? I got some idea that was also inspired from my friend Haobo. As I'm familiar with Erlang AST tree when developing ErlyBird, I took a try and got some good result. I named it recbird.

The magic behind recbird is, it parses the Erlang AST tree, and adds some setter/getter functions for each record's field. Then, at runtime, it will detect the first element of record var, and thus knows which setter/getter function should be redirected, and call it.

It just works now, with none limits, you can write R.a.b.c and R.a.b.c = Sth almost every where.

Notice: There may be some bugs.

The perfomance is also reasonable, for example, when you do R.a.b = 'yes' 1,000,000 times, the original Erlang record syntax takes about 300ms in my machine, the recbird is about 310ms. When you run it 10,000,000 times, the recbird is about 150% more time costed than original Erlang record accessing.

The recbird's source code can be got at: recbird.erl

To use it, compile it, include compiled recbird.beam under your code path, add

-compile({parse_transform, recbird}).

in your source file.

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, {
    qname = 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 ({Key, Value}, {Pos, AccAttrs}) ->
                Pos1 = Pos + 1,
                Attr = {atom_to_list(Key), to_poet_value(Value)}, 
                %parents = [{LocalName, Pos1}|Parents]},
                {Pos1, [Attr|AccAttrs]}
        end, {0, []}, Attrs),
    Parents1 = [{QName, 0}|Parents],
    %% push new state of Attributes, Content and Parents to StateStack
    NewState = #poetsmState{qname = QName,
                            attributes = Attributes1,
                            content = [],
                            parents = Parents1},
    [NewState|StateStack];
state({endElement, _Uri, _LocalName, QName}, StateStack) ->
    %% pop current State
    [State|StatesPrev] = StateStack,
    #poetsmState{qname=ElemName,
                 attributes=Attributes,
                 content=Content,
                 parents=Parents} = State,
    %io:fwrite(user, "Element end with Name: ~p~n", [Name]),
    if  QName == undefined -> %% don't care 
            undefined; 
        QName /= ElemName -> 
            throw(lists:flatten(io_lib:format(
                "Element name match error: ~p should be ~p~n", 
                [QName, ElemName])));
        true -> undefined
    end,
    %% composite a new object
    [_|_ParentsPrev] = Parents,
    Object = 
        if  Attributes == [] ->
                {QName, lists:reverse(Content)};
            true ->
                {QName, lists:reverse(Attributes), lists:reverse(Content)} 
                %parents = ParentsPrev
        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{qname=_,
                 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, feed, [{link, "http://lightpole.net"}, {author, "Caoyuan"}]},
        {characters, "feed text"},
        {startElement, "", entry, entry, [{tag, "Erlang, Function"}]},
        {characters, "Entry1's text"},
        {endElement, "", entry, entry},
        {startElement, "", entry, entry, []},
        {characters, "Entry2's text"},
        {endElement, "", entry, entry},
        {endElement, "", feed, feed},
        {endDocument}
    ],

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

    {feed,[{"link",<<"http://lightpole.net">>},{"author",<<"Caoyuan">>}],
          [<<"feed text">>,
           {entry,[{"tag",<<"Erlang, Function">>}],[<<"Entry1's text">>]},
           {entry,[<<"Entry2's text">>]}]} = 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.

ErlyBird 0.12.1 released - Erlang IDE based on NetBeans

I'm pleased to announce ErlyBird 0.12.1, an Erlang IDE based on NetBeans.

This is a performance improvement release. This release will only provide all-in-one IDE package, which is in size of 17.3M.

By refining the LL(k) definition of Erlang syntax, I've got ErlyBird parsing performance improved a lot, for example, the time of indexing whole OTP libs is cut to half now.

And this is the first time, ErlyBird works smoothly enough in my compter. I'm with full confidence on Generic Languages Framework of NetBeans now.

Java JRE 5.0+ is requested.

To download, please go to: http://sourceforge.net/project/showfiles.php?group_id=192439

To install:

  1. Unzip erlybird-bin-0.12.1-ide.zip to somewhere. For Windows user, execute 'bin/erlybird.exe'. For *nix user, 'bin/erlybird'.
  2. Check/set your OTP path. From [Tools]->[Options], click on 'Miscellanous', then expand 'Erlang Installation', fill in the full path of your 'erl.exe' or 'erl' file. For instance: "C:/erl/bin/erl.exe"
  3. The default -Xmx option for jvm is set to 256M, if you want to increase it, please open the config file that is located at etc/erlybird.conf, set -J-Xmx of 'default_options'.

When you run ErlyBird first time, the OTP libs will be indexed. The indexing time varies from 10 to 30 minutes deponding on your computer.

Notice: If you have previous version of ErlyBird installed, please delete the old cache files which are located at:

  • *nix: "${HOME}/.erlybird/dev"
  • mac os x: "${HOME}/Library/Application Support/erlybird/dev"
  • windows: "C:\Documents and Settings\yourusername\.erlybird\dev" or some where

The status of ErlyBird is still Alpha, feedback and bug reports are welcome.

CHANGELOG:

  • Performance improvement

ErlyBird 0.12.0 released - Erlang IDE based on NetBeans

I'm pleased to announce ErlyBird 0.12.0, an Erlang IDE based on NetBeans.

This is a bug-fix, performance improvement release. This release will only provide all-in-one IDE package, which is in size of 15.9M.

Java JRE 5.0+ is requested.

To download, please go to: http://sourceforge.net/project/showfiles.php?group_id=192439

To install:

  1. Unzip erlybird-bin-0.12.0-ide.zip to somewhere. For Windows user, execute 'bin/erlybird.exe'. For *nix user, 'bin/erlybird'.
  2. Check/set your OTP path. From [Tools]->[Options], click on 'Miscellanous', then expand 'Erlang Installation', fill in the full path of your 'erl.exe' or 'erl' file. For instance: "C:/erl/bin/erl.exe"
  3. The default -Xmx option for jvm is set to 256M, if you want to increase it, please open the config file that is located at etc/erlybird.conf, set -J-Xmx of 'default_options'.

When you run ErlyBird first time, the OTP libs will be indexed. The indexing time varies from 30 to 60 minutes deponding on your computer.

Notice: If you have previous version of ErlyBird installed, please delete the old cache files which are located at:

  • *nix: "${HOME}/.erlybird/dev"
  • mac os x: "${HOME}/Library/Application Support/erlybird/dev"
  • windows: "C:\Documents and Settings\yourusername\.erlybird\dev" or some where

The status of ErlyBird is still Alpha, feedback and bug reports are welcome.

CHANGELOG:

  • Performance improvement, especially source code rendering performance.
  • Highlighting for unbound/unused variables.
  • Completion for macros and records.
  • Go to source files of -include and -include_lib.
  • Erlang shell window in Mac OS X should work now.
  • Various bug fixes.

Parse JSON to xmerl Compitable XML Tree via A Simple XML State Machine

Updated Aug 16: Fix bugs when json is an array. Add a 'json:root' element always since valid xml should have a root. Remove 'obj' tag that is not necessary.

Updated Aug 15: A more complete json_parser.erl. Thanks for tonyg's beautiful work, fixed some bugs.

Updated Aug 5: rewrote json_parser.erl base on tonyg's RFC4627 implementation, fixed some bugs.

In my previous blog: A Simple XML State Machine Accepting SAX Events to Build xmerl Compitable XML Tree: icalendar demo, I wrote a simple state machine to parse icalendar to xmerl compitable XML tree. This time, I'll use this state machine to parse a JSON expression to xmerl compitable XML tree, the work is fairly simple:

%%---------------------------------------------------------------------------
%% Copyright (c) 2007 Tony Garnock-Jones 
%% Copyright (c) 2007 LShift Ltd. 
%% Copyright (c) 2007 LightPole, Inc. 
%%
%% Permission is hereby granted, free of charge, to any person
%% obtaining a copy of this software and associated documentation
%% files (the "Software"), to deal in the Software without
%% restriction, including without limitation the rights to use, copy,
%% modify, merge, publish, distribute, sublicense, and/or sell copies
%% of the Software, and to permit persons to whom the Software is
%% furnished to do so, subject to the following conditions:
%%
%% The above copyright notice and this permission notice shall be
%% included in all copies or substantial portions of the Software.
%%
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
%% SOFTWARE.
%%---------------------------------------------------------------------------
%%
-module(json_parser).

-define(stateMachine, fun xml_sm:state/2).

-define(JsonNSUri,   "http://www.lightpole.net/xmlns/1.0").
-define(JsonNSAtrr,  {'xmlns:json', ?JsonNSUri}).
-define(JsonNSRoot,  'json:root').
-define(JsonNSArray, 'json:array').

-record(context, {machine,
                  qname}).

-export([parse_to_xml/1,
         parse_to_poet/1]).

-export([test/0]).

parse_to_xml(Data) ->
    parse(Data, #context{machine = fun xml_sm:state/2}).
        
parse_to_poet(Data) ->
    parse(Data, #context{machine = fun poet_sm:state/2}).

parse(Bin, Context) when is_binary(Bin) ->
    parse(binary_to_list(Bin), Context);
parse(Str, #context{machine=MachineFun}=Context) ->
    State1 = MachineFun({startDocument}, undefined),
    State2 = parse_root(skip_ws(Str), State1, Context),
    _State = MachineFun({endDocument}, State2).

%% since a valid xml should have a root element, we add one here.
parse_root([${|T], State, #context{machine=MachineFun}=Context) ->
    State1 = MachineFun({startElement, ?JsonNSUri, root, ?JsonNSRoot, [?JsonNSAtrr]}, State),
    Context1 = Context#context{qname = undefined},
    {_Rest, State2} = parse_object(skip_ws(T), State1, Context1),
    _State = MachineFun({endElement, ?JsonNSUri, root, ?JsonNSRoot}, State2); 
parse_root([$[|T], State, #context{machine=MachineFun}=Context) ->
    State1 = MachineFun({startElement, ?JsonNSUri, root, ?JsonNSRoot, [?JsonNSAtrr]}, State),
    Context1 = Context#context{qname = ?JsonNSArray},
    {_Rest, State2} = parse_array(skip_ws(T), State1, Context1),
    _State = MachineFun({endElement, ?JsonNSUri, root, ?JsonNSRoot}, State2). 

parse_object([$}|T], State, _Context) ->
    {T, State};
parse_object([$,|T], State, Context) ->
    parse_object(skip_ws(T), State, Context);
parse_object([$"|T], State, #context{machine=MachineFun}=Context) ->
    {Rest, ObjNameStr} = parse_string(skip_ws(T), []),
    ObjName = list_to_atom(ObjNameStr),
    Context1 = Context#context{qname = ObjName},
    [$:|T1] = skip_ws(Rest),
    {Rest1, State1} = 
        case skip_ws(T1) of
            [$[|T2] ->
                %% the value is array, we'll create a list of elements named as this 'ObjName'
                parse_array(skip_ws(T2), State, Context1);
            _ ->
                StateX1 = MachineFun({startElement, "", ObjName, ObjName, []}, State),
                {RestX, StateX2} = parse_value(skip_ws(T1), StateX1, Context1),
                StateX3 = MachineFun({endElement, "", ObjName, ObjName}, StateX2),
                {RestX, StateX3}
        end,
    parse_object(skip_ws(Rest1), State1, Context1).

parse_array([$]|T], State, _Context) ->
    {T, State};
parse_array([$,|T], State, Context) ->
    parse_array(skip_ws(T), State, Context);
parse_array(Chars, State, #context{machine=MachineFun, qname=QName}=Context) ->
    State1 = MachineFun({startElement, "", QName, QName, []}, State),
    {Rest, State2} = parse_value(Chars, State1, Context),
    State3 = MachineFun({endElement, "", QName, QName}, State2),
    parse_array(skip_ws(Rest), State3, Context).

parse_value([], State, _Context) -> 
    {[], State};
parse_value("true"++T, State, #context{machine=MachineFun}) -> 
    State1 = MachineFun({characters, "true"}, State),
    {T, State1};
parse_value("false"++T, State, #context{machine=MachineFun}) ->
    State1 = MachineFun({characters, "false"}, State),
    {T, State1};
parse_value("null"++T, State, #context{machine=MachineFun}) ->
    State1 = MachineFun({characters, "null"}, State),
    {T, State1};
parse_value([$"|T], State, #context{machine=MachineFun}) -> 
    {Rest, Value} = parse_string(T, []),
    State1 = MachineFun({characters, Value}, State),
    {Rest, State1};
parse_value([${|T], State, Context) -> 
    parse_object(skip_ws(T), State, Context);
parse_value([$[|T], State, Context) -> 
    parse_array(skip_ws(T), State, Context);
parse_value(Chars, State, #context{machine=MachineFun}) -> 
    {Rest, Value} = parse_number(skip_ws(Chars), []),
    State1 = MachineFun({characters, Value}, State),
    {Rest, State1}.



parse_string([$"|T], Acc) ->
    {T, lists:reverse(Acc)};
parse_string([$\\, Key|T], Acc) ->
    parse_escaped_char(Key, T, Acc);
parse_string([H|T], Acc) ->
    parse_string(T, [H|Acc]).

parse_escaped_char($b,  Rest, Acc) -> parse_string(Rest, [8|Acc]);
parse_escaped_char($t,  Rest, Acc) -> parse_string(Rest, [9|Acc]);
parse_escaped_char($n,  Rest, Acc) -> parse_string(Rest, [10|Acc]);
parse_escaped_char($f,  Rest, Acc) -> parse_string(Rest, [12|Acc]);
parse_escaped_char($r,  Rest, Acc) -> parse_string(Rest, [13|Acc]);
parse_escaped_char($/,  Rest, Acc) -> parse_string(Rest, [$/|Acc]);
parse_escaped_char($\\, Rest, Acc) -> parse_string(Rest, [$\\|Acc]);
parse_escaped_char($",  Rest, Acc) -> parse_string(Rest, [$"|Acc]);
parse_escaped_char($u,  [D0, D1, D2, D3|Rest], Acc) ->
    parse_string(Rest, [(digit_hex(D0) bsl 12) +
			(digit_hex(D1) bsl 8) +
			(digit_hex(D2) bsl 4) +
			(digit_hex(D3))|Acc]).

digit_hex($0) -> 0;
digit_hex($1) -> 1;
digit_hex($2) -> 2;
digit_hex($3) -> 3;
digit_hex($4) -> 4;
digit_hex($5) -> 5;
digit_hex($6) -> 6;
digit_hex($7) -> 7;
digit_hex($8) -> 8;
digit_hex($9) -> 9;
digit_hex($A) -> 10;
digit_hex($B) -> 11;
digit_hex($C) -> 12;
digit_hex($D) -> 13;
digit_hex($E) -> 14;
digit_hex($F) -> 15;
digit_hex($a) -> 10;
digit_hex($b) -> 11;
digit_hex($c) -> 12;
digit_hex($d) -> 13;
digit_hex($e) -> 14;
digit_hex($f) -> 15.

finish_number(Rest, Acc) ->
    Value = lists:reverse(Acc),
%    Value = 
%        case catch list_to_integer(Str) of
%	    {'EXIT', _} -> list_to_float(Str);
%	    Number -> Number
%        end,
    {Rest, Value}.

parse_number([], _Acc) ->
    exit(syntax_error);
parse_number([$-|T], Acc) ->
    parse_number1(T, [$-|Acc]);
parse_number(Rest, Acc) ->
    parse_number1(Rest, Acc).

parse_number1(Rest, Acc) ->
    {Acc1, Rest1} = parse_int_part(Rest, Acc),
    case Rest1 of
	[] -> finish_number([], Acc1);
	[$.|More] ->
            {Acc2, Rest2} = parse_int_part(More, [$.| Acc1]),
            parse_exp(Rest2, Acc2, false);
        _ ->
            parse_exp(Rest1, Acc1, true)
    end.


parse_int_part([], Acc) ->
    {Acc, []};
parse_int_part([Ch|Rest], Acc) ->
    case is_digit(Ch) of
	true  -> parse_int_part(Rest, [Ch | Acc]);
	false -> {Acc, [Ch | Rest]}
    end.

parse_exp([$e|T], Acc, NeedFrac) ->
    parse_exp1(T, Acc, NeedFrac);
parse_exp([$E|T], Acc, NeedFrac) ->
    parse_exp1(T, Acc, NeedFrac);
parse_exp(Rest, Acc, _NeedFrac) ->
    finish_number(Rest, Acc).

parse_exp1(Rest, Acc, NeedFrac) ->
    {Acc1, Rest1} = parse_signed_int_part(Rest, if  NeedFrac -> [$e, $0, $.|Acc];
						    true -> [$e|Acc]
						end),
    finish_number(Rest1, Acc1).

parse_signed_int_part([$+|T], Acc) ->
    parse_int_part(T, [$+|Acc]);
parse_signed_int_part([$-|T], Acc) ->
    parse_int_part(T, [$-|Acc]);
parse_signed_int_part(Rest, Acc) ->
    parse_int_part(Rest, Acc).

is_digit(C) when is_integer(C) andalso C >= $0 andalso C =< $9 -> true;
is_digit(_) -> false.
    

skip_ws([H|T]) when H =< 32 ->
    skip_ws(T);
skip_ws(Chars) ->
    Chars.



test() ->
    Text1 = "{\"firstname\":\"Caoyuan\", \"iq\":\"150\"}",
    {ok, Xml1} = parse_to_xml(Text1),
    XmlText1 = lists:flatten(xmerl:export_simple([Xml1], xmerl_xml)),
    io:fwrite(user, "Parsed XML: ~n~p~n", [XmlText1]),
    {ok, Poet1} = parse_to_poet(Text1),
    io:fwrite(user, "Parsed POET: ~n~p~n", [Poet1]),

    Text2 = "[{\"firstname\":\"Caoyuan\", \"iq\":\"150\"}, 
              {\"firstname\":\"Haobo\", \"iq\":150}]", 
    {ok, Xml2} = parse_to_xml(Text2),
    XmlText2 = lists:flatten(xmerl:export_simple([Xml2], xmerl_xml)),
    io:fwrite(user, "Parsed: ~n~p~n", [XmlText2]),

    Text = "
{\"businesses\": [{\"address1\": \"650 Mission Street\",
                   \"address2\": \"\",
                   \"avg_rating\": 4.5,
                   \"categories\": [{\"category_filter\": \"localflavor\",
                                     \"name\": \"Local Flavor\",
                                     \"search_url\": \"http://lightpole.net/search\"}],
                   \"city\": \"San Francisco\",
                   \"distance\": 0.085253790020942688,
                   \"id\": \"4kMBvIEWPxWkWKFN__8SxQ\",
                   \"latitude\": 37.787185668945298,
                   \"longitude\": -122.40093994140599},
                  {\"address1\": \"25 Maiden Lane\",
                   \"address2\": \"\",
                   \"avg_rating\": 5.0,
                   \"categories\": [{\"category_filter\": \"localflavor\",
                                     \"name\": \"Local Flavor\",
                                     \"search_url\": \"http://lightpole.net/search\"}],
                   \"city\": \"San Francisco\",
                   \"distance\": 0.23186808824539185,
                   \"id\": \"O1zPF_b7RyEY_NNsizX7Yw\",
                   \"latitude\": 37.788387,
                   \"longitude\": -122.40401}]} ",
    {ok, Xml} = parse_to_xml(Text),
    %io:fwrite(user, "Xml Tree: ~p~n", [Xml]),
    XmlText = lists:flatten(xmerl:export_simple([Xml], xmerl_xml)),
    io:fwrite(user, "Parsed: ~n~p~n", [XmlText]),
    Latitude1 = xmerl_xpath:string("/lp:root/businesses[1]/latitude/text()", Xml),
    io:format(user, "Latitude1: ~p~n", [Latitude1]).

The result will be something like:


<?xml version="1.0"?>
<json:root xmlns:json="http://www.lightpole.net/xmlns/1.0">
  <businesses>
    <address1>650 Mission Street</address1>
    <address2></address2>
    <avg_rating>4.5</avg_rating>
    <categories>
      <category_filter>localflavor</category_filter>
      <name>Local Flavor</name>
      <search_url>http://lightpole.net/search</search_url>
    </categories>
    <city>San Francisco</city>
    <distance>0.085253790020942688</distance>
    <id>4kMBvIEWPxWkWKFN__8SxQ</id>
    <latitude>37.787185668945298</latitude>
    <longitude>-122.40093994140599</longitude>
  </businesses>
  <businesses>
    <address1>25 Maiden Lane</address1>
    <address2></address2>
    <avg_rating>5.0</avg_rating>
    <categories>
      <category_filter>localflavor</category_filter>
      <name>Local Flavor</name>
      <search_url>http://lightpole.net/search</search_url>
    </categories>
    <city>San Francisco</city>
    <distance>0.23186808824539185</distance>
    <id>O1zPF_b7RyEY_NNsizX7Yw</id>
    <latitude>37.788387</latitude>
    <longitude>-122.40401</longitude>
  </businesses>
</root>

Now you fecth element by:

> [Latitude1] = xmerl_xpath:string("/json:root/businesses[1]/latitude/text()", Xml),
> Latitude1#xmlText.value.
"37.787185668945298"

Next time, I'll write a simple Erlang Data state machine, which will parse icalendar and json to simple Erlang Lists + Tuples.

The code of xml_sm.erl can be found in my previous blog.