1:- module( pub_graph,
    2               [ 
    3                    pub_graph_abstracts/2,
    4                    pub_graph_cited_by/2, pub_graph_cited_by/3,
    5                    pub_graph_cites/2, pub_graph_cites/3,
    6                    pub_graph_cache_open/5,
    7                    pub_graph_cache_save/4,
    8                    pub_graph_cited_by_graph/3,
    9                    pub_graph_cited_by_treadmill/3,
   10                    pub_graph_summary_info/3,
   11                    pub_graph_search/2, pub_graph_search/3,
   12                    pub_graph_summary_display/1, pub_graph_summary_display/2,
   13                    pub_graph_summary_display/3,
   14                    pub_graph_summary_display_info/2,
   15                    pub_graph_table/3,
   16                    pub_graph_id/2,
   17                    pub_graph_version/2
   18                   ]
   19         ).   20
   21:- use_module(library(http/http_open)).   % http_open/3
   22:- use_module(library(http/json)).        % json_read/2,3

Access, cache and visualise citation relations in publications servers.

A simple library for communicating with publication information servers: pub med and semantic scholar.
Currently allows (a) searching on conjunctions and disjunctions, (b) fetching the details of a paper
(c) the publications citing a paper, (d) publications cited by a paper, (e) simple reporting of fetched information and (f) storing fethed information to local databases.

Since version 0.1 the library supports caching of the paper information on Prolog term or csv data files
and odbc connected or sqlite databases. Also as of 0.1 pub_graph is debug/1 aware. To see information regarding
the progress of execution, use

    ?- debug(pub_graph).

The pack requires the curl executable to be in the path. Only tested on Linux.
It is being developed on SWI-Prolog 6.1.8 and it should also work on Yap Prolog.

To install under SWI simply do

    ?- pack_install(pub_graph).
    % and load with
    ?- use_module(library(pub_graph)).

