:- module(episode_data, [retract_episode/1, last_episode/1, lookup_episode/3, lookup_episode_local/3, lookup_episode_remote/3]). :- use_module(library(clpfd)). :- use_module(library(dcg/basics)). :- use_module(library(http/http_open)). :- use_module(library(sgml)). :- use_module(library(xpath)). :- use_module(library(persistency)). :- use_module(atom_dcg). :- persistent episode_name_data(episode:integer, name:atom, data:list). attach :- absolute_file_name('episode_data.db', F, [access(write)]), db_attach(F, []). detach :- db_detach. % Interface. last_episode(Ep) :- ensure, setof(E, N^D^lookup_episode_local(E,N,D), Es), last(Es, Ep). lookup_episode(Ep, Name, Data) :- lookup_episode_local(Ep, Name, Data), !. lookup_episode(Ep, Name, Data) :- lookup_episode_remote(Ep, Name, Data). lookup_episode_local(Ep, Name, Data) :- episode_name_data(Ep, Name, Data). lookup_episode_remote(Ep, Name, Data) :- update, !, episode_name_data(Ep, Name, Data). retract_episode(Ep) :- ( episode_name_data(Ep, _, _) -> retractall_episode_name_data(Ep, _, _) ; true ). % Parsing. padding(Ep) --> { Ep #< 10 }, "00". padding(Ep) --> { Ep #>= 10, Ep #< 100 }, "0". padding(Ep) --> { Ep #>= 100 }. episode_number(Ep) --> padding(Ep), integer(Ep). episode_number(Ep) --> padding(Ep), integer(Ep), "WPS", integer(_). % Database updating. ensure :- episode_name_data(_, _, _), !. ensure :- update. update :- findall(Ep-Name-Data, (remote_row(R), row_episode(R, Ep), row_episode_name_data(R, Ep, Name, Data)), ENDs), !, maplist(update, ENDs). update(Ep-Name-Data) :- episode_name_data(Ep, Name, Data), !. update(Ep-Name-Data) :- assert_episode_name_data(Ep, Name, Data). % Remote retrieval. remote_row(R) :- catch(http_load_html( 'https://www.detectiveconanworld.com/wiki/Next_Conan%27s_Hint', R0), _, fail), !, xpath(R0, //tr, R). row_episode(R, Ep) :- xpath(R, td(index(1),text), T), atom_phrase(episode_number(Ep), T). row_episode_name_data(R, Ep, Name, Data) :- xpath(R, td(index(1),text), T), atom_phrase(episode_number(Ep), T), xpath(R, td(index(2),text), Name), xpath(R, td(index(3),text), Hint), Data = ['Hint'(Hint)]. http_load_html(URL, DOM) :- setup_call_cleanup(http_open(URL, In, [ timeout(60) ]), ( dtd(html, DTD), load_structure(stream(In), DOM, [ dtd(DTD), dialect(sgml), shorttag(false), max_errors(-1), syntax_errors(quiet) ]) ), close(In)).