Current section

Files

Jump to
feeder src feeder.erl
Raw

src/feeder.erl

%%
%% feeder - parse RSS and Atom formatted XML documents
%%
-module(feeder).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
-include("feeder_records.hrl").
-export([file/2]).
-export([stream/2]).
-record(state, {
author :: boolean(),
category :: boolean(),
chars :: undefined | [binary()],
entry :: entry(),
feed :: feed(),
image :: boolean(),
user :: {user_state(), user_fun()}
}).
-type state() :: #state{}.
-type user_fun() :: function().
-type user_state() :: term().
-export_type([user_fun/0, user_state/0]).
%% Updating the feed record
-spec feed(feed(), atom(), state()) -> feed().
feed(F, author, State) ->
feeder_tuples:update(F, #feed.author, State#state.chars);
feed(F, name, State) when State#state.author =:= true ->
feeder_tuples:update(F, #feed.author, State#state.chars);
feed(F, id, State) ->
feeder_tuples:update(F, #feed.id, State#state.chars);
feed(F, image, State) ->
feeder_tuples:update(F, #feed.image, State#state.chars);
feed(F, url, State) when State#state.image =:= true ->
feeder_tuples:update(F, #feed.image, State#state.chars);
feed(F, url, State) ->
feeder_tuples:update(F, #feed.url, State#state.chars);
feed(F, language, State) ->
feeder_tuples:update(F, #feed.language, State#state.chars);
feed(F, link, State) ->
feeder_tuples:update(F, #feed.link, State#state.chars);
feed(F, subtitle, State) ->
feeder_tuples:update(F, #feed.subtitle, State#state.chars);
feed(F, summary, State) ->
feeder_tuples:update(F, #feed.summary, State#state.chars);
feed(F, title, State) ->
feeder_tuples:update(F, #feed.title, State#state.chars);
feed(F, updated, State) ->
feeder_tuples:update(F, #feed.updated, State#state.chars);
feed(F, _, _) ->
F.
%% Updating the current entry record
-spec entry(entry(), atom(), state()) -> entry().
entry(E, author, State) ->
feeder_tuples:update(E, #entry.author, State#state.chars);
entry(E, name, State) when State#state.author =:= true ->
feeder_tuples:update(E, #entry.author, State#state.chars);
entry(E, category, State) ->
feeder_tuples:append(E, #entry.categories, State#state.chars);
entry(E, duration, State) ->
feeder_tuples:update(E, #entry.duration, State#state.chars);
entry(E, enclosure, _State) -> E;
entry(E, id, State) ->
feeder_tuples:update(E, #entry.id, State#state.chars);
entry(E, image, State) ->
feeder_tuples:update(E, #entry.image, State#state.chars);
entry(E, link, State) ->
feeder_tuples:update(E, #entry.link, State#state.chars);
entry(E, subtitle, State) ->
feeder_tuples:update(E, #entry.subtitle, State#state.chars);
entry(E, summary, State) ->
feeder_tuples:update(E, #entry.summary, State#state.chars);
entry(E, title, State) ->
feeder_tuples:update(E, #entry.title, State#state.chars);
entry(E, updated, State) ->
feeder_tuples:update(E, #entry.updated, State#state.chars);
entry(E, _, _) ->
E.
%% Extracting and formatting characters
-define(IS_FEED,
State#state.feed =/= undefined,
State#state.entry =:= undefined).
-define(IS_ENTRY,
State#state.entry =/= undefined).
-spec attribute(atom(), state(), atom(), list()) -> feed() | entry().
attribute(feed, State, link, Attrs) ->
feed(State#state.feed, link, State#state{chars=feeder_attributes:href(Attrs)});
attribute(feed, State, image, Attrs) ->
feed(State#state.feed, image, State#state{chars=feeder_attributes:href(Attrs)});
attribute(feed, State, url, Attrs) ->
feed(State#state.feed, url, State#state{chars=feeder_attributes:href(Attrs)});
attribute(feed, State, _, _) ->
State#state.feed;
attribute(entry, State, link, Attrs) ->
entry(State#state.entry, link, State#state{chars=feeder_attributes:href(Attrs)});
attribute(entry, State, image, Attrs) ->
entry(State#state.entry, image, State#state{chars=feeder_attributes:href(Attrs)});
attribute(entry, State, category, Attrs) ->
entry(State#state.entry, category, State#state{chars=feeder_attributes:category(Attrs)});
attribute(entry, State, enclosure, Attrs) ->
Entry = State#state.entry,
Entry#entry{enclosure=feeder_attributes:enclosure(Attrs)};
attribute(entry, State, _, _) ->
State#state.entry.
flag(author, State, Flag) ->
State#state{author=Flag};
flag(image, State, Flag) ->
State#state{image=Flag};
flag(category, State, Flag) ->
State#state{category=Flag};
flag(_, State, _) ->
State.
%% Handling parser events
-spec end_element(atom(), state()) -> state().
end_element(undefined, State) ->
State;
end_element(document, State) ->
{UserState, UserFun} = State#state.user,
UserFun(endFeed, UserState);
end_element(feed, State) ->
{UserState, UserFun} = State#state.user,
NewUserState = UserFun({feed, State#state.feed}, UserState),
State#state{feed=undefined, user={NewUserState, UserFun}};
end_element(entry, State) ->
{UserState, UserFun} = State#state.user,
NewUserState = UserFun({entry, State#state.entry}, UserState),
State#state{entry=undefined, user={NewUserState, UserFun}};
end_element(E, State) when ?IS_FEED ->
NewState = flag(E, State, false),
Feed = feed(NewState#state.feed, E, State),
NewState#state{feed=Feed};
end_element(E, State) when ?IS_ENTRY ->
NewState = flag(E, State, false),
Entry = entry(NewState#state.entry, E, State),
NewState#state{entry=Entry}.
-spec start_element(atom(), list(), state()) -> state().
start_element(undefined, _, State) ->
State#state{chars=[]};
start_element(feed, _, State) ->
State#state{feed=#feed{}};
start_element(entry, _, State) ->
State#state{entry=#entry{}};
start_element(E, Attrs, State) when ?IS_FEED ->
NewState = flag(E, State, true),
Feed = attribute(feed, NewState, E, Attrs),
NewState#state{chars=[], feed=Feed};
start_element(E, Attrs, State) when ?IS_ENTRY ->
NewState = flag(E, State, true),
Entry = attribute(entry, NewState, E, Attrs),
NewState#state{chars=[], entry=Entry}.
%% Routing parser events
event(startDocument, _, S) ->
S;
event({startElement, _, _LocalName, QName, Attrs}, _, S) ->
start_element(feeder_elements:qname(QName), Attrs, S);
event({endElement, _, _LocalName, QName}, _, S) ->
end_element(feeder_elements:qname(QName), S);
event({characters, C}, _, S) ->
S#state{chars=[S#state.chars|C]};
event(endDocument, _, S) ->
end_element(document, S);
event(_, _, S) ->
S.
%% API
event({entry, Entry}, {Feed, Entries}) ->
{Feed, [Entry|Entries]};
event({feed, Feed}, {[], Entries}) ->
{Feed, Entries};
event(endFeed, {Feed, Entries}) ->
{Feed, lists:reverse(Entries)}.
user_state(undefined) -> {[], []};
user_state(S) -> S.
user_fun(undefined) -> fun event/2;
user_fun(F) -> F.
opts(file, Opts) ->
US = user_state(proplists:get_value(event_state, Opts)),
UF = user_fun(proplists:get_value(event_fun, Opts)),
User = {US, UF},
[{event_state, #state{user=User}}, {event_fun, fun event/3}];
opts(stream, Opts) ->
CS = proplists:get_value(continuation_state, Opts),
CF = proplists:get_value(continuation_fun, Opts),
[{continuation_state, CS}, {continuation_fun, CF}] ++ opts(file, Opts).
-spec file(binary() | maybe_improper_list(), [any()]) -> any().
file(Filename, Opts) ->
xmerl_sax_parser:file(Filename, opts(file, Opts)).
-spec stream(binary() | maybe_improper_list(), [any()]) -> any().
stream(Xml, Opts) ->
xmerl_sax_parser:stream(Xml, opts(stream, Opts)).