The storing of paper and citation depends on db_facts and for sqlite connectivity on proSQlite (both available as SWI packs and from http://stoics.org.uk/~nicos/sware/)

author
- Nicos Angelopoulos
version
- 0.1.0 2014/7/22 (was pubmed)
- 1.0 2018/9/22
- 1.1 2018/9/23, wrap/hide caching libs errors
See also
- http://stoics.org.uk/~nicos/sware/pub_graph
- http://www.ncbi.nlm.nih.gov/books/NBK25500/
- http://api.semanticscholar.org
- files in examples/ directory
- sources at http://stoics.org.uk/~nicos/sware/pub_graph/
license
- MIT
To be done
-
currently the info tables are wastefull in the interest of simplicity. Eg they are of the form info(ID,Key,O,Value). But Key is really a type of information. so we could split this to a number of tables (info:)key(Id,O,Value). Alternatively you could make key an enumerate type, which will save loads of space

*/

   71:- ensure_loaded( library(sgml) ).   72:- ensure_loaded( library(ordsets) ).  % ord_add_element/3.
   73:- ensure_loaded( library(http/html_write) ).   74
   75:- use_module( library(options) ).   76
   77% This does (no longer ?) work: [try abs_fname/n]
   78:- ( catch(use_module(library(prosqlite)),_,fail) -> true
   79     ;  
   80     debug(pub_graph, 'proSQLite not available. Caching through prosqlite disabled', [] ) 
   81   ).   82
   83:- ( catch(use_module(library(odbc)),_,fail) -> true
   84     ;  
   85     debug(pub_graph, 'proSQLite not available. Caching through odbc disabled', [] ) 
   86   ).   87:- ( catch(use_module(library(db_facts)),_,fail) -> true
   88     ;  
   89     debug(pub_graph, 'pack(db_facts) not available. Caching is disabled', [] ) 
   90   ).   91
   92% Section: defaults, shortcuts.
   93
   94file_type_extension( csv, csv ).
   95file_type_extension( prolog, pl ).
   96file_type_extension( sqlite, sqlite ).
   97
   98url_eutils( 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/' ).
   99url_efetch_pubmed( 'efetch.fcgi?db=pubmed' ).
  100
  101url_semscholar('http://api.semanticscholar.org/v1/paper/').
  102
  103default_names( Names ) :- 
  104    default_names( ncbi, Names ).
  105default_names( semscholar, Names ) :-
  106    Names = [arxivId,authors,doi,title,topics,venue,year].
  107    % citationVelocity, citations, influentialCitationCount, paperId, references, url
  108default_names( ncbi, Names ) :-
  109     Names = ['Author','Title','Source','Pages','PubDate',
  110              'Volume','Issue','ISSN','PmcRefCount',
  111              'PubType','FullJournalName'].
  112
  113pub_graph_search_defaults( [verbose(false),retmax(100),tmp_keep(false)] ).
  114
  115% pub_graph_summary_display_defaults( [display(['Title','Author']),names(Names)] ) :-
  116%     default_names( Names ).
  117pub_graph_summary_display_defaults( [display([title,'Title',authors,'Author'])] ).
  118
  119pub_graph_graph(true).  % needed for options_append/3
  120pub_graph_graph_defaults( [depth(0),verbose(false),update(true),date(AgesAgo),flat(false)] ) :-
  121     a_month_ago( AgesAgo ).
  122
  123pub_graph_treadmill_defaults( [file(graph_treadmilling),single_file(false),depth(5),ext(pl)] ).
  124
  125:- dynamic pub_graph_cache:cited_by/3.  126:- dynamic pub_graph_cache:info/4.  127:- dynamic pub_graph_cache:info_date/2.
 pub_graph_id(+Id, -IdType)
True if Id corresponds to a paper identifier from server typed by IdType.
Currently ncbi (https://www.ncbi.nlm.nih.gov/pubmed/) and semscholar (http://semanticscholar.org/) are the known IdTypes.

The predicate does not connect to the server, it only type checks the shape of Id.
If Id is an integer or an atom that can be turned to an integer, then IdType is instantiated to ncbi.
There are three term forms for semscholar.

hex
such as cbd251a03b1a29a94f7348f4f5c2f830ab80a909
doi
presented as, doi:'10.1109/TITB.2002.1006298' (doi is stripped before request is posted)
arXiv
as, arXiv:1705.10311 (arXiv forms part of the semanticscholar.org request)

The following two ids correspond to the same paper.

?-
    pub_graph_id( 12075665, Type ).

Type = ncbi.

?-
    pub_graph_id( cbd251a03b1a29a94f7348f4f5c2f830ab80a909, Type ).

Type = semscholar.
author
- nicos angelopoulos
version
- 0.1 2018/9/11
See also
- https://www.ncbi.nlm.nih.gov/pubmed/
- http://semanticscholar.org

*/

  165pub_graph_id( Id, IdType ) :-
  166    ( integer(Id) ; (catch(atom_number(Id,Numb),_,fail),integer(Numb)) ),
  167    !,
  168    IdType = ncbi.
  169pub_graph_id( Term, IdType ) :-
  170    (
  171        catch( hex_bytes(Term,_Bytes), _, fail )
  172        ;
  173        Term = doi:_
  174        ;
  175        Term = arXiv
  176    ),
  177    !,
  178    IdType = semscholar.
  179
  180% Section: interface predicates
 pub_graph_version(+Version, +Date)
Get version information and date of publication.
?-
    pub_graph_version(V,D).

V = 1:1:0,
D = date(2018, 9, 23).

*/

  195pub_graph_version( 1:1:0, date(2018,9,23) ).
  196% pub_graph_version( 1:0:0, date(2018,9,22) ).
  197% pub_graph_version( 0:0:3, date(2012,08,15) ).
  198
  199
  200pub_graph_search( STerm, Ids ) :-
  201     pub_graph_search( STerm, Ids, [] ).
 pub_graph_search(+STerm, -Ids)
 pub_graph_search(+STerm, -Ids, +Options)
This is currently only implemented for ncbi ids as there is no means for searching in the semantic scholar API.

Search in pub_graph for terms in the search term STerm. In this, conjunction is marked by , (comma) and disjunction by ; (semi-column). '-' pair terms are considered as Key-Value and interpreted as Value[Key] in the query. List are thought to be flat conjoint search terms with no pair values in them which are interpreted by pub_graph also as OR operations. (See example below.) Known keys are : journal, pdat. au, All Fields The predicate constructs a query that is posted via the http API provided by NCBI (http://www.ncbi.nlm.nih.gov/books/NBK25500/).

Options should be a term or list of terms from:

retmax(RetMax)
the maximum number of records that will be returned def: 100
verbose(Verbose)
if Verbose == true then the predicate is verbose about its progress by, for instance, requesting query is printed on current output stream.
tmp_file(Tmp)
file to use, or when Tmp is variable the file that was used to receive the results from pub_graph.
tmp_keep(Keep)
keep the file with the xml result iff Keep==true
qtranslation(QTrans)
return in QTrans the actual query ran on the the pub_graph server.
reldate(Rdat)
When reldate is set to an integer n, ELink returns only those items that have a date specified by datetype within the last n days.
mindate(Ndat)
Date range used to limit a link operation by the date specified by datetype. These two parameters (mindate, maxdate) must be used together to specify an arbitrary date range. The general date format is YYYY/MM/DD, and these variants are also allowed: YYYY, YYYY/MM.
maxdate(Xdat)
see mindate Option For instance, taking an example from the url we show how to find all breast cancer articles that were published in Science in 2008.
?-
    St = (journal=science,[breast,cancer],pdat=2008),
    pub_graph_search( St, Ids, [verbose(true),qtranslation(QTrans)] ),
    length( Ids, Len ), write( number_of:Len ), nl,
    pub_graph_summary_display( Ids, _, display(all) ).

https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmax=100&term=science[journal]+AND+breast+cancer+AND+2008[pdat]
tmp_file(/tmp/swipl_3884_9)
number_of:6
----
1:19008416
        Author=[Varambally S,Cao Q,Mani RS,Shankar S,Wang X,Ateeq B,Laxman B,Cao X,Jing X,Ramnarayanan K,Brenner JC,Yu J,Kim JH,Han B,Tan P,Kumar-Sinha C,Lonigro RJ,Palanisamy N,Maher CA,Chinnaiyan AM]
        Title=Genomic loss of microRNA-101 leads to overexpression of histone methyltransferase EZH2 in cancer.
        Source=Science
        Pages=1695-9
        PubDate=2008 Dec 12
        Volume=322
        Issue=5908
        ISSN=0036-8075
        PmcRefCount=352
        PubType=Journal Article
        FullJournalName=Science (New York, N.Y.)
----
2:18927361
        Author=Couzin J
        Title=Genetics. DNA test for breast cancer risk draws criticism.
        Source=Science
...
...
...
6:18239125
        Author=[Silva JM,Marran K,Parker JS,Silva J,Golding M,Schlabach MR,Elledge SJ,Hannon GJ,Chang K]
        Title=Profiling essential genes in human mammary cells by multiplex RNAi screening.
        Source=Science
        Pages=617-20
        PubDate=2008 Feb 1
        Volume=319
        Issue=5863
        ISSN=0036-8075
        PmcRefCount=132
        PubType=Journal Article
        FullJournalName=Science (New York, N.Y.)
----
St =  (journal=science, [breast, cancer], pdat=2008),
Ids = ['19008416', '18927361', '18787170', '18487186', '18239126', '18239125'],
QTrans = ['("Science"[Journal] OR "Science (80- )"[Journal] OR "J Zhejiang Univ Sci"[Journal]) AND ("breast neoplasms"[MeSH Terms] OR ("breast"[All Fields] AND "neoplasms"[All Fields]) OR "breast neoplasms"[All Fields] OR ("breast"[All Fields] AND "cancer"[All Fields]) OR "breast cancer"[All Fields]) AND 2008[pdat]'],
Len = 6.

?-
     date(Date),
     St = (author='Borst Piet'),
     pub_graph_search( St, Ids, verbose(true) ),
     length( Ids, Len ), write( number_of:Len ), nl.

https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmax=100&term=Borst%20Piet\[author\]
tmp_file(/tmp/swipl_18703_0)
number_of:83
Date = date(2018, 9, 22),
St =  (author='Borst Piet'),
Ids = ['29894693', '29256493', '28821557', '27021571', '26774285', '26530471', '26515061', '25799992', '25662217'|...],
Len = 83.

?-
    date(Date), pub_graph_search( prolog, Ids ),
    length( Ids, Len ), write( number_of:Len ), nl.

number_of:100
Date = date(2018, 9, 22),
Ids = ['30089663', '28647861', '28486579', '27684214', '27142769', '25509153', '24995073', '22586414', '22462194'|...],
Len = 100.

?-
    date(Date), pub_graph_search( prolog, Ids, retmax(200) ),
    length( Ids, Len ), write( number_of:Len ), nl.

number_of:127
Date = date(2018, 9, 22),
Ids = ['30089663', '28647861', '28486579', '27684214', '27142769', '25509153', '24995073', '22586414', '22462194'|...],
Len = 127.

?-
   St = ('breast','cancer','Publication Type'='Review'),
   date(Date), pub_graph_search( St, Ids, reldate(30) ),
   length( Ids, Len ).

Date = date(2018, 9, 22),
Ids = ['30240898', '30240537', '30240152', '30238542', '30238005', '30237735', '30236642', '30236594', '30234119'|...],
Len = 100.

?-
    pub_graph_summary_display( 30243159, _, true ).
----
1:30243159
        Author=[Wang K,Yee C,Tam S,Drost L,Chan S,Zaki P,Rico V,Ariello K,Dasios M,Lam H,DeAngelis C,Chow E]
        Title=Prevalence of pain in patients with breast cancer post-treatment: A systematic review.
----
true.
author
- nicos angelopoulos
version
- 0:1 2012/07/15
- 0:2 2018/09/22, small update on \ escape on eutils, ncbi, queries

*/

  350pub_graph_search( Sterm, Ids, Args ) :-
  351    options_append( pub_graph_search, Args, Opts ),
  352    url_eutils( Eutils ),
  353    ( ground(Sterm) -> true; type_error(ground,Sterm) ),
  354    search_term_to_query( Sterm, Query ),
  355    memberchk( retmax(Ret), Opts ), 
  356    pub_graph_search_period_opts( '', Period, Opts ),
  357    atomic_list_concat( [Eutils,'esearch.fcgi?db=pubmed&retmax=',Ret,Period,'&term=',Query], Url ),
  358    memberchk_optional( tmp_file(Tmp), Opts ),
  359    memberchk( verbose(Verb), Opts ),
  360    true_writes( Verb, Url ),
  361    debug( pub_graph, 'Get url is: ~w', Url ),
  362    get_url_in_file( Url, Verb, Tmp ),
  363    true_writes( Verb, tmp_file(Tmp) ),
  364    load_xml_file( Tmp, Xml ),
  365    ( (memberchk(qtranslation(QTrans),Opts),
  366        QT = 'QueryTranslation',
  367        search_element_in_list(Xml,QT,[],element(_,_,QTrans))) -> true; true
  368    ),
  369    all_subs_in_xml_single( Xml, 'IdList', 'Id', NastyIds ),
  370    flatten( NastyIds, Ids ),
  371    memberchk_optional( tmp_keep(Keep), Opts ),
  372    true_atom_keeps_file( Keep, Tmp ).
 pub_graph_summary_display(+Ids)
Short for pub_graph_summary_display( Ids, _Summary, [] ).

*/

  379pub_graph_summary_display( Ids ) :-
  380     pub_graph_summary_display( Ids, _Summary, [] ).
 pub_graph_summary_display(+Ids, -Summary)
Short for pub_graph_summary_display( Ids, Summary, [] ). */
  386pub_graph_summary_display( Ids, Summary ) :-
  387     pub_graph_summary_display( Ids, Summary, [] ).
 pub_graph_summary_display(+IdS, -Summaries, +Opts)
A wrapper around pub_graph_summary_info/3. It call this predicate with same arguments before displaying the Summary information. Opts can be a single term option or a list of such terms. In addition to pub_graph_summary_info/3 options this wrapper also recognises the term:

Opts

display(Disp)
a list of article information keys that will displayed one on a line for each Id in Ids. Disp values of var(Disp), '*' and 'all', list all available values.
?-
    date(Date),
    pub_graph_search((programming,'Prolog'), Ids),
    length( Ids, Len),
    Ids = [A,B,C|_], pub_graph_summary_display( [A,B,C] ).

----
1:28486579
    Author=[Holmes IH,Mungall CJ]
    Title=BioMake: a GNU make-compatible utility for declarative workflow management.
----
2:24995073
    Author=[Melioli G,Spenser C,Reggiardo G,Passalacqua G,Compalati E,Rogkakou A,Riccio AM,Di Leo E,Nettis E,Canonica GW]
    Title=Allergenius, an expert system for the interpretation of allergen microarray results.
----
3:22215819
    Author=[Mørk S,Holmes I]
    Title=Evaluating bacterial gene-finding HMM structures as probabilistic logic programs.
----
Date = date(2018, 9, 22),
Ids = ['28486579', '24995073', '22215819', '21980276', '15360781', '11809317', '9783213', '9293715', '9390313'|...],
Len = 43.
A = '28486579',
B = '24995073',
C = '22215819'.
?-
    pub_graph_summary_display( 30235570, _, display(*) ).

----
1:30235570
    Author=[Morgan CC,Huyck S,Jenkins M,Chen L,Bedding A,Coffey CS,Gaydos B,Wathen JK]
    Title=Adaptive Design: Results of 2012 Survey on Perception and Use.
    Source=Ther Innov Regul Sci
    Pages=473-481
    PubDate=2014 Jul
    Volume=48
    Issue=4
    ISSN=2168-4790
    PmcRefCount=0
    PubType=Journal Article
    FullJournalName=Therapeutic innovation & regulatory science
----
?-
     pub_graph_cited_by( 20195494, These ),
     pub_graph_summary_display( These, _, [display(['Title','Author','PubDate'])] ).

----
1:29975690
    Author=[Tang K,Boudreau CG,Brown CM,Khadra A]
    Title=Paxillin phosphorylation at serine 273 and its effects on Rac, Rho and adhesion dynamics.
    PubDate=2018 Jul
----
2:29694862
    Author=[McKenzie M,Ha SM,Rammohan A,Radhakrishnan R,Ramakrishnan N]
    Title=Multivalent Binding of a Ligand-Coated Particle: Role of Shape, Size, and Ligand Heterogeneity.
    PubDate=2018 Apr 24
----
3:29669897
    Author=[Padmanabhan P,Goodhill GJ]
    Title=Axon growth regulation by a bistable molecular switch.
    PubDate=2018 Apr 25
...
...
26:20473365
    Author=[Welf ES,Haugh JM]
    Title=Stochastic Dynamics of Membrane Protrusion Mediated by the DOCK180/Rac Pathway in Migrating Cells.
    PubDate=2010 Mar 1
----
These = [29975690, 29694862, 29669897, 28752950, 27939309, 27588610, 27276271, 25969948, 25904526|...].


?-
    pub_graph_summary_display( 20195494, _Res, true ).

----
1:20195494
    Author=[Cirit M,Krajcovic M,Choi CK,Welf ES,Horwitz AF,Haugh JM]
    Title=Stochastic model of integrin-mediated signaling and adhesion dynamics at the leading edges of migrating cells.
----
true.

?-
    pub_graph_summary_display( cbd251a03b1a29a94f7348f4f5c2f830ab80a909, _, display(all) ).

----
1:cbd251a03b1a29a94f7348f4f5c2f830ab80a909
        arxivId=[]
        authors=[Graham J. L. Kemp,Nicos Angelopoulos,Peter M. D. Gray]
        doi=10.1109/TITB.2002.1006298
        title=Architecture of a mediator for a bioinformatics database federation
        topics=[]
        venue=IEEE Transactions on Information Technology in Biomedicine
        year=2002
----
true.

*/

  507pub_graph_summary_display( IdS, Summary, Args ) :-
  508    % pub_graph_summary_display_defaults( Defs ),
  509    % options_append( Opts, Defs, All ),
  510    non_var_list( IdS, Ids ),
  511    options_append( pub_graph_summary_display, Args, Opts ),
  512    options( display(Disp), Opts ),
  513    pub_graph_summary_info( Ids, Summary, Opts ),
  514    pub_graph_summary_display_info( Summary, Disp ).
 pub_graph_summary_display_info(+Summaries, +Entries)
Display the Entries information for Summaries, which should be a list of summaries. If Entries is a variable all info will be printed.
  521pub_graph_summary_display_info( SummaryIn, Disp ) :-
  522     pg_en_list( SummaryIn, Summary ),
  523     write( '----' ), nl,
  524     nth1( N, Summary, Id-Rec ),
  525     write( N:Id ), nl,
  526     findall( _, (member(D-Val,Rec),once((var(Disp);Disp=all;Disp='*';member(D,Disp))),put(0'\t), write(D=Val),nl), _ ), 
  527     write( '----' ), nl, fail.
  528pub_graph_summary_display_info( _Summary, _Disp ).
  529
  530
  531%
  532% Redirects to pub_graph_cited_by( Id, Ids, [] ).
  533%
  534pub_graph_cited_by( Id, Ids) :-
  535     pub_graph_cited_by( Id, Ids, [] ).
 pub_graph_cited_by(+Id, -Ids)
 pub_graph_cited_by(+Id, -Ids, +Options)
Ids is the list of pub_graph ids that cite Id.

Options is a term option or list of terms from the following;

verbose(Verb=false)
be verbose
cache(Type, Handle, Date, Update)
use cache with Handle and Type, cutting off cached items that are (strictly) older than Date. For Update = true update the cache if you do an explicit retrieval.
?-
     date(D), pub_graph_cited_by( 12075665, By ), length( By, Len ).

D = date(2018, 9, 22),
By = [25825659, 19497389, 19458771],
Len = 3.

?-
    date(D), pub_graph_cited_by( cbd251a03b1a29a94f7348f4f5c2f830ab80a909, By ), length( By, Len ).

D = date(2018, 9, 22),
By = ['2e1f686c2357cead711c8db034ff9aa2b7509621', '6f125881788967e1eec87e78b3d2db61d1a8d0ac'|...],
Len = 12.

*/

  569pub_graph_cited_by( Id, Ids, Args ) :-
  570     options_append( pub_graph_graph, Args, Opts ),
  571     pub_graph_cited_by_1( Id, Ids, Opts ).
  572
  573pub_graph_cited_by_1( Id, Ids, Opts ) :-
  574     ( ( memberchk(cache(Type,Handle,Date,_Upd), Opts),
  575         pub_graph_date_cached(Type,Handle,cited_by,Date,Id,Ids) ) ->
  576          ( memberchk(verbose(true),Opts) ->
  577               write( got_from_cache(Id,Ids) ), nl
  578               ;
  579               true
  580          )
  581          ;
  582          pub_graph_cited_by_uncached( Id, Ids, Opts ),
  583          ( (memberchk(cache(Type,Handle,_Date,Upd),Opts),Upd==true) ->
  584               pub_graph_update_cache( Type, Handle, cited_by, Id, Ids )
  585               ;
  586               true
  587          )
  588     ).
  589
  590pub_graph_cited_by_uncached( Id, Ids, Opts ) :-
  591    pub_graph_id( Id, IdType ),
  592    pub_graph_cited_by_uncached( IdType, Id, Ids, Opts ).
  593
  594pub_graph_cited_by_uncached( semscholar, Id, Ids, _Verb ) :-
  595    semscholar_id_json( Id, Json ),
  596    memberchk( citations=Citations, Json ),
  597    findall( ACit, (    member(json(Sub),Citations),
  598                        member(paperId=ACit,Sub),
  599                        ACit \== ''
  600                   ),
  601                        Ids ).
  602pub_graph_cited_by_uncached( ncbi, Id, Ids, Opts ) :-
  603     ( memberchk(verbose(Verb),Opts) -> true; Verb = false ),
  604     url_eutils( Eutils ),
  605     Query = 'elink.fcgi?report=xml&mode=text&tool=curl&db=pubmed&cmd=neighbor&linkname=pubmed_pubmed_citedin&id=',
  606     atomic_list_concat( [Eutils,Query,Id], Url ),
  607     get_url_in_file( Url, Verb, Tmp ),
  608     ( pub_graph_cited_by_parse_file(Tmp, Ids) -> 
  609          true
  610          ;
  611          Ids = []
  612     ),
  613     delete_file( Tmp ).
 pub_graph_id_journal(+Id, +Provisional, -Journal)
Allows for "correcting" Journal entries of Id. Journal is Provisional, except if id_has_journal/2 exists and id_has_journal( Id, Jounal ) succeeds (det.)
  621pub_graph_id_journal( IdPrv, _Provisional, Journal ) :-
  622    current_predicate( user:id_has_journal/2 ),
  623    to_number( IdPrv, Id ),
  624    user:id_has_journal( Id, Journal ),
  625    !.
  626pub_graph_id_journal( _Id, Journal, Journal ).
  627
  628%
  629% Redirects to pub_graph_cites( Id, Ids, [] ).
  630%
  631pub_graph_cites( Id, Ids ) :-
  632     pub_graph_cites( Id, Ids, [] ).
 pub_graph_cites(+Id, -Ids)
 pub_graph_cites(+Id, -Ids, +Options)
Ids is the list of pub_graph Ids that are cited by Id.

Options is a term option or list of terms from the following;

verbose(Verb)
be verbose
?-
    date(D),
    pub_graph_cites( 20195494, Ids ),
    length( Ids, Len ), write( D:Len ), nl.

date(2018,9,22):38
D = date(2018, 9, 22),
Ids = ['19160484', '19118212', '18955554', '18800171', '18586481'|...],
Len = 38.

% pubmed does not have references cited by the following paper:

?-
    date(D),
    pub_graph_cites( 12075665, Ids ),
    length( Ids, Len ), write( D:Len ), nl.

false.

% whereas, semanticscholar.org finds 17 (non '') of the 21:
?-
    date(D),
    pub_graph_cites( cbd251a03b1a29a94f7348f4f5c2f830ab80a909, Ids ),
    length( Ids, Len ), write( D:Len ), nl.

date(2018,9,22):17
D = date(2018, 9, 22),
Ids = ['6477792829dd059c7d318927858d307347c54c2e', '1448901572d1afd0019c86c42288108a94f1fb25', |...],
Len = 17.

?-
    pub_graph_summary_display( 12075665, Results, true ).

----
1:12075665
    Author=[Kemp GJ,Angelopoulos N,Gray PM]
    Title=Architecture of a mediator for a bioinformatics database federation.
----
Results = [12075665-['Author'-['Kemp GJ', 'Angelopoulos N', 'Gray PM'], ... - ...|...]].

*/

  686pub_graph_cites( Id, Ids, OptsIn ) :-
  687    non_var_list( OptsIn, Opts ),
  688    pub_graph_id( Id, IdType ),
  689    ( memberchk(verbose(Verb),Opts) -> true; Verb = false ),
  690    pub_graph_id_type_cites( IdType, Id, Ids, Verb ).
  691
  692pub_graph_id_type_cites( ncbi, Id, Ids, Verb ) :-
  693     url_eutils( Eutils ),
  694     % Query = 'elink.fcgi?report=xml&mode=text&tool=curl&db=pmc&DbFrom=pubmed&Cmd=link&linkname=pubmed_pmc_refs&id=',
  695     Query = 'elink.fcgi?report=xml&mode=text&tool=curl&db=pubmed&Cmd=neighbor&linkname=pubmed_pubmed_refs&id=',
  696     atomic_list_concat( [Eutils,Query,Id], Url ),
  697     get_url_in_file( Url, Verb, Tmp ),
  698     load_xml_file( Tmp, Xml ),
  699     once( search_element_in_list( Xml, 'LinkSetDb', [], element(_,_,LXml) ) ),
  700     findall( CId, search_element_in_list(LXml,'Id',[],element(_,_,[CId])),Ids ),
  701     delete_file( Tmp ).
  702pub_graph_id_type_cites( semscholar, Id, Ids, _Verb ) :-
  703    semscholar_id_json( Id, Json ),
  704    memberchk( references=Citations, Json ),
  705    findall( ACit, (    member(json(Sub),Citations),
  706                        member(paperId=ACit,Sub),
  707                        ACit \== ''
  708                   ),
  709                        Ids ).
  710
  711pub_graph_table_defaults( Defs ) :-
  712    NoSearch = 'No search term available',
  713    Defs = [ include_if(false), output(csv), stem([]), spy([]), search(NoSearch) ].
 pub_graph_table(+Ids, -Rows, +Opts)
fixme Assumes jif predicate.

Opts

Output rows should contain #citing, [IF ,] Date, Journal, Title, Author, (Title urled to pubmed/$id)

*/

  743pub_graph_table( Ids, Rows, Args ) :-
  744    options_append( pub_graph_table, Args, Opts ),
  745    OTerms = [include_if(Iif),missing_if(Mif),output(Form),stem(Stem),spy(SpY)],
  746    options( OTerms, Opts ),
  747    pg_en_list( SpY, Spy ),
  748    % ground( Iif ), % i think options checks.
  749    maplist( pub_graph_table_id_row(Iif,Mif,Spy), Ids, Rows ),
  750    options( search(Search), Opts ),
  751    pub_graph_table_output( Stem, Form, Iif, Search, Rows ).
  752
  753pub_graph_table_id_row( Iif, Mif, Spy, Id, Row ) :-
  754    ( memberchk(Id,Spy) -> trace; true ),
  755    pub_graph_table_id_main_args( Id, Journal, Args ), 
  756    pub_graph_table_iif( Iif, Mif, Args, Journal, Irgs ),
  757    Row =.. [row|Irgs].
  758
  759pub_graph_table_id_main_args( Id, Journal, Args ) :-
  760    pub_graph_table_id_main_args_all( Id, Journal, Args ),
  761    !.
  762pub_graph_table_id_main_args( Id, Journal, Args ) :-
  763    throw( could_not_find_all_info_for_table_row_of(Id,Journal,Args) ).
  764
  765pub_graph_table_id_main_args_all( Id, Journal, Args ) :-
  766    pub_graph_summary_info( Id, Summ, [] ),
  767    memberchk( 'PmcRefCount'-PRCount, Summ ),
  768    memberchk( 'FullJournalName'-JournalPrv, Summ ),
  769    memberchk( 'Title'-Title, Summ ),
  770    memberchk( 'Author'-AuthorS, Summ ),
  771    pg_en_list( AuthorS, Authors ),
  772    atomic_list_concat( Authors, ', ', Author ),
  773    memberchk( 'PubDate'-Pdate, Summ ),
  774    pub_graph_id_journal( Id, JournalPrv, JournalPrv2 ),
  775    ( pub_graph_journal_synonym(JournalPrv2,Journal) -> true; Journal = JournalPrv2 ),
  776    Args = [PRCount,Pdate,Journal,Title,Author,Id].
  777
  778pub_graph_table_iif( false, _Mif, Args, _J, Args ).
  779pub_graph_table_iif( true, Mif, [A|Rgs], J, [A,Jif|Rgs] ) :-
  780    downcase_atom( J, DownJ ),
  781    pub_graph_table_jif( DownJ, J, Mif, Jif ).
  782
  783pub_graph_table_jif( DownJ, _J, _Mif, Jif ) :-
  784    user:jif( _, DownJ, _, JifGot, _, _ ),
  785    ( number(JifGot) -> JifGot = Jif; Jif is 0 ),
  786    !.
  787pub_graph_table_jif( DownJ, _J, _Mif, Jif ) :-
  788    pub_graph_jif_alternative( DownJ, AlteJ ),
  789    user:jif( _, AlteJ, _, JifGot, _, _ ),
  790    ( number(JifGot) -> JifGot = Jif; Jif is 0 ),
  791    !.
  792pub_graph_table_jif( DownJ, _J, _Mif, Jif ) :-
  793    missing_jif( DownJ, Jif ),
  794    !.
  795pub_graph_table_jif( _DownJ, J, Mif, Jif ) :-
  796    pub_graph_table_jif_missing( Mif, J, Jif ),
  797    !. % i dont think the above leaves b.points
  798
  799pub_graph_table_jif_missing( has(Val), J, Val ) :-
  800    debug( pub_graph, 'No IF for: ~w', J ).
  801pub_graph_table_jif_missing( quiet(Val), _J, Val ).
  802pub_graph_table_jif_missing( throw, J, _Jif ) :-
  803    throw( no_jif_for_journal(J) ).
  804
  805pub_graph_jif_alternative( Jname, Alternative ) :-
  806    member( Mfx, [', ',' : ',' and ','. '] ),
  807    atom_length( Mfx, Len ),
  808    sub_atom( Jname, Bef, Len, Aft, Mfx ),
  809    sub_atom( Jname, 0, Bef, _, Pfx ), 
  810    ToAft is Bef + Len,
  811    sub_atom( Jname, ToAft, Aft, 0, Psf ),
  812    member( Nfx, ['-',' ',' & '] ),
  813    atomic_list_concat( [Pfx,Nfx,Psf], Alternative ).
  814pub_graph_jif_alternative( Jname, Alternative ) :-
  815    member( Bp, [' : ','; '] ),
  816    atom_length( Bp, Len ),
  817    sub_atom( Jname, Bef, Len, _Aft, ' : ' ),
  818    sub_atom( Jname, 0, Bef, _, Alternative ).
  819pub_graph_jif_alternative( Jname, Alternative ) :-
  820    atom_concat( 'the ', Alternative, Jname ).
  821
  822missing_jif( metabolites, 0 ).
  823missing_jif( 'genomics, proteomics & bioinformatics', 0 ).
  824missing_jif( 'frontiers in pharmacology', 0 ). % http://www.frontiersin.org/news/Frontiers_Impact_Factors_2013/875
  825missing_jif( 'advances in experimental medicine and biology', 2.012 ).  % http://www.springer.com/series/5584
  826missing_jif( 'expert review of clinical pharmacology', -2 ). % don't think this has IF in general
  827missing_jif( 'dental research journal', -2 ). % don't think this has IF in general
  828missing_jif( 'brain and nerve = shinkei kenkyu no shinpo', -2 ). % only in Japanese
  829missing_jif( 'critical reviews in biomedical engineering', -2 ). % discontinued i think
  830missing_jif( 'yao xue xue bao = acta pharmaceutica sinica', -2 ). % in Chinese
  831missing_jif( 'gan to kagaku ryoho. cancer & chemotherapy', -2 ). % in Japanese
  832missing_jif( 'chinese journal of cancer', 0 ). % http://www.researchgate.net/journal/1944-446X_Chinese_journal_of_cancer
  833missing_jif( 'seminars in oncology nursing', -2 ). % http://www.researchgate.net/journal/1878-3449_Seminars_in_Oncology_Nursing
  834missing_jif( 'critical reviews in oncogenesis', -2 ). % http://www.researchgate.net/journal/0893-9675_Critical_reviews_in_oncogenesis
  835missing_jif( 'praxis', -2 ). % in German
  836
  837
  838pub_graph_journal_synonym( 'Cellular and molecular life sciences : CMLS', 'CELLULAR AND MOLECULAR LIFE SCIENCES' ).
  839pub_graph_journal_synonym( 'Tumour biology : the journal of the International Society for Oncodevelopmental Biology and Medicine', 'TUMOR BIOLOGY' ). % http://www.ncbi.nlm.nih.gov/nlmcatalog/8409922
  840pub_graph_journal_synonym( 'American journal of physiology. Heart and circulatory physiology', 'AMERICAN JOURNAL OF PHYSIOLOGY-HEART AND CIRCULATORY PHYSIOLOGY' ). % http://www.ncbi.nlm.nih.gov/nlmcatalog/8409922
  841pub_graph_journal_synonym( 'The journals of gerontology. Series A, Biological sciences and medical sciences', 'JOURNALS OF GERONTOLOGY SERIES A-BIOLOGICAL SCIENCES AND MEDICAL SCIENCES' ).
  842pub_graph_journal_synonym( 'Proteomics. Clinical applications', 'Proteomics Clinical applications' ).
  843pub_graph_journal_synonym( 'Future oncology (London, England)', 'Future Oncology' ).
  844pub_graph_journal_synonym( 'The oncologist', 'Oncologist' ).
  845pub_graph_journal_synonym( 'Cancer control : journal of the Moffitt Cancer Center', 'Cancer Control' ).
  846pub_graph_journal_synonym( 'Breast (Edinburgh, Scotland)', 'Breast' ).
  847pub_graph_journal_synonym( 'Cancer journal (Sudbury, Mass.)', 'Cancer Journal' ).
  848pub_graph_journal_synonym( 'Hematology / the Education Program of the American Society of Hematology. American Society of Hematology. Education Program', 'Hematology-American Society of Hematology Education Program' ).
  849
  850pub_graph_table_output( [], _Form,  _Iif, _Search, _Rows ) :- !.
  851pub_graph_table_output( Stem, Form, Iif, Search, Rows ) :-
  852    file_name_extension( Stem, Form, OutF ),
  853    pub_graph_table_file_output( Form, OutF, Iif, Search, Rows ),
  854    !.
  855pub_graph_table_output( _Stem, Form, _Iif, _Search, _Rows ) :-
  856    write( user_error, 'Dont know how to write onto: ~w', [Form] ), nl( user_error ). % fixme
  857
  858pub_graph_table_file_output( html, OutF, _Iif, Search, Rows ) :-
  859    tell( OutF ),
  860    maplist( pub_graph_table_html_row, Rows, HtmlRows ),
  861    term_string( Search, SearchString ),
  862    atom_string( SearchAtom, SearchString ),
  863    atom_concat( 'search term: ', SearchAtom, HtmlH1 ),
  864    reply_html_page( title(HtmlH1), [h1(HtmlH1),table(HtmlRows)] ).
  865pub_graph_table_file_output( csv, OutF, Iif, _Search, Rows ) :-
  866    pub_graph_iif_header( Iif, Hdr ),
  867    csv_write_file( OutF, [Hdr|Rows] ).
  868
  869pub_graph_table_html_row( row(C,I,P,J,T,A,B), Html ) :-
  870    atomic_list_concat( ['https://www.ncbi.nlm.nih.gov/pubmed/?term=',B], Href ),
  871    Anc = a(href(Href),T),
  872    atomic_list_concat( PParts, ' ', P ),
  873    atomic_list_concat( PParts, '_', U ),
  874    Html = tr( [
  875                   td(C),
  876                td(I),
  877                td(U),
  878                td(J),
  879                td(Anc),
  880                td(A),
  881                td(B)
  882            ] ).
  883
  884pub_graph_iif_header( true, Hdr ) :- 
  885    Hdr = row('Cited','IF','Published','Journal','Title','Author','Pubmed').
  886pub_graph_iif_header( false, Hdr ) :- 
  887    Hdr = row('Cited','Published','Journal','Title','Author','Pubmed').
 pub_graph_summary_info(+IdS, -Summaries, +Opts)
Summaries is the summary information for pub_graph id(s) IdS.
The form of results depends on whether IdS is a single PubMed Id,
in which case Summaries is a list of Name-Value pairs.
Whereas, when IdS is a list, Summaries is a list Id-Info pairs, where Info
is a Name-Value list. The predicate fetches the information with curn
via the http interface Summaries are deposited in local temporary files which are subsequently parsed.

Options is a single term, or list of the following terms:

names(Names)
list of info slot names to be found in the xml file
retmax(100)
the maximum number of records that will be returned
tmp_file(Tmp)
temporary file to be used for saving xml files. If Tmp is a variable, or option is missing, a temporary file is created with tmp_file_stream/3.
tmp_keep(false)
if true, keep the temporary xml file, otherwise, and by default, delete it.
verbose(Verb)
When true be verbose.
cache(Type, Handle, Update)
Use a cache with Type and Handle. Update should be boolean, set to false if you dont want the cache to be updated with newly downloaded information.
?-
  date(Date),
  Opts = names(['Author','PmcRefCount','Title']),
  pub_graph_summary_info( 12075665, Results, Opts ),
  write( date:Date ), nl,
  member( R, Results ), write( R ), nl,
  fail.

date:date(2018,9,22)
Author-[Kemp GJ,Angelopoulos N,Gray PM]
PmcRefCount-3
Title-Architecture of a mediator for a bioinformatics database federation.
false.


?-
    pub_graph_summary_info(12075665,Res,[]),
    member(R,Res), write( R ), nl,
    fail.

Author-[Kemp GJ,Angelopoulos N,Gray PM]
Title-Architecture of a mediator for a bioinformatics database federation.
Source-IEEE Trans Inf Technol Biomed
Pages-116-22
PubDate-2002 Jun
Volume-6
Issue-2
ISSN-1089-7771
PmcRefCount-3
PubType-Journal Article
FullJournalName-IEEE transactions on information technology in biomedicine : a publication of the IEEE Engineering in Medicine and Biology Society

?-
    pub_graph_summary_info( cbd251a03b1a29a94f7348f4f5c2f830ab80a909, Res, true ),
    member( R, Res ), write( R ), nl,
    fail.

arxivId-[]
authors-[Graham J. L. Kemp,Nicos Angelopoulos,Peter M. D. Gray]
doi-10.1109/TITB.2002.1006298
title-Architecture of a mediator for a bioinformatics database federation
topics-[]
venue-IEEE Transactions on Information Technology in Biomedicine
year-2002
false.

*/

  967pub_graph_summary_info( IdS, Results, OptS ) :-
  968    non_var_list( OptS, Opts ),
  969    non_var_list( IdS, Ids ),
  970     !,
  971     maplist( pub_graph_summary_info_single(Opts), Ids, UnsResults ),
  972     ( memberchk(sort_by(SortField,Order,Nums),Opts) ->
  973               pub_graph_summary_sort( UnsResults, SortField, Order, Nums, OrdResults )
  974               ;
  975               UnsResults=OrdResults
  976     ),
  977    de_kv_list_on( OrdResults, IdS, Results ).
  978pub_graph_summary_info( Id, Results, OptS ) :-
  979    non_var_list( OptS, Opts ),
  980     pub_graph_summary_info_single( Opts, Id, Id-Results ).
  981
  982pub_graph_summary_info_single( Opts, Id, Id-Results ) :-
  983     memberchk( cache(Type,Handle,_Update), Opts ),
  984     pub_graph_summary_info_cached( Type, Handle, Id, Results, Opts ),
  985     debug( pub_graph, 'Summary from cache for: ~w', Id ),
  986     !.
  987pub_graph_summary_info_single( Opts, Id, Id-Info ) :-
  988     % fixme use _defaults
  989     debug( pub_graph, 'Summary not in cache for: ~w', Id ),
  990     pub_graph_id( Id, IdType ),
  991     ( memberchk(names(Names),Opts) -> true; default_names(IdType,Names) ),
  992     ( memberchk(tmp_file(Tmp),Opts) -> true; true ),
  993     ( memberchk(cache(Type,Handle,true),Opts) -> 
  994               summary_info( Tmp, Id, all, ResAll, Opts ) ,
  995               findall( Name-Val, (member(Name,Names),memberchk(Name-Val,ResAll)), Info ),
  996               pub_graph_summary_info_cached_update( Type, Handle, Id, ResAll )
  997               ;
  998               summary_info( Tmp, Id, Names, Info, Opts)
  999     ).
 1000
 1001pub_graph_summary_info_cached( csv, Handle, Id, Results, Opts ) :-
 1002     pub_graph_summary_info_cached( in_mem, Handle, Id, Results, Opts ).
 1003pub_graph_summary_info_cached( prolog, Handle, Id, Results, Opts ) :-
 1004     pub_graph_summary_info_cached( in_mem, Handle, Id, Results, Opts ).
 1005pub_graph_summary_info_cached( sqlite, Handle, Id, Results, Opts ) :-
 1006     pub_graph_summary_info_cached( db_facts, Handle, Id, Results, Opts ).
 1007pub_graph_summary_info_cached( odbc, Handle, Id, Results, Opts ) :-
 1008     pub_graph_summary_info_cached( db_facts, Handle, Id, Results, Opts ).
 1009pub_graph_summary_info_cached( db_facts, Handle, Id, Results, Opts ) :-
 1010     ( memberchk(names(Names),Opts) -> true; default_names(Names) ),
 1011     findall(K-(O-V),(member(K,Names),db_holds(Handle,info(Id,K,O,V))), OPRes),
 1012     OPRes \== [],
 1013     keysort( OPRes, OPRord ),
 1014     % kv_decompose_vs( OPRord, PRord ),
 1015     nest_pair_flatten_removes( OPRord, PRord ),
 1016     PRord = [K1-V1|PRTail],
 1017     kvs_to_unique_k_v_as_list( PRTail, K1, [V1], Results ).
 1018pub_graph_summary_info_cached( in_mem, _Handle, Id, Results, Opts ) :-
 1019     ( memberchk(names(Names),Opts) -> true; default_names(Names) ),
 1020     findall( K-V, (member(K,Names),pub_graph_cache_info:info(Id,K,V)), PRes ),
 1021     PRes \== [],
 1022     keysort( PRes, PRord ),
 1023     PRord = [K1-V1|PRTail],
 1024     kvs_to_unique_k_v_as_list( PRTail, K1, [V1], Results ).
 1025
 1026pub_graph_summary_info_cached_update( csv, Alias, Id, KVs ) :-
 1027     pub_graph_summary_info_cached_update( in_mem, Alias, Id, KVs ).
 1028pub_graph_summary_info_cached_update( prolog, Alias, Id, KVs ) :-
 1029     pub_graph_summary_info_cached_update( in_mem, Alias, Id, KVs ).
 1030pub_graph_summary_info_cached_update( in_mem, Alias, Id, KVs ) :-
 1031     retractall( info(Id,_K,_O,_V) ),
 1032     retractall( info_date(Id,_) ),
 1033     date( Date ), 
 1034     assert( info_date(Id,Date) ),
 1035     findall( _, ( member(K-V,KVs), (is_list(V)->nth1(N,V,Ve);Ve=V,N=1),assert(Alias, info(Id,K,N,Ve),_) ), _ ).
 1036pub_graph_summary_info_cached_update( sqlite, Alias, Id, KVs ) :-
 1037     pub_graph_summary_info_cached_update( db_facts, Alias, Id, KVs ).
 1038pub_graph_summary_info_cached_update( odbc, Alias, Id, KVs ) :-
 1039     pub_graph_summary_info_cached_update( db_facts, Alias, Id, KVs ).
 1040pub_graph_summary_info_cached_update( db_facts, Alias, Id, KVs ) :-
 1041     db_retractall( Alias, info(Id,_K,_O,_V), _Aff1 ),
 1042     db_retractall( Alias, info_date(Id,_), _Aff2 ),
 1043     date( Date ), 
 1044     db_date_sql_atom( Date, SqlDate ),
 1045     db_assert( Alias, info_date(Id,SqlDate), _ ),
 1046     findall( _, ( member(K-V,KVs), (is_list(V)->nth1(N,V,Ve);Ve=V,N=1),db_assert(Alias, info(Id,K,N,Ve),_) ), _ ).
 pub_graph_abstracts(+IdS, -IdsAbs)
For a list of IdS get all their respective IdAbs (ID-Abstracts) pairs. If IdS is a single PubMed Id then IDsAbs is simply the abstract (not a pair). Abstracts are returned as lists of atom, representing lines in the original reply.
  ?- pub_graph_abstracts( 24939894, Abs ).
  Abs = ['Lemur tyrosine kinase 3 (LMTK3) is associated with cell proliferation and',...].
To be done
- add option for returning the full response of the querny (includes sections for, Citation, Title, Aurhors, Affiliation and PMCID if one exists (last is in PMID section).
 1062pub_graph_abstracts( IdS, Abstracts ) :-
 1063    non_var_list( IdS, Ids ),
 1064    pub_graph_get_abstracts( Ids, Ibs ),
 1065    de_kv_list_on( Ibs, IdS, Abstracts ).
 1066
 1067pub_graph_get_abstracts( Ids, Ibs ) :-
 1068    atomic_list_concat( Ids, ',', IdsAtom ),
 1069    atomic_list_concat( [id,IdsAtom], '=', IdsReq ),
 1070    url_eutils( Eutils ),
 1071    url_efetch_pubmed( Efetch ),
 1072    atom_concat( Eutils, Efetch, EEReq ),
 1073    RmdReq = 'retmode=text',
 1074    RtpReq = 'rettype=abstract',
 1075    atomic_list_concat( [EEReq,IdsReq,RmdReq,RtpReq], '&', Url ),
 1076    get_url_in_file( Url, true, Tmp ),
 1077    % write( file_is(Tmp) ), nl,
 1078    file_abstracts( Tmp, Ibs ).
 1079
 1080file_abstracts( File, Ibs ) :-
 1081    read_file_to_codes( File, Codes, [] ),
 1082    Codes = [0'\n|Tcodes],
 1083    codes_abstracts( Tcodes, Ibs ).
 1084
 1085% Each abstract-entry has sections seperated by \n.
 1086codes_abstracts( Codes, Ibs ) :-
 1087    % write( codes(Codes) ), nl,
 1088    codes_abstracts_sections( Sections, [], Codes, [] ),
 1089    % length( Sections, Len ),
 1090    % maplist( write_codes_ln, Sections ),
 1091    % write( sections_length(Len) ), nl,
 1092    code_sections_abstracts( Sections, Ibs ).
 1093
 1094write_codes_ln( Codes ) :-
 1095    atom_codes( Atom, Codes ),
 1096    write( Atom ), nl.
 1097
 1098codes_abstracts_sections( Sects, Acc ) -->
 1099    "\n\n", consume_new_line,
 1100    !,
 1101    { ( Acc==[] -> Sects=TSects; 
 1102        reverse(Acc,Sect),Sects=[Sect|TSects]
 1103         %, atom_codes( Att, Sect), write( att_acc(Att,Acc) ), nl 
 1104      ) },
 1105    codes_abstracts_sections( TSects, [] ).
 1106codes_abstracts_sections( Sects, Acc ) -->
 1107    [C],
 1108    !,
 1109    codes_abstracts_sections( Sects, [C|Acc] ).
 1110codes_abstracts_sections( Sects, Acc ) -->
 1111    { ( Acc == [] -> Sects = []
 1112                 ; reverse( Acc, Sect ), 
 1113                % atom_codes( SectAtom, Sect ),
 1114                % write( section(SectAtom,Acc) ), nl,
 1115                 Sects = [Sect] ) }.
 1116
 1117consume_new_line --> "\n", {!}.
 1118consume_new_line --> [].
 1119
 1120code_sections_abstracts( [], [] ).
 1121code_sections_abstracts( [_A,_B,_C,_D,_Comm,E,_Copy,F|T], [Id-Elns|Ibs] ) :-
 1122    atom_codes( Fat, F ),
 1123    debug( pub_graph, 'Parsing abstract from line:~w', Fat ),
 1124    code_section_pub_med_id( Id, F, _ ),
 1125    debug( pub_graph, 'Parsing abstract for id:~w', Id ),
 1126    code_section_lines( E, Elns ),
 1127    !,
 1128    code_sections_abstracts( T, Ibs ).
 1129code_sections_abstracts( [_Ref,_Ttl,_Ath,_Ainf,Abs,Copy,Pmi|T], [Id-AbsLns|Ibs] ) :-
 1130    copyright_line( Copy, _ ),
 1131    atom_codes( Pat, Pmi ),
 1132    debug( pub_graph, 'Parsing abstract from line:~w', Pat ),
 1133    code_section_pub_med_id( Id, Pmi, _ ),
 1134    debug( pub_graph, 'Parsing abstract for id:~w', Id ),
 1135    code_section_lines( Abs, AbsLns ),
 1136    !,
 1137    code_sections_abstracts( T, Ibs ).
 1138code_sections_abstracts( [_Ref,_Ttl,_Ath,_Ainf,_Cmnt,Abs,Pmi|T], [Id-AbsLns|Ibs] ) :-
 1139    atom_codes( Pat, Pmi ),
 1140    debug( pub_graph, 'Parsing abstract from line:~w', Pat ),
 1141    code_section_pub_med_id( Id, Pmi, _ ),
 1142    !,
 1143    debug( pub_graph, 'Parsing abstract for id:~w', Id ),
 1144    code_section_lines( Abs, AbsLns ),
 1145    code_sections_abstracts( T, Ibs ).
 1146code_sections_abstracts( [_Ref,_Ttl,_Ath,_Anf,Abs,Pmi|T], [Id-AbsLns|Ibs] ) :-
 1147    atom_codes( Pat, Pmi ),
 1148    debug( pub_graph, 'Parsing abstract from line:~w', Pat ),
 1149    code_section_pub_med_id( Id, Pmi, _ ),
 1150    !,
 1151    debug( pub_graph, 'Parsing abstract for id:~w', Id ),
 1152    code_section_lines( Abs, AbsLns ),
 1153    code_sections_abstracts( T, Ibs ).
 1154code_sections_abstracts( [_Ref,_Ttl,_Ath,Pmi|T], [Id-[]|Ibs] ) :-
 1155    atom_codes( Pat, Pmi ),
 1156    debug( pub_graph, 'Parsing abstract from line:~w', Pat ),
 1157    code_section_pub_med_id( Id, Pmi, _ ),
 1158    debug( pub_graph, 'Parsing abstract for id:~w', Id ),
 1159    !,
 1160    code_sections_abstracts( T, Ibs ).
 1161code_sections_abstracts( [_A,_B,_C,_D,F|T], [Id-Elns|Ibs] ) :-
 1162    atom_codes( Fat, F ),
 1163    debug( pub_graph, 'Parsing non-abstract from line:~w', Fat ),
 1164    code_section_pub_med_id( Id, F, _ ),
 1165    Elns = [],
 1166    code_sections_abstracts( T, Ibs ).
 1167
 1168/*
 1169code_sections_abstracts( [_Ref,_Ttl,_Ath,Abs,Copy,Pmi|T], [Id-AbsLns|Ibs] ) :-
 1170    copyright_line( Copy, _ ),
 1171    atom_codes( Pat, Pmi ),
 1172    debug( pub_graph, 'Parsing abstract from line:~w', Pat ),
 1173    code_section_pub_med_id( Id, Pmi, _ ),
 1174    !,
 1175    debug( pub_graph, 'Parsing abstract for id:~w', Id ),
 1176    code_section_lines( Abs, AbsLns ),
 1177    code_sections_abstracts( T, Ibs ).
 1178code_sections_abstracts( [_Ref,_Ttl,_Ath,_Anf,Abs,Pmi|T], [Id-AbsLns|Ibs] ) :-
 1179    atom_codes( Pat, Pmi ),
 1180    debug( pub_graph, 'Parsing abstract from line:~w', Pat ),
 1181    code_section_pub_med_id( Id, Pmi, _ ),
 1182    !,
 1183    debug( pub_graph, 'Parsing abstract for id:~w', Id ),
 1184    code_section_lines( Abs, AbsLns ),
 1185    code_sections_abstracts( T, Ibs ).
 1186code_sections_abstracts( [_Ref,_Ttl,_Ath,Pmi|T], [Id-[]|Ibs] ) :-
 1187    atom_codes( Pat, Pmi ),
 1188    debug( pub_graph, 'Parsing abstract from line:~w', Pat ),
 1189    code_section_pub_med_id( Id, Pmi, _ ),
 1190    !,
 1191    debug( pub_graph, 'Parsing abstract for id:~w', Id ),
 1192    code_sections_abstracts( T, Ibs ).
 1193code_sections_abstracts( [_A,_B,_C,_D,F|T], [Id-Elns|Ibs] ) :-
 1194    atom_codes( Fat, F ),
 1195    debug( pub_graph, 'Parsing non-abstract from line:~w', Fat ),
 1196    code_section_pub_med_id( Id, F, _ ),
 1197    Elns = [],
 1198    code_sections_abstracts( T, Ibs ).
 1199    */
 1200
 1201code_section_lines( E, Elns ) :-
 1202    code_section_code_lines( EcLns, [], E, [] ),
 1203    maplist( atom_codes, Elns, EcLns ).
 1204
 1205code_section_code_lines( Lns, Acc ) -->
 1206    "\n",
 1207    !,
 1208    { Acc == [] -> Lns = TLns; reverse( Acc, Ln ), Lns = [Ln|TLns] },
 1209    code_section_code_lines( TLns, [] ).
 1210code_section_code_lines( Lns, Acc ) -->
 1211    [C],
 1212    !,
 1213    code_section_code_lines( Lns, [C|Acc] ).
 1214code_section_code_lines( Lns, Acc ) -->
 1215    { Acc == [] -> Lns = []; reverse(Acc,Ln), Lns = [Ln] }.
 1216
 1217copyright_line -->
 1218    "Copyright", !.
 1219copyright_line -->
 1220    "©", !.
 1221
 1222code_section_pub_med_id( Id ) -->
 1223    "PMID: ",
 1224    !,
 1225    code_section_in_pub_med_id( IdCs ),
 1226    {number_codes(Id,IdCs)}.
 1227code_section_pub_med_id( Id ) -->
 1228    [_C],
 1229    code_section_pub_med_id( Id ).
 1230
 1231code_section_in_pub_med_id( [C|Cs] ) -->
 1232    [C],
 1233    { 0'0 =< C, C =< 0'9},
 1234    !,
 1235    code_section_in_pub_med_id( Cs ).
 1236code_section_in_pub_med_id( [] ) --> [].
 1237
 1238pub_graph_summary_sort( Summ, By, Ord, Nums, Sorted ) :-
 1239     summary_items_have( Summ, By, Nums, Have, Havenots ),
 1240     sort( Have, Sorto ),
 1241     ( Ord == asc -> Sorto = Ordo; reverse(Sorto,Ordo) ),
 1242     kv_decompose_vs( Ordo, Disc ),
 1243     % disconnect_double_minus( Ordo, Disc ),
 1244     append( Disc, Havenots, Sorted ).
 1245
 1246disconnect_double_minus( [], [] ).
 1247disconnect_double_minus( [_K1-K-V|T], [K-V|M] ) :-
 1248     disconnect_double_minus( T, M ).
 1249
 1250summary_items_have( [], _By, _Nums, [], [] ).
 1251summary_items_have( [K-List|T], By, Nums, Haves, Havenots ) :-
 1252     ( memberchk(By-ByItem,List) ->
 1253          ( Nums == true ->
 1254               to_number( ByItem, ByItemNum ),
 1255               Haves = [ByItemNum-(K-List)|THaves],
 1256               Havenots = THavenots
 1257               ;
 1258               Haves = [ByItem-(K-List)|THaves],
 1259               Havenots = THavenots
 1260          )
 1261          ;
 1262          Haves = THaves,
 1263          Havenots = [K-List|THavenots]
 1264     ),
 1265     summary_items_have( T, By, Nums, THaves, THavenots ).
 pub_graph_cited_by_graph(+Ids, -Graph, +Opts)
Graph of all ancestors reaching Ids within Depth moves. The graph grows upwards from the roots (Ids) to find the papers that cite the growing bag of papers recursively.

Options is a single term, or list of the following terms:

cache(Type)
use cache of Type. Type == false or absent to turn caching off
location(Object)
if using cache, which location should be used
date(CoffDate)
if caching is used, at what date do cache expires. Default: 1 month ago.
depth(Depth)
maximum depth to chase
ext(Ext)
superseed the extension on Object.
flat(Flat)
Boolean value. If csv cited_by should be one per line or of the form Id1;Id2;
flat_input(InpFlat)
should the input cache be imported as flat (def. = Flat).
verbose(Verbose)
prints progress messages if true

Type is one of csv,prolog,sqlite and odbc. In the first 3 cases, Object should be a filename and for odbc it should be a DSN token. In the case of filenames, the default value for Object is formed as, <type>_<id1>{_<id2>}.<type_ext>. <type_ext> is either set to Ext or if this is missing it is deduced from Type. It can be set to '' if you want no extension added.

Graph is compatible with the graph representation of Prolog unweighted graphs. That is, all vertices should appear in a keysorted list as V-Ns pairs, where V is the vertex and Ns is the sorted list of all its neighbours. Ns is the empty list if V has no neighbours, although this should only be the case here, if one of the input Ids has no citing papers or for the nodes at the edge of Depth.

?-
     pub_graph_cited_by_graph( 12075665, G, cache(sqlite) ).

*/

 1310pub_graph_cited_by_graph( IdS, Graph, Args ) :-
 1311    % non_var_list( OptS, Opts ),
 1312     % pub_graph_graph_defaults( Defs ),
 1313     options_append( pub_graph_graph, Args, Opts ),
 1314     options( depth(Depth), Opts ),
 1315     ( Depth =:= 0 -> Ds = inf, De = 0; integer(Depth), Ds = bound, De = Depth ),
 1316     non_var_list( IdS, Ids ),
 1317     maplist( kv_elem_set(0), Ids, IDs ),
 1318    options( verbose(Pgs), Opts ),
 1319     opts_list_opens_cby_cache( Opts, Ids, COpts ), % maybe listed(Opts) instead of All ?
 1320     pub_graph_cited_by_graph_depth( IDs, [], Ds, De, Pgs, [], COpts, Orph, Graph1 ),
 1321     opts_list_closes_cby_cache( COpts ),
 1322     sort( Orph, Overts ),
 1323     keysort( Graph1, GraphOrd ),
 1324     add_vertices( GraphOrd, Overts, Graph ).
 1325
 1326opts_list_opens_cby_cache( Opts, Ids, Copts ) :-
 1327     ( (memberchk(cache(Type),Opts),\+Type==false) ->
 1328          ( memberchk(ext(Ext),Opts) -> true ; file_type_extension(Type,Ext) ),
 1329          ( memberchk(location(Loc),Opts) ->
 1330               ext_file( Loc, Ext, Obj )
 1331               ;
 1332               ( Ids = [A,B|_] -> IList = [A,B]; Ids = [A|_], IList = [A] ),
 1333               atomic_list_concat( [Type|IList], '_', FStem ),
 1334               file_name_extension( FStem, Ext, Obj )
 1335          ),
 1336          memberchk( date(Date), Opts ),
 1337          memberchk( update(Upd), Opts ),
 1338          pub_graph_cache_open( Type, Obj, cited_by, Handle, Opts ),
 1339          Copts = [cache(Type,Handle,Date,Upd)|Opts]
 1340          ;
 1341          Copts = Opts
 1342     ).
 1343
 1344opts_list_closes_cby_cache( COpts ) :-
 1345     memberchk( cache(Type,Handle,_,_) ,COpts ),
 1346     !,
 1347     pub_graph_cache_save( Type, Handle, cited_by/3, [] ).
 1348opts_list_closes_cby_cache( _ ).
 pub_graph_cited_by_treadmill(+Ids, -Graph, +Opts)
Use iterative increase of depth limit on pumed_cited_by_graph/3 with until to the overall Depth is reached. Results are saved to a cache file before proceeding to rerun the whole thing with an unit increase on the depth limit. Previous results will be fished out from the cache files.

Options is a single term or list of the following:

To be done
- use ODBC

*/

 1367pub_graph_cited_by_treadmill( Ids, Graph, Args ) :-
 1368     % pub_graph_treadmill_defaults( Defs ),
 1369     options_append( pub_graph_treadmill, Args, Opts ),
 1370     options( file(File), Opts ),
 1371     options( single_file(Single), Opts ),
 1372     options( depth(D), Opts ),
 1373     options( ext(Ext), Opts ),
 1374     ext_file( File, Ext, PlFile ),
 1375     ( exists_file(PlFile) -> 
 1376          consult( pub_graph_cache:PlFile )
 1377          ;
 1378          true
 1379     ),
 1380     Oterm = topts(D,File,Single),
 1381     pub_graph_cited_by_treadmill_deepening( 1, Oterm, Ids, _, Graph ).
 1382
 1383
 1384pub_graph_cited_by_treadmill_deepening( Di, topts(Dlim,_,_), _Ids, Graph, Graph ) :-
 1385     Di > Dlim,
 1386     !, 
 1387     % write( overall_depth_limit_reached(Dlim) ), nl.
 1388     true.
 1389pub_graph_cited_by_treadmill_deepening( Di, Topts, Ids, _Current, Graph ) :-
 1390     Topts = topts(Dlim,File,Single),
 1391     Opts = [verbose(true),cache(prolog,_,date(2012,09,01),true),depth(Di),verbose(true)],
 1392     pub_graph_cited_by_graph( Ids, GraphI, Opts ),
 1393     % length( GraphI, LenGI ),
 1394     % write( population_at_depth(Di,LenGI) ), nl,
 1395     pub_graph_treadmill_file_name( Single, File, Di, Dlim, Fname ),
 1396     % write( onto_file(Fname) ), nl,
 1397     pub_graph_cache_save( prolog, Fname, cited_by/3, [] ),
 1398     Dstar is Di + 1,
 1399     pub_graph_cited_by_treadmill_deepening( Dstar, Topts, Ids, GraphI, Graph ).
 pub_graph_cache_open(+Type, +File, +Which, -Handle, Opts)
Open a pub_graph File of a given Type. A Handle is returned if appropriate. Currently csv,prolog,odbc and sqlite files are recognised. The former two are consulted into module pub_graph_cache, and Handle is therofore not used. For odbc/sqlite files the lookups and database access is via the odbc and prosqlite libraries respectively. Handle can be named to an alias of choise, otherwise a opaque atom is returned with which the db is accessed. Which, should either be cited_by or info .

Options is a term or list of terms from:

Options are also passed to the underlying open operations for the type chosen. So for instance you can provide the username and passward for the odbc connection with user(U) and password(P).

*/

 1420pub_graph_cache_open( csv, FileIn, Which, Handle, Opts ) :-
 1421     memberchk( Which, [cited_by,info] ),
 1422     % atom_concat( pub_graph_cache_, Which, Module ),
 1423     pub_graph_cache_open_csv_file( FileIn, pub_graph_cache, Which, Handle, Opts ).
 1424pub_graph_cache_open( prolog, FileIn, Which, Handle, Opts ) :-
 1425     memberchk( Which, [cited_by,info] ),
 1426     % atom_concat( pub_graph_cache_, Which, Module ),
 1427     pub_graph_cache_open_pl_file( FileIn, pub_graph_cache, Handle, Opts ).
 1428pub_graph_cache_open( sqlite, FileIn, Which, Alias, Opts ) :-
 1429     memberchk( Which, [cited_by,info] ),
 1430     pub_graph_cache_open_sqlite( FileIn, Alias, Which,  Opts ).
 1431pub_graph_cache_open( odbc, Dsn, _Which, Alias, Opts ) :-
 1432     ( db_current_connection(Alias,Type) -> 
 1433          Fail = failed_to_open_odbc_on_existing_alias(Alias,Type,Dsn),
 1434          write( user_error, Fail ), nl( user_error ), fail
 1435          ;
 1436          Defs = [alias(Alias),user(pub_graph),password(pmed123)],
 1437          append( Opts, Defs, All ),
 1438          odbc_connect( Dsn, Alias, All )
 1439     ).
 1440     
 1441pub_graph_cache_open_csv_file( FileIn, Module, Pname, CbyF, Opts ) :-
 1442     append( Opts, [ext(csv)], All ),
 1443     options( ext(Ext), All ),
 1444     ext_file( FileIn, Ext, CbyF ),
 1445     (exists_file(CbyF) -> 
 1446               csv_read_file( CbyF, Rows, [functor(Pname)] ),
 1447               ( memberchk(flat_input(IFlat),Opts) -> true; memberchk(flat(IFlat),Opts) ),
 1448               ( IFlat==true -> 
 1449                         sort(Rows,Ord),
 1450                         ord_facts_aggregate_arg(Ord,3,Agr)
 1451                         ;
 1452                         maplist( csv_row_to_nest_row, Rows, Agr ) 
 1453               ),
 1454               maplist( assert_at(Module), Agr )
 1455               ;
 1456               true
 1457     ).
 1458pub_graph_cache_open_pl_file( FileIn, Module, CbyF, Opts ) :-
 1459     append( Opts, [ext(pl)], All ),
 1460     memberchk( ext(Ext), All ),
 1461     ext_file( FileIn, Ext, CbyF ),
 1462     (exists_file(CbyF) -> call( Module:consult(CbyF) ) ; true ).
 1463
 1464pub_graph_cache_open_sqlite( FileIn, Alias, Type,  Opts ) :-
 1465     append( Opts, [ext(sqlite)], All ),
 1466     memberchk( ext(Ext), All ),
 1467     ext_file( FileIn, Ext, SqliteF ),
 1468     ( exists_file(SqliteF) ->
 1469          true
 1470          ;
 1471          atomic_list_concat( ['Creating pub_graph',Type,'SQLite database at'], ' ', Create ),
 1472          write( Create:SqliteF ), nl,
 1473          sqlite_connect( SqliteF, HandleC, exists(false) ),
 1474          findall( _, 
 1475                         ( pub_graph_create_sqlite_type_statement( Type, Statement ),
 1476                           prosqlite:sqlite_query( HandleC, Statement, _Row )
 1477                         ),
 1478                                   _ ),
 1479
 1480          sqlite_disconnect( HandleC )  % fixme: make sure prosqlite closes with handles as well as with aliases.
 1481     ),
 1482     % atom_concat( pub_graph_cache_, Type, Mod ),
 1483     Mod = pub_graph_cache,
 1484     ( var(Alias) -> Alias = HandleD ; Ao = [alias(Alias)] ),
 1485     OptsD = [as_predicates(true),at_module(Mod)|Ao],
 1486     sqlite_connect( SqliteF, HandleD, OptsD ).
 1487
 1488pub_graph_create_sqlite_type_statement( cited_by, 'CREATE TABLE cited_by (pubmed_id bigint(20), ret_date date, citer bigint(20), Primary Key (pubmed_id,citer) );' ).
 1489pub_graph_create_sqlite_type_statement( info, 'CREATE TABLE info_date (pubmed_id bigint(20), rec_date date, Primary Key (pubmed_id) );' ).
 1490pub_graph_create_sqlite_type_statement( info, 'CREATE TABLE info (pubmed_id bigint(20), key_name tinytext, nth integer, key_value smalltext, Primary Key (pubmed_id,key_name,nth) );' ).
 pub_graph_cache_save(+Type, +FileinORHandle, What, Opts)
Close or save a cache to a file. Currently Types csv, `prolog', `odbc' and `sqlite' are recognised. In the case of prolog, the list of predicates What is dumped to the prolog file Filein. Likewise for `csv' but as data rows. The predicates are looked for in module `pub_graph_cache'. Once the preds are saved, their retracted from memory.

Opts a term or list of terms from:

flat(Flat)
should csv and prolog rows be compressed by third argument ? */
 1503pub_graph_cache_save( prolog, FileIn, WhatIf, Opts ) :-
 1504     pub_graph_cache_save( in_mem,  prolog, FileIn, WhatIf, Opts ).
 1505pub_graph_cache_save( csv, FileIn, WhatIf, Opts ) :-
 1506     pub_graph_cache_save_in_mem( csv, FileIn, WhatIf, Opts ).
 1507pub_graph_cache_save( sqlite, Handle, _, _Opts ) :-
 1508     sqlite_disconnect( Handle ).
 1509pub_graph_cache_save( odbc, Handle, _, _Opts ) :-
 1510     odbc_disconnect( Handle ).
 1511pub_graph_cache_save( db_facts, Handle, _, Opts ) :-
 1512     db_current_connection( Handle, Type ),
 1513     Type \== db_facts,
 1514     pub_graph_cache_save( Type, Handle, _, Opts ).
 1515
 1516pub_graph_cache_save_in_mem( Which, File, WhatIf, Opts ) :-
 1517     pg_en_list( WhatIf, What ),
 1518     /* ( file_name_extension(_,'',FileIn) ->
 1519          file_name_extension( FileIn, pl, File ) ; File = FileIn),
 1520     */
 1521     ( (memberchk(flat(true),Opts),Which=_Something/3) -> Flat = true; Flat = false ),
 1522     open( File, write, Out ),
 1523     pub_graph_cache_dump( Which, What, Out, Flat ),
 1524     close( Out ),
 1525     forall( member(W,What), (W=Pname/Arity,functor(G,Pname,Arity),retractall(pub_graph_cache:G)) ).
 1526
 1527pub_graph_cache_dump( Which, What, Out, Flat ) :-
 1528     member( Name/Arity, What ),
 1529     functor( Goal, Name, Arity ),
 1530     once(pl_cache_predicate_module(Name,Arity,Module) ),
 1531     forall( call(Module:Goal), record_in_mem_clause(Which,Flat,Out,Goal) ).
 1532
 1533record_in_mem_clause( Which, Flat, Out, Fact ) :-
 1534     ( Flat == true ->
 1535          arg( 3, Fact, Third ),
 1536          ( Third == [] -> 
 1537               arg( 1, Fact, First ), arg( 2, Fact, Second ),
 1538               functor( Fact, Name, 3 ),
 1539               functor( New, Name, 3 ),
 1540               arg( 1, New, First ), arg( 2, New, Second ), arg( 3, New, 0 )
 1541               ;
 1542               ( is_list(Third) ->
 1543                    arg( 1, Fact, First ), arg( 2, Fact, Second ),
 1544                    functor( Fact, Name, 3 ),
 1545                    functor( New, Name, 3 ),
 1546                    arg( 1, New, First ), arg( 2, New, Second ),
 1547                    forall( member(Elem,Third), (arg(3,New,Elem),record_fact(Which,Out,New)) )
 1548                    ;
 1549                    record_fact( Which, Out, Fact )
 1550               )
 1551          )
 1552          ;
 1553          record_fact( Which, Out, Fact )
 1554     ).
 1555     /* wouldn't be cool to have portray clause/2 write on csvs automatically ?
 1556        then we need open_csv to register the csv files 
 1557     */
 1558record_fact( prolog, Out, Fact ) :-
 1559     portray_clause( Out, Fact ).
 1560record_fact( csv, Out, Fact ) :-
 1561     % not-checked !
 1562     % see if we can get away with the []
 1563     mapargs( pl_term_csv_atom, Fact, Csv ),
 1564     csv_write_stream( Out, [Csv], [] ).
 1565
 1566pl_cache_predicate_module( cited_by, 3, pub_graph_cache ).
 1567pl_cache_predicate_module( info, 3, pub_graph_cache ).
 1568pl_cache_predicate_module( info_date, 2, pub_graph_cache ).
 1569
 1570% Section: non-interface predicates...
 1571
 1572pub_graph_cited_by_graph_depth( [], _Seen, _Ds, _De, _Pgs, Graph, _Opts, [], Graph ).
 1573pub_graph_cited_by_graph_depth( [Id-D|T], Seen, Ds, De, Pgs,  Acc, Opts, Orph, Graph ) :-
 1574     ( Pgs==true -> length(T,Len), 
 1575                    writeq(id(Id,Len)), write('.'), nl
 1576                    ; true ),
 1577     ( ord_memberchk(Id,Seen) -> 
 1578          Pairs = T,
 1579          NxSeen = Seen,
 1580          Nxt = Acc,
 1581          Orph = TOrph
 1582          ;
 1583          pub_graph_cited_by( Id, IdAnc, Opts ),
 1584          ( Pgs==true -> 
 1585               write( has(Id,IdAnc) ), write( '.' ), nl
 1586               ;
 1587               true
 1588          ),
 1589          D1 is D + 1,
 1590          ( depth_limit_reached(Ds,De,D1) -> 
 1591               Pairs = T,
 1592               NxSeen = Seen,
 1593               Nxt = Acc,
 1594               Orph = [Id|TOrph]
 1595               ;
 1596               ord_add_element( Seen, Id, NxSeen ),
 1597               maplist( kv_elem_set(D1), IdAnc, New ),
 1598               append( T, New, Pairs ),
 1599               sort( IdAnc, AncSort ),
 1600               Nxt = [Id-AncSort|Acc],
 1601               Orph = TOrph
 1602          )
 1603     ),
 1604     pub_graph_cited_by_graph_depth( Pairs, NxSeen, Ds, De, Pgs, Nxt, Opts, TOrph, Graph ).
 1605
 1606depth_limit_reached( bound, Lim, Val ) :-
 1607     Lim < Val.
 1608
 1609kv_elem_set( Val, Key, Key-Val ).
 1610          
 1611summary_info( Tmp, Id, WhichIn, Results, OptS ) :-
 1612    pub_graph_id( Id, IdType ),
 1613    non_var_list( OptS, Opts ),
 1614    ( WhichIn == all -> default_names(IdType,Which) ;  pg_en_list(WhichIn,Which) ),
 1615    summary_info( IdType, Tmp, Id, Which, Results, Opts ).
 1616
 1617summary_info( ncbi, Tmp, Id, Which, Results, Opts ) :-
 1618     url_eutils( Eutils ),
 1619     ( memberchk(retmax(RMax),Opts) -> true; RMax = 100 ),
 1620     atomic_list_concat( ['esummary.fcgi?report=xml&mode=text&tool=wget&retmax=',RMax,'&db=pubmed&id='], Query ),
 1621     atomic_list_concat( [Eutils,Query,Id], Url ),
 1622     get_url_in_file( Url, false, Tmp ), % fixme
 1623     load_xml_file( Tmp, Xml ),
 1624     Elem = element(_,_,[Entry]),
 1625     findall( Name-Info, ( 
 1626                           member( Name, Which ),
 1627                           findall( Entry,
 1628                             search_element_in_list(Xml, 'Item', ['Name'=Name], Elem ),
 1629                                   PInfo ),
 1630                           ( PInfo = [Info] -> true; PInfo = Info )
 1631                         ), Results ),
 1632     ( memberchk(tmp_keep(true),Opts) -> true; delete_file(Tmp) ).
 1633summary_info( semscholar, _Tmp, Id, Which, Results, _Opts ) :-
 1634    semscholar_id_json( Id, Json ),
 1635    findall( Name-Info, ( member(Name,Which),
 1636                          member(Name=InfoPrv,Json),
 1637                          json_value(InfoPrv,Info)
 1638                        ),
 1639                            Results
 1640           ).
 1641
 1642pub_graph_date_cached( prolog, Explicit, Default, Cutoff, Id, Return ) :-
 1643     pub_graph_date_cached( in_mem, Explicit, Default, Cutoff, Id, Return ).
 1644pub_graph_date_cached( csv, Explicit, Default, Cutoff, Id, Return ) :-
 1645     pub_graph_date_cached( in_mem, Explicit, Default, Cutoff, Id, Return ).
 1646pub_graph_date_cached( in_mem, _Handle, Pname, Cutoff, Id, Return ) :-
 1647     % ( var(Explicit) -> Explicit=Default; true ),
 1648     G =.. [Pname,Id,Date,Return],
 1649     once(pl_cache_predicate_module(Pname,3,Module) ),
 1650     call( Module:G ),
 1651     Cutoff @=< Date.
 1652pub_graph_date_cached( sqlite, Handle, Pname, Cutoff, Id, Return ) :-
 1653     pub_graph_date_cached( db_facts, Handle, Pname, Cutoff, Id, Return ).
 1654pub_graph_date_cached( odbc, Handle, Pname, Cutoff, Id, Return ) :-
 1655     pub_graph_date_cached( db_facts, Handle, Pname, Cutoff, Id, Return ).
 1656pub_graph_date_cached( db_facts, Handle, Pname, Cutoff, Id, Return ) :-
 1657     G =.. [Pname,Id,SQLDate,R],
 1658     findall( R, db_holds(Handle,G), PrvReturn ),
 1659     PrvReturn \== [],
 1660     once( db_holds(Handle,G) ),
 1661     ( atom(SQLDate) -> 
 1662          db_date_sql_atom( Date, SQLDate )
 1663          ;
 1664          % odbc translates this, so should sqlite
 1665          Date = SQLDate
 1666     ),
 1667
 1668     ( PrvReturn = [0] -> Return = [] ; Return = PrvReturn ),
 1669     Cutoff @=< Date.
 1670
 1671/* commenting this out probably makes as_predicates(true) obsolete
 1672     G =.. [PredName,Id,SQLDate,R],
 1673     once(pl_cache_predicate_module(PredName,3,Module) ),
 1674     findall( R, call(Module:G), PrvReturn ),
 1675     PrvReturn \== [],
 1676     once( Module:G ), 
 1677     date_sql_atom( Date, SQLDate ),
 1678     ( PrvReturn = [0] -> Return = [] ; Return = PrvReturn ),
 1679     Cutoff @=< Date.
 1680     */
 1681
 1682pub_graph_update_cache( csv, Explicit, Default, Id, Return ) :-
 1683     pub_graph_update_cache( in_mem, Explicit, Default, Id, Return ).
 1684pub_graph_update_cache( prolog, Explicit, Default, Id, Return ) :-
 1685     pubned_update_cache( in_mem, Explicit, Default, Id, Return ).
 1686pub_graph_update_cache( in_mem, _Handle, Pname, Id, Return ) :-
 1687     % ( var(Explicit) -> Explicit=Default; true ),
 1688     date(Date),
 1689     R =.. [Pname,Id,_,_],
 1690     retractall( pub_graph_cache:R ),
 1691     G =.. [Pname,Id,Date,Return],
 1692     assert( pub_graph_cache:G ).
 1693
 1694pub_graph_update_cache( sqlite, Handle, PredName, Id, PrvReturn ) :-
 1695     pub_graph_update_cache( db_facts, Handle, PredName, Id, PrvReturn ).
 1696pub_graph_update_cache( odbc, Handle, PredName, Id, PrvReturn ) :-
 1697     pub_graph_update_cache( db_facts, Handle, PredName, Id, PrvReturn ).
 1698pub_graph_update_cache( db_facts, Handle, PredName, Id, PrvReturn ) :-
 1699     % ( var(Explicit) -> Explicit=Default; true ),
 1700     date(Date),
 1701     R =.. [PredName,Id,_,_],
 1702     db_retractall( Handle, R, _ ),
 1703     db_date_sql_atom( Date, SqlDate ),
 1704     G =.. [PredName,Id,SqlDate,Ret],
 1705     ( PrvReturn = [] -> Return = [0]; Return = PrvReturn ),
 1706     findall( Ret, (member(Ret,Return),db_assert(Handle,G,_)), _ ).
 1707
 1708pub_graph_cited_by_parse_file(Tmp, Ids) :-
 1709     load_xml_file( Tmp, Xml ),
 1710     once( search_element_in_list( Xml, 'LinkSetDb', [], element(_,_,LXml) ) ),
 1711     findall( CId, 
 1712               ( search_element_in_list(LXml,'Id',[],element(_,_,[CIdA])),
 1713                 atom_codes(CIdA,CIdCs), number_codes(CId,CIdCs)
 1714               ),
 1715               Ids ).
 1716
 1717% Section: auxiliaries
 pub_graph_search_period_opts(+Pfx, -Period, +Opts)
Create a Period atom DateKey=DateValue separated by '&' for each of the DateKey(DateValue) terms in Opts for which pub_graph_elink_date_option(DateKey) holds.
 ?- pub_graph_search_period_opts( '', Period, [reldate(60)] ).
 Period = 'reldate=60'.

 ?- pub_graph_search_period_opts( '', Period, [mindate(2014),mindate(2015)] ).
 Period = 'mindate=2014&mindate=2015'.

 ?- pub_graph_search_period_opts( '', Period, [mindate(2014),maxdate(2015)] ).
 Period = 'mindate=2014&maxdate=2015'.

 
 1738pub_graph_search_period_opts( Pfx, Period, Opts ) :-
 1739    select( Opt, Opts, Rpts ),
 1740    functor( Opt, Oname, 1 ),
 1741    pub_graph_elink_date_option( Oname ),
 1742    arg( 1, Opt, Arg ),
 1743    !,
 1744    atomic_list_concat( [Oname,Arg], '=', Atom ),
 1745    atomic_list_concat( [Pfx,Atom], '&', Rfx ),
 1746    pub_graph_search_period_opts( Rfx, Period, Rpts ).
 1747pub_graph_search_period_opts( Period, Period, _Opts ).
 1748
 1749pub_graph_elink_date_option( reldate ).
 1750pub_graph_elink_date_option( mindate ).
 1751pub_graph_elink_date_option( maxdate ).
 search_term_to_query(Sterm, Query)
Convert a pubmed term to an atomic query that can be passed through http.
 1757search_term_to_query( (A,B), Query ) :-
 1758     !,
 1759     search_term_to_query( A, Aq ),
 1760     search_term_to_query( B, Bq ),
 1761     atomic_list_concat( [Aq,'+AND+',Bq], Query ).
 1762search_term_to_query( (A;B), Query ) :-
 1763     !,
 1764     search_term_to_query( A, Aq ),
 1765     search_term_to_query( B, Bq ),
 1766     atomic_list_concat( [Aq,'OR',Bq], '+', Query ).
 1767search_term_to_query( (A=B), Query ) :-
 1768     !,
 1769     maplist( quote_curl_atom, [A,B], [Aq,Bq] ),
 1770     atomic_list_concat( [Bq,'[',Aq,']'], Query ).
 1771search_term_to_query( C, Query ) :-
 1772     pg_en_list( C, Clist ),
 1773     maplist( quote_curl_atom, Clist, Qlist ),
 1774     atomic_list_concat( Qlist, '+', Query ).
 1775
 1776% very basic. for now we are just translating space to %20
 1777% we should use some SWI http internals here
 1778%
 1779quote_curl_atom( In, Out ) :-
 1780     atom_codes( In, InCs ),
 1781     maplist( quote_curl_code, InCs, NestOutCs ),
 1782     flatten( NestOutCs, OutCs ),
 1783     atom_codes( Out, OutCs ).
 1784
 1785quote_curl_code( 0' , [0'%,0'2,0'0] ) :- !.
 1786quote_curl_code( Code, Code ).
 memberchk_optional(Elem, List)
Unifies Elem with the first matching term in List if one exists. The predicate always succeeds exactly once.
 1793memberchk_optional( Elem, List ) :-
 1794     memberchk( Elem, List ),
 1795     !.
 1796memberchk_optional( _Elem, _List ).
 1797     
 1798true_atom_keeps_file( Keep, _File ) :-
 1799     Keep == true,
 1800     !.
 1801true_atom_keeps_file( _Keep, File ) :-
 1802     delete_file( File ).
 1803
 1804/*
 1805to_list( Either, List ) :-
 1806    ( (var(Either);(Either\=[_H|_T],Either\==[]) ) ->
 1807        List = [Either]
 1808        ;
 1809        List = Either
 1810    ).
 1811    */
 1812
 1813true_writes( true, Report ) :-
 1814     !,
 1815     write( Report ), nl.
 1816true_writes( _Opts, _Report ).
 get_url_in_file(+URL, +Verbose, -In)
From SWI-Prolog's doc files (July 2012). tmp_file_stream/3.
 1822get_url_in_file(Url, _Verb, File) :-
 1823    ( var(File) -> tmp_file_stream(text, File, Stream), close(Stream) ; true),
 1824    debug( pub_grapsh, 'Downloading URL: ~p, onto file: ~p', [Url,File] ),
 1825    setup_call_cleanup(
 1826        http_open(Url, In,
 1827              [ % cert_verify_hook(ssl_verify) % checkme:
 1828              ]),
 1829        setup_call_cleanup(
 1830        open(File, write, Out, [type(binary)]),
 1831        copy_stream_data(In, Out),
 1832        close(Out)),
 1833        close(In)
 1834    ).
 1835
 1836% this was the old version: currently not called from anywhere 18.09.22
 1837get_url_in_file(curl, URL, Verb, File) :-
 1838        ( var(File) -> tmp_file_stream(text, File, Stream), close(Stream) ; true),
 1839        ( Verb==true -> Args = ['-o',File,URL] ; Args = ['-s','-o',File,URL] ),
 1840        % true_writes( Verb, process_create(path(curl),Args,[]) ),
 1841       debug( pub_graph, 'Getting url via curl with args:~w', [Args] ),
 1842        % fixme: use url_file/2
 1843        process_create( path(curl), Args, [] ),
 1844        exists_file( File ).
 1845
 1846/*
 1847http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=science[journal]+AND+breast+cancer+AND+2008[pdat]
 1848        
 1849        */
 1850
 1851all_subs_in_xml_single( Xml, Single, SubSel, Subs ) :-
 1852     once( search_element_in_list( Xml, Single, [], element(_,_,Nest) ) ),
 1853     findall( Sub, 
 1854               search_element_in_list(Nest,SubSel,[],element(_,_,Sub)), 
 1855              Subs ).
 search_element_in_list(+Term, +Name, +Attrs, -Elem)
Find an element in an sgml file that have specific Attrs. Got from hostip.pl .
 1862search_element_in_list([Content|MoreContent], Name, ListAttributes, Element) :-
 1863    (   search_element(Content, Name, ListAttributes, Element)
 1864    ;   search_element_in_list(MoreContent, Name, ListAttributes, Element)
 1865    ).
 1866
 1867search_element(HTML, Name, ListAttributes, HTML) :-
 1868    compound(HTML),
 1869    arg(1, HTML, Name),
 1870    arg(2, HTML, HTML_Attributi),
 1871    forall(member(Attribute, ListAttributes),
 1872           memberchk(Attribute, HTML_Attributi)).
 1873search_element(HTML, Name, ListAttributes, Element) :-
 1874    compound(HTML),
 1875    arg(3, HTML, Contents),
 1876    search_element_in_list(Contents, Name, ListAttributes, Element).
 1877
 1878/*
 1879to_atom_ids( In, Ids ) :-
 1880     to_list( In, List ),
 1881     maplist( to_atom_id, List, Ids ).
 1882
 1883to_atom_id( Either, Atom ) :-
 1884     ( number(Either) -> number_codes( Either, Codes ), atom_codes( Atom, Codes )
 1885                       ; Atom = Either ).
 1886                       */
 1887
 1888pub_graph_treadmill_file_name( true, File, _Di, _Dl, File ).
 1889pub_graph_treadmill_file_name( false, File, Di, Dl, Full ) :-
 1890     number_codes( Dl, DLcs ),
 1891     number_codes( Di, DIcs ),
 1892     reverse( DLcs, DLcsR ),
 1893     reverse( DIcs, DIcsR ),
 1894     pad_codes( DLcsR, DIcsR, 0'0, DFcsR ),
 1895     reverse( DFcsR, DFcs ),
 1896     atom_codes( DF, DFcs ),
 1897     atomic_list_concat( [File,DF], '_d', Full ).
 1898     
 1899/*
 1900options_append( Opts, Defs, All ) :-
 1901     to_list( Opts, OptsList ),
 1902     append( OptsList, Defs, All ).
 1903    */
 1904
 1905ext_file( FileIn, Ext, File ) :-
 1906     ( file_name_extension(_,Ext,FileIn) ->
 1907          File = FileIn
 1908          ;
 1909          file_name_extension(FileIn,Ext,File)
 1910     ).
 1911     
 1912pad_codes( [], [], _Code, [] ) :- !.
 1913pad_codes( [_H|T], [], Code, [Code|M] ) :- !,
 1914     pad_codes( T, [], Code, M ).
 1915pad_codes( [_H|T], [F|R], Code, [F|M] ) :- !,
 1916     pad_codes( T, R, Code, M ).
 1917
 1918kvs_to_unique_k_v_as_list( [], K, PrvVs, [K-V] ) :-
 1919     ( PrvVs = [V] -> true ; reverse(PrvVs,V) ).
 1920kvs_to_unique_k_v_as_list( [K1-V1|T], K, Vs, KVs ) :-
 1921     ( K == K1 -> 
 1922          Vs2 = [V1|Vs],
 1923          TKVs = KVs
 1924          ;
 1925          Vs2 = [V1],
 1926          ( Vs=[VofK] -> true; reverse(Vs,VofK) ),
 1927          KVs = [K-VofK|TKVs]
 1928     ),
 1929     kvs_to_unique_k_v_as_list( T, K1, Vs2, TKVs ).
 1930     
 1931ord_facts_aggregate_arg( [H|T], N, Agr ) :-
 1932     functor( H, Pname, Arity ),
 1933     findall( On, (between(1,Arity,On),On =\= N), Ons ),
 1934     maplist( term_n_arg(H), Ons, Hons ),
 1935     ord_facts_aggregate_arg( T, Ons, N, Pname/Arity, Hons, [], Agr ).
 1936
 1937ord_facts_aggregate_arg( [], Ons, N, Pn/Ar, Hons, HAgrs, [Fact] ) :-
 1938     de_singleton( HAgrs, Agr ),
 1939     functor( Fact, Pn, Ar ),
 1940     maplist( csv_atom_pl_term, Hons, Cons),
 1941     maplist( term_n_arg(Fact), [N|Ons], [Agr|Cons] ).
 1942ord_facts_aggregate_arg( [H1|T], Ons, N, Funct, Hons, HAgrs, Facts ) :-
 1943     term_ons( Ons, H1, Hons1 ),
 1944     arg( N, H1, Nth ),
 1945     ( Hons = Hons1 -> 
 1946          HAgrs2 = [Nth|HAgrs],
 1947          Facts = TFacts 
 1948          ;
 1949          HAgrs2 = [Nth],
 1950          Funct = Pname/Arity,
 1951          functor( Fact, Pname, Arity ),
 1952          de_singleton( HAgrs, Agr ),
 1953          maplist( csv_atom_pl_term, Hons, Cons ),
 1954          maplist( term_n_arg(Fact), [N|Ons], [Agr|Cons] ),
 1955          Facts = [Fact|TFacts]
 1956     ),
 1957     ord_facts_aggregate_arg( T, Ons, N, Funct, Hons1, HAgrs2, TFacts ).
 1958
 1959term_n_arg( Term, N, Arg ) :- arg( N, Term, Arg ).
 1960
 1961pl_term_csv_atom( date(Yn,Mn,Dn), Csv ) :-
 1962     !,
 1963     maplist( atom_number, [Y,M,D], [Yn,Mn,Dn] ),
 1964     atomic_list_concat( [Y,M,D], '/', Csv ).
 1965pl_term_csv_atom( Term, Csv ) :-
 1966     ( atomic(Term) -> 
 1967          ( Term == [] -> Csv = 0; Csv = Term )
 1968          ;
 1969          ( is_list(Term) ->
 1970               atomic_list_concat( Term, ';', Csv )
 1971               ;
 1972               term_to_atom(Term,Csv)
 1973          )
 1974     ).
 1975
 1976csv_atom_pl_term( 0, [] ) :- !.
 1977csv_atom_pl_term( CsvDate, Date ) :-
 1978     \+ CsvDate = [_|_],
 1979     atomic_list_concat( [Y,D,M], '/', CsvDate ),
 1980     !,
 1981     Date = date(Y,D,M).
 1982csv_atom_pl_term( Other, Other ).
 1983
 1984csv_row_to_nest_row( Csv, Fact ) :-
 1985     functor( Csv, Pname, Functor ),
 1986     functor( Fact, Pname, Functor ),
 1987     arg( 1, Csv, First ),
 1988     arg( 1, Fact, First ),
 1989     arg( 2, Csv, Second ),
 1990     csv_atom_pl_term( Second, Deutepo ),
 1991     arg( 2, Fact, Deutepo ),
 1992     ( Functor =:= 3 -> 
 1993          arg( 3, Csv, Third ),
 1994          ( (Third=:=0;Third=='0') -> 
 1995               Tpith = []
 1996               ;
 1997               atomic_list_concat( Atoms, ';', Third ),
 1998               maplist( to_number, Atoms, Tpith )
 1999          ),
 2000          arg( 3, Fact, Tpith )
 2001     ).
 2002
 2003assert_at( Module, Fact ) :-
 2004     % mapargs( csv_atom_pl_term, Row, Fact ), 
 2005     assert( Module:Fact ).
 2006
 2007get_url( Url ) :-
 2008     file_base_name(Url, Base),
 2009     directory_file_path(_Dir, Base, File),
 2010     file_mime_type(File, Mime ),
 2011     mime_file_type( Mime, FType ),
 2012     get_url( Url, File, FType ).
 2013
 2014get_url( Url, File, Type ) :-
 2015     setup_call_cleanup(
 2016        http_open(Url, In, []),
 2017        setup_call_cleanup(
 2018        open(File, write, Out, [type(Type)]),
 2019        copy_stream_data(In, Out),
 2020        close(Out)),
 2021        close(In) ).
 2022
 2023mime_file_type(text/_, text) :- !.
 2024mime_file_type(_, binary).
 2025% mime_file_type(text/_, binary). % was this changed it above ?
 2026
 2027a_month_ago( date(Y1,M1,D1) ) :-
 2028     date( date(Y,M,D) ),
 2029     ( M =:= 1 -> Y1 is Y - 1,
 2030                  M1 is 12,
 2031                  D is D1   % Dec. has at least as many days as Jan.
 2032                  ;
 2033                  Y1 is Y,
 2034                  M1 is M - 1,
 2035                  % actually for this application D1 is D   should suffice...
 2036                  month_days( M, Ds ),
 2037                  D1 is min(Ds,D)
 2038     ).
 2039
 2040month_days(  1, 31 ).
 2041month_days(  2, 28 ).  % ok ok 
 2042month_days(  3, 31 ).
 2043month_days(  4, 30 ).
 2044month_days(  5, 31 ).
 2045month_days(  6, 30 ).
 2046month_days(  7, 31 ).
 2047month_days(  8, 31 ).
 2048month_days(  9, 30 ).
 2049month_days( 10, 31 ).
 2050month_days( 11, 30 ).
 2051month_days( 12, 31 ).
 2052
 2053non_var_list( IdS, Ids ) :-
 2054    \+ var(IdS),
 2055    pg_en_list( IdS, Ids ).
 2056
 2057de_kv_list_on( [_K-Single], On, Single ) :-
 2058    \+ is_list( On ),
 2059    !.
 2060de_kv_list_on( List, _On, List ).
 2061
 2062mapargs( Partial, Goal1, Goal2 ) :-
 2063     functor( Goal1, Gname, Garity ),
 2064     functor( Goal2, Gname, Garity ),
 2065     mapargs_1( Garity, Partial, Goal1, Goal2 ).
 2066
 2067mapargs_1( 0, _Partial, _Goal1, _Goal2 ) :- !.
 2068mapargs_1( I, Partial, Goal1, Goal2 ) :-
 2069     % functor( Call, Pname, 2 ),
 2070     arg( I, Goal1, Arg1 ),
 2071     % arg( 1, Call, Arg1 ),
 2072     % arg( 2, Call, Arg2 ),
 2073     call( Partial, Arg1, Arg2 ),
 2074     arg( I, Goal2, Arg2 ),
 2075     K is I - 1,
 2076     mapargs_1( K, Partial, Goal1, Goal2 ).
 2077
 2078to_number( Atom, Num ) :-
 2079     atom( Atom ),
 2080     !,
 2081     atom_number( Atom, Num ).
 2082to_number( Num, Num ).
 2083
 2084nest_pair_flatten_removes( [], [] ).
 2085nest_pair_flatten_removes( [K-(_-V)|T], [K-V|M] ) :-
 2086     nest_pair_flatten_removes( T, M ).
 2087
 2088kv_decompose_vs( [], [] ).
 2089kv_decompose_vs( [_K-V|T], [V|Tv] ) :-
 2090    kv_decompose_vs( T, Tv ).
 2091
 2092pg_en_list( List, TheList ) :-
 2093    is_list(List),
 2094    !,
 2095    TheList = List.
 2096pg_en_list( Elem, [Elem] ).
 2097
 2098semscholar_id_json( Id, Json ) :-
 2099    url_semscholar( SemScholar ),
 2100    Incl = '?include_unknown_references=true',
 2101    atomic_list_concat( [SemScholar,Id,Incl], '', Url ),
 2102    http_open( Url, In, [] ),
 2103    % json_read_dict( In, Dict ),
 2104    json_read( In, JsonT ),
 2105    close( In ),
 2106    JsonT = json(Json).
 2107
 2108json_value( Json, Atom ) :-
 2109    atomic( Json ),
 2110    !,
 2111    Atom = Json.
 2112json_value( Json, Names ) :-
 2113    findall( Name, ( member(json(Sub),Json), 
 2114                     member(name=Name,Sub)
 2115                     ),
 2116                Names